Files
geo/server/internal/ops/transport/admin_user_handler.go
T
Xu Liang 6dd3bd8e9d feat: improve login UX and add admin login lock reset
- Translate all login error messages to Chinese (network errors, HTTP
  status codes, URL validation errors)
- Add password visibility toggle (eye icon) to login form
- Add live countdown timer when login is locked, disable login button
  during cooldown, auto-clear error when countdown ends
- Add X button red hover feedback in server settings dialog
- Add LoginGuard.Unlock() method to clear Redis lock/failure keys
- Expose POST /admin-users/:id/reset-login-lock endpoint in ops API
- Add "重置登录保护" button in ops-web user management table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 19:25:15 +08:00

302 lines
7.7 KiB
Go

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})
}
}
func resetAdminUserLoginLockHandler(svc *app.AdminUserService) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := parseIDParam(c)
if err != nil {
response.Error(c, err)
return
}
if err := svc.ResetLoginLock(c.Request.Context(), actorFromGin(c), id); err != nil {
response.Error(c, err)
return
}
response.Success(c, gin.H{"id": id})
}
}