Files
digital-psychology/apps/api/internal/service/admin/lifecycle.go
T
jackyu66gitandCursor e25cd94b0c feat(ECR-013B): AccountLifecycle Closed;启用 Loop 连续执行
UserStatus 迁移、DeviceAuth 拒绝非 active、admin-h5 CTA;
Reviewer Closed。Human 授权 LOOP_AUTHORIZATION(免逐闸确认)。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 17:47:18 +08:00

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
}