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

View File

@ -4,6 +4,7 @@
package admin package admin
import ( import (
"bytes"
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
@ -15,6 +16,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/zeebo/errs/v2"
"storj.io/common/macaroon" "storj.io/common/macaroon"
"storj.io/common/memory" "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) { func (server *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() 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) { func (server *Server) checkInvoicing(ctx context.Context, w http.ResponseWriter, projectID uuid.UUID) (openInvoices bool) {
year, month, _ := server.nowFn().UTC().Date() year, month, _ := server.nowFn().UTC().Date()
firstOfMonth := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC) 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/private/testplanet"
"storj.io/storj/satellite" "storj.io/storj/satellite"
"storj.io/storj/satellite/accounting" "storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/attribution"
"storj.io/storj/satellite/buckets" "storj.io/storj/satellite/buckets"
"storj.io/storj/satellite/console" "storj.io/storj/satellite/console"
"storj.io/storj/satellite/payments/stripe" "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) { func TestProjectRename(t *testing.T) {
testplanet.Run(t, testplanet.Config{ testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, SatelliteCount: 1,

View File

@ -21,6 +21,7 @@ import (
"storj.io/common/errs2" "storj.io/common/errs2"
"storj.io/storj/satellite/accounting" "storj.io/storj/satellite/accounting"
adminui "storj.io/storj/satellite/admin/ui" adminui "storj.io/storj/satellite/admin/ui"
"storj.io/storj/satellite/attribution"
"storj.io/storj/satellite/buckets" "storj.io/storj/satellite/buckets"
"storj.io/storj/satellite/console" "storj.io/storj/satellite/console"
"storj.io/storj/satellite/console/consoleweb" "storj.io/storj/satellite/console/consoleweb"
@ -64,6 +65,10 @@ type DB interface {
OIDC() oidc.DB OIDC() oidc.DB
// StripeCoinPayments returns database for satellite stripe coin payments // StripeCoinPayments returns database for satellite stripe coin payments
StripeCoinPayments() stripe.DB 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. // 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.updateUser).Methods("PUT")
fullAccessAPI.HandleFunc("/users/{useremail}", server.deleteUser).Methods("DELETE") fullAccessAPI.HandleFunc("/users/{useremail}", server.deleteUser).Methods("DELETE")
fullAccessAPI.HandleFunc("/users/{useremail}/mfa", server.disableUserMFA).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", server.createOAuthClient).Methods("POST")
fullAccessAPI.HandleFunc("/oauth/clients/{id}", server.updateOAuthClient).Methods("PUT") fullAccessAPI.HandleFunc("/oauth/clients/{id}", server.updateOAuthClient).Methods("PUT")
fullAccessAPI.HandleFunc("/oauth/clients/{id}", server.deleteOAuthClient).Methods("DELETE") 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.createGeofenceForBucket).Methods("POST")
fullAccessAPI.HandleFunc("/projects/{project}/buckets/{bucket}/geofence", server.deleteGeofenceForBucket).Methods("DELETE") fullAccessAPI.HandleFunc("/projects/{project}/buckets/{bucket}/geofence", server.deleteGeofenceForBucket).Methods("DELETE")
fullAccessAPI.HandleFunc("/projects/{project}/usage", server.checkProjectUsage).Methods("GET") 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.getAPIKey).Methods("GET")
fullAccessAPI.HandleFunc("/apikeys/{apikey}", server.deleteAPIKey).Methods("DELETE") fullAccessAPI.HandleFunc("/apikeys/{apikey}", server.deleteAPIKey).Methods("DELETE")
fullAccessAPI.HandleFunc("/restkeys/{useremail}", server.addRESTKey).Methods("POST") fullAccessAPI.HandleFunc("/restkeys/{useremail}", server.addRESTKey).Methods("POST")

View File

@ -4,6 +4,7 @@
package admin package admin
import ( import (
"bytes"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
@ -11,8 +12,10 @@ import (
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/zeebo/errs"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"storj.io/common/memory" "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). // updateLimits updates user limits and all project limits for that user (future and existing).
func (server *Server) updateLimits(w http.ResponseWriter, r *http.Request) { func (server *Server) updateLimits(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() 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) { func TestDisableMFA(t *testing.T) {
testplanet.Run(t, testplanet.Config{ testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, SatelliteCount: 1,

View File

@ -44,6 +44,8 @@ type DB interface {
Get(ctx context.Context, projectID uuid.UUID, bucketName []byte) (*Info, error) Get(ctx context.Context, projectID uuid.UUID, bucketName []byte) (*Info, error)
// Insert creates and stores new Info. // Insert creates and stores new Info.
Insert(ctx context.Context, info *Info) (*Info, error) 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 queries partner bucket attribution data.
QueryAttribution(ctx context.Context, userAgent []byte, start time.Time, end time.Time) ([]*BucketUsage, error) QueryAttribution(ctx context.Context, userAgent []byte, start time.Time, end time.Time) ([]*BucketUsage, error)
// QueryAllAttribution queries all partner bucket usage data. // 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) GetBucketID(ctx context.Context, bucket metabase.BucketLocation) (id uuid.UUID, err error)
// UpdateBucket updates an existing bucket // UpdateBucket updates an existing bucket
UpdateBucket(ctx context.Context, bucket Bucket) (_ Bucket, err error) 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 deletes a bucket
DeleteBucket(ctx context.Context, bucketName []byte, projectID uuid.UUID) (err error) DeleteBucket(ctx context.Context, bucketName []byte, projectID uuid.UUID) (err error)
// ListBuckets returns all buckets for a project // 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 is a method for updating project's usage limits.
UpdateUsageLimits(ctx context.Context, id uuid.UUID, limits UsageLimits) error 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. // 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 Update(ctx context.Context, userID uuid.UUID, request UpdateUserRequest) error
// UpdatePaidTier sets whether the user is in the paid tier. // 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 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 is a method to update the user's usage limits for new projects.
UpdateUserProjectLimits(ctx context.Context, id uuid.UUID, limits UsageLimits) error UpdateUserProjectLimits(ctx context.Context, id uuid.UUID, limits UsageLimits) error
// GetProjectLimit is a method to get the users project limit // 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) 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. // Insert implements create partner info.
func (keys *attributionDB) Insert(ctx context.Context, info *attribution.Info) (_ *attribution.Info, err error) { func (keys *attributionDB) Insert(ctx context.Context, info *attribution.Info) (_ *attribution.Info, err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)

View File

@ -168,6 +168,20 @@ func (db *bucketsDB) UpdateBucket(ctx context.Context, bucket buckets.Bucket) (_
return convertDBXtoBucket(dbxBucket) 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. // DeleteBucket deletes a bucket.
func (db *bucketsDB) DeleteBucket(ctx context.Context, bucketName []byte, projectID uuid.UUID) (err error) { func (db *bucketsDB) DeleteBucket(ctx context.Context, bucketName []byte, projectID uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err) 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. // max_buckets is the maximum number of buckets that can be created for the project.
field max_buckets int ( nullable, updatable ) field max_buckets int ( nullable, updatable )
// user_agent is the referred partner who created the project. // 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. // owner_id refers to the user UUID in user.id.
field owner_id blob field owner_id blob
// salt is used for salting the user passphrase for the content. // 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. // user_agent is the first User-Agent that was used to upload data.
// unless the user signed up with a specific partner. // unless the user signed up with a specific partner.
// note: this field is duplicated in bucket_metainfo.user_agent. // 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 // TODO remove as part of release 1.81 or after
field partner_id blob (nullable, default null) field partner_id blob (nullable, default null)
// last_updated is updated whenever the row changes. // last_updated is updated whenever the row changes.
@ -156,6 +156,10 @@ model value_attribution (
) )
create value_attribution () create value_attribution ()
update value_attribution (
where value_attribution.project_id = ?
where value_attribution.bucket_name = ?
)
read one ( read one (
select value_attribution select value_attribution

View File

@ -5573,6 +5573,7 @@ type Project_Update_Fields struct {
RateLimit Project_RateLimit_Field RateLimit Project_RateLimit_Field
BurstLimit Project_BurstLimit_Field BurstLimit Project_BurstLimit_Field
MaxBuckets Project_MaxBuckets_Field MaxBuckets Project_MaxBuckets_Field
UserAgent Project_UserAgent_Field
DefaultPlacement Project_DefaultPlacement_Field DefaultPlacement Project_DefaultPlacement_Field
} }
@ -9335,6 +9336,7 @@ type User_Update_Fields struct {
ShortName User_ShortName_Field ShortName User_ShortName_Field
PasswordHash User_PasswordHash_Field PasswordHash User_PasswordHash_Field
Status User_Status_Field Status User_Status_Field
UserAgent User_UserAgent_Field
ProjectLimit User_ProjectLimit_Field ProjectLimit User_ProjectLimit_Field
ProjectBandwidthLimit User_ProjectBandwidthLimit_Field ProjectBandwidthLimit User_ProjectBandwidthLimit_Field
ProjectStorageLimit User_ProjectStorageLimit_Field ProjectStorageLimit User_ProjectStorageLimit_Field
@ -10313,6 +10315,7 @@ type ValueAttribution_Create_Fields struct {
} }
type ValueAttribution_Update_Fields struct { type ValueAttribution_Update_Fields struct {
UserAgent ValueAttribution_UserAgent_Field
} }
type ValueAttribution_ProjectId_Field struct { 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 = ?")) __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 { if update.DefaultPlacement._set {
__values = append(__values, update.DefaultPlacement.value()) __values = append(__values, update.DefaultPlacement.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("default_placement = ?")) __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 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, func (obj *pgxImpl) Update_User_By_Id(ctx context.Context,
user_id User_Id_Field, user_id User_Id_Field,
update User_Update_Fields) ( 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 = ?")) __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 { if update.ProjectLimit._set {
__values = append(__values, update.ProjectLimit.value()) __values = append(__values, update.ProjectLimit.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("project_limit = ?")) __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 = ?")) __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 { if update.DefaultPlacement._set {
__values = append(__values, update.DefaultPlacement.value()) __values = append(__values, update.DefaultPlacement.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("default_placement = ?")) __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 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, func (obj *pgxcockroachImpl) Update_User_By_Id(ctx context.Context,
user_id User_Id_Field, user_id User_Id_Field,
update User_Update_Fields) ( 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 = ?")) __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 { if update.ProjectLimit._set {
__values = append(__values, update.ProjectLimit.value()) __values = append(__values, update.ProjectLimit.value())
__sets_sql.SQLs = append(__sets_sql.SQLs, __sqlbundle_Literal("project_limit = ?")) __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) 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, func (rx *Rx) Update_WebappSession_By_Id(ctx context.Context,
webapp_session_id WebappSession_Id_Field, webapp_session_id WebappSession_Id_Field,
update WebappSession_Update_Fields) ( update WebappSession_Update_Fields) (
@ -30713,6 +30834,12 @@ type Methods interface {
update User_Update_Fields) ( update User_Update_Fields) (
user *User, err error) 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, Update_WebappSession_By_Id(ctx context.Context,
webapp_session_id WebappSession_Id_Field, webapp_session_id WebappSession_Id_Field,
update WebappSession_Update_Fields) ( 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. // status indicates whether the user is inactive=0, active=1, or deleted=2. See console.UserStatus for details.
field status int ( updatable, autoinsert ) field status int ( updatable, autoinsert )
// user_agent contains the partner parameter from registration. // 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. // created_at indicates when the user was created.
field created_at timestamp ( autoinsert ) field created_at timestamp ( autoinsert )

View File

@ -291,6 +291,19 @@ func (projects *projects) UpdateBucketLimit(ctx context.Context, id uuid.UUID, n
return err 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. // 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) { func (projects *projects) List(ctx context.Context, offset int64, limit int, before time.Time) (_ console.ProjectsPage, err error) {
defer mon.Task()(&ctx)(&err) 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 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. // 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) { func (users *users) UpdateUserProjectLimits(ctx context.Context, id uuid.UUID, limits console.UsageLimits) (err error) {
defer mon.Task()(&ctx)(&err) 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/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
github.com/zeebo/admission/v3 v3.0.3 // indirect github.com/zeebo/admission/v3 v3.0.3 // indirect
github.com/zeebo/blake3 v0.2.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/float16 v0.1.0 // indirect
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect
github.com/zeebo/mwc v0.0.4 // 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 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 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 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 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo= 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= 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/admission/v3 v3.0.3 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.3 // indirect
github.com/zeebo/errs v1.3.0 // 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/float16 v0.1.0 // indirect
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect
github.com/zeebo/mwc v0.0.4 // 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 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= 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 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 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo= 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= github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 h1:+cwNE5KJ3pika4HuzmDHkDlK5myo0G9Sv+eO7WWxnUQ=