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 ( ErrInvalidReportTemplate = errors.New("invalid report template") ErrReportTemplateConflict = errors.New("report template code conflict") reportTemplateCodeRe = regexp.MustCompile(`^[a-z][a-z0-9_]{1,62}$`) reportTemplateSceneRe = regexp.MustCompile(`^[a-z][a-z0-9_]{0,30}$`) ) // ReportTemplateWriteBody is JSON for create/update. type ReportTemplateWriteBody struct { Code string `json:"code"` Title string `json:"title"` Scene string `json:"scene"` Active bool `json:"active"` } // CreateReportTemplate validates, inserts, audits. func (s *Service) CreateReportTemplate(ctx context.Context, adminID uuid.UUID, body ReportTemplateWriteBody) (*repository.ReportTemplateRow, error) { in, err := normalizeReportTemplateWrite(body) if err != nil { return nil, err } meta, _ := json.Marshal(map[string]any{"code": in.Code, "scene": in.Scene, "active": in.Active}) row, err := s.Repo.CreateReportTemplateWithAudit(ctx, adminID, in, meta) if repository.ReportTemplateCodeConflict(err) { return nil, ErrReportTemplateConflict } return row, err } // UpdateReportTemplate validates, updates, audits. func (s *Service) UpdateReportTemplate(ctx context.Context, adminID, id uuid.UUID, body ReportTemplateWriteBody) (*repository.ReportTemplateRow, error) { in, err := normalizeReportTemplateWrite(body) if err != nil { return nil, err } meta, _ := json.Marshal(map[string]any{"code": in.Code, "scene": in.Scene, "active": in.Active}) row, err := s.Repo.UpdateReportTemplateWithAudit(ctx, adminID, id, in, meta) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrReportTemplateNotFound } if repository.ReportTemplateCodeConflict(err) { return nil, ErrReportTemplateConflict } return row, err } func normalizeReportTemplateWrite(body ReportTemplateWriteBody) (repository.ReportTemplateWriteInput, error) { code := strings.TrimSpace(body.Code) title := strings.TrimSpace(body.Title) scene := strings.TrimSpace(body.Scene) if !reportTemplateCodeRe.MatchString(code) { return repository.ReportTemplateWriteInput{}, ErrInvalidReportTemplate } if !reportTemplateSceneRe.MatchString(scene) { return repository.ReportTemplateWriteInput{}, ErrInvalidReportTemplate } if title == "" || utf8.RuneCountInString(title) > 128 { return repository.ReportTemplateWriteInput{}, ErrInvalidReportTemplate } return repository.ReportTemplateWriteInput{Code: code, Title: title, Scene: scene, Active: body.Active}, nil }