diff --git a/hack/gen/main.go b/hack/gen/main.go index 5117172..0a1f052 100644 --- a/hack/gen/main.go +++ b/hack/gen/main.go @@ -12,7 +12,7 @@ func main() { }) // Generate basic type-safe DAO API for struct `model.User` following conventions - g.ApplyBasic(entity.Storage{}, entity.Matter{}, entity.RecycleBin{}) + g.ApplyBasic(entity.Storage{}, entity.Matter{}, entity.RecycleBin{}, entity.UserStorage{}) // Generate the code g.Execute() diff --git a/internal/app/api/recyclebin.go b/internal/app/api/recyclebin.go index 8419923..e1a575f 100644 --- a/internal/app/api/recyclebin.go +++ b/internal/app/api/recyclebin.go @@ -67,7 +67,7 @@ func (rs *RecycleBinResource) delete(c *gin.Context) { } func (rs *RecycleBinResource) clean(c *gin.Context) { - if err := rs.rbf.Clean(c, ginutil.QueryInt64(c, "sid")); err != nil { + if err := rs.rbf.Clean(c, ginutil.QueryInt64(c, "sid"), authed.UidGet(c)); err != nil { ginutil.JSONServerError(c, err) return } diff --git a/internal/app/dao/dao.go b/internal/app/dao/dao.go index a4f3c62..1bee7d0 100644 --- a/internal/app/dao/dao.go +++ b/internal/app/dao/dao.go @@ -39,10 +39,6 @@ type DBQueryFactory struct { } func NewDBQueryFactory() *DBQueryFactory { - return &DBQueryFactory{} -} - -func (D *DBQueryFactory) Q() *query.Query { if !viper.IsSet("installed") { return nil } @@ -51,5 +47,9 @@ func (D *DBQueryFactory) Q() *query.Query { log.Fatalln(err) } + return &DBQueryFactory{} +} + +func (D *DBQueryFactory) Q() *query.Query { return query.Use(gdb) } diff --git a/internal/app/dao/user.go b/internal/app/dao/user.go index 303693f..edab31e 100644 --- a/internal/app/dao/user.go +++ b/internal/app/dao/user.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/saltbo/gopkg/strutil" + "github.com/saltbo/zpan/internal/app/entity" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -85,8 +86,8 @@ func (u *User) Create(user *model.User, storageMax uint64) (*model.User, error) Uid: user.Id, Nickname: user.Email[:strings.Index(user.Email, "@")], } - user.Storage = model.UserStorage{ - Max: model.UserStorageDefaultSize, + user.Storage = entity.UserStorage{ + Max: entity.UserStorageDefaultSize, } if storageMax > 0 { user.Storage.Max = storageMax @@ -144,5 +145,5 @@ func (u *User) UpdateProfile(uid int64, up *model.UserProfile) error { } func (u *User) UpdateStorage(uid int64, quota uint64) error { - return gdb.Model(model.UserStorage{}).Where("uid=?", uid).Update("max", quota).Error + return gdb.Model(entity.UserStorage{}).Where("uid=?", uid).Update("max", quota).Error } diff --git a/internal/app/model/user_storage.go b/internal/app/entity/user_storage.go similarity index 78% rename from internal/app/model/user_storage.go rename to internal/app/entity/user_storage.go index 2d80ca6..654414f 100644 --- a/internal/app/model/user_storage.go +++ b/internal/app/entity/user_storage.go @@ -1,4 +1,4 @@ -package model +package entity import ( "time" @@ -21,12 +21,12 @@ type UserStorage struct { Deleted gorm.DeletedAt `json:"-"` } -func (UserStorage) TableName() string { +func (us *UserStorage) TableName() string { return "zp_storage_quota" } -func (sq *UserStorage) Overflowed(addonSize int64) bool { - if sq.Used+uint64(addonSize) >= sq.Max { +func (us *UserStorage) Overflowed(addonSize int64) bool { + if us.Used+uint64(addonSize) >= us.Max { return true } diff --git a/internal/app/model/base.go b/internal/app/model/base.go index 74e6976..dd4fba7 100644 --- a/internal/app/model/base.go +++ b/internal/app/model/base.go @@ -8,7 +8,7 @@ func Tables() []interface{} { new(User), new(UserKey), new(UserProfile), - new(UserStorage), + new(entity.UserStorage), new(entity.Storage), new(entity.Matter), new(Share), diff --git a/internal/app/model/user.go b/internal/app/model/user.go index 25ac3ad..7bdfc4c 100644 --- a/internal/app/model/user.go +++ b/internal/app/model/user.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/saltbo/zpan/internal/app/entity" "gorm.io/gorm" ) @@ -45,20 +46,20 @@ func NewUserCreateOption() UserCreateOption { } type User struct { - Id int64 `json:"id"` - Email string `json:"email" gorm:"size:32;unique_index;not null"` - Username string `json:"username" gorm:"size:20;unique_index;not null"` - Password string `json:"-" gorm:"size:32;not null"` - Status uint8 `json:"-" gorm:"size:1;not null"` - StatusTxt string `json:"status" gorm:"-"` - Roles string `json:"-" gorm:"size:64;not null"` - RoleTxt string `json:"role" gorm:"-"` - Ticket string `json:"ticket" gorm:"size:6;unique_index;not null"` - Profile UserProfile `json:"profile,omitempty" gorm:"foreignKey:Uid"` - Storage UserStorage `json:"storage,omitempty" gorm:"foreignKey:Uid"` - Created time.Time `json:"created" gorm:"autoCreateTime;not null"` - Updated time.Time `json:"updated" gorm:"autoUpdateTime;not null"` - Deleted gorm.DeletedAt `json:"-"` + Id int64 `json:"id"` + Email string `json:"email" gorm:"size:32;unique_index;not null"` + Username string `json:"username" gorm:"size:20;unique_index;not null"` + Password string `json:"-" gorm:"size:32;not null"` + Status uint8 `json:"-" gorm:"size:1;not null"` + StatusTxt string `json:"status" gorm:"-"` + Roles string `json:"-" gorm:"size:64;not null"` + RoleTxt string `json:"role" gorm:"-"` + Ticket string `json:"ticket" gorm:"size:6;unique_index;not null"` + Profile UserProfile `json:"profile,omitempty" gorm:"foreignKey:Uid"` + Storage entity.UserStorage `json:"storage,omitempty" gorm:"foreignKey:Uid"` + Created time.Time `json:"created" gorm:"autoCreateTime;not null"` + Updated time.Time `json:"updated" gorm:"autoUpdateTime;not null"` + Deleted gorm.DeletedAt `json:"-"` Token string `json:"-" gorm:"-"` } diff --git a/internal/app/repo/query/gen.go b/internal/app/repo/query/gen.go index 4f35b8c..47db34f 100644 --- a/internal/app/repo/query/gen.go +++ b/internal/app/repo/query/gen.go @@ -16,10 +16,11 @@ import ( ) var ( - Q = new(Query) - Matter *matter - RecycleBin *recycleBin - Storage *storage + Q = new(Query) + Matter *matter + RecycleBin *recycleBin + Storage *storage + UserStorage *userStorage ) func SetDefault(db *gorm.DB, opts ...gen.DOOption) { @@ -27,33 +28,37 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) { Matter = &Q.Matter RecycleBin = &Q.RecycleBin Storage = &Q.Storage + UserStorage = &Q.UserStorage } func Use(db *gorm.DB, opts ...gen.DOOption) *Query { return &Query{ - db: db, - Matter: newMatter(db, opts...), - RecycleBin: newRecycleBin(db, opts...), - Storage: newStorage(db, opts...), + db: db, + Matter: newMatter(db, opts...), + RecycleBin: newRecycleBin(db, opts...), + Storage: newStorage(db, opts...), + UserStorage: newUserStorage(db, opts...), } } type Query struct { db *gorm.DB - Matter matter - RecycleBin recycleBin - Storage storage + Matter matter + RecycleBin recycleBin + Storage storage + UserStorage userStorage } func (q *Query) Available() bool { return q.db != nil } func (q *Query) clone(db *gorm.DB) *Query { return &Query{ - db: db, - Matter: q.Matter.clone(db), - RecycleBin: q.RecycleBin.clone(db), - Storage: q.Storage.clone(db), + db: db, + Matter: q.Matter.clone(db), + RecycleBin: q.RecycleBin.clone(db), + Storage: q.Storage.clone(db), + UserStorage: q.UserStorage.clone(db), } } @@ -67,24 +72,27 @@ func (q *Query) WriteDB() *Query { func (q *Query) ReplaceDB(db *gorm.DB) *Query { return &Query{ - db: db, - Matter: q.Matter.replaceDB(db), - RecycleBin: q.RecycleBin.replaceDB(db), - Storage: q.Storage.replaceDB(db), + db: db, + Matter: q.Matter.replaceDB(db), + RecycleBin: q.RecycleBin.replaceDB(db), + Storage: q.Storage.replaceDB(db), + UserStorage: q.UserStorage.replaceDB(db), } } type queryCtx struct { - Matter IMatterDo - RecycleBin IRecycleBinDo - Storage IStorageDo + Matter IMatterDo + RecycleBin IRecycleBinDo + Storage IStorageDo + UserStorage IUserStorageDo } func (q *Query) WithContext(ctx context.Context) *queryCtx { return &queryCtx{ - Matter: q.Matter.WithContext(ctx), - RecycleBin: q.RecycleBin.WithContext(ctx), - Storage: q.Storage.WithContext(ctx), + Matter: q.Matter.WithContext(ctx), + RecycleBin: q.RecycleBin.WithContext(ctx), + Storage: q.Storage.WithContext(ctx), + UserStorage: q.UserStorage.WithContext(ctx), } } diff --git a/internal/app/repo/query/zp_storage_quota.gen.go b/internal/app/repo/query/zp_storage_quota.gen.go new file mode 100644 index 0000000..caab627 --- /dev/null +++ b/internal/app/repo/query/zp_storage_quota.gen.go @@ -0,0 +1,404 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/saltbo/zpan/internal/app/entity" +) + +func newUserStorage(db *gorm.DB, opts ...gen.DOOption) userStorage { + _userStorage := userStorage{} + + _userStorage.userStorageDo.UseDB(db, opts...) + _userStorage.userStorageDo.UseModel(&entity.UserStorage{}) + + tableName := _userStorage.userStorageDo.TableName() + _userStorage.ALL = field.NewAsterisk(tableName) + _userStorage.Id = field.NewInt64(tableName, "id") + _userStorage.Uid = field.NewInt64(tableName, "uid") + _userStorage.Max = field.NewUint64(tableName, "max") + _userStorage.Used = field.NewUint64(tableName, "used") + _userStorage.Created = field.NewTime(tableName, "created") + _userStorage.Updated = field.NewTime(tableName, "updated") + _userStorage.Deleted = field.NewField(tableName, "deleted") + + _userStorage.fillFieldMap() + + return _userStorage +} + +type userStorage struct { + userStorageDo + + ALL field.Asterisk + Id field.Int64 + Uid field.Int64 + Max field.Uint64 + Used field.Uint64 + Created field.Time + Updated field.Time + Deleted field.Field + + fieldMap map[string]field.Expr +} + +func (u userStorage) Table(newTableName string) *userStorage { + u.userStorageDo.UseTable(newTableName) + return u.updateTableName(newTableName) +} + +func (u userStorage) As(alias string) *userStorage { + u.userStorageDo.DO = *(u.userStorageDo.As(alias).(*gen.DO)) + return u.updateTableName(alias) +} + +func (u *userStorage) updateTableName(table string) *userStorage { + u.ALL = field.NewAsterisk(table) + u.Id = field.NewInt64(table, "id") + u.Uid = field.NewInt64(table, "uid") + u.Max = field.NewUint64(table, "max") + u.Used = field.NewUint64(table, "used") + u.Created = field.NewTime(table, "created") + u.Updated = field.NewTime(table, "updated") + u.Deleted = field.NewField(table, "deleted") + + u.fillFieldMap() + + return u +} + +func (u *userStorage) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := u.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (u *userStorage) fillFieldMap() { + u.fieldMap = make(map[string]field.Expr, 7) + u.fieldMap["id"] = u.Id + u.fieldMap["uid"] = u.Uid + u.fieldMap["max"] = u.Max + u.fieldMap["used"] = u.Used + u.fieldMap["created"] = u.Created + u.fieldMap["updated"] = u.Updated + u.fieldMap["deleted"] = u.Deleted +} + +func (u userStorage) clone(db *gorm.DB) userStorage { + u.userStorageDo.ReplaceConnPool(db.Statement.ConnPool) + return u +} + +func (u userStorage) replaceDB(db *gorm.DB) userStorage { + u.userStorageDo.ReplaceDB(db) + return u +} + +type userStorageDo struct{ gen.DO } + +type IUserStorageDo interface { + gen.SubQuery + Debug() IUserStorageDo + WithContext(ctx context.Context) IUserStorageDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IUserStorageDo + WriteDB() IUserStorageDo + As(alias string) gen.Dao + Session(config *gorm.Session) IUserStorageDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IUserStorageDo + Not(conds ...gen.Condition) IUserStorageDo + Or(conds ...gen.Condition) IUserStorageDo + Select(conds ...field.Expr) IUserStorageDo + Where(conds ...gen.Condition) IUserStorageDo + Order(conds ...field.Expr) IUserStorageDo + Distinct(cols ...field.Expr) IUserStorageDo + Omit(cols ...field.Expr) IUserStorageDo + Join(table schema.Tabler, on ...field.Expr) IUserStorageDo + LeftJoin(table schema.Tabler, on ...field.Expr) IUserStorageDo + RightJoin(table schema.Tabler, on ...field.Expr) IUserStorageDo + Group(cols ...field.Expr) IUserStorageDo + Having(conds ...gen.Condition) IUserStorageDo + Limit(limit int) IUserStorageDo + Offset(offset int) IUserStorageDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IUserStorageDo + Unscoped() IUserStorageDo + Create(values ...*entity.UserStorage) error + CreateInBatches(values []*entity.UserStorage, batchSize int) error + Save(values ...*entity.UserStorage) error + First() (*entity.UserStorage, error) + Take() (*entity.UserStorage, error) + Last() (*entity.UserStorage, error) + Find() ([]*entity.UserStorage, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.UserStorage, err error) + FindInBatches(result *[]*entity.UserStorage, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*entity.UserStorage) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IUserStorageDo + Assign(attrs ...field.AssignExpr) IUserStorageDo + Joins(fields ...field.RelationField) IUserStorageDo + Preload(fields ...field.RelationField) IUserStorageDo + FirstOrInit() (*entity.UserStorage, error) + FirstOrCreate() (*entity.UserStorage, error) + FindByPage(offset int, limit int) (result []*entity.UserStorage, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IUserStorageDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (u userStorageDo) Debug() IUserStorageDo { + return u.withDO(u.DO.Debug()) +} + +func (u userStorageDo) WithContext(ctx context.Context) IUserStorageDo { + return u.withDO(u.DO.WithContext(ctx)) +} + +func (u userStorageDo) ReadDB() IUserStorageDo { + return u.Clauses(dbresolver.Read) +} + +func (u userStorageDo) WriteDB() IUserStorageDo { + return u.Clauses(dbresolver.Write) +} + +func (u userStorageDo) Session(config *gorm.Session) IUserStorageDo { + return u.withDO(u.DO.Session(config)) +} + +func (u userStorageDo) Clauses(conds ...clause.Expression) IUserStorageDo { + return u.withDO(u.DO.Clauses(conds...)) +} + +func (u userStorageDo) Returning(value interface{}, columns ...string) IUserStorageDo { + return u.withDO(u.DO.Returning(value, columns...)) +} + +func (u userStorageDo) Not(conds ...gen.Condition) IUserStorageDo { + return u.withDO(u.DO.Not(conds...)) +} + +func (u userStorageDo) Or(conds ...gen.Condition) IUserStorageDo { + return u.withDO(u.DO.Or(conds...)) +} + +func (u userStorageDo) Select(conds ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.Select(conds...)) +} + +func (u userStorageDo) Where(conds ...gen.Condition) IUserStorageDo { + return u.withDO(u.DO.Where(conds...)) +} + +func (u userStorageDo) Order(conds ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.Order(conds...)) +} + +func (u userStorageDo) Distinct(cols ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.Distinct(cols...)) +} + +func (u userStorageDo) Omit(cols ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.Omit(cols...)) +} + +func (u userStorageDo) Join(table schema.Tabler, on ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.Join(table, on...)) +} + +func (u userStorageDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.LeftJoin(table, on...)) +} + +func (u userStorageDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.RightJoin(table, on...)) +} + +func (u userStorageDo) Group(cols ...field.Expr) IUserStorageDo { + return u.withDO(u.DO.Group(cols...)) +} + +func (u userStorageDo) Having(conds ...gen.Condition) IUserStorageDo { + return u.withDO(u.DO.Having(conds...)) +} + +func (u userStorageDo) Limit(limit int) IUserStorageDo { + return u.withDO(u.DO.Limit(limit)) +} + +func (u userStorageDo) Offset(offset int) IUserStorageDo { + return u.withDO(u.DO.Offset(offset)) +} + +func (u userStorageDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserStorageDo { + return u.withDO(u.DO.Scopes(funcs...)) +} + +func (u userStorageDo) Unscoped() IUserStorageDo { + return u.withDO(u.DO.Unscoped()) +} + +func (u userStorageDo) Create(values ...*entity.UserStorage) error { + if len(values) == 0 { + return nil + } + return u.DO.Create(values) +} + +func (u userStorageDo) CreateInBatches(values []*entity.UserStorage, batchSize int) error { + return u.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (u userStorageDo) Save(values ...*entity.UserStorage) error { + if len(values) == 0 { + return nil + } + return u.DO.Save(values) +} + +func (u userStorageDo) First() (*entity.UserStorage, error) { + if result, err := u.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.UserStorage), nil + } +} + +func (u userStorageDo) Take() (*entity.UserStorage, error) { + if result, err := u.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.UserStorage), nil + } +} + +func (u userStorageDo) Last() (*entity.UserStorage, error) { + if result, err := u.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.UserStorage), nil + } +} + +func (u userStorageDo) Find() ([]*entity.UserStorage, error) { + result, err := u.DO.Find() + return result.([]*entity.UserStorage), err +} + +func (u userStorageDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.UserStorage, err error) { + buf := make([]*entity.UserStorage, 0, batchSize) + err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (u userStorageDo) FindInBatches(result *[]*entity.UserStorage, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return u.DO.FindInBatches(result, batchSize, fc) +} + +func (u userStorageDo) Attrs(attrs ...field.AssignExpr) IUserStorageDo { + return u.withDO(u.DO.Attrs(attrs...)) +} + +func (u userStorageDo) Assign(attrs ...field.AssignExpr) IUserStorageDo { + return u.withDO(u.DO.Assign(attrs...)) +} + +func (u userStorageDo) Joins(fields ...field.RelationField) IUserStorageDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Joins(_f)) + } + return &u +} + +func (u userStorageDo) Preload(fields ...field.RelationField) IUserStorageDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Preload(_f)) + } + return &u +} + +func (u userStorageDo) FirstOrInit() (*entity.UserStorage, error) { + if result, err := u.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.UserStorage), nil + } +} + +func (u userStorageDo) FirstOrCreate() (*entity.UserStorage, error) { + if result, err := u.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.UserStorage), nil + } +} + +func (u userStorageDo) FindByPage(offset int, limit int) (result []*entity.UserStorage, count int64, err error) { + result, err = u.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = u.Offset(-1).Limit(-1).Count() + return +} + +func (u userStorageDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = u.Count() + if err != nil { + return + } + + err = u.Offset(offset).Limit(limit).Scan(result) + return +} + +func (u userStorageDo) Scan(result interface{}) (err error) { + return u.DO.Scan(result) +} + +func (u userStorageDo) Delete(models ...*entity.UserStorage) (result gen.ResultInfo, err error) { + return u.DO.Delete(models) +} + +func (u *userStorageDo) withDO(do gen.Dao) *userStorageDo { + u.DO = *do.(*gen.DO) + return u +} diff --git a/internal/app/repo/user.go b/internal/app/repo/user.go new file mode 100644 index 0000000..68ebb43 --- /dev/null +++ b/internal/app/repo/user.go @@ -0,0 +1,50 @@ +package repo + +import ( + "context" + + "github.com/saltbo/zpan/internal/app/entity" + "gorm.io/gorm" +) + +type User interface { + GetUserStorage(ctx context.Context, uid int64) (*entity.UserStorage, error) + UserStorageUsedIncr(ctx context.Context, matter *entity.Matter) error + UserStorageUsedDecr(ctx context.Context, matter *entity.Matter) error +} + +var _ User = (*UserDBQuery)(nil) + +type UserDBQuery struct { + DBQuery +} + +func NewUserDBQuery(q DBQuery) *UserDBQuery { + return &UserDBQuery{DBQuery: q} +} + +func (u *UserDBQuery) GetUserStorage(ctx context.Context, uid int64) (*entity.UserStorage, error) { + return u.Q().UserStorage.WithContext(ctx).Where(u.Q().UserStorage.Uid.Eq(uid)).First() +} + +func (u *UserDBQuery) UserStorageUsedIncr(ctx context.Context, matter *entity.Matter) error { + q := u.Q().UserStorage.WithContext(ctx).Where(u.Q().UserStorage.Uid.Eq(matter.Uid)) + _, err := q.Update(u.Q().UserStorage.Used, gorm.Expr("used+?", matter.Size)) + return err +} + +func (u *UserDBQuery) UserStorageUsedDecr(ctx context.Context, matter *entity.Matter) error { + q := u.Q().UserStorage.WithContext(ctx).Where(u.Q().UserStorage.Uid.Eq(matter.Uid)) + userStorage, err := q.First() + if err != nil { + return err + } + + used := uint64(matter.Size) + if used > userStorage.Used { + used = userStorage.Used // 使用量不能变成负数 + } + + _, err = q.Update(u.Q().UserStorage.Used, gorm.Expr("used-?", used)) + return err +} diff --git a/internal/app/repo/wire.go b/internal/app/repo/wire.go index 51e42fe..a347c32 100644 --- a/internal/app/repo/wire.go +++ b/internal/app/repo/wire.go @@ -13,12 +13,17 @@ func NewRepository(storage Storage, matter Matter, recycleBin RecycleBin) *Repos } var ProviderSet = wire.NewSet( - NewStorageDBQuery, - NewMatterDBQuery, - NewRecycleBinDBQuery, + NewUserDBQuery, + wire.Bind(new(User), new(*UserDBQuery)), + NewStorageDBQuery, wire.Bind(new(Storage), new(*StorageDBQuery)), + + NewMatterDBQuery, wire.Bind(new(Matter), new(*MatterDBQuery)), + + NewRecycleBinDBQuery, wire.Bind(new(RecycleBin), new(*RecycleBinDBQuery)), + NewRepository, ) diff --git a/internal/app/service/user.go b/internal/app/service/user.go index 08b76a7..e7a4eb8 100644 --- a/internal/app/service/user.go +++ b/internal/app/service/user.go @@ -6,6 +6,7 @@ import ( "github.com/saltbo/gopkg/regexputil" "github.com/saltbo/gopkg/strutil" + "github.com/saltbo/zpan/internal/app/entity" "github.com/saltbo/zpan/internal/app/dao" "github.com/saltbo/zpan/internal/app/model" @@ -74,7 +75,7 @@ func (u *User) Active(token string) error { return fmt.Errorf("account already activated") } - u.dUser.UpdateStorage(uid, model.UserStorageActiveSize) // 激活即送1G空间 + u.dUser.UpdateStorage(uid, entity.UserStorageActiveSize) // 激活即送1G空间 return u.dUser.Activate(uid) } diff --git a/internal/app/usecase/vfs/interfaces.go b/internal/app/usecase/vfs/interfaces.go index 16ff2d3..8573b27 100644 --- a/internal/app/usecase/vfs/interfaces.go +++ b/internal/app/usecase/vfs/interfaces.go @@ -20,5 +20,5 @@ type VirtualFs interface { type RecycleBinFs interface { Recovery(ctx context.Context, alias string) error Delete(ctx context.Context, alias string) error - Clean(ctx context.Context, sid int64) error + Clean(ctx context.Context, sid, uid int64) error } diff --git a/internal/app/usecase/vfs/recyclebin.go b/internal/app/usecase/vfs/recyclebin.go index 3ccde60..1b132b5 100644 --- a/internal/app/usecase/vfs/recyclebin.go +++ b/internal/app/usecase/vfs/recyclebin.go @@ -12,11 +12,12 @@ var _ RecycleBinFs = (*RecycleBin)(nil) type RecycleBin struct { recycleRepo repo.RecycleBin matterRepo repo.Matter + userRepo repo.User storage storage.Storage } -func NewRecycleBin(recycleRepo repo.RecycleBin, matterRepo repo.Matter, storage storage.Storage) *RecycleBin { - return &RecycleBin{recycleRepo: recycleRepo, matterRepo: matterRepo, storage: storage} +func NewRecycleBin(recycleRepo repo.RecycleBin, matterRepo repo.Matter, userRepo repo.User, storage storage.Storage) *RecycleBin { + return &RecycleBin{recycleRepo: recycleRepo, matterRepo: matterRepo, userRepo: userRepo, storage: storage} } func (rb *RecycleBin) Recovery(ctx context.Context, alias string) error { @@ -55,11 +56,12 @@ func (rb *RecycleBin) Delete(ctx context.Context, alias string) error { } } + defer rb.userRepo.UserStorageUsedDecr(ctx, matter) return rb.recycleRepo.Delete(ctx, alias) } -func (rb *RecycleBin) Clean(ctx context.Context, sid int64) error { - rbs, _, err := rb.recycleRepo.FindAll(ctx, &repo.RecycleBinFindOptions{Sid: sid}) +func (rb *RecycleBin) Clean(ctx context.Context, sid, uid int64) error { + rbs, _, err := rb.recycleRepo.FindAll(ctx, &repo.RecycleBinFindOptions{Sid: sid, Uid: uid}) if err != nil { return err } diff --git a/internal/app/usecase/vfs/vfs.go b/internal/app/usecase/vfs/vfs.go index 131d9a2..7425909 100644 --- a/internal/app/usecase/vfs/vfs.go +++ b/internal/app/usecase/vfs/vfs.go @@ -16,12 +16,13 @@ var _ VirtualFs = (*Vfs)(nil) type Vfs struct { matterRepo repo.Matter recycleBinRepo repo.RecycleBin + userRepo repo.User uploader uploader.Uploader eventWorker *EventWorker } -func NewVfs(matterRepo repo.Matter, recycleBinRepo repo.RecycleBin, uploader uploader.Uploader) *Vfs { - vfs := &Vfs{matterRepo: matterRepo, recycleBinRepo: recycleBinRepo, uploader: uploader, eventWorker: NewWorker()} +func NewVfs(matterRepo repo.Matter, recycleBinRepo repo.RecycleBin, userRepo repo.User, uploader uploader.Uploader) *Vfs { + vfs := &Vfs{matterRepo: matterRepo, recycleBinRepo: recycleBinRepo, userRepo: userRepo, uploader: uploader, eventWorker: NewWorker()} vfs.eventWorker.registerEventHandler(EventActionCreated, vfs.matterCreatedEventHandler) vfs.eventWorker.registerEventHandler(EventActionDeleted, vfs.matterDeletedEventHandler) _ = cron.New().AddFunc("30 1 * * *", vfs.cleanExpiredMatters) @@ -31,6 +32,13 @@ func NewVfs(matterRepo repo.Matter, recycleBinRepo repo.RecycleBin, uploader upl func (v *Vfs) Create(ctx context.Context, m *entity.Matter) error { if !m.IsDir() { + us, err := v.userRepo.GetUserStorage(ctx, m.Uid) + if err != nil { + return fmt.Errorf("error getting user storage: %v", err) + } else if us.Overflowed(m.Size) { + return fmt.Errorf("insufficient storage space") + } + if err := v.uploader.CreateUploadURL(ctx, m); err != nil { return err } diff --git a/internal/app/usecase/vfs/vfs_jobs.go b/internal/app/usecase/vfs/vfs_jobs.go index 4821b33..6d4bf02 100644 --- a/internal/app/usecase/vfs/vfs_jobs.go +++ b/internal/app/usecase/vfs/vfs_jobs.go @@ -19,6 +19,7 @@ func (v *Vfs) matterCreatedEventHandler(matter *entity.Matter) error { return } + _ = v.userRepo.UserStorageUsedIncr(ctx, matter) c.Stop() }) } diff --git a/internal/app/usecase/vfs/vfs_test.go b/internal/app/usecase/vfs/vfs_test.go index 19df9b1..33973f2 100644 --- a/internal/app/usecase/vfs/vfs_test.go +++ b/internal/app/usecase/vfs/vfs_test.go @@ -28,7 +28,7 @@ func TestVfs_Create_File(t *testing.T) { ctx := context.Background() mockMatter := mock.NewMatter() assert.NoError(t, mockMatter.Create(ctx, matter)) - vfs := NewVfs(mockMatter, nil, &uploader.FakeUploader{CreateUploadURLFn: func(ctx context.Context, m *entity.Matter) error { + vfs := NewVfs(mockMatter, nil, mock.NewUser(), &uploader.FakeUploader{CreateUploadURLFn: func(ctx context.Context, m *entity.Matter) error { m.Uploader = mockUploader return nil }}) @@ -44,7 +44,7 @@ func TestVfs_Create_Folder(t *testing.T) { Parent: "/", Name: "abc", } - vfs := NewVfs(&mock.Matter{}, nil, nil) + vfs := NewVfs(&mock.Matter{}, nil, nil, nil) assert.NoError(t, vfs.Create(context.Background(), matter)) assert.True(t, matter.IsDir()) assert.Equal(t, "/abc/", matter.FullPath()) @@ -61,7 +61,7 @@ func TestVfs_Rename(t *testing.T) { ctx := context.Background() mockMatter := mock.NewMatter() assert.NoError(t, mockMatter.Create(ctx, matter)) - vfs := NewVfs(mockMatter, nil, nil) + vfs := NewVfs(mockMatter, nil, nil, nil) assert.NoError(t, vfs.Rename(ctx, "test", "new.txt")) assert.Equal(t, "new.txt", matter.Name) } @@ -75,7 +75,7 @@ func TestVfs_Move(t *testing.T) { ctx := context.Background() mockMatter := mock.NewMatter() assert.NoError(t, mockMatter.Create(ctx, matter)) - vfs := NewVfs(mockMatter, nil, nil) + vfs := NewVfs(mockMatter, nil, nil, nil) assert.NoError(t, vfs.Move(context.Background(), "test", "newDir")) assert.Equal(t, "newDir/abc.txt", matter.FullPath()) } @@ -89,7 +89,7 @@ func TestVfs_Copy(t *testing.T) { ctx := context.Background() mockMatter := mock.NewMatter() assert.NoError(t, mockMatter.Create(ctx, matter)) - vfs := NewVfs(mockMatter, nil, nil) + vfs := NewVfs(mockMatter, nil, nil, nil) newMatter, err := vfs.Copy(context.Background(), "test", "newDir") assert.NoError(t, err) assert.Equal(t, "newDir/abc.txt", newMatter.FullPath()) @@ -105,7 +105,7 @@ func TestVfs_Delete(t *testing.T) { ctx := context.Background() mockMatter := mock.NewMatter() assert.NoError(t, mockMatter.Create(ctx, matter)) - vfs := NewVfs(mockMatter, mock.NewRecycleBin(), nil) + vfs := NewVfs(mockMatter, mock.NewRecycleBin(), nil, nil) assert.NoError(t, vfs.Delete(ctx, "test")) _, err := vfs.Get(ctx, "test") assert.Error(t, err) diff --git a/internal/app/wire_gen.go b/internal/app/wire_gen.go index 85adccb..1bdd8a7 100644 --- a/internal/app/wire_gen.go +++ b/internal/app/wire_gen.go @@ -25,11 +25,12 @@ func InitializeServer() *Server { matterDBQuery := repo.NewMatterDBQuery(dbQueryFactory) cloudUploader := uploader.NewCloudUploader(cloudStorage, matterDBQuery) recycleBinDBQuery := repo.NewRecycleBinDBQuery(dbQueryFactory) - vfsVfs := vfs.NewVfs(matterDBQuery, recycleBinDBQuery, cloudUploader) + userDBQuery := repo.NewUserDBQuery(dbQueryFactory) + vfsVfs := vfs.NewVfs(matterDBQuery, recycleBinDBQuery, userDBQuery, cloudUploader) repository := usecase.NewRepository(cloudStorage, cloudUploader, vfsVfs) repoRepository := repo.NewRepository(storageDBQuery, matterDBQuery, recycleBinDBQuery) fileResource := api.NewFileResource(vfsVfs, cloudUploader) - recycleBin := vfs.NewRecycleBin(recycleBinDBQuery, matterDBQuery, cloudStorage) + recycleBin := vfs.NewRecycleBin(recycleBinDBQuery, matterDBQuery, userDBQuery, cloudStorage) recycleBinResource := api.NewRecycleBinResource(recycleBinDBQuery, recycleBin) shareResource := api.NewShareResource(matterDBQuery, vfsVfs) storageResource := api.NewStorageResource(storageDBQuery, cloudStorage) diff --git a/internal/mock/user.go b/internal/mock/user.go new file mode 100644 index 0000000..3eba2ac --- /dev/null +++ b/internal/mock/user.go @@ -0,0 +1,29 @@ +package mock + +import ( + "context" + + "github.com/saltbo/zpan/internal/app/entity" + "github.com/saltbo/zpan/internal/app/repo" +) + +var _ repo.User = (*User)(nil) + +type User struct { +} + +func NewUser() *User { + return &User{} +} + +func (u *User) GetUserStorage(ctx context.Context, uid int64) (*entity.UserStorage, error) { + return &entity.UserStorage{Uid: uid, Max: 1000, Used: 10}, nil +} + +func (u *User) UserStorageUsedIncr(ctx context.Context, matter *entity.Matter) error { + return nil +} + +func (u *User) UserStorageUsedDecr(ctx context.Context, matter *entity.Matter) error { + return nil +}