feat(ops): add tenant admin user management module
Adds /admin-users CRUD on the ops backend, covering listing, plan/role/KOL toggles, subscription expiry, status flips and password resets, with a configurable default plan code. The ops console exposes a new top-level "用户管理" view and reorganises the sidebar so 操作员管理 and 审计日志 move under a "系统设置" group; AccountsView is renamed accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/ops/app"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||
)
|
||||
|
||||
type createAdminUserRequest struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
PlanCode string `json:"plan_code"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
type updateAdminUserRequest struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type changeAdminUserPlanRequest struct {
|
||||
PlanCode string `json:"plan_code" binding:"required"`
|
||||
}
|
||||
|
||||
type updateAdminUserSubscriptionExpiryRequest struct {
|
||||
EndAt time.Time `json:"end_at" binding:"required"`
|
||||
}
|
||||
|
||||
type changeAdminUserRoleRequest struct {
|
||||
Role string `json:"role" binding:"required"`
|
||||
}
|
||||
|
||||
type setAdminUserKOLRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
DisplayName string `json:"display_name"`
|
||||
MarketEnabled bool `json:"market_enabled"`
|
||||
}
|
||||
|
||||
func listPlansHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
plans, err := svc.ListPlans(c.Request.Context())
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, plans)
|
||||
}
|
||||
}
|
||||
|
||||
func listRolesHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
response.Success(c, svc.ListRoles())
|
||||
}
|
||||
}
|
||||
|
||||
func listAdminUsersHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||||
result, err := svc.List(c.Request.Context(), app.AdminUserListInput{
|
||||
Keyword: c.Query("keyword"),
|
||||
Status: c.Query("status"),
|
||||
Page: page,
|
||||
Size: size,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
}
|
||||
|
||||
func createAdminUserHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var body createAdminUserRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
detail, err := svc.Create(c.Request.Context(), actorFromGin(c), app.CreateAdminUserInput{
|
||||
Phone: body.Phone,
|
||||
Email: body.Email,
|
||||
Name: body.Name,
|
||||
Password: body.Password,
|
||||
PlanCode: body.PlanCode,
|
||||
Role: body.Role,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, detail)
|
||||
}
|
||||
}
|
||||
|
||||
func getAdminUserHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
detail, err := svc.Get(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, detail)
|
||||
}
|
||||
}
|
||||
|
||||
func updateAdminUserHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body updateAdminUserRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
if err := svc.Update(c.Request.Context(), actorFromGin(c), id, app.UpdateAdminUserInput{
|
||||
Phone: body.Phone,
|
||||
Email: body.Email,
|
||||
Name: body.Name,
|
||||
}); err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"id": id})
|
||||
}
|
||||
}
|
||||
|
||||
func changeAdminUserPlanHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body changeAdminUserPlanRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
detail, err := svc.ChangePlan(c.Request.Context(), actorFromGin(c), id, body.PlanCode)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, detail)
|
||||
}
|
||||
}
|
||||
|
||||
func updateAdminUserSubscriptionExpiryHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body updateAdminUserSubscriptionExpiryRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
detail, err := svc.UpdateSubscriptionExpiry(c.Request.Context(), actorFromGin(c), id, body.EndAt)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, detail)
|
||||
}
|
||||
}
|
||||
|
||||
func resetAdminUserPlanUsageHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
result, err := svc.ResetCurrentPlanUsage(c.Request.Context(), actorFromGin(c), id)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, result)
|
||||
}
|
||||
}
|
||||
|
||||
func changeAdminUserRoleHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body changeAdminUserRoleRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
detail, err := svc.ChangeRole(c.Request.Context(), actorFromGin(c), id, body.Role)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, detail)
|
||||
}
|
||||
}
|
||||
|
||||
func setAdminUserKOLHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body setAdminUserKOLRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
detail, err := svc.SetKOL(c.Request.Context(), actorFromGin(c), id, app.SetAdminUserKOLInput{
|
||||
Enabled: body.Enabled,
|
||||
DisplayName: body.DisplayName,
|
||||
MarketEnabled: body.MarketEnabled,
|
||||
})
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, detail)
|
||||
}
|
||||
}
|
||||
|
||||
func setAdminUserStatusHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body setStatusRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
if err := svc.SetStatus(c.Request.Context(), actorFromGin(c), id, body.Status); err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"id": id, "status": body.Status})
|
||||
}
|
||||
}
|
||||
|
||||
func resetAdminUserPasswordHandler(svc *app.AdminUserService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, err := parseIDParam(c)
|
||||
if err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
var body resetPasswordRequest
|
||||
if err := c.ShouldBindJSON(&body); err != nil {
|
||||
response.Error(c, response.ErrBadRequest(40000, "invalid_payload", err.Error()))
|
||||
return
|
||||
}
|
||||
if err := svc.ResetPassword(c.Request.Context(), actorFromGin(c), id, body.Password); err != nil {
|
||||
response.Error(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"id": id})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user