satellite/{db, admin}: added endpoints to update user's and project's user_agent

Added backend (for now) implementation for updating user's and projects's user_agent using admin API.
Updating both user and project also updates bucket_metainfo and value_attribution tables.

Issue:
https://github.com/storj/storj-private/issues/297

Change-Id: I40244bbaa08b46834c1b1d0720e7d84d0c2a0330
This commit is contained in:
Vitalii 2023-06-13 18:58:24 +03:00 committed by Storj Robot
parent fe0c3a13b4
commit 1eee2fad69
22 changed files with 580 additions and 4 deletions

2
go.mod
View File

@ -44,6 +44,7 @@ require (
github.com/zeebo/blake3 v0.2.3
github.com/zeebo/clingy v0.0.0-20230602044025-906be850f10d
github.com/zeebo/errs v1.3.0
github.com/zeebo/errs/v2 v2.0.3
github.com/zeebo/ini v0.0.0-20210514163846-cc8fbd8d9599
github.com/zyedidia/generic v1.2.1
go.etcd.io/bbolt v1.3.5
@ -124,7 +125,6 @@ require (
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
github.com/zeebo/admission/v3 v3.0.3 // indirect
github.com/zeebo/errs/v2 v2.0.3 // indirect
github.com/zeebo/float16 v0.1.0 // indirect
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect
github.com/zeebo/mwc v0.0.4 // indirect

View File

@ -4,6 +4,7 @@
package admin
import (
"bytes"
"context"
"database/sql"
"encoding/json"
@ -15,6 +16,7 @@ import (
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/zeebo/errs/v2"
"storj.io/common/macaroon"
"storj.io/common/memory"
@ -440,6 +442,82 @@ func (server *Server) renameProject(w http.ResponseWriter, r *http.Request) {
}
}
func (server *Server) updateProjectsUserAgent(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
projectUUIDString, ok := vars["project"]
if !ok {
sendJSONError(w, "project-uuid missing",
"", http.StatusBadRequest)
return
}
projectUUID, err := uuid.FromString(projectUUIDString)
if err != nil {
sendJSONError(w, "invalid project-uuid",
err.Error(), http.StatusBadRequest)
return
}
project, err := server.db.Console().Projects().Get(ctx, projectUUID)
if errors.Is(err, sql.ErrNoRows) {
sendJSONError(w, "project with specified uuid does not exist",
"", http.StatusNotFound)
return
}
if err != nil {
sendJSONError(w, "error getting project",
err.Error(), http.StatusInternalServerError)
return
}
creationDatePlusMonth := project.CreatedAt.AddDate(0, 1, 0)
if time.Now().After(creationDatePlusMonth) {
sendJSONError(w, "this project was created more than a month ago",
"we should update user agent only for recently created projects", http.StatusBadRequest)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
sendJSONError(w, "failed to read body",
err.Error(), http.StatusInternalServerError)
return
}
var input struct {
UserAgent string `json:"userAgent"`
}
err = json.Unmarshal(body, &input)
if err != nil {
sendJSONError(w, "failed to unmarshal request",
err.Error(), http.StatusBadRequest)
return
}
if input.UserAgent == "" {
sendJSONError(w, "UserAgent was not provided",
"", http.StatusBadRequest)
return
}
newUserAgent := []byte(input.UserAgent)
if bytes.Equal(project.UserAgent, newUserAgent) {
sendJSONError(w, "new UserAgent is equal to existing projects UserAgent",
"", http.StatusBadRequest)
return
}
err = server._updateProjectsUserAgent(ctx, project.ID, newUserAgent)
if err != nil {
sendJSONError(w, "failed to update projects user agent",
err.Error(), http.StatusInternalServerError)
}
}
func (server *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -502,6 +580,45 @@ func (server *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
}
}
func (server *Server) _updateProjectsUserAgent(ctx context.Context, projectID uuid.UUID, newUserAgent []byte) (err error) {
err = server.db.Console().Projects().UpdateUserAgent(ctx, projectID, newUserAgent)
if err != nil {
return err
}
listOptions := buckets.ListOptions{
Direction: buckets.DirectionForward,
}
allowedBuckets := macaroon.AllowedBuckets{
All: true,
}
projectBuckets, err := server.db.Buckets().ListBuckets(ctx, projectID, listOptions, allowedBuckets)
if err != nil {
return err
}
var errList errs.Group
for _, bucket := range projectBuckets.Items {
err = server.db.Buckets().UpdateUserAgent(ctx, projectID, bucket.Name, newUserAgent)
if err != nil {
errList.Append(err)
}
err = server.db.Attribution().UpdateUserAgent(ctx, projectID, bucket.Name, newUserAgent)
if err != nil {
errList.Append(err)
}
}
if errList.Err() != nil {
return errList.Err()
}
return nil
}
func (server *Server) checkInvoicing(ctx context.Context, w http.ResponseWriter, projectID uuid.UUID) (openInvoices bool) {
year, month, _ := server.nowFn().UTC().Date()
firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)

View File

@ -23,6 +23,7 @@ import (
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite"
"storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/attribution"
"storj.io/storj/satellite/buckets"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/payments/stripe"
@ -244,6 +245,109 @@ func TestProjectAdd(t *testing.T) {
})
}
func TestUpdateProjectsUserAgent(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
db := planet.Satellites[0].DB
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
project := planet.Uplinks[0].Projects[0]
newUserAgent := "awesome user agent value"
t.Run("OK", func(t *testing.T) {
bucketName := "testName"
bucketName1 := "testName1"
bucketID, err := uuid.New()
require.NoError(t, err)
bucketID1, err := uuid.New()
require.NoError(t, err)
_, err = db.Buckets().CreateBucket(ctx, buckets.Bucket{
ID: bucketID,
Name: bucketName,
ProjectID: project.ID,
UserAgent: nil,
})
require.NoError(t, err)
_, err = db.Buckets().CreateBucket(ctx, buckets.Bucket{
ID: bucketID1,
Name: bucketName1,
ProjectID: project.ID,
UserAgent: nil,
})
require.NoError(t, err)
_, err = db.Attribution().Insert(ctx, &attribution.Info{
ProjectID: project.ID,
BucketName: []byte(bucketName),
UserAgent: nil,
})
require.NoError(t, err)
_, err = db.Attribution().Insert(ctx, &attribution.Info{
ProjectID: project.ID,
BucketName: []byte(bucketName1),
UserAgent: nil,
})
require.NoError(t, err)
body := strings.NewReader(fmt.Sprintf(`{"userAgent":"%s"}`, newUserAgent))
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("http://"+address.String()+"/api/projects/%s/useragent", project.ID.String()), body)
require.NoError(t, err)
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
response, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
require.NoError(t, response.Body.Close())
newUserAgentBytes := []byte(newUserAgent)
updatedProject, err := db.Console().Projects().Get(ctx, project.ID)
require.NoError(t, err)
require.Equal(t, newUserAgentBytes, updatedProject.UserAgent)
projectBuckets, err := planet.Satellites[0].API.Buckets.Service.ListBuckets(ctx, updatedProject.ID, buckets.ListOptions{Direction: buckets.DirectionForward}, macaroon.AllowedBuckets{All: true})
require.NoError(t, err)
require.Equal(t, 2, len(projectBuckets.Items))
for _, bucket := range projectBuckets.Items {
require.Equal(t, newUserAgentBytes, bucket.UserAgent)
}
bucketAttribution, err := db.Attribution().Get(ctx, project.ID, []byte(bucketName))
require.Equal(t, newUserAgentBytes, bucketAttribution.UserAgent)
require.NoError(t, err)
bucketAttribution1, err := db.Attribution().Get(ctx, project.ID, []byte(bucketName1))
require.Equal(t, newUserAgentBytes, bucketAttribution1.UserAgent)
require.NoError(t, err)
})
t.Run("Same UserAgent", func(t *testing.T) {
newProject, err := db.Console().Projects().Insert(ctx, &console.Project{
Name: "test",
UserAgent: []byte(newUserAgent),
})
require.NoError(t, err)
link := fmt.Sprintf("http://"+address.String()+"/api/projects/%s/useragent", newProject.ID)
body := fmt.Sprintf(`{"userAgent":"%s"}`, newUserAgent)
responseBody := assertReq(ctx, t, link, http.MethodPatch, body, http.StatusBadRequest, "", planet.Satellites[0].Config.Console.AuthToken)
require.Contains(t, string(responseBody), "new UserAgent is equal to existing projects UserAgent")
})
})
}
func TestProjectRename(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,

View File

@ -21,6 +21,7 @@ import (
"storj.io/common/errs2"
"storj.io/storj/satellite/accounting"
adminui "storj.io/storj/satellite/admin/ui"
"storj.io/storj/satellite/attribution"
"storj.io/storj/satellite/buckets"
"storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleweb"
@ -64,6 +65,10 @@ type DB interface {
OIDC() oidc.DB
// StripeCoinPayments returns database for satellite stripe coin payments
StripeCoinPayments() stripe.DB
// Buckets returns database for buckets metainfo.
Buckets() buckets.DB
// Attribution returns database for value attribution.
Attribution() attribution.DB
}
// Server provides endpoints for administrative tasks.
@ -117,6 +122,7 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.S
fullAccessAPI.HandleFunc("/users/{useremail}", server.updateUser).Methods("PUT")
fullAccessAPI.HandleFunc("/users/{useremail}", server.deleteUser).Methods("DELETE")
fullAccessAPI.HandleFunc("/users/{useremail}/mfa", server.disableUserMFA).Methods("DELETE")
fullAccessAPI.HandleFunc("/users/{useremail}/useragent", server.updateUsersUserAgent).Methods("PATCH")
fullAccessAPI.HandleFunc("/oauth/clients", server.createOAuthClient).Methods("POST")
fullAccessAPI.HandleFunc("/oauth/clients/{id}", server.updateOAuthClient).Methods("PUT")
fullAccessAPI.HandleFunc("/oauth/clients/{id}", server.deleteOAuthClient).Methods("DELETE")
@ -131,6 +137,7 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.S
fullAccessAPI.HandleFunc("/projects/{project}/buckets/{bucket}/geofence", server.createGeofenceForBucket).Methods("POST")
fullAccessAPI.HandleFunc("/projects/{project}/buckets/{bucket}/geofence", server.deleteGeofenceForBucket).Methods("DELETE")
fullAccessAPI.HandleFunc("/projects/{project}/usage", server.checkProjectUsage).Methods("GET")
fullAccessAPI.HandleFunc("/projects/{project}/useragent", server.updateProjectsUserAgent).Methods("PATCH")
fullAccessAPI.HandleFunc("/apikeys/{apikey}", server.getAPIKey).Methods("GET")
fullAccessAPI.HandleFunc("/apikeys/{apikey}", server.deleteAPIKey).Methods("DELETE")
fullAccessAPI.HandleFunc("/restkeys/{useremail}", server.addRESTKey).Methods("POST")

View File

@ -4,6 +4,7 @@
package admin
import (
"bytes"
"database/sql"
"encoding/json"
"errors"
@ -11,8 +12,10 @@ import (
"io"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/zeebo/errs"
"golang.org/x/crypto/bcrypt"
"storj.io/common/memory"
@ -343,6 +346,101 @@ func (server *Server) updateUser(w http.ResponseWriter, r *http.Request) {
}
}
func (server *Server) updateUsersUserAgent(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
userEmail, ok := vars["useremail"]
if !ok {
sendJSONError(w, "user-email missing",
"", http.StatusBadRequest)
return
}
user, err := server.db.Console().Users().GetByEmail(ctx, userEmail)
if errors.Is(err, sql.ErrNoRows) {
sendJSONError(w, fmt.Sprintf("user with email %q does not exist", userEmail),
"", http.StatusNotFound)
return
}
if err != nil {
sendJSONError(w, "failed to get user",
err.Error(), http.StatusInternalServerError)
return
}
creationDatePlusMonth := user.CreatedAt.AddDate(0, 1, 0)
if time.Now().After(creationDatePlusMonth) {
sendJSONError(w, "this user was created more than a month ago",
"we should update user agent only for recently created users", http.StatusBadRequest)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
sendJSONError(w, "failed to read body",
err.Error(), http.StatusInternalServerError)
return
}
var input struct {
UserAgent string `json:"userAgent"`
}
err = json.Unmarshal(body, &input)
if err != nil {
sendJSONError(w, "failed to unmarshal request",
err.Error(), http.StatusBadRequest)
return
}
if input.UserAgent == "" {
sendJSONError(w, "UserAgent was not provided",
"", http.StatusBadRequest)
return
}
newUserAgent := []byte(input.UserAgent)
if bytes.Equal(user.UserAgent, newUserAgent) {
sendJSONError(w, "new UserAgent is equal to existing users UserAgent",
"", http.StatusBadRequest)
return
}
err = server.db.Console().Users().UpdateUserAgent(ctx, user.ID, newUserAgent)
if err != nil {
sendJSONError(w, "failed to update user's user agent",
err.Error(), http.StatusInternalServerError)
return
}
projects, err := server.db.Console().Projects().GetOwn(ctx, user.ID)
if err != nil {
sendJSONError(w, "failed to get users projects",
err.Error(), http.StatusInternalServerError)
return
}
var errList errs.Group
for _, project := range projects {
if bytes.Equal(project.UserAgent, newUserAgent) {
errList.Add(errs.New("projectID: %s. New UserAgent is equal to existing users UserAgent", project.ID))
continue
}
err = server._updateProjectsUserAgent(ctx, project.ID, newUserAgent)
if err != nil {
errList.Add(errs.New("projectID: %s. Failed to update projects user agent: %s", project.ID, err))
}
}
if errList.Err() != nil {
sendJSONError(w, "failed to update projects user agent",
errList.Err().Error(), http.StatusInternalServerError)
}
}
// updateLimits updates user limits and all project limits for that user (future and existing).
func (server *Server) updateLimits(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

View File

@ -272,6 +272,56 @@ func TestUserUpdate(t *testing.T) {
})
}
func TestUpdateUsersUserAgent(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
StorageNodeCount: 0,
UplinkCount: 1,
Reconfigure: testplanet.Reconfigure{
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
db := planet.Satellites[0].DB
address := planet.Satellites[0].Admin.Admin.Listener.Addr()
project := planet.Uplinks[0].Projects[0]
newUserAgent := "awesome user agent value"
t.Run("OK", func(t *testing.T) {
body := strings.NewReader(fmt.Sprintf(`{"userAgent":"%s"}`, newUserAgent))
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, fmt.Sprintf("http://"+address.String()+"/api/users/%s/useragent", project.Owner.Email), body)
require.NoError(t, err)
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
response, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
require.NoError(t, response.Body.Close())
newUserAgentBytes := []byte(newUserAgent)
updatedUser, err := db.Console().Users().Get(ctx, project.Owner.ID)
require.NoError(t, err)
require.Equal(t, newUserAgentBytes, updatedUser.UserAgent)
updatedProject, err := db.Console().Projects().Get(ctx, project.ID)
require.NoError(t, err)
require.Equal(t, newUserAgentBytes, updatedProject.UserAgent)
})
t.Run("Same UserAgent", func(t *testing.T) {
err := db.Console().Users().UpdateUserAgent(ctx, project.Owner.ID, []byte(newUserAgent))
require.NoError(t, err)
link := fmt.Sprintf("http://"+address.String()+"/api/users/%s/useragent", project.Owner.Email)
body := fmt.Sprintf(`{"userAgent":"%s"}`, newUserAgent)
responseBody := assertReq(ctx, t, link, http.MethodPatch, body, http.StatusBadRequest, "", planet.Satellites[0].Config.Console.AuthToken)
require.Contains(t, string(responseBody), "new UserAgent is equal to existing users UserAgent")
})
})
}
func TestDisableMFA(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,

View File

@ -44,6 +44,8 @@ type DB interface {
Get(ctx context.Context, projectID uuid.UUID, bucketName []byte) (*Info, error)
// Insert creates and stores new Info.
Insert(ctx context.Context, info *Info) (*Info, error)
// UpdateUserAgent updates bucket attribution data.
UpdateUserAgent(ctx context.Context, projectID uuid.UUID, bucketName string, userAgent []byte) error
// QueryAttribution queries partner bucket attribution data.
QueryAttribution(ctx context.Context, userAgent []byte, start time.Time, end time.Time) ([]*BucketUsage, error)
// QueryAllAttribution queries all partner bucket usage data.

View File

@ -101,6 +101,8 @@ type DB interface {
GetBucketID(ctx context.Context, bucket metabase.BucketLocation) (id uuid.UUID, err error)
// UpdateBucket updates an existing bucket
UpdateBucket(ctx context.Context, bucket Bucket) (_ Bucket, err error)
// UpdateUserAgent updates buckets user agent.
UpdateUserAgent(ctx context.Context, projectID uuid.UUID, bucketName string, userAgent []byte) error
// DeleteBucket deletes a bucket
DeleteBucket(ctx context.Context, bucketName []byte, projectID uuid.UUID) (err error)
// ListBuckets returns all buckets for a project

View File

@ -55,6 +55,9 @@ type Projects interface {
// UpdateUsageLimits is a method for updating project's usage limits.
UpdateUsageLimits(ctx context.Context, id uuid.UUID, limits UsageLimits) error
// UpdateUserAgent is a method for updating projects user agent.
UpdateUserAgent(ctx context.Context, id uuid.UUID, userAgent []byte) error
}
// UsageLimitsConfig is a configuration struct for default per-project usage limits.

View File

@ -42,6 +42,8 @@ type Users interface {
Update(ctx context.Context, userID uuid.UUID, request UpdateUserRequest) error
// UpdatePaidTier sets whether the user is in the paid tier.
UpdatePaidTier(ctx context.Context, id uuid.UUID, paidTier bool, projectBandwidthLimit, projectStorageLimit memory.Size, projectSegmentLimit int64, projectLimit int) error
// UpdateUserAgent is a method to update the user's user agent.
UpdateUserAgent(ctx context.Context, id uuid.UUID, userAgent []byte) error
// UpdateUserProjectLimits is a method to update the user's usage limits for new projects.
UpdateUserProjectLimits(ctx context.Context, id uuid.UUID, limits UsageLimits) error
// GetProjectLimit is a method to get the users project limit

View File

@ -243,6 +243,20 @@ func (keys *attributionDB) Get(ctx context.Context, projectID uuid.UUID, bucketN
return attributionFromDBX(dbxInfo)
}
// UpdateUserAgent updates bucket attribution data.
func (keys *attributionDB) UpdateUserAgent(ctx context.Context, projectID uuid.UUID, bucketName string, userAgent []byte) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = keys.db.Update_ValueAttribution_By_ProjectId_And_BucketName(ctx,
dbx.ValueAttribution_ProjectId(projectID[:]),
dbx.ValueAttribution_BucketName([]byte(bucketName)),
dbx.ValueAttribution_Update_Fields{
UserAgent: dbx.ValueAttribution_UserAgent(userAgent),
})
return err
}
// Insert implements create partner info.
func (keys *attributionDB) Insert(ctx context.Context, info *attribution.Info) (_ *attribution.Info, err error) {
defer mon.Task()(&ctx)(&err)

View File

@ -168,6 +168,20 @@ func (db *bucketsDB) UpdateBucket(ctx context.Context, bucket buckets.Bucket) (_
return convertDBXtoBucket(dbxBucket)
}
// UpdateUserAgent updates buckets user agent.
func (db *bucketsDB) UpdateUserAgent(ctx context.Context, projectID uuid.UUID, bucketName string, userAgent []byte) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = db.db.Update_BucketMetainfo_By_ProjectId_And_Name(ctx,
dbx.BucketMetainfo_ProjectId(projectID[:]),
dbx.BucketMetainfo_Name([]byte(bucketName)),
dbx.BucketMetainfo_Update_Fields{
UserAgent: dbx.BucketMetainfo_UserAgent(userAgent),
})
return err
}
// DeleteBucket deletes a bucket.
func (db *bucketsDB) DeleteBucket(ctx context.Context, bucketName []byte, projectID uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err)

View File

@ -40,7 +40,7 @@ model project (
// max_buckets is the maximum number of buckets that can be created for the project.
field max_buckets int ( nullable, updatable )
// user_agent is the referred partner who created the project.
field user_agent blob ( nullable )
field user_agent blob ( nullable, updatable )
// owner_id refers to the user UUID in user.id.
field owner_id blob
// salt is used for salting the user passphrase for the content.

View File

@ -148,7 +148,7 @@ model value_attribution (
// user_agent is the first User-Agent that was used to upload data.
// unless the user signed up with a specific partner.
// note: this field is duplicated in bucket_metainfo.user_agent.
field user_agent blob ( nullable )
field user_agent blob ( updatable, nullable )
// TODO remove as part of release 1.81 or after
field partner_id blob (nullable, default null)
// last_updated is updated whenever the row changes.
@ -156,6 +156,10 @@ model value_attribution (
)
create value_attribution ()
update value_attribution (
where value_attribution.project_id = ?
where value_attribution.bucket_name = ?
)
read one (
select value_attribution

View File

@ -5573,6 +5573,7 @@ type Project_Update_Fields struct {
RateLimit Project_RateLimit_Field
BurstLimit Project_BurstLimit_Field
MaxBuckets Project_MaxBuckets_Field
UserAgent Project_UserAgent_Field
DefaultPlacement Project_DefaultPlacement_Field
}
@ -9335,6 +9336,7 @@ type User_Update_Fields struct {
ShortName User_ShortName_Field
PasswordHash User_PasswordHash_Field
Status User_Status_Field
UserAgent User_UserAgent_Field
ProjectLimit User_ProjectLimit_Field
ProjectBandwidthLimit User_ProjectBandwidthLimit_Field
ProjectStorageLimit User_ProjectStorageLimit_Field
@ -10313,6 +10315,7 @@ type ValueAttribution_Create_Fields struct {
}
type ValueAttribution_Update_Fields struct {
UserAgent ValueAttribution_UserAgent_Field
}
type ValueAttribution_ProjectId_Field struct {
@ -18304,6 +18307,11 @@ func (obj *pgxImpl) Update_Project_By_Id(ctx context.Context,
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("max_buckets = ?"))
}
if update.UserAgent._set {
__values = append(__values, update.UserAgent.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("user_agent = ?"))
}
if update.DefaultPlacement._set {
__values = append(__values, update.DefaultPlacement.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("default_placement = ?"))
@ -18461,6 +18469,49 @@ func (obj *pgxImpl) Update_BucketMetainfo_By_ProjectId_And_Name(ctx context.Cont
return bucket_metainfo, nil
}
func (obj *pgxImpl) Update_ValueAttribution_By_ProjectId_And_BucketName(ctx context.Context,
value_attribution_project_id ValueAttribution_ProjectId_Field,
value_attribution_bucket_name ValueAttribution_BucketName_Field,
update ValueAttribution_Update_Fields) (
value_attribution *ValueAttribution, err error) {
defer mon.Task()(&ctx)(&err)
var __sets = &__sqlbundle_Hole{}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE value_attributions SET "), __sets, __sqlbundle_Literal(" WHERE value_attributions.project_id = ? AND value_attributions.bucket_name = ? RETURNING value_attributions.project_id, value_attributions.bucket_name, value_attributions.user_agent, value_attributions.partner_id, value_attributions.last_updated")}}
__sets_sql := __sqlbundle_Literals{Join: ", "}
var __values []interface{}
var __args []interface{}
if update.UserAgent._set {
__values = append(__values, update.UserAgent.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("user_agent = ?"))
}
__now := obj.db.Hooks.Now().UTC()
__values = append(__values, __now)
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("last_updated = ?"))
__args = append(__args, value_attribution_project_id.value(), value_attribution_bucket_name.value())
__values = append(__values, __args...)
__sets.SQL = __sets_sql
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
value_attribution = &ValueAttribution{}
err = obj.queryRowContext(ctx, __stmt, __values...).Scan(&value_attribution.ProjectId, &value_attribution.BucketName, &value_attribution.UserAgent, &value_attribution.PartnerId, &value_attribution.LastUpdated)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, obj.makeErr(err)
}
return value_attribution, nil
}
func (obj *pgxImpl) Update_User_By_Id(ctx context.Context,
user_id User_Id_Field,
update User_Update_Fields) (
@ -18504,6 +18555,11 @@ func (obj *pgxImpl) Update_User_By_Id(ctx context.Context,
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("status = ?"))
}
if update.UserAgent._set {
__values = append(__values, update.UserAgent.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("user_agent = ?"))
}
if update.ProjectLimit._set {
__values = append(__values, update.ProjectLimit.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("project_limit = ?"))
@ -26211,6 +26267,11 @@ func (obj *pgxcockroachImpl) Update_Project_By_Id(ctx context.Context,
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("max_buckets = ?"))
}
if update.UserAgent._set {
__values = append(__values, update.UserAgent.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("user_agent = ?"))
}
if update.DefaultPlacement._set {
__values = append(__values, update.DefaultPlacement.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("default_placement = ?"))
@ -26368,6 +26429,49 @@ func (obj *pgxcockroachImpl) Update_BucketMetainfo_By_ProjectId_And_Name(ctx con
return bucket_metainfo, nil
}
func (obj *pgxcockroachImpl) Update_ValueAttribution_By_ProjectId_And_BucketName(ctx context.Context,
value_attribution_project_id ValueAttribution_ProjectId_Field,
value_attribution_bucket_name ValueAttribution_BucketName_Field,
update ValueAttribution_Update_Fields) (
value_attribution *ValueAttribution, err error) {
defer mon.Task()(&ctx)(&err)
var __sets = &__sqlbundle_Hole{}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPDATE value_attributions SET "), __sets, __sqlbundle_Literal(" WHERE value_attributions.project_id = ? AND value_attributions.bucket_name = ? RETURNING value_attributions.project_id, value_attributions.bucket_name, value_attributions.user_agent, value_attributions.partner_id, value_attributions.last_updated")}}
__sets_sql := __sqlbundle_Literals{Join: ", "}
var __values []interface{}
var __args []interface{}
if update.UserAgent._set {
__values = append(__values, update.UserAgent.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("user_agent = ?"))
}
__now := obj.db.Hooks.Now().UTC()
__values = append(__values, __now)
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("last_updated = ?"))
__args = append(__args, value_attribution_project_id.value(), value_attribution_bucket_name.value())
__values = append(__values, __args...)
__sets.SQL = __sets_sql
var __stmt = __sqlbundle_Render(obj.dialect, __embed_stmt)
obj.logStmt(__stmt, __values...)
value_attribution = &ValueAttribution{}
err = obj.queryRowContext(ctx, __stmt, __values...).Scan(&value_attribution.ProjectId, &value_attribution.BucketName, &value_attribution.UserAgent, &value_attribution.PartnerId, &value_attribution.LastUpdated)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, obj.makeErr(err)
}
return value_attribution, nil
}
func (obj *pgxcockroachImpl) Update_User_By_Id(ctx context.Context,
user_id User_Id_Field,
update User_Update_Fields) (
@ -26411,6 +26515,11 @@ func (obj *pgxcockroachImpl) Update_User_By_Id(ctx context.Context,
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("status = ?"))
}
if update.UserAgent._set {
__values = append(__values, update.UserAgent.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("user_agent = ?"))
}
if update.ProjectLimit._set {
__values = append(__values, update.ProjectLimit.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("project_limit = ?"))
@ -29786,6 +29895,18 @@ func (rx *Rx) Update_User_By_Id(ctx context.Context,
return tx.Update_User_By_Id(ctx, user_id, update)
}
func (rx *Rx) Update_ValueAttribution_By_ProjectId_And_BucketName(ctx context.Context,
value_attribution_project_id ValueAttribution_ProjectId_Field,
value_attribution_bucket_name ValueAttribution_BucketName_Field,
update ValueAttribution_Update_Fields) (
value_attribution *ValueAttribution, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Update_ValueAttribution_By_ProjectId_And_BucketName(ctx, value_attribution_project_id, value_attribution_bucket_name, update)
}
func (rx *Rx) Update_WebappSession_By_Id(ctx context.Context,
webapp_session_id WebappSession_Id_Field,
update WebappSession_Update_Fields) (
@ -30713,6 +30834,12 @@ type Methods interface {
update User_Update_Fields) (
user *User, err error)
Update_ValueAttribution_By_ProjectId_And_BucketName(ctx context.Context,
value_attribution_project_id ValueAttribution_ProjectId_Field,
value_attribution_bucket_name ValueAttribution_BucketName_Field,
update ValueAttribution_Update_Fields) (
value_attribution *ValueAttribution, err error)
Update_WebappSession_By_Id(ctx context.Context,
webapp_session_id WebappSession_Id_Field,
update WebappSession_Update_Fields) (

View File

@ -26,7 +26,7 @@ model user (
// status indicates whether the user is inactive=0, active=1, or deleted=2. See console.UserStatus for details.
field status int ( updatable, autoinsert )
// user_agent contains the partner parameter from registration.
field user_agent blob ( nullable )
field user_agent blob ( updatable, nullable )
// created_at indicates when the user was created.
field created_at timestamp ( autoinsert )

View File

@ -291,6 +291,19 @@ func (projects *projects) UpdateBucketLimit(ctx context.Context, id uuid.UUID, n
return err
}
// UpdateUserAgent is a method for updating projects user agent.
func (projects *projects) UpdateUserAgent(ctx context.Context, id uuid.UUID, userAgent []byte) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = projects.db.Update_Project_By_Id(ctx,
dbx.Project_Id(id[:]),
dbx.Project_Update_Fields{
UserAgent: dbx.Project_UserAgent(userAgent),
})
return err
}
// List returns paginated projects, created before provided timestamp.
func (projects *projects) List(ctx context.Context, offset int64, limit int, before time.Time) (_ console.ProjectsPage, err error) {
defer mon.Task()(&ctx)(&err)

View File

@ -305,6 +305,21 @@ func (users *users) UpdatePaidTier(ctx context.Context, id uuid.UUID, paidTier b
return err
}
// UpdateUserAgent is a method to update the user's user agent.
func (users *users) UpdateUserAgent(ctx context.Context, id uuid.UUID, userAgent []byte) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = users.db.Update_User_By_Id(
ctx,
dbx.User_Id(id[:]),
dbx.User_Update_Fields{
UserAgent: dbx.User_UserAgent(userAgent),
},
)
return err
}
// UpdateUserProjectLimits is a method to update the user's usage limits for new projects.
func (users *users) UpdateUserProjectLimits(ctx context.Context, id uuid.UUID, limits console.UsageLimits) (err error) {
defer mon.Task()(&ctx)(&err)

View File

@ -125,6 +125,7 @@ require (
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
github.com/zeebo/admission/v3 v3.0.3 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
github.com/zeebo/errs/v2 v2.0.3 // indirect
github.com/zeebo/float16 v0.1.0 // indirect
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect
github.com/zeebo/mwc v0.0.4 // indirect

View File

@ -836,6 +836,7 @@ github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtC
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/errs/v2 v2.0.3 h1:WwqAmopgot4ZC+CgIveP+H91Nf78NDEGWjtAXen45Hw=
github.com/zeebo/errs/v2 v2.0.3/go.mod h1:OKmvVZt4UqpyJrYFykDKm168ZquJ55pbbIVUICNmLN0=
github.com/zeebo/float16 v0.1.0 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo=
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 h1:+cwNE5KJ3pika4HuzmDHkDlK5myo0G9Sv+eO7WWxnUQ=

View File

@ -185,6 +185,7 @@ require (
github.com/zeebo/admission/v3 v3.0.3 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
github.com/zeebo/errs v1.3.0 // indirect
github.com/zeebo/errs/v2 v2.0.3 // indirect
github.com/zeebo/float16 v0.1.0 // indirect
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect
github.com/zeebo/mwc v0.0.4 // indirect

View File

@ -1203,6 +1203,7 @@ github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtC
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/errs/v2 v2.0.3 h1:WwqAmopgot4ZC+CgIveP+H91Nf78NDEGWjtAXen45Hw=
github.com/zeebo/errs/v2 v2.0.3/go.mod h1:OKmvVZt4UqpyJrYFykDKm168ZquJ55pbbIVUICNmLN0=
github.com/zeebo/float16 v0.1.0 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo=
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 h1:+cwNE5KJ3pika4HuzmDHkDlK5myo0G9Sv+eO7WWxnUQ=