193 lines
5.6 KiB
Go
193 lines
5.6 KiB
Go
|
|
package usecase
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
app "haixun-backend/internal/library/errors"
|
||
|
|
"haixun-backend/internal/library/errors/code"
|
||
|
|
"haixun-backend/internal/model/persona/domain/entity"
|
||
|
|
domrepo "haixun-backend/internal/model/persona/domain/repository"
|
||
|
|
domusecase "haixun-backend/internal/model/persona/domain/usecase"
|
||
|
|
|
||
|
|
"github.com/google/uuid"
|
||
|
|
)
|
||
|
|
|
||
|
|
type personaUseCase struct {
|
||
|
|
repo domrepo.Repository
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewUseCase(repo domrepo.Repository) domusecase.UseCase {
|
||
|
|
return &personaUseCase{repo: repo}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *personaUseCase) List(ctx context.Context, tenantID, ownerUID string) (*domusecase.ListResult, error) {
|
||
|
|
if err := requireActor(tenantID, ownerUID); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
items, err := u.repo.ListByOwner(ctx, tenantID, ownerUID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
list := make([]domusecase.PersonaSummary, 0, len(items))
|
||
|
|
for _, item := range items {
|
||
|
|
list = append(list, toSummary(item))
|
||
|
|
}
|
||
|
|
return &domusecase.ListResult{List: list}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *personaUseCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.PersonaSummary, error) {
|
||
|
|
if err := requireActor(req.TenantID, req.OwnerUID); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
displayName := strings.TrimSpace(req.DisplayName)
|
||
|
|
if displayName == "" {
|
||
|
|
existing, err := u.repo.ListByOwner(ctx, req.TenantID, req.OwnerUID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
displayName = "人設 " + itoa(len(existing)+1)
|
||
|
|
}
|
||
|
|
item, err := u.repo.Create(ctx, &entity.Persona{
|
||
|
|
ID: uuid.NewString(),
|
||
|
|
TenantID: req.TenantID,
|
||
|
|
OwnerUID: req.OwnerUID,
|
||
|
|
DisplayName: displayName,
|
||
|
|
Status: entity.StatusOpen,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
summary := toSummary(item)
|
||
|
|
return &summary, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *personaUseCase) Get(ctx context.Context, tenantID, ownerUID, personaID string) (*domusecase.PersonaSummary, error) {
|
||
|
|
item, err := u.assertOwned(ctx, tenantID, ownerUID, personaID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
summary := toSummary(item)
|
||
|
|
return &summary, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *personaUseCase) Delete(ctx context.Context, tenantID, ownerUID, personaID string) error {
|
||
|
|
if _, err := u.assertOwned(ctx, tenantID, ownerUID, personaID); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return u.repo.SoftDelete(ctx, tenantID, ownerUID, personaID)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *personaUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.PersonaSummary, error) {
|
||
|
|
if _, err := u.assertOwned(ctx, req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
patch := patchToMap(req.Patch)
|
||
|
|
item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.PersonaID, patch)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
summary := toSummary(item)
|
||
|
|
return &summary, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *personaUseCase) assertOwned(ctx context.Context, tenantID, ownerUID, personaID string) (*entity.Persona, error) {
|
||
|
|
if err := requireActor(tenantID, ownerUID); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if strings.TrimSpace(personaID) == "" {
|
||
|
|
return nil, app.For(code.Persona).InputMissingRequired("persona id is required")
|
||
|
|
}
|
||
|
|
return u.repo.FindByID(ctx, tenantID, ownerUID, personaID)
|
||
|
|
}
|
||
|
|
|
||
|
|
func requireActor(tenantID, ownerUID string) error {
|
||
|
|
if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
|
||
|
|
return app.For(code.Persona).InputMissingRequired("tenant_id and uid are required")
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func toSummary(item *entity.Persona) domusecase.PersonaSummary {
|
||
|
|
if item == nil {
|
||
|
|
return domusecase.PersonaSummary{}
|
||
|
|
}
|
||
|
|
return domusecase.PersonaSummary{
|
||
|
|
ID: item.ID,
|
||
|
|
DisplayName: item.DisplayName,
|
||
|
|
Persona: item.Persona,
|
||
|
|
Brief: item.Brief,
|
||
|
|
ProductBrief: item.ProductBrief,
|
||
|
|
TargetAudience: item.TargetAudience,
|
||
|
|
Goals: item.Goals,
|
||
|
|
StyleProfile: item.StyleProfile,
|
||
|
|
StyleBenchmark: item.StyleBenchmark,
|
||
|
|
SeedQuery: item.SeedQuery,
|
||
|
|
CopyResearchMap: toCopyMapSummary(item.CopyResearchMap),
|
||
|
|
CreateAt: item.CreateAt,
|
||
|
|
UpdateAt: item.UpdateAt,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func toCopyMapSummary(m entity.CopyResearchMap) domusecase.CopyResearchMapSummary {
|
||
|
|
return domusecase.CopyResearchMapSummary{
|
||
|
|
AudienceSummary: m.AudienceSummary,
|
||
|
|
ContentGoal: m.ContentGoal,
|
||
|
|
Questions: append([]string(nil), m.Questions...),
|
||
|
|
Pillars: append([]string(nil), m.Pillars...),
|
||
|
|
Exclusions: append([]string(nil), m.Exclusions...),
|
||
|
|
SuggestedTags: append([]string(nil), m.SuggestedTags...),
|
||
|
|
BenchmarkNotes: m.BenchmarkNotes,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func patchToMap(patch domusecase.PersonaPatch) map[string]interface{} {
|
||
|
|
out := map[string]interface{}{}
|
||
|
|
if patch.DisplayName != nil {
|
||
|
|
out["display_name"] = strings.TrimSpace(*patch.DisplayName)
|
||
|
|
}
|
||
|
|
if patch.Persona != nil {
|
||
|
|
out["persona"] = strings.TrimSpace(*patch.Persona)
|
||
|
|
}
|
||
|
|
if patch.Brief != nil {
|
||
|
|
out["brief"] = strings.TrimSpace(*patch.Brief)
|
||
|
|
}
|
||
|
|
if patch.ProductBrief != nil {
|
||
|
|
out["product_brief"] = strings.TrimSpace(*patch.ProductBrief)
|
||
|
|
}
|
||
|
|
if patch.TargetAudience != nil {
|
||
|
|
out["target_audience"] = strings.TrimSpace(*patch.TargetAudience)
|
||
|
|
}
|
||
|
|
if patch.Goals != nil {
|
||
|
|
out["goals"] = strings.TrimSpace(*patch.Goals)
|
||
|
|
}
|
||
|
|
if patch.StyleProfile != nil {
|
||
|
|
out["style_profile"] = strings.TrimSpace(*patch.StyleProfile)
|
||
|
|
}
|
||
|
|
if patch.StyleBenchmark != nil {
|
||
|
|
out["style_benchmark"] = strings.TrimSpace(*patch.StyleBenchmark)
|
||
|
|
}
|
||
|
|
if patch.SeedQuery != nil {
|
||
|
|
out["seed_query"] = strings.TrimSpace(*patch.SeedQuery)
|
||
|
|
}
|
||
|
|
if patch.CopyResearchMap != nil {
|
||
|
|
out["copy_research_map"] = *patch.CopyResearchMap
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
func itoa(n int) string {
|
||
|
|
if n <= 0 {
|
||
|
|
return "1"
|
||
|
|
}
|
||
|
|
buf := make([]byte, 0, 12)
|
||
|
|
for n > 0 {
|
||
|
|
buf = append(buf, byte('0'+n%10))
|
||
|
|
n /= 10
|
||
|
|
}
|
||
|
|
for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
|
||
|
|
buf[i], buf[j] = buf[j], buf[i]
|
||
|
|
}
|
||
|
|
return string(buf)
|
||
|
|
}
|