From bd2069fbf06807c385665ebbc6614e98a71b4913 Mon Sep 17 00:00:00 2001 From: Sydonian <794346190@qq.com> Date: Tue, 11 Feb 2025 11:15:39 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/internal/http/server.go | 4 +- client/internal/http/user.go | 57 ++++++++++++ client/internal/services/user.go | 43 +++++++++ common/pkgs/db2/model/model.go | 9 -- common/pkgs/db2/user.go | 30 +++++- common/pkgs/db2/user_bucket.go | 10 ++ common/pkgs/db2/user_hub.go | 34 +++++++ common/pkgs/db2/user_storage.go | 34 +++++++ common/pkgs/mq/coordinator/server.go | 2 + common/pkgs/mq/coordinator/user.go | 67 ++++++++++++++ common/pkgs/storage/s3/shard_store.go | 4 +- coordinator/internal/cmd/migrate.go | 2 +- coordinator/internal/mq/user.go | 127 ++++++++++++++++++++++++++ 13 files changed, 407 insertions(+), 16 deletions(-) create mode 100644 client/internal/http/user.go create mode 100644 client/internal/services/user.go create mode 100644 common/pkgs/db2/user_hub.go create mode 100644 common/pkgs/db2/user_storage.go create mode 100644 common/pkgs/mq/coordinator/user.go create mode 100644 coordinator/internal/mq/user.go diff --git a/client/internal/http/server.go b/client/internal/http/server.go index 8f0216a..ceb22c5 100644 --- a/client/internal/http/server.go +++ b/client/internal/http/server.go @@ -79,7 +79,6 @@ func (s *Server) initRouters() { rt.POST(cdsapi.BucketCreatePath, s.Bucket().Create) rt.POST(cdsapi.BucketDeletePath, s.Bucket().Delete) rt.GET(cdsapi.BucketListUserBucketsPath, s.Bucket().ListUserBuckets) - } func (s *Server) routeV1(eg *gin.Engine) { @@ -117,4 +116,7 @@ func (s *Server) routeV1(eg *gin.Engine) { v1.POST(cdsapi.BucketCreatePath, s.awsAuth.Auth, s.Bucket().Create) v1.POST(cdsapi.BucketDeletePath, s.awsAuth.Auth, s.Bucket().Delete) v1.GET(cdsapi.BucketListUserBucketsPath, s.awsAuth.Auth, s.Bucket().ListUserBuckets) + + v1.POST(cdsapi.UserCreatePath, s.User().Create) + v1.POST(cdsapi.UserDeletePath, s.User().Delete) } diff --git a/client/internal/http/user.go b/client/internal/http/user.go new file mode 100644 index 0000000..cb25ccc --- /dev/null +++ b/client/internal/http/user.go @@ -0,0 +1,57 @@ +package http + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/pkgs/logger" + "gitlink.org.cn/cloudream/common/sdks/storage/cdsapi" +) + +type UserService struct { + *Server +} + +func (s *Server) User() *UserService { + return &UserService{ + Server: s, + } +} + +func (s *UserService) Create(ctx *gin.Context) { + log := logger.WithField("HTTP", "User.Create") + var req cdsapi.UserCreate + if err := ctx.ShouldBindJSON(&req); err != nil { + log.Warnf("binding body: %s", err.Error()) + ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) + return + } + + user, err := s.svc.UserSvc().Create(req.Name) + if err != nil { + log.Warnf("create user: %s", err.Error()) + ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, err.Error())) + return + } + + ctx.JSON(http.StatusOK, OK(user)) +} + +func (s *UserService) Delete(ctx *gin.Context) { + log := logger.WithField("HTTP", "User.Delete") + var req cdsapi.UserDelete + if err := ctx.ShouldBindJSON(&req); err != nil { + log.Warnf("binding body: %s", err.Error()) + ctx.JSON(http.StatusBadRequest, Failed(errorcode.BadArgument, "missing argument or invalid argument")) + return + } + + if err := s.svc.UserSvc().Delete(req.UserID); err != nil { + log.Warnf("delete user: %s", err.Error()) + ctx.JSON(http.StatusOK, Failed(errorcode.OperationFailed, err.Error())) + return + } + + ctx.JSON(http.StatusOK, OK(nil)) +} diff --git a/client/internal/services/user.go b/client/internal/services/user.go new file mode 100644 index 0000000..cfb440c --- /dev/null +++ b/client/internal/services/user.go @@ -0,0 +1,43 @@ +package services + +import ( + "fmt" + + cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" + stgglb "gitlink.org.cn/cloudream/storage/common/globals" + coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" +) + +type UserService struct { + *Service +} + +func (svc *Service) UserSvc() *UserService { + return &UserService{Service: svc} +} + +func (svc *UserService) Create(name string) (cdssdk.User, error) { + coorCli, err := stgglb.CoordinatorMQPool.Acquire() + if err != nil { + return cdssdk.User{}, fmt.Errorf("new coordinator client: %w", err) + } + defer stgglb.CoordinatorMQPool.Release(coorCli) + + resp, err := coorCli.CreateUser(coormq.ReqCreateUser(name)) + if err != nil { + return cdssdk.User{}, err + } + + return resp.User, nil +} + +func (svc *UserService) Delete(userID cdssdk.UserID) error { + coorCli, err := stgglb.CoordinatorMQPool.Acquire() + if err != nil { + return fmt.Errorf("new coordinator client: %w", err) + } + defer stgglb.CoordinatorMQPool.Release(coorCli) + + _, err = coorCli.DeleteUser(coormq.ReqDeleteUser(userID)) + return err +} diff --git a/common/pkgs/db2/model/model.go b/common/pkgs/db2/model/model.go index ccbcf85..e69a64b 100644 --- a/common/pkgs/db2/model/model.go +++ b/common/pkgs/db2/model/model.go @@ -10,15 +10,6 @@ import ( // TODO 可以考虑逐步迁移到cdssdk中。迁移思路:数据对象应该包含的字段都迁移到cdssdk中,内部使用的一些特殊字段则留在这里 type Storage = cdssdk.Storage -type User struct { - UserID cdssdk.UserID `gorm:"column:UserID; primaryKey; type:bigint" json:"userID"` - Password string `gorm:"column:Password; type:varchar(255); not null" json:"password"` -} - -func (User) TableName() string { - return "User" -} - type UserBucket struct { UserID cdssdk.UserID `gorm:"column:UserID; primaryKey; type:bigint" json:"userID"` BucketID cdssdk.BucketID `gorm:"column:BucketID; primaryKey; type:bigint" json:"bucketID"` diff --git a/common/pkgs/db2/user.go b/common/pkgs/db2/user.go index 7c2b9d2..b94e73b 100644 --- a/common/pkgs/db2/user.go +++ b/common/pkgs/db2/user.go @@ -2,7 +2,7 @@ package db2 import ( cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" - "gitlink.org.cn/cloudream/storage/common/pkgs/db2/model" + "gorm.io/gorm" ) type UserDB struct { @@ -13,8 +13,32 @@ func (db *DB) User() *UserDB { return &UserDB{DB: db} } -func (db *UserDB) GetByID(ctx SQLContext, userID cdssdk.UserID) (model.User, error) { - var ret model.User +func (db *UserDB) GetByID(ctx SQLContext, userID cdssdk.UserID) (cdssdk.User, error) { + var ret cdssdk.User err := ctx.Table("User").Where("UserID = ?", userID).First(&ret).Error return ret, err } + +func (db *UserDB) GetByName(ctx SQLContext, name string) (cdssdk.User, error) { + var ret cdssdk.User + err := ctx.Table("User").Where("Name = ?", name).First(&ret).Error + return ret, err +} + +func (db *UserDB) Create(ctx SQLContext, name string) (cdssdk.User, error) { + _, err := db.GetByName(ctx, name) + if err == nil { + return cdssdk.User{}, gorm.ErrDuplicatedKey + } + if err != gorm.ErrRecordNotFound { + return cdssdk.User{}, err + } + + user := cdssdk.User{Name: name} + err = ctx.Table("User").Create(&user).Error + return user, err +} + +func (*UserDB) Delete(ctx SQLContext, userID cdssdk.UserID) error { + return ctx.Table("User").Delete(&cdssdk.User{UserID: userID}).Error +} diff --git a/common/pkgs/db2/user_bucket.go b/common/pkgs/db2/user_bucket.go index 4af3183..4bf8bff 100644 --- a/common/pkgs/db2/user_bucket.go +++ b/common/pkgs/db2/user_bucket.go @@ -13,6 +13,12 @@ func (db *DB) UserBucket() *UserBucketDB { return &UserBucketDB{DB: db} } +func (*UserBucketDB) GetByUserID(ctx SQLContext, userID cdssdk.UserID) ([]model.UserBucket, error) { + var userBuckets []model.UserBucket + err := ctx.Table("UserBucket").Where("UserID = ?", userID).Find(&userBuckets).Error + return userBuckets, err +} + func (*UserBucketDB) Create(ctx SQLContext, userID cdssdk.UserID, bucketID cdssdk.BucketID) error { userBucket := model.UserBucket{ UserID: userID, @@ -24,3 +30,7 @@ func (*UserBucketDB) Create(ctx SQLContext, userID cdssdk.UserID, bucketID cdssd func (*UserBucketDB) DeleteByBucketID(ctx SQLContext, bucketID cdssdk.BucketID) error { return ctx.Table("UserBucket").Where("BucketID = ?", bucketID).Delete(&model.UserBucket{}).Error } + +func (*UserBucketDB) DeleteByUserID(ctx SQLContext, userID cdssdk.UserID) error { + return ctx.Table("UserBucket").Where("UserID = ?", userID).Delete(&model.UserBucket{}).Error +} diff --git a/common/pkgs/db2/user_hub.go b/common/pkgs/db2/user_hub.go new file mode 100644 index 0000000..6d94eca --- /dev/null +++ b/common/pkgs/db2/user_hub.go @@ -0,0 +1,34 @@ +package db2 + +import ( + cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" + "gitlink.org.cn/cloudream/storage/common/pkgs/db2/model" +) + +type UserHubDB struct { + *DB +} + +func (db *DB) UserHub() *UserHubDB { + return &UserHubDB{db} +} + +func (*UserHubDB) GetByUserID(ctx SQLContext, userID cdssdk.UserID) ([]model.UserHub, error) { + var userHubs []model.UserHub + if err := ctx.Table("UserHub").Where("UserID = ?", userID).Find(&userHubs).Error; err != nil { + return nil, err + } + + return userHubs, nil +} + +func (*UserHubDB) Create(ctx SQLContext, userID cdssdk.UserID, hubID cdssdk.HubID) error { + return ctx.Table("UserHub").Create(&model.UserHub{ + UserID: userID, + HubID: hubID, + }).Error +} + +func (*UserHubDB) DeleteByUserID(ctx SQLContext, userID cdssdk.UserID) error { + return ctx.Table("UserHub").Delete(&model.UserHub{}, "UserID = ?", userID).Error +} diff --git a/common/pkgs/db2/user_storage.go b/common/pkgs/db2/user_storage.go new file mode 100644 index 0000000..8ef7d77 --- /dev/null +++ b/common/pkgs/db2/user_storage.go @@ -0,0 +1,34 @@ +package db2 + +import ( + cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" + "gitlink.org.cn/cloudream/storage/common/pkgs/db2/model" +) + +type UserStorageDB struct { + *DB +} + +func (db *DB) UserStorage() *UserStorageDB { + return &UserStorageDB{db} +} + +func (*UserStorageDB) GetByUserID(ctx SQLContext, userID cdssdk.UserID) ([]model.UserStorage, error) { + var userStgs []model.UserStorage + if err := ctx.Table("UserStorage").Where("UserID = ?", userID).Find(&userStgs).Error; err != nil { + return nil, err + } + + return userStgs, nil +} + +func (*UserStorageDB) Create(ctx SQLContext, userID cdssdk.UserID, stgID cdssdk.StorageID) error { + return ctx.Table("UserStorage").Create(&model.UserStorage{ + UserID: userID, + StorageID: stgID, + }).Error +} + +func (*UserStorageDB) DeleteByUserID(ctx SQLContext, userID cdssdk.UserID) error { + return ctx.Table("UserStorage").Delete(&model.UserStorage{}, "UserID = ?", userID).Error +} diff --git a/common/pkgs/mq/coordinator/server.go b/common/pkgs/mq/coordinator/server.go index d286b2f..1a8faec 100644 --- a/common/pkgs/mq/coordinator/server.go +++ b/common/pkgs/mq/coordinator/server.go @@ -21,6 +21,8 @@ type Service interface { PackageService StorageService + + UserService } type Server struct { diff --git a/common/pkgs/mq/coordinator/user.go b/common/pkgs/mq/coordinator/user.go new file mode 100644 index 0000000..9dcf294 --- /dev/null +++ b/common/pkgs/mq/coordinator/user.go @@ -0,0 +1,67 @@ +package coordinator + +import ( + "gitlink.org.cn/cloudream/common/pkgs/mq" + cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" +) + +type UserService interface { + CreateUser(msg *CreateUser) (*CreateUserResp, *mq.CodeMessage) + + DeleteUser(msg *DeleteUser) (*DeleteUserResp, *mq.CodeMessage) +} + +// 创建用户 +var _ = Register(Service.CreateUser) + +type CreateUser struct { + mq.MessageBodyBase + Name string `json:"name"` +} + +type CreateUserResp struct { + mq.MessageBodyBase + User cdssdk.User `json:"user"` +} + +func ReqCreateUser(name string) *CreateUser { + return &CreateUser{ + Name: name, + } +} + +func RespCreateUser(user cdssdk.User) *CreateUserResp { + return &CreateUserResp{ + User: user, + } +} + +func (c *Client) CreateUser(msg *CreateUser) (*CreateUserResp, error) { + return mq.Request(Service.CreateUser, c.rabbitCli, msg) +} + +// 删除用户 +var _ = Register(Service.DeleteUser) + +type DeleteUser struct { + mq.MessageBodyBase + UserID cdssdk.UserID `json:"userID"` +} + +type DeleteUserResp struct { + mq.MessageBodyBase +} + +func ReqDeleteUser(userID cdssdk.UserID) *DeleteUser { + return &DeleteUser{ + UserID: userID, + } +} + +func RespDeleteUser() *DeleteUserResp { + return &DeleteUserResp{} +} + +func (c *Client) DeleteUser(msg *DeleteUser) (*DeleteUserResp, error) { + return mq.Request(Service.DeleteUser, c.rabbitCli, msg) +} diff --git a/common/pkgs/storage/s3/shard_store.go b/common/pkgs/storage/s3/shard_store.go index b53d956..34252bb 100644 --- a/common/pkgs/storage/s3/shard_store.go +++ b/common/pkgs/storage/s3/shard_store.go @@ -178,7 +178,7 @@ func (s *ShardStore) createWithAwsSha256(stream io.Reader) (types.FileInfo, erro key, fileName := s.createTempFile() - counter := io2.NewCounter(stream) + counter := io2.Counter(stream) resp, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String(s.Bucket), @@ -218,7 +218,7 @@ func (s *ShardStore) createWithCalcSha256(stream io.Reader) (types.FileInfo, err key, fileName := s.createTempFile() hashStr := io2.NewReadHasher(sha256.New(), stream) - counter := io2.NewCounter(hashStr) + counter := io2.Counter(hashStr) _, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String(s.Bucket), diff --git a/coordinator/internal/cmd/migrate.go b/coordinator/internal/cmd/migrate.go index 3367862..309b832 100644 --- a/coordinator/internal/cmd/migrate.go +++ b/coordinator/internal/cmd/migrate.go @@ -55,7 +55,7 @@ func migrate(configPath string) { migrateOne(db, cdssdk.Storage{}) migrateOne(db, model.UserStorage{}) migrateOne(db, model.UserBucket{}) - migrateOne(db, model.User{}) + migrateOne(db, cdssdk.User{}) migrateOne(db, model.UserHub{}) fmt.Println("migrate success") diff --git a/coordinator/internal/mq/user.go b/coordinator/internal/mq/user.go new file mode 100644 index 0000000..24e8ecf --- /dev/null +++ b/coordinator/internal/mq/user.go @@ -0,0 +1,127 @@ +package mq + +import ( + "errors" + "fmt" + + "gitlink.org.cn/cloudream/common/consts/errorcode" + "gitlink.org.cn/cloudream/common/pkgs/logger" + "gitlink.org.cn/cloudream/common/pkgs/mq" + cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" + "gitlink.org.cn/cloudream/storage/common/pkgs/db2" + coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" + "gorm.io/gorm" +) + +func (svc *Service) CreateUser(msg *coormq.CreateUser) (*coormq.CreateUserResp, *mq.CodeMessage) { + var user cdssdk.User + err := svc.db2.DoTx(func(tx db2.SQLContext) error { + var err error + + user, err = svc.db2.User().Create(tx, msg.Name) + if err != nil { + return fmt.Errorf("creating user: %w", err) + } + + // TODO 目前新建用户的权限与ID 1的相同 + hubs, err := svc.db2.UserHub().GetByUserID(tx, 1) + if err != nil { + return fmt.Errorf("getting user hubs: %w", err) + } + + stgs, err := svc.db2.UserStorage().GetByUserID(tx, 1) + if err != nil { + return fmt.Errorf("getting user storages: %w", err) + } + + for _, hub := range hubs { + err := svc.db2.UserHub().Create(tx, user.UserID, hub.HubID) + if err != nil { + return fmt.Errorf("creating user hub: %w", err) + } + } + + for _, stg := range stgs { + err := svc.db2.UserStorage().Create(tx, user.UserID, stg.StorageID) + if err != nil { + return fmt.Errorf("creating user storage: %w", err) + } + } + + return nil + }) + if err != nil { + logger.WithField("Name", msg.Name). + Warn(err.Error()) + + if errors.Is(err, gorm.ErrDuplicatedKey) { + return nil, mq.Failed(errorcode.DataExists, "user name already exists") + } + + return nil, mq.Failed(errorcode.OperationFailed, err.Error()) + } + + return mq.ReplyOK(coormq.RespCreateUser(user)) +} + +func (svc *Service) DeleteUser(msg *coormq.DeleteUser) (*coormq.DeleteUserResp, *mq.CodeMessage) { + // TODO 目前不能删除ID 1的用户 + if msg.UserID == 1 { + return nil, mq.Failed(errorcode.OperationFailed, "cannot delete the default user") + } + + err := svc.db2.DoTx(func(tx db2.SQLContext) error { + err := svc.db2.User().Delete(tx, msg.UserID) + if err != nil { + return fmt.Errorf("deleting user: %w", err) + } + + err = svc.db2.UserHub().DeleteByUserID(tx, msg.UserID) + if err != nil { + return fmt.Errorf("deleting user hubs: %w", err) + } + + err = svc.db2.UserStorage().DeleteByUserID(tx, msg.UserID) + if err != nil { + return fmt.Errorf("deleting user storages: %w", err) + } + + bkts, err := svc.db2.UserBucket().GetByUserID(tx, msg.UserID) + if err != nil { + return fmt.Errorf("getting user buckets: %w", err) + } + + for _, bkt := range bkts { + pkgs, err := svc.db2.Package().GetBucketPackages(tx, bkt.BucketID) + if err != nil { + return fmt.Errorf("getting bucket packages: %w", err) + } + + for _, pkg := range pkgs { + err := svc.db2.Package().DeleteComplete(tx, pkg.PackageID) + if err != nil { + return fmt.Errorf("deleting package %v: %w", pkg.PackageID, err) + } + } + + err = svc.db2.Bucket().Delete(tx, bkt.BucketID) + if err != nil { + return fmt.Errorf("deleting bucket: %w", err) + } + } + + err = svc.db2.UserBucket().DeleteByUserID(tx, msg.UserID) + if err != nil { + return fmt.Errorf("deleting user buckets: %w", err) + } + + return nil + }) + if err != nil { + logger.WithField("UserID", msg.UserID). + Warn(err.Error()) + return nil, mq.Failed(errorcode.OperationFailed, err.Error()) + } + + return mq.ReplyOK(coormq.RespDeleteUser()) +} From 9e5ee64f1ebe08018384e35704b11a604b8f601a Mon Sep 17 00:00:00 2001 From: JeshuaRen <270813223@qq.com> Date: Wed, 12 Feb 2025 16:21:40 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/internal/http/server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/internal/http/server.go b/client/internal/http/server.go index ceb22c5..aca1faf 100644 --- a/client/internal/http/server.go +++ b/client/internal/http/server.go @@ -45,7 +45,7 @@ func (s *Server) initRouters() { initTemp(rt, s) - s.routeV1(s.engine) + s.routeV1(s.engine, rt) rt.GET(cdsapi.ObjectListPathByPath, s.Object().ListByPath) rt.POST(cdsapi.ObjectListByIDsPath, s.Object().ListByIDs) @@ -81,7 +81,7 @@ func (s *Server) initRouters() { rt.GET(cdsapi.BucketListUserBucketsPath, s.Bucket().ListUserBuckets) } -func (s *Server) routeV1(eg *gin.Engine) { +func (s *Server) routeV1(eg *gin.Engine, rt gin.IRoutes) { v1 := eg.Group("/v1") v1.GET(cdsapi.ObjectListPathByPath, s.awsAuth.Auth, s.Object().ListByPath) @@ -117,6 +117,6 @@ func (s *Server) routeV1(eg *gin.Engine) { v1.POST(cdsapi.BucketDeletePath, s.awsAuth.Auth, s.Bucket().Delete) v1.GET(cdsapi.BucketListUserBucketsPath, s.awsAuth.Auth, s.Bucket().ListUserBuckets) - v1.POST(cdsapi.UserCreatePath, s.User().Create) - v1.POST(cdsapi.UserDeletePath, s.User().Delete) + rt.POST(cdsapi.UserCreatePath, s.User().Create) + rt.POST(cdsapi.UserDeletePath, s.User().Delete) } From f7affd1f3fcd00c463119a534e4b113749058ac4 Mon Sep 17 00:00:00 2001 From: Sydonian <794346190@qq.com> Date: Wed, 12 Feb 2025 16:36:37 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/internal/http/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/internal/http/user.go b/client/internal/http/user.go index cb25ccc..8b110c1 100644 --- a/client/internal/http/user.go +++ b/client/internal/http/user.go @@ -35,7 +35,7 @@ func (s *UserService) Create(ctx *gin.Context) { return } - ctx.JSON(http.StatusOK, OK(user)) + ctx.JSON(http.StatusOK, OK(cdsapi.UserCreateResp{User: user})) } func (s *UserService) Delete(ctx *gin.Context) {