diff --git a/backend/app/http/super/dto/tenant.go b/backend/app/http/super/dto/tenant.go index 40b4f39..2562ae3 100644 --- a/backend/app/http/super/dto/tenant.go +++ b/backend/app/http/super/dto/tenant.go @@ -6,6 +6,7 @@ import ( "quyun/v2/app/requests" "quyun/v2/database/models" + "quyun/v2/pkg/consts" ) type TenantFilter struct { @@ -35,3 +36,7 @@ func (form *TenantExpireUpdateForm) ParseDuration() (time.Duration, error) { } return duration, nil } + +type TenantStatusUpdateForm struct { + Status consts.TenantStatus `json:"status" validate:"required,oneof=normal disabled"` +} diff --git a/backend/app/http/super/dto/user.go b/backend/app/http/super/dto/user.go new file mode 100644 index 0000000..cef980d --- /dev/null +++ b/backend/app/http/super/dto/user.go @@ -0,0 +1,20 @@ +package dto + +import ( + "quyun/v2/app/requests" + "quyun/v2/database/models" +) + +type UserPageFilter struct { + requests.Pagination + requests.SortQueryFilter + + Username *string `query:"username"` + TenantID *int64 `query:"tenant_id"` +} + +type UserItem struct { + *models.User + + StatusDescription string `json:"status_description,omitempty"` +} diff --git a/backend/app/http/super/provider.gen.go b/backend/app/http/super/provider.gen.go index 5097eaa..e16dd84 100755 --- a/backend/app/http/super/provider.gen.go +++ b/backend/app/http/super/provider.gen.go @@ -26,13 +26,13 @@ func Provide(opts ...opt.Option) error { } if err := container.Container.Provide(func( authController *authController, - staticController *staticController, tenant *tenant, + user *user, ) (contracts.HttpRoute, error) { obj := &Routes{ - authController: authController, - staticController: staticController, - tenant: tenant, + authController: authController, + tenant: tenant, + user: user, } if err := obj.Prepare(); err != nil { return nil, err @@ -56,5 +56,12 @@ func Provide(opts ...opt.Option) error { }); err != nil { return err } + if err := container.Container.Provide(func() (*user, error) { + obj := &user{} + + return obj, nil + }); err != nil { + return err + } return nil } diff --git a/backend/app/http/super/routes.gen.go b/backend/app/http/super/routes.gen.go index 2c2be0a..99dabb6 100644 --- a/backend/app/http/super/routes.gen.go +++ b/backend/app/http/super/routes.gen.go @@ -5,13 +5,12 @@ package super import ( - "quyun/v2/app/http/super/dto" - "github.com/gofiber/fiber/v3" log "github.com/sirupsen/logrus" _ "go.ipao.vip/atom" _ "go.ipao.vip/atom/contracts" . "go.ipao.vip/atom/fen" + "quyun/v2/app/http/super/dto" ) // Routes implements the HttpRoute contract and provides route registration @@ -21,9 +20,9 @@ import ( type Routes struct { log *log.Entry `inject:"false"` // Controller instances - authController *authController - staticController *staticController - tenant *tenant + authController *authController + tenant *tenant + user *user } // Prepare initializes the routes provider with logging configuration. @@ -59,8 +58,18 @@ func (r *Routes) Register(router fiber.Router) { PathParam[int64]("tenantID"), Body[dto.TenantExpireUpdateForm]("form"), )) - - router.Get("/super/*", Func(r.staticController.static)) + r.log.Debugf("Registering route: Patch /super/v1/tenants/:tenantID/status -> tenant.updateStatus") + router.Patch("/super/v1/tenants/:tenantID/status", Func2( + r.tenant.updateStatus, + PathParam[int64]("tenantID"), + Body[dto.TenantStatusUpdateForm]("form"), + )) + // Register routes for controller: user + r.log.Debugf("Registering route: Get /super/v1/users -> user.list") + router.Get("/super/v1/users", DataFunc1( + r.user.list, + Query[dto.UserPageFilter]("filter"), + )) r.log.Info("Successfully registered all routes") } diff --git a/backend/app/http/super/static.go b/backend/app/http/super/static.go index a0db12b..f205ac1 100644 --- a/backend/app/http/super/static.go +++ b/backend/app/http/super/static.go @@ -1,28 +1,21 @@ package super -import ( - "os" - "path/filepath" - - "github.com/gofiber/fiber/v3" -) - // @provider type staticController struct{} -// Static -// -// @Tags Super -// @Router /super/* -func (ctl *staticController) static(ctx fiber.Ctx) error { - root := "/home/rogee/Projects/quyun_v2/frontend/superadmin/dist/" - param := ctx.Params("*") - file := filepath.Join(root, param) +// // Static +// // +// // @Tags Super +// // @Router /super/* +// func (ctl *staticController) static(ctx fiber.Ctx) error { +// root := "/home/rogee/Projects/quyun_v2/frontend/superadmin/dist/" +// param := ctx.Params("*") +// file := filepath.Join(root, param) - // if file not exits use index.html - if _, err := os.Stat(file); os.IsNotExist(err) { - file = filepath.Join(root, "index.html") - } +// // if file not exits use index.html +// if _, err := os.Stat(file); os.IsNotExist(err) { +// file = filepath.Join(root, "index.html") +// } - return ctx.SendFile(file) -} +// return ctx.SendFile(file) +// } diff --git a/backend/app/http/super/tenant.go b/backend/app/http/super/tenant.go index d04c437..528b467 100644 --- a/backend/app/http/super/tenant.go +++ b/backend/app/http/super/tenant.go @@ -47,3 +47,19 @@ func (*tenant) updateExpire(ctx fiber.Ctx, tenantID int64, form *dto.TenantExpir return services.Tenant.AddExpireDuration(ctx, tenantID, duration) } + +// updateStatus +// +// @Summary 更新租户状态 +// @Tags Super +// @Accept json +// @Produce json +// @Param tenantID path int64 true "TenantID" +// @Param form body dto.TenantStatusUpdateForm true "Form" +// +// @Router /super/v1/tenants/:tenantID/status [patch] +// @Bind tenantID path +// @Bind form body +func (*tenant) updateStatus(ctx fiber.Ctx, tenantID int64, form *dto.TenantStatusUpdateForm) error { + return services.Tenant.UpdateStatus(ctx, tenantID, form.Status) +} diff --git a/backend/app/http/super/user.go b/backend/app/http/super/user.go new file mode 100644 index 0000000..d351655 --- /dev/null +++ b/backend/app/http/super/user.go @@ -0,0 +1,28 @@ +package super + +import ( + "quyun/v2/app/http/super/dto" + "quyun/v2/app/requests" + "quyun/v2/app/services" + _ "quyun/v2/database/models" + + "github.com/gofiber/fiber/v3" +) + +// @provider +type user struct{} + +// list +// +// @Summary 租户列表 +// @Tags Super +// @Accept json +// @Produce json +// @Param filter query dto.UserPageFilter true "Filter" +// @Success 200 {object} requests.Pager{items=dto.UserItem} +// +// @Router /super/v1/users [get] +// @Bind filter query +func (*user) list(ctx fiber.Ctx, filter *dto.UserPageFilter) (*requests.Pager, error) { + return services.User.Page(ctx, filter) +} diff --git a/backend/app/services/tenant.go b/backend/app/services/tenant.go index 28e5788..2e4b235 100644 --- a/backend/app/services/tenant.go +++ b/backend/app/services/tenant.go @@ -195,3 +195,21 @@ func (t *tenant) AddExpireDuration(ctx context.Context, tenantID int64, duration } return m.Save(ctx) } + +// UpdateStatus +func (t *tenant) UpdateStatus(ctx context.Context, tenantID int64, status consts.TenantStatus) error { + logrus.WithField("tenant_id", tenantID).WithField("status", status).Info("update tenant status") + + m, err := t.FindByID(ctx, tenantID) + if err != nil { + return err + } + + m.Status = status + _, err = m.Update(ctx) + if err != nil { + return err + } + + return nil +} diff --git a/backend/app/services/user.go b/backend/app/services/user.go index b1dab86..dba4e54 100644 --- a/backend/app/services/user.go +++ b/backend/app/services/user.go @@ -3,11 +3,13 @@ package services import ( "context" + "quyun/v2/app/http/super/dto" "quyun/v2/app/requests" "quyun/v2/database/models" "quyun/v2/pkg/consts" "github.com/pkg/errors" + "github.com/samber/lo" "go.ipao.vip/gen" ) @@ -52,16 +54,8 @@ func (t *user) SetStatus(ctx context.Context, userID int64, status consts.UserSt return m.Save(ctx) } -type UserPageFilter struct { - requests.Pagination - requests.SortQueryFilter - - Username *string `query:"username"` - TenantID *int64 `query:"tenant_id"` -} - // Page -func (t *user) Page(ctx context.Context, filter *UserPageFilter) (*requests.Pager, error) { +func (t *user) Page(ctx context.Context, filter *dto.UserPageFilter) (*requests.Pager, error) { tbl, query := models.UserQuery.QueryContext(ctx) conds := []gen.Condition{} @@ -76,11 +70,18 @@ func (t *user) Page(ctx context.Context, filter *UserPageFilter) (*requests.Page } filter.Pagination.Format() - items, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit)) + users, total, err := query.Where(conds...).Order(tbl.ID.Desc()).FindByPage(int(filter.Offset()), int(filter.Limit)) if err != nil { return nil, err } + items := lo.Map(users, func(model *models.User, _ int) *dto.UserItem { + return &dto.UserItem{ + User: model, + StatusDescription: model.Status.Description(), + } + }) + return &requests.Pager{ Pagination: filter.Pagination, Total: total, diff --git a/backend/app/services/user_test.go b/backend/app/services/user_test.go index 55de731..55801a2 100644 --- a/backend/app/services/user_test.go +++ b/backend/app/services/user_test.go @@ -6,6 +6,7 @@ import ( "testing" "quyun/v2/app/commands/testx" + "quyun/v2/app/http/super/dto" "quyun/v2/database" "quyun/v2/database/models" "quyun/v2/pkg/consts" @@ -115,7 +116,7 @@ func (t *UserTestSuite) Test_Page() { err := m.Create(t.T().Context()) So(err, ShouldBeNil) - pager, err := User.Page(t.T().Context(), &UserPageFilter{ + pager, err := User.Page(t.T().Context(), &dto.UserPageFilter{ Username: &username, }) @@ -192,7 +193,7 @@ func (t *UserTestSuite) Test_Page() { So(err, ShouldBeNil) } - pager, err := User.Page(t.T().Context(), &UserPageFilter{ + pager, err := User.Page(t.T().Context(), &dto.UserPageFilter{ TenantID: lo.ToPtr(int64(1)), }) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 3e43970..c3facea 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -151,9 +151,8 @@ const docTemplate = `{ "responses": {} } }, - "/v1/medias/{id}": { - "post": { - "description": "Test", + "/super/v1/tenants/{tenantID}/status": { + "patch": { "consumes": [ "application/json" ], @@ -161,27 +160,52 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Test" + "Super" ], - "summary": "Test", + "summary": "更新租户状态", "parameters": [ { "type": "integer", - "description": "ID", - "name": "id", + "format": "int64", + "description": "TenantID", + "name": "tenantID", "in": "path", "required": true }, { - "type": "integer", - "description": "年龄", - "name": "age", + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TenantStatusUpdateForm" + } + } + ], + "responses": {} + } + }, + "/super/v1/users": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "租户列表", + "parameters": [ + { + "type": "string", + "name": "asc", "in": "query" }, { "type": "string", - "description": "名称", - "name": "name", + "name": "desc", "in": "query" }, { @@ -193,11 +217,21 @@ const docTemplate = `{ "type": "integer", "name": "page", "in": "query" + }, + { + "type": "integer", + "name": "tenantID", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" } ], "responses": { "200": { - "description": "成功", + "description": "OK", "schema": { "allOf": [ { @@ -206,8 +240,8 @@ const docTemplate = `{ { "type": "object", "properties": { - "list": { - "$ref": "#/definitions/v1.ResponseItem" + "items": { + "$ref": "#/definitions/dto.UserItem" } } } @@ -320,16 +354,17 @@ const docTemplate = `{ "status": { "$ref": "#/definitions/consts.TenantStatus" }, + "status_description": { + "type": "string" + }, "updated_at": { "type": "string" }, - "userBalance": { - "type": "integer", - "format": "int64" + "user_balance": { + "type": "integer" }, - "userCount": { - "type": "integer", - "format": "int64" + "user_count": { + "type": "integer" }, "user_id": { "type": "integer" @@ -345,6 +380,78 @@ const docTemplate = `{ } } }, + "dto.TenantStatusUpdateForm": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "enum": [ + "normal", + "disabled" + ], + "allOf": [ + { + "$ref": "#/definitions/consts.TenantStatus" + } + ] + } + } + }, + "dto.UserItem": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted_at": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "id": { + "type": "integer" + }, + "metas": { + "type": "array", + "items": { + "type": "integer" + } + }, + "owned": { + "$ref": "#/definitions/models.Tenant" + }, + "password": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/consts.Role" + } + }, + "status": { + "$ref": "#/definitions/consts.UserStatus" + }, + "status_description": { + "type": "string" + }, + "tenants": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tenant" + } + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + }, + "verified_at": { + "type": "string" + } + } + }, "gorm.DeletedAt": { "type": "object", "properties": { @@ -465,9 +572,6 @@ const docTemplate = `{ "type": "integer" } } - }, - "v1.ResponseItem": { - "type": "object" } }, "securityDefinitions": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index d5dc66f..54c6e46 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -145,9 +145,8 @@ "responses": {} } }, - "/v1/medias/{id}": { - "post": { - "description": "Test", + "/super/v1/tenants/{tenantID}/status": { + "patch": { "consumes": [ "application/json" ], @@ -155,27 +154,52 @@ "application/json" ], "tags": [ - "Test" + "Super" ], - "summary": "Test", + "summary": "更新租户状态", "parameters": [ { "type": "integer", - "description": "ID", - "name": "id", + "format": "int64", + "description": "TenantID", + "name": "tenantID", "in": "path", "required": true }, { - "type": "integer", - "description": "年龄", - "name": "age", + "description": "Form", + "name": "form", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TenantStatusUpdateForm" + } + } + ], + "responses": {} + } + }, + "/super/v1/users": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Super" + ], + "summary": "租户列表", + "parameters": [ + { + "type": "string", + "name": "asc", "in": "query" }, { "type": "string", - "description": "名称", - "name": "name", + "name": "desc", "in": "query" }, { @@ -187,11 +211,21 @@ "type": "integer", "name": "page", "in": "query" + }, + { + "type": "integer", + "name": "tenantID", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" } ], "responses": { "200": { - "description": "成功", + "description": "OK", "schema": { "allOf": [ { @@ -200,8 +234,8 @@ { "type": "object", "properties": { - "list": { - "$ref": "#/definitions/v1.ResponseItem" + "items": { + "$ref": "#/definitions/dto.UserItem" } } } @@ -314,16 +348,17 @@ "status": { "$ref": "#/definitions/consts.TenantStatus" }, + "status_description": { + "type": "string" + }, "updated_at": { "type": "string" }, - "userBalance": { - "type": "integer", - "format": "int64" + "user_balance": { + "type": "integer" }, - "userCount": { - "type": "integer", - "format": "int64" + "user_count": { + "type": "integer" }, "user_id": { "type": "integer" @@ -339,6 +374,78 @@ } } }, + "dto.TenantStatusUpdateForm": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "enum": [ + "normal", + "disabled" + ], + "allOf": [ + { + "$ref": "#/definitions/consts.TenantStatus" + } + ] + } + } + }, + "dto.UserItem": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "deleted_at": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "id": { + "type": "integer" + }, + "metas": { + "type": "array", + "items": { + "type": "integer" + } + }, + "owned": { + "$ref": "#/definitions/models.Tenant" + }, + "password": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/consts.Role" + } + }, + "status": { + "$ref": "#/definitions/consts.UserStatus" + }, + "status_description": { + "type": "string" + }, + "tenants": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tenant" + } + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + }, + "verified_at": { + "type": "string" + } + } + }, "gorm.DeletedAt": { "type": "object", "properties": { @@ -459,9 +566,6 @@ "type": "integer" } } - }, - "v1.ResponseItem": { - "type": "object" } }, "securityDefinitions": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 1040948..b1cea63 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -71,16 +71,16 @@ definitions: type: string status: $ref: '#/definitions/consts.TenantStatus' + status_description: + type: string updated_at: type: string + user_balance: + type: integer + user_count: + type: integer user_id: type: integer - userBalance: - format: int64 - type: integer - userCount: - format: int64 - type: integer users: items: $ref: '#/definitions/models.User' @@ -88,6 +88,52 @@ definitions: uuid: type: string type: object + dto.TenantStatusUpdateForm: + properties: + status: + allOf: + - $ref: '#/definitions/consts.TenantStatus' + enum: + - normal + - disabled + required: + - status + type: object + dto.UserItem: + properties: + created_at: + type: string + deleted_at: + $ref: '#/definitions/gorm.DeletedAt' + id: + type: integer + metas: + items: + type: integer + type: array + owned: + $ref: '#/definitions/models.Tenant' + password: + type: string + roles: + items: + $ref: '#/definitions/consts.Role' + type: array + status: + $ref: '#/definitions/consts.UserStatus' + status_description: + type: string + tenants: + items: + $ref: '#/definitions/models.Tenant' + type: array + updated_at: + type: string + username: + type: string + verified_at: + type: string + type: object gorm.DeletedAt: properties: time: @@ -168,8 +214,6 @@ definitions: total: type: integer type: object - v1.ResponseItem: - type: object externalDocs: description: OpenAPI url: https://swagger.io/resources/open-api/ @@ -265,24 +309,39 @@ paths: summary: 更新过期时间 tags: - Super - /v1/medias/{id}: - post: + /super/v1/tenants/{tenantID}/status: + patch: consumes: - application/json - description: Test parameters: - - description: ID + - description: TenantID + format: int64 in: path - name: id + name: tenantID required: true type: integer - - description: 年龄 - in: query - name: age - type: integer - - description: 名称 - in: query - name: name + - description: Form + in: body + name: form + required: true + schema: + $ref: '#/definitions/dto.TenantStatusUpdateForm' + produces: + - application/json + responses: {} + summary: 更新租户状态 + tags: + - Super + /super/v1/users: + get: + consumes: + - application/json + parameters: + - in: query + name: asc + type: string + - in: query + name: desc type: string - in: query name: limit @@ -290,21 +349,27 @@ paths: - in: query name: page type: integer + - in: query + name: tenantID + type: integer + - in: query + name: username + type: string produces: - application/json responses: "200": - description: 成功 + description: OK schema: allOf: - $ref: '#/definitions/requests.Pager' - properties: - list: - $ref: '#/definitions/v1.ResponseItem' + items: + $ref: '#/definitions/dto.UserItem' type: object - summary: Test + summary: 租户列表 tags: - - Test + - Super securityDefinitions: BasicAuth: type: basic diff --git a/backend/pkg/consts/consts.go b/backend/pkg/consts/consts.go index 5398dfb..efa963d 100644 --- a/backend/pkg/consts/consts.go +++ b/backend/pkg/consts/consts.go @@ -15,6 +15,20 @@ type Role string // ENUM(pending_verify, verified, banned, ) type UserStatus string +// Description +func (t UserStatus) Description() string { + switch t { + case UserStatusPendingVerify: + return "待审核" + case UserStatusVerified: + return "已审核" + case UserStatusBanned: + return "已封禁" + default: + return "未知状态" + } +} + // tenants // swagger:enum TenantStatus diff --git a/frontend/superadmin/dist/index.html b/frontend/superadmin/dist/index.html index 6664392..6459065 100644 --- a/frontend/superadmin/dist/index.html +++ b/frontend/superadmin/dist/index.html @@ -7,8 +7,8 @@