satellite/{console,satellitedb}: add account freeze service

This change adds an account freeze service with methods for checking
if a user is frozen, freezing a user, and unfreezing a user.
Furthermore, methods for altering the usage limits of a user or project
have been implemented for use by the account freeze service.

Change-Id: I77fecfac5c152f134bec90165acfe4f1dea957e7
This commit is contained in:
Jeremy Wharton 2022-12-15 01:11:03 -06:00 committed by Storj Robot
parent 7b851b42f7
commit 471f9e4e10
12 changed files with 477 additions and 67 deletions

View File

@ -5,21 +5,26 @@ package console
import ( import (
"context" "context"
"database/sql"
"errors"
"time" "time"
"github.com/zeebo/errs"
"storj.io/common/uuid" "storj.io/common/uuid"
) )
// ErrAccountFreeze is the class for errors that occur during operation of the account freeze service.
var ErrAccountFreeze = errs.Class("account freeze service")
// AccountFreezeEvents exposes methods to manage the account freeze events table in database. // AccountFreezeEvents exposes methods to manage the account freeze events table in database.
// //
// architecture: Database // architecture: Database
type AccountFreezeEvents interface { type AccountFreezeEvents interface {
// Insert is a method for inserting account freeze event into the database. // Upsert is a method for updating an account freeze event if it exists and inserting it otherwise.
Insert(ctx context.Context, event *AccountFreezeEvent) (*AccountFreezeEvent, error) Upsert(ctx context.Context, event *AccountFreezeEvent) (*AccountFreezeEvent, error)
// Get is a method for querying account freeze event from the database by user ID and event type. // Get is a method for querying account freeze event from the database by user ID and event type.
Get(ctx context.Context, userID uuid.UUID, eventType AccountFreezeEventType) (*AccountFreezeEvent, error) Get(ctx context.Context, userID uuid.UUID, eventType AccountFreezeEventType) (*AccountFreezeEvent, error)
// UpdateLimits is a method for updating the limits of an account freeze event by user ID and event type.
UpdateLimits(ctx context.Context, userID uuid.UUID, eventType AccountFreezeEventType, limits *AccountFreezeEventLimits) error
// DeleteAllByUserID is a method for deleting all account freeze events from the database by user ID. // DeleteAllByUserID is a method for deleting all account freeze events from the database by user ID.
DeleteAllByUserID(ctx context.Context, userID uuid.UUID) error DeleteAllByUserID(ctx context.Context, userID uuid.UUID) error
} }
@ -47,3 +52,140 @@ const (
// Warning signifies that the user has been warned that they may be frozen soon. // Warning signifies that the user has been warned that they may be frozen soon.
Warning AccountFreezeEventType = 1 Warning AccountFreezeEventType = 1
) )
// AccountFreezeService encapsulates operations concerning account freezes.
type AccountFreezeService struct {
freezeEventsDB AccountFreezeEvents
usersDB Users
projectsDB Projects
}
// NewAccountFreezeService creates a new account freeze service.
func NewAccountFreezeService(freezeEventsDB AccountFreezeEvents, usersDB Users, projectsDB Projects) *AccountFreezeService {
return &AccountFreezeService{
freezeEventsDB: freezeEventsDB,
usersDB: usersDB,
projectsDB: projectsDB,
}
}
// IsUserFrozen returns whether the user specified by the given ID is frozen.
func (s *AccountFreezeService) IsUserFrozen(ctx context.Context, userID uuid.UUID) (_ bool, err error) {
defer mon.Task()(&ctx)(&err)
_, err = s.freezeEventsDB.Get(ctx, userID, Freeze)
switch {
case errors.Is(err, sql.ErrNoRows):
return false, nil
case err != nil:
return false, ErrAccountFreeze.Wrap(err)
default:
return true, nil
}
}
// FreezeUser freezes the user specified by the given ID.
func (s *AccountFreezeService) FreezeUser(ctx context.Context, userID uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err)
user, err := s.usersDB.Get(ctx, userID)
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
event, err := s.freezeEventsDB.Get(ctx, userID, Freeze)
if errors.Is(err, sql.ErrNoRows) {
event = &AccountFreezeEvent{
UserID: userID,
Type: Freeze,
Limits: &AccountFreezeEventLimits{
User: UsageLimits{
Storage: user.ProjectStorageLimit,
Bandwidth: user.ProjectBandwidthLimit,
Segment: user.ProjectSegmentLimit,
},
Projects: make(map[uuid.UUID]UsageLimits),
},
}
} else if err != nil {
return ErrAccountFreeze.Wrap(err)
}
userLimits := UsageLimits{
Storage: user.ProjectStorageLimit,
Bandwidth: user.ProjectBandwidthLimit,
Segment: user.ProjectSegmentLimit,
}
// If user limits have been zeroed already, we should not override what is in the freeze table.
if userLimits != (UsageLimits{}) {
event.Limits.User = userLimits
}
projects, err := s.projectsDB.GetOwn(ctx, userID)
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
for _, p := range projects {
projLimits := UsageLimits{}
if p.StorageLimit != nil {
projLimits.Storage = p.StorageLimit.Int64()
}
if p.BandwidthLimit != nil {
projLimits.Bandwidth = p.BandwidthLimit.Int64()
}
if p.SegmentLimit != nil {
projLimits.Segment = *p.SegmentLimit
}
// If project limits have been zeroed already, we should not override what is in the freeze table.
if projLimits != (UsageLimits{}) {
event.Limits.Projects[p.ID] = projLimits
}
}
_, err = s.freezeEventsDB.Upsert(ctx, event)
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
err = s.usersDB.UpdateUserProjectLimits(ctx, userID, UsageLimits{})
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
for _, proj := range projects {
err := s.projectsDB.UpdateUsageLimits(ctx, proj.ID, UsageLimits{})
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
}
return nil
}
// UnfreezeUser reverses the freeze placed on the user specified by the given ID.
func (s *AccountFreezeService) UnfreezeUser(ctx context.Context, userID uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err)
event, err := s.freezeEventsDB.Get(ctx, userID, Freeze)
if errors.Is(err, sql.ErrNoRows) {
return ErrAccountFreeze.New("user is not frozen")
}
if event.Limits == nil {
return ErrAccountFreeze.New("freeze event limits are nil")
}
for id, limits := range event.Limits.Projects {
err := s.projectsDB.UpdateUsageLimits(ctx, id, limits)
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
}
err = s.usersDB.UpdateUserProjectLimits(ctx, userID, event.Limits.User)
if err != nil {
return ErrAccountFreeze.Wrap(err)
}
return ErrAccountFreeze.Wrap(s.freezeEventsDB.DeleteAllByUserID(ctx, userID))
}

View File

@ -0,0 +1,210 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
package console_test
import (
"math/rand"
"testing"
"github.com/stretchr/testify/require"
"storj.io/common/testcontext"
"storj.io/storj/private/testplanet"
"storj.io/storj/satellite/console"
)
func getUserLimits(u *console.User) console.UsageLimits {
return console.UsageLimits{
Storage: u.ProjectStorageLimit,
Bandwidth: u.ProjectBandwidthLimit,
Segment: u.ProjectSegmentLimit,
}
}
func getProjectLimits(p *console.Project) console.UsageLimits {
return console.UsageLimits{
Storage: p.StorageLimit.Int64(),
Bandwidth: p.BandwidthLimit.Int64(),
Segment: *p.SegmentLimit,
}
}
func randUsageLimits() console.UsageLimits {
return console.UsageLimits{Storage: rand.Int63(), Bandwidth: rand.Int63(), Segment: rand.Int63()}
}
func TestAccountFreeze(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
usersDB := sat.DB.Console().Users()
projectsDB := sat.DB.Console().Projects()
service := console.NewAccountFreezeService(sat.DB.Console().AccountFreezeEvents(), usersDB, projectsDB)
userLimits := randUsageLimits()
user, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User",
Email: "user@mail.test",
}, 2)
require.NoError(t, err)
require.NoError(t, usersDB.UpdateUserProjectLimits(ctx, user.ID, userLimits))
projLimits := randUsageLimits()
proj, err := sat.AddProject(ctx, user.ID, "")
require.NoError(t, err)
require.NoError(t, projectsDB.UpdateUsageLimits(ctx, proj.ID, projLimits))
frozen, err := service.IsUserFrozen(ctx, user.ID)
require.NoError(t, err)
require.False(t, frozen)
require.NoError(t, service.FreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Zero(t, getUserLimits(user))
proj, err = projectsDB.Get(ctx, proj.ID)
require.NoError(t, err)
require.Zero(t, getProjectLimits(proj))
frozen, err = service.IsUserFrozen(ctx, user.ID)
require.NoError(t, err)
require.True(t, frozen)
})
}
func TestAccountUnfreeze(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
usersDB := sat.DB.Console().Users()
projectsDB := sat.DB.Console().Projects()
service := console.NewAccountFreezeService(sat.DB.Console().AccountFreezeEvents(), usersDB, projectsDB)
userLimits := randUsageLimits()
user, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User",
Email: "user@mail.test",
}, 2)
require.NoError(t, err)
require.NoError(t, usersDB.UpdateUserProjectLimits(ctx, user.ID, userLimits))
projLimits := randUsageLimits()
proj, err := sat.AddProject(ctx, user.ID, "")
require.NoError(t, err)
require.NoError(t, projectsDB.UpdateUsageLimits(ctx, proj.ID, projLimits))
require.NoError(t, service.FreezeUser(ctx, user.ID))
require.NoError(t, service.UnfreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Equal(t, userLimits, getUserLimits(user))
proj, err = projectsDB.Get(ctx, proj.ID)
require.NoError(t, err)
require.Equal(t, projLimits, getProjectLimits(proj))
frozen, err := service.IsUserFrozen(ctx, user.ID)
require.NoError(t, err)
require.False(t, frozen)
})
}
func TestAccountFreezeAlreadyFrozen(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
sat := planet.Satellites[0]
usersDB := sat.DB.Console().Users()
projectsDB := sat.DB.Console().Projects()
service := console.NewAccountFreezeService(sat.DB.Console().AccountFreezeEvents(), usersDB, projectsDB)
userLimits := randUsageLimits()
user, err := sat.AddUser(ctx, console.CreateUser{
FullName: "Test User",
Email: "user@mail.test",
}, 2)
require.NoError(t, err)
require.NoError(t, usersDB.UpdateUserProjectLimits(ctx, user.ID, userLimits))
proj1Limits := randUsageLimits()
proj1, err := sat.AddProject(ctx, user.ID, "")
require.NoError(t, err)
require.NoError(t, projectsDB.UpdateUsageLimits(ctx, proj1.ID, proj1Limits))
// Freezing a frozen user should freeze any projects that were unable to be frozen prior.
// The limits stored for projects frozen by the prior freeze should not be modified.
t.Run("Project limits", func(t *testing.T) {
require.NoError(t, service.FreezeUser(ctx, user.ID))
proj2Limits := randUsageLimits()
proj2, err := sat.AddProject(ctx, user.ID, "")
require.NoError(t, err)
require.NoError(t, projectsDB.UpdateUsageLimits(ctx, proj2.ID, proj2Limits))
require.NoError(t, service.FreezeUser(ctx, user.ID))
user, err := usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Zero(t, getUserLimits(user))
proj2, err = projectsDB.Get(ctx, proj2.ID)
require.NoError(t, err)
require.Zero(t, getProjectLimits(proj2))
require.NoError(t, service.UnfreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Equal(t, userLimits, getUserLimits(user))
proj1, err = projectsDB.Get(ctx, proj1.ID)
require.NoError(t, err)
require.Equal(t, proj1Limits, getProjectLimits(proj1))
proj2, err = projectsDB.Get(ctx, proj2.ID)
require.NoError(t, err)
require.Equal(t, proj2Limits, getProjectLimits(proj2))
})
// Freezing a frozen user should freeze the user's limits if they were unable to be frozen prior.
t.Run("Unfrozen user limits", func(t *testing.T) {
user, err := usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.NoError(t, service.FreezeUser(ctx, user.ID))
require.NoError(t, usersDB.UpdateUserProjectLimits(ctx, user.ID, userLimits))
require.NoError(t, service.FreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Zero(t, getUserLimits(user))
require.NoError(t, service.UnfreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Equal(t, userLimits, getUserLimits(user))
})
// Freezing a frozen user should not modify user limits stored by the prior freeze.
t.Run("Frozen user limits", func(t *testing.T) {
require.NoError(t, service.FreezeUser(ctx, user.ID))
require.NoError(t, service.FreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Zero(t, getUserLimits(user))
require.NoError(t, service.UnfreezeUser(ctx, user.ID))
user, err = usersDB.Get(ctx, user.ID)
require.NoError(t, err)
require.Equal(t, userLimits, getUserLimits(user))
})
})
}

View File

@ -51,6 +51,9 @@ type Projects interface {
GetMaxBuckets(ctx context.Context, id uuid.UUID) (*int, error) GetMaxBuckets(ctx context.Context, id uuid.UUID) (*int, error)
// UpdateBucketLimit is a method for updating projects bucket limit. // UpdateBucketLimit is a method for updating projects bucket limit.
UpdateBucketLimit(ctx context.Context, id uuid.UUID, newLimit int) error UpdateBucketLimit(ctx context.Context, id uuid.UUID, newLimit int) error
// UpdateUsageLimits is a method for updating project's usage limits.
UpdateUsageLimits(ctx context.Context, id uuid.UUID, limits UsageLimits) 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

@ -39,6 +39,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
// 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 // GetProjectLimit is a method to get the users project limit
GetProjectLimit(ctx context.Context, id uuid.UUID) (limit int, err error) GetProjectLimit(ctx context.Context, id uuid.UUID) (limit int, err error)
// GetUserProjectLimits is a method to get the users storage and bandwidth limits for new projects. // GetUserProjectLimits is a method to get the users storage and bandwidth limits for new projects.

View File

@ -20,8 +20,8 @@ type accountFreezeEvents struct {
db dbx.Methods db dbx.Methods
} }
// Insert is a method for inserting account freeze event into the database. // Upsert is a method for updating an account freeze event if it exists and inserting it otherwise.
func (events *accountFreezeEvents) Insert(ctx context.Context, event *console.AccountFreezeEvent) (_ *console.AccountFreezeEvent, err error) { func (events *accountFreezeEvents) Upsert(ctx context.Context, event *console.AccountFreezeEvent) (_ *console.AccountFreezeEvent, err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
if event == nil { if event == nil {
@ -37,7 +37,7 @@ func (events *accountFreezeEvents) Insert(ctx context.Context, event *console.Ac
createFields.Limits = dbx.AccountFreezeEvent_Limits(limitBytes) createFields.Limits = dbx.AccountFreezeEvent_Limits(limitBytes)
} }
dbxEvent, err := events.db.Create_AccountFreezeEvent(ctx, dbxEvent, err := events.db.Replace_AccountFreezeEvent(ctx,
dbx.AccountFreezeEvent_UserId(event.UserID.Bytes()), dbx.AccountFreezeEvent_UserId(event.UserID.Bytes()),
dbx.AccountFreezeEvent_Event(int(event.Type)), dbx.AccountFreezeEvent_Event(int(event.Type)),
createFields, createFields,
@ -64,26 +64,6 @@ func (events *accountFreezeEvents) Get(ctx context.Context, userID uuid.UUID, ev
return fromDBXAccountFreezeEvent(dbxEvent) return fromDBXAccountFreezeEvent(dbxEvent)
} }
// UpdateLimits is a method for updating the limits of an account freeze event by user ID and event type.
func (events *accountFreezeEvents) UpdateLimits(ctx context.Context, userID uuid.UUID, eventType console.AccountFreezeEventType, limits *console.AccountFreezeEventLimits) (err error) {
defer mon.Task()(&ctx)(&err)
limitBytes, err := json.Marshal(limits)
if err != nil {
return err
}
_, err = events.db.Update_AccountFreezeEvent_By_UserId_And_Event(ctx,
dbx.AccountFreezeEvent_UserId(userID.Bytes()),
dbx.AccountFreezeEvent_Event(int(eventType)),
dbx.AccountFreezeEvent_Update_Fields{
Limits: dbx.AccountFreezeEvent_Limits(limitBytes),
},
)
return err
}
// DeleteAllByUserID is a method for deleting all account freeze events from the database by user ID. // DeleteAllByUserID is a method for deleting all account freeze events from the database by user ID.
func (events *accountFreezeEvents) DeleteAllByUserID(ctx context.Context, userID uuid.UUID) (err error) { func (events *accountFreezeEvents) DeleteAllByUserID(ctx context.Context, userID uuid.UUID) (err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)

View File

@ -22,11 +22,7 @@ import (
func TestAccountFreezeEvents(t *testing.T) { func TestAccountFreezeEvents(t *testing.T) {
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) { satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
randUsageLimits := func() console.UsageLimits { randUsageLimits := func() console.UsageLimits {
return console.UsageLimits{ return console.UsageLimits{Storage: rand.Int63(), Bandwidth: rand.Int63(), Segment: rand.Int63()}
Storage: rand.Int63(),
Bandwidth: rand.Int63(),
Segment: rand.Int63(),
}
} }
event := &console.AccountFreezeEvent{ event := &console.AccountFreezeEvent{
@ -44,12 +40,12 @@ func TestAccountFreezeEvents(t *testing.T) {
eventsDB := db.Console().AccountFreezeEvents() eventsDB := db.Console().AccountFreezeEvents()
t.Run("Can't insert nil event", func(t *testing.T) { t.Run("Can't insert nil event", func(t *testing.T) {
_, err := eventsDB.Insert(ctx, nil) _, err := eventsDB.Upsert(ctx, nil)
require.Error(t, err) require.Error(t, err)
}) })
t.Run("Insert event", func(t *testing.T) { t.Run("Insert event", func(t *testing.T) {
dbEvent, err := eventsDB.Insert(ctx, event) dbEvent, err := eventsDB.Upsert(ctx, event)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, dbEvent) require.NotNil(t, dbEvent)
require.WithinDuration(t, time.Now(), dbEvent.CreatedAt, time.Minute) require.WithinDuration(t, time.Now(), dbEvent.CreatedAt, time.Minute)
@ -57,11 +53,6 @@ func TestAccountFreezeEvents(t *testing.T) {
require.Equal(t, event, dbEvent) require.Equal(t, event, dbEvent)
}) })
t.Run("Can't insert duplicate event", func(t *testing.T) {
_, err := eventsDB.Insert(ctx, event)
require.Error(t, err)
})
t.Run("Get event", func(t *testing.T) { t.Run("Get event", func(t *testing.T) {
dbEvent, err := eventsDB.Get(ctx, event.UserID, event.Type) dbEvent, err := eventsDB.Get(ctx, event.UserID, event.Type)
require.NoError(t, err) require.NoError(t, err)
@ -71,18 +62,22 @@ func TestAccountFreezeEvents(t *testing.T) {
}) })
t.Run("Update event limits", func(t *testing.T) { t.Run("Update event limits", func(t *testing.T) {
limits := &console.AccountFreezeEventLimits{ event.Limits = &console.AccountFreezeEventLimits{
User: randUsageLimits(), User: randUsageLimits(),
Projects: map[uuid.UUID]console.UsageLimits{ Projects: map[uuid.UUID]console.UsageLimits{
testrand.UUID(): randUsageLimits(), testrand.UUID(): randUsageLimits(),
}, },
} }
require.NoError(t, eventsDB.UpdateLimits(ctx, event.UserID, event.Type, limits))
_, err := eventsDB.Upsert(ctx, event)
require.NoError(t, err)
dbEvent, err := eventsDB.Get(ctx, event.UserID, event.Type) dbEvent, err := eventsDB.Get(ctx, event.UserID, event.Type)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, limits, dbEvent.Limits) require.Equal(t, event.Limits, dbEvent.Limits)
require.NoError(t, eventsDB.UpdateLimits(ctx, event.UserID, event.Type, nil)) event.Limits = nil
_, err = eventsDB.Upsert(ctx, event)
require.NoError(t, err)
dbEvent, err = eventsDB.Get(ctx, event.UserID, event.Type) dbEvent, err = eventsDB.Get(ctx, event.UserID, event.Type)
require.NoError(t, err) require.NoError(t, err)
require.Nil(t, dbEvent.Limits) require.Nil(t, dbEvent.Limits)

View File

@ -10,7 +10,7 @@ model account_freeze_event (
field created_at timestamp ( default current_timestamp ) field created_at timestamp ( default current_timestamp )
) )
create account_freeze_event() create account_freeze_event( replace )
read one ( read one (
select account_freeze_event select account_freeze_event

View File

@ -12830,7 +12830,7 @@ type WalletAddress_Row struct {
WalletAddress []byte WalletAddress []byte
} }
func (obj *pgxImpl) Create_AccountFreezeEvent(ctx context.Context, func (obj *pgxImpl) Replace_AccountFreezeEvent(ctx context.Context,
account_freeze_event_user_id AccountFreezeEvent_UserId_Field, account_freeze_event_user_id AccountFreezeEvent_UserId_Field,
account_freeze_event_event AccountFreezeEvent_Event_Field, account_freeze_event_event AccountFreezeEvent_Event_Field,
optional AccountFreezeEvent_Create_Fields) ( optional AccountFreezeEvent_Create_Fields) (
@ -12844,7 +12844,7 @@ func (obj *pgxImpl) Create_AccountFreezeEvent(ctx context.Context,
var __placeholders = &__sqlbundle_Hole{SQL: __sqlbundle_Literal("?, ?, ?")} var __placeholders = &__sqlbundle_Hole{SQL: __sqlbundle_Literal("?, ?, ?")}
var __clause = &__sqlbundle_Hole{SQL: __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("("), __columns, __sqlbundle_Literal(") VALUES ("), __placeholders, __sqlbundle_Literal(")")}}} var __clause = &__sqlbundle_Hole{SQL: __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("("), __columns, __sqlbundle_Literal(") VALUES ("), __placeholders, __sqlbundle_Literal(")")}}}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("INSERT INTO account_freeze_events "), __clause, __sqlbundle_Literal(" RETURNING account_freeze_events.user_id, account_freeze_events.event, account_freeze_events.limits, account_freeze_events.created_at")}} var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("INSERT INTO account_freeze_events "), __clause, __sqlbundle_Literal(" ON CONFLICT ( user_id, event ) DO UPDATE SET user_id = EXCLUDED.user_id, event = EXCLUDED.event, limits = EXCLUDED.limits, created_at = EXCLUDED.created_at RETURNING account_freeze_events.user_id, account_freeze_events.event, account_freeze_events.limits, account_freeze_events.created_at")}}
var __values []interface{} var __values []interface{}
__values = append(__values, __user_id_val, __event_val, __limits_val) __values = append(__values, __user_id_val, __event_val, __limits_val)
@ -20781,7 +20781,7 @@ func (obj *pgxImpl) deleteAll(ctx context.Context) (count int64, err error) {
} }
func (obj *pgxcockroachImpl) Create_AccountFreezeEvent(ctx context.Context, func (obj *pgxcockroachImpl) Replace_AccountFreezeEvent(ctx context.Context,
account_freeze_event_user_id AccountFreezeEvent_UserId_Field, account_freeze_event_user_id AccountFreezeEvent_UserId_Field,
account_freeze_event_event AccountFreezeEvent_Event_Field, account_freeze_event_event AccountFreezeEvent_Event_Field,
optional AccountFreezeEvent_Create_Fields) ( optional AccountFreezeEvent_Create_Fields) (
@ -20795,7 +20795,7 @@ func (obj *pgxcockroachImpl) Create_AccountFreezeEvent(ctx context.Context,
var __placeholders = &__sqlbundle_Hole{SQL: __sqlbundle_Literal("?, ?, ?")} var __placeholders = &__sqlbundle_Hole{SQL: __sqlbundle_Literal("?, ?, ?")}
var __clause = &__sqlbundle_Hole{SQL: __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("("), __columns, __sqlbundle_Literal(") VALUES ("), __placeholders, __sqlbundle_Literal(")")}}} var __clause = &__sqlbundle_Hole{SQL: __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("("), __columns, __sqlbundle_Literal(") VALUES ("), __placeholders, __sqlbundle_Literal(")")}}}
var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("INSERT INTO account_freeze_events "), __clause, __sqlbundle_Literal(" RETURNING account_freeze_events.user_id, account_freeze_events.event, account_freeze_events.limits, account_freeze_events.created_at")}} var __embed_stmt = __sqlbundle_Literals{Join: "", SQLs: []__sqlbundle_SQL{__sqlbundle_Literal("UPSERT INTO account_freeze_events "), __clause, __sqlbundle_Literal(" RETURNING account_freeze_events.user_id, account_freeze_events.event, account_freeze_events.limits, account_freeze_events.created_at")}}
var __values []interface{} var __values []interface{}
__values = append(__values, __user_id_val, __event_val, __limits_val) __values = append(__values, __user_id_val, __event_val, __limits_val)
@ -29171,19 +29171,6 @@ func (rx *Rx) CreateNoReturn_StorjscanWallet(ctx context.Context,
} }
func (rx *Rx) Create_AccountFreezeEvent(ctx context.Context,
account_freeze_event_user_id AccountFreezeEvent_UserId_Field,
account_freeze_event_event AccountFreezeEvent_Event_Field,
optional AccountFreezeEvent_Create_Fields) (
account_freeze_event *AccountFreezeEvent, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Create_AccountFreezeEvent(ctx, account_freeze_event_user_id, account_freeze_event_event, optional)
}
func (rx *Rx) Create_ApiKey(ctx context.Context, func (rx *Rx) Create_ApiKey(ctx context.Context,
api_key_id ApiKey_Id_Field, api_key_id ApiKey_Id_Field,
api_key_project_id ApiKey_ProjectId_Field, api_key_project_id ApiKey_ProjectId_Field,
@ -30515,6 +30502,19 @@ func (rx *Rx) ReplaceNoReturn_StoragenodePaystub(ctx context.Context,
} }
func (rx *Rx) Replace_AccountFreezeEvent(ctx context.Context,
account_freeze_event_user_id AccountFreezeEvent_UserId_Field,
account_freeze_event_event AccountFreezeEvent_Event_Field,
optional AccountFreezeEvent_Create_Fields) (
account_freeze_event *AccountFreezeEvent, err error) {
var tx *Tx
if tx, err = rx.getTx(ctx); err != nil {
return
}
return tx.Replace_AccountFreezeEvent(ctx, account_freeze_event_user_id, account_freeze_event_event, optional)
}
func (rx *Rx) UpdateNoReturn_AccountingTimestamps_By_Name(ctx context.Context, func (rx *Rx) UpdateNoReturn_AccountingTimestamps_By_Name(ctx context.Context,
accounting_timestamps_name AccountingTimestamps_Name_Field, accounting_timestamps_name AccountingTimestamps_Name_Field,
update AccountingTimestamps_Update_Fields) ( update AccountingTimestamps_Update_Fields) (
@ -30989,12 +30989,6 @@ type Methods interface {
storjscan_wallet_wallet_address StorjscanWallet_WalletAddress_Field) ( storjscan_wallet_wallet_address StorjscanWallet_WalletAddress_Field) (
err error) err error)
Create_AccountFreezeEvent(ctx context.Context,
account_freeze_event_user_id AccountFreezeEvent_UserId_Field,
account_freeze_event_event AccountFreezeEvent_Event_Field,
optional AccountFreezeEvent_Create_Fields) (
account_freeze_event *AccountFreezeEvent, err error)
Create_ApiKey(ctx context.Context, Create_ApiKey(ctx context.Context,
api_key_id ApiKey_Id_Field, api_key_id ApiKey_Id_Field,
api_key_project_id ApiKey_ProjectId_Field, api_key_project_id ApiKey_ProjectId_Field,
@ -31612,6 +31606,12 @@ type Methods interface {
storagenode_paystub_distributed StoragenodePaystub_Distributed_Field) ( storagenode_paystub_distributed StoragenodePaystub_Distributed_Field) (
err error) err error)
Replace_AccountFreezeEvent(ctx context.Context,
account_freeze_event_user_id AccountFreezeEvent_UserId_Field,
account_freeze_event_event AccountFreezeEvent_Event_Field,
optional AccountFreezeEvent_Create_Fields) (
account_freeze_event *AccountFreezeEvent, err error)
UpdateNoReturn_AccountingTimestamps_By_Name(ctx context.Context, UpdateNoReturn_AccountingTimestamps_By_Name(ctx context.Context,
accounting_timestamps_name AccountingTimestamps_Name_Field, accounting_timestamps_name AccountingTimestamps_Name_Field,
update AccountingTimestamps_Update_Fields) ( update AccountingTimestamps_Update_Fields) (

View File

@ -455,3 +455,18 @@ func (projects *projects) GetMaxBuckets(ctx context.Context, id uuid.UUID) (maxB
} }
return dbxRow.MaxBuckets, nil return dbxRow.MaxBuckets, nil
} }
// UpdateUsageLimits is a method for updating project's bandwidth, storage, and segment limits.
func (projects *projects) UpdateUsageLimits(ctx context.Context, id uuid.UUID, limits console.UsageLimits) (err error) {
defer mon.Task()(&ctx)(&err)
_, err = projects.db.Update_Project_By_Id(ctx,
dbx.Project_Id(id[:]),
dbx.Project_Update_Fields{
BandwidthLimit: dbx.Project_BandwidthLimit(limits.Bandwidth),
UsageLimit: dbx.Project_UsageLimit(limits.Storage),
SegmentLimit: dbx.Project_SegmentLimit(limits.Segment),
},
)
return err
}

View File

@ -4,6 +4,7 @@
package satellitedb_test package satellitedb_test
import ( import (
"math/rand"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -54,3 +55,23 @@ func TestProjectsGetSalt(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
} }
func TestUpdateProjectUsageLimits(t *testing.T) {
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
limits := console.UsageLimits{Storage: rand.Int63(), Bandwidth: rand.Int63(), Segment: rand.Int63()}
projectsRepo := db.Console().Projects()
proj, err := projectsRepo.Insert(ctx, &console.Project{})
require.NoError(t, err)
require.NotNil(t, proj)
err = projectsRepo.UpdateUsageLimits(ctx, proj.ID, limits)
require.NoError(t, err)
proj, err = projectsRepo.Get(ctx, proj.ID)
require.NoError(t, err)
require.Equal(t, limits.Bandwidth, proj.BandwidthLimit.Int64())
require.Equal(t, limits.Storage, proj.StorageLimit.Int64())
require.Equal(t, limits.Segment, *proj.SegmentLimit)
})
}

View File

@ -240,6 +240,23 @@ func (users *users) UpdatePaidTier(ctx context.Context, id uuid.UUID, paidTier b
return err 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)
_, err = users.db.Update_User_By_Id(
ctx,
dbx.User_Id(id[:]),
dbx.User_Update_Fields{
ProjectBandwidthLimit: dbx.User_ProjectBandwidthLimit(limits.Bandwidth),
ProjectStorageLimit: dbx.User_ProjectStorageLimit(limits.Storage),
ProjectSegmentLimit: dbx.User_ProjectSegmentLimit(limits.Segment),
},
)
return err
}
// GetProjectLimit is a method to get the users project limit. // GetProjectLimit is a method to get the users project limit.
func (users *users) GetProjectLimit(ctx context.Context, id uuid.UUID) (limit int, err error) { func (users *users) GetProjectLimit(ctx context.Context, id uuid.UUID) (limit int, err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)

View File

@ -4,6 +4,7 @@
package satellitedb_test package satellitedb_test
import ( import (
"math/rand"
"testing" "testing"
"time" "time"
@ -301,3 +302,27 @@ func TestUpdateUser(t *testing.T) {
require.Equal(t, u, updatedUser) require.Equal(t, u, updatedUser)
}) })
} }
func TestUpdateUserProjectLimits(t *testing.T) {
satellitedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db satellite.DB) {
limits := console.UsageLimits{Storage: rand.Int63(), Bandwidth: rand.Int63(), Segment: rand.Int63()}
usersRepo := db.Console().Users()
user, err := usersRepo.Insert(ctx, &console.User{
ID: testrand.UUID(),
FullName: "User",
Email: "test@mail.test",
PasswordHash: []byte("123a123"),
})
require.NoError(t, err)
err = usersRepo.UpdateUserProjectLimits(ctx, user.ID, limits)
require.NoError(t, err)
user, err = usersRepo.Get(ctx, user.ID)
require.NoError(t, err)
require.Equal(t, limits.Bandwidth, user.ProjectBandwidthLimit)
require.Equal(t, limits.Storage, user.ProjectStorageLimit)
require.Equal(t, limits.Segment, user.ProjectSegmentLimit)
})
}