Files
root bbfeabdaa5
Frontend CI / Frontend (push) Successful in 3m13s
Backend CI / Backend (push) Successful in 15m21s
feat(login-guard): unlock entire scope and report deleted keys
Admin login-lock reset now SCANs and clears all auth:login:* keys (any
scope) and surfaces redis_deleted_keys / fallback_deleted_keys back to
ops-web so the operator can tell whether the cache was actually hit.
Also tracks paired keys via a per-identifier index plus an unlock marker
so legacy/in-flight pair locks get cleared on the next acquire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:56:38 +08:00

303 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
}
result, err := svc.ResetLoginLock(c.Request.Context(), actorFromGin(c), id)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, result)
}
}