136 lines
4.2 KiB
Go
136 lines
4.2 KiB
Go
|
|
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)
|
|||
|
|
}
|