package usecase import ( "context" "fmt" "strings" "haixun-backend/internal/library/clock" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" personadomain "haixun-backend/internal/model/persona/domain/usecase" "haixun-backend/internal/model/style_preset/domain/entity" domrepo "haixun-backend/internal/model/style_preset/domain/repository" domusecase "haixun-backend/internal/model/style_preset/domain/usecase" "github.com/google/uuid" ) type stylePresetUseCase struct { repo domrepo.Repository persona personadomain.UseCase } func NewUseCase(repo domrepo.Repository, persona personadomain.UseCase) domusecase.UseCase { return &stylePresetUseCase{repo: repo, persona: persona} } func (u *stylePresetUseCase) List(ctx context.Context, tenantID, ownerUID, personaID string) ([]domusecase.PresetSummary, error) { if err := u.require(ctx, tenantID, ownerUID, personaID); err != nil { return nil, err } items, err := u.repo.List(ctx, tenantID, ownerUID, personaID) if err != nil { return nil, err } out := make([]domusecase.PresetSummary, 0, len(items)) for _, item := range items { out = append(out, toSummary(item)) } return out, nil } func (u *stylePresetUseCase) Upsert(ctx context.Context, req domusecase.UpsertRequest) (*domusecase.PresetSummary, error) { if err := u.require(ctx, req.TenantID, req.OwnerUID, req.PersonaID); err != nil { return nil, err } name := strings.TrimSpace(req.Name) if name == "" { return nil, app.For(code.Persona).InputMissingRequired("name is required") } now := clock.NowUnixNano() id := strings.TrimSpace(req.PresetID) item := &entity.Preset{ID: id} if id == "" || id == "new" { item.ID = uuid.NewString() item.CreateAt = now } else if existing, err := u.repo.Get(ctx, req.TenantID, req.OwnerUID, req.PersonaID, id); err == nil { item = existing } item.TenantID = req.TenantID item.OwnerUID = req.OwnerUID item.PersonaID = req.PersonaID item.Name = name item.Tone = strings.TrimSpace(req.Tone) item.CTA = cleanList(req.CTA) item.BannedWords = cleanList(req.BannedWords) item.Notes = strings.TrimSpace(req.Notes) item.UpdateAt = now if item.CreateAt == 0 { item.CreateAt = now } saved, err := u.repo.Upsert(ctx, item) if err != nil { return nil, err } if req.Apply && u.persona != nil { profile := formatPresetProfile(saved) _, _ = u.persona.Update(ctx, personadomain.UpdateRequest{ TenantID: req.TenantID, OwnerUID: req.OwnerUID, PersonaID: req.PersonaID, Patch: personadomain.PersonaPatch{StyleProfile: &profile}, }) } out := toSummary(*saved) return &out, nil } func (u *stylePresetUseCase) Delete(ctx context.Context, tenantID, ownerUID, personaID, presetID string) error { if err := u.require(ctx, tenantID, ownerUID, personaID); err != nil { return err } return u.repo.Delete(ctx, tenantID, ownerUID, personaID, presetID) } func (u *stylePresetUseCase) require(ctx context.Context, tenantID, ownerUID, personaID string) error { if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" { return app.For(code.Auth).AuthUnauthorized("missing actor") } if strings.TrimSpace(personaID) == "" { return app.For(code.Persona).InputMissingRequired("persona_id is required") } if u.persona != nil { _, err := u.persona.Get(ctx, tenantID, ownerUID, personaID) return err } return nil } func toSummary(item entity.Preset) domusecase.PresetSummary { return domusecase.PresetSummary{ ID: item.ID, PersonaID: item.PersonaID, Name: item.Name, Tone: item.Tone, CTA: append([]string(nil), item.CTA...), BannedWords: append([]string(nil), item.BannedWords...), Notes: item.Notes, CreateAt: item.CreateAt, UpdateAt: item.UpdateAt, } } func cleanList(values []string) []string { out := make([]string, 0, len(values)) seen := map[string]struct{}{} for _, value := range values { trimmed := strings.TrimSpace(value) if trimmed == "" { continue } if _, ok := seen[trimmed]; ok { continue } seen[trimmed] = struct{}{} out = append(out, trimmed) } return out } func formatPresetProfile(item *entity.Preset) string { return fmt.Sprintf("語調:%s\nCTA:%s\n禁用詞:%s\n備註:%s", item.Tone, strings.Join(item.CTA, "、"), strings.Join(item.BannedWords, "、"), item.Notes) }