package admin import ( "context" "encoding/json" "errors" "regexp" "strings" "unicode/utf8" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/yuxingu/digital-psychology/apps/api/internal/repository" ) var ( ErrInvalidRhythmConfig = errors.New("invalid rhythm config") ErrRhythmConfigConflict = errors.New("rhythm config code conflict") rhythmConfigCodeRe = regexp.MustCompile(`^[a-z][a-z0-9_]{1,62}$`) ) // RhythmConfigWriteBody is JSON for create/update. type RhythmConfigWriteBody struct { Code string `json:"code"` Title string `json:"title"` Active bool `json:"active"` } // CreateRhythmConfig validates, inserts, audits. func (s *Service) CreateRhythmConfig(ctx context.Context, adminID uuid.UUID, body RhythmConfigWriteBody) (*repository.RhythmConfigRow, error) { in, err := normalizeRhythmConfigWrite(body) if err != nil { return nil, err } meta, _ := json.Marshal(map[string]any{"code": in.Code, "active": in.Active}) row, err := s.Repo.CreateRhythmConfigWithAudit(ctx, adminID, in, meta) if repository.RhythmConfigCodeConflict(err) { return nil, ErrRhythmConfigConflict } return row, err } // UpdateRhythmConfig validates, updates, audits. func (s *Service) UpdateRhythmConfig(ctx context.Context, adminID, id uuid.UUID, body RhythmConfigWriteBody) (*repository.RhythmConfigRow, error) { in, err := normalizeRhythmConfigWrite(body) if err != nil { return nil, err } meta, _ := json.Marshal(map[string]any{"code": in.Code, "active": in.Active}) row, err := s.Repo.UpdateRhythmConfigWithAudit(ctx, adminID, id, in, meta) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrRhythmConfigNotFound } if repository.RhythmConfigCodeConflict(err) { return nil, ErrRhythmConfigConflict } return row, err } func normalizeRhythmConfigWrite(body RhythmConfigWriteBody) (repository.RhythmConfigWriteInput, error) { code := strings.TrimSpace(body.Code) title := strings.TrimSpace(body.Title) if !rhythmConfigCodeRe.MatchString(code) { return repository.RhythmConfigWriteInput{}, ErrInvalidRhythmConfig } if title == "" || utf8.RuneCountInString(title) > 128 { return repository.RhythmConfigWriteInput{}, ErrInvalidRhythmConfig } return repository.RhythmConfigWriteInput{Code: code, Title: title, Active: body.Active}, nil }