feat(ECR-046): ScaleDefinition 元数据写面闭环并 Closed
explore.write POST/PUT · create→draft · status 仍 ECR-008 · migration 000055 · Loop STOP Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user