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 ( ErrInvalidScaleDefinition = errors.New("invalid scale definition") ErrScaleDefinitionConflict = errors.New("scale slug conflict") scaleSlugRe = regexp.MustCompile(`^[a-z][a-z0-9-]{1,62}$`) ) // ScaleDefinitionWriteBody is explore-side JSON (no status). type ScaleDefinitionWriteBody struct { Slug string `json:"slug"` Title string `json:"title"` Description string `json:"description"` } // CreateScaleDefinition creates draft metadata. func (s *Service) CreateScaleDefinition(ctx context.Context, adminID uuid.UUID, body ScaleDefinitionWriteBody) (*repository.ScaleAdminItem, error) { if s.Scales == nil { return nil, errors.New("scales unavailable") } in, err := normalizeScaleDefinitionWrite(body) if err != nil { return nil, err } meta, _ := json.Marshal(map[string]any{"slug": in.Slug}) row, err := s.Scales.CreateScaleDefinitionWithAudit(ctx, adminID, in, meta) if repository.ScaleSlugConflict(err) { return nil, ErrScaleDefinitionConflict } return row, err } // UpdateScaleDefinition updates metadata; preserves status. func (s *Service) UpdateScaleDefinition(ctx context.Context, adminID, id uuid.UUID, body ScaleDefinitionWriteBody) (*repository.ScaleAdminItem, error) { if s.Scales == nil { return nil, errors.New("scales unavailable") } in, err := normalizeScaleDefinitionWrite(body) if err != nil { return nil, err } meta, _ := json.Marshal(map[string]any{"slug": in.Slug}) row, err := s.Scales.UpdateScaleDefinitionWithAudit(ctx, adminID, id, in, meta) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrScaleNotFound } if repository.ScaleSlugConflict(err) { return nil, ErrScaleDefinitionConflict } return row, err } func normalizeScaleDefinitionWrite(body ScaleDefinitionWriteBody) (repository.ScaleDefinitionWriteInput, error) { slug := strings.TrimSpace(body.Slug) title := strings.TrimSpace(body.Title) desc := strings.TrimSpace(body.Description) if !scaleSlugRe.MatchString(slug) { return repository.ScaleDefinitionWriteInput{}, ErrInvalidScaleDefinition } if title == "" || utf8.RuneCountInString(title) > 128 { return repository.ScaleDefinitionWriteInput{}, ErrInvalidScaleDefinition } if utf8.RuneCountInString(desc) > 512 { return repository.ScaleDefinitionWriteInput{}, ErrInvalidScaleDefinition } return repository.ScaleDefinitionWriteInput{Slug: slug, Title: title, Description: desc}, nil }