UserStatus 迁移、DeviceAuth 拒绝非 active、admin-h5 CTA; Reviewer Closed。Human 授权 LOOP_AUTHORIZATION(免逐闸确认)。 Co-authored-by: Cursor <cursoragent@cursor.com>
77 lines
2.0 KiB
Go
77 lines
2.0 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/yuxingu/digital-psychology/apps/api/internal/repository"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidStatusEdge = errString("invalid status transition")
|
|
ErrReasonRequired = errString("reason required")
|
|
)
|
|
|
|
var allowedStatusEdges = map[string]map[string]struct{}{
|
|
"active": {"disabled": {}, "banned": {}, "suspended": {}},
|
|
"disabled": {"active": {}, "banned": {}},
|
|
"suspended": {"active": {}, "banned": {}, "disabled": {}},
|
|
"banned": {"active": {}, "disabled": {}},
|
|
}
|
|
|
|
// TransitionUserStatus migrates UserStatus with audit.
|
|
func (s *Service) TransitionUserStatus(ctx context.Context, adminID, userID uuid.UUID, toStatus, reason string) error {
|
|
toStatus = strings.TrimSpace(toStatus)
|
|
reason = strings.TrimSpace(reason)
|
|
if reason == "" {
|
|
return ErrReasonRequired
|
|
}
|
|
ok, err := s.Repo.UserExists(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return ErrUserNotFound
|
|
}
|
|
from, err := s.Repo.GetUserStatus(ctx, userID)
|
|
if err != nil || from == "" {
|
|
return ErrUserNotFound
|
|
}
|
|
if from == toStatus {
|
|
return ErrInvalidStatusEdge
|
|
}
|
|
next, okEdge := allowedStatusEdges[from]
|
|
if !okEdge {
|
|
return ErrInvalidStatusEdge
|
|
}
|
|
if _, okEdge = next[toStatus]; !okEdge {
|
|
return ErrInvalidStatusEdge
|
|
}
|
|
meta, _ := json.Marshal(map[string]string{
|
|
"from": from, "to": toStatus, "reason": reason,
|
|
})
|
|
return s.Repo.TransitionUserStatusWithAudit(ctx, adminID, userID, from, toStatus, reason, meta)
|
|
}
|
|
|
|
// ListStatusTransitions returns recent transitions.
|
|
func (s *Service) ListStatusTransitions(ctx context.Context, userID uuid.UUID, limit int) ([]repository.AccountTransition, error) {
|
|
ok, err := s.Repo.UserExists(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
return nil, ErrUserNotFound
|
|
}
|
|
items, err := s.Repo.ListStatusTransitions(ctx, userID, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if items == nil {
|
|
items = []repository.AccountTransition{}
|
|
}
|
|
return items, nil
|
|
}
|