thread-master/backend/internal/model/brand/usecase/usecase.go

242 lines
7.0 KiB
Go
Raw Permalink Normal View History

2026-06-26 08:37:04 +00:00
package usecase
import (
"context"
"strings"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
libkg "haixun-backend/internal/library/knowledge"
"haixun-backend/internal/library/placement"
"haixun-backend/internal/model/brand/domain/entity"
domrepo "haixun-backend/internal/model/brand/domain/repository"
domusecase "haixun-backend/internal/model/brand/domain/usecase"
"github.com/google/uuid"
)
type brandUseCase struct {
repo domrepo.Repository
}
func NewUseCase(repo domrepo.Repository) domusecase.UseCase {
return &brandUseCase{repo: repo}
}
func (u *brandUseCase) 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.BrandSummary, 0, len(items))
for _, item := range items {
list = append(list, toSummary(item))
}
return &domusecase.ListResult{List: list}, nil
}
func (u *brandUseCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.BrandSummary, 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)
}
productBrief := strings.TrimSpace(req.ProductBrief)
if productBrief == "" && strings.TrimSpace(req.ProductContext) != "" {
productBrief = strings.TrimSpace(req.ProductContext)
}
brand := &entity.Brand{
ID: uuid.NewString(),
TenantID: req.TenantID,
OwnerUID: req.OwnerUID,
DisplayName: displayName,
SeedQuery: strings.TrimSpace(req.SeedQuery),
Brief: strings.TrimSpace(req.Brief),
ProductBrief: productBrief,
ProductContext: strings.TrimSpace(req.ProductContext),
TargetAudience: strings.TrimSpace(req.TargetAudience),
Goals: strings.TrimSpace(req.Goals),
Status: entity.StatusOpen,
}
if req.ResearchMap != nil {
brand.ResearchMap = *req.ResearchMap
}
item, err := u.repo.Create(ctx, brand)
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
func (u *brandUseCase) Get(ctx context.Context, tenantID, ownerUID, brandID string) (*domusecase.BrandSummary, error) {
item, err := u.assertOwned(ctx, tenantID, ownerUID, brandID)
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
func (u *brandUseCase) Delete(ctx context.Context, tenantID, ownerUID, brandID string) error {
if _, err := u.assertOwned(ctx, tenantID, ownerUID, brandID); err != nil {
return err
}
return u.repo.SoftDelete(ctx, tenantID, ownerUID, brandID)
}
func (u *brandUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.BrandSummary, error) {
brand, err := u.assertOwned(ctx, req.TenantID, req.OwnerUID, req.BrandID)
if err != nil {
return nil, err
}
patch := patchToMap(req.Patch)
if req.Patch.ProductID != nil {
snapshot := placement.ResolveBrandProductContext(*brand, strings.TrimSpace(*req.Patch.ProductID), "")
patch["product_context"] = snapshot
}
item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.BrandID, patch)
if err != nil {
return nil, err
}
summary := toSummary(item)
return &summary, nil
}
func (u *brandUseCase) assertOwned(ctx context.Context, tenantID, ownerUID, brandID string) (*entity.Brand, error) {
if err := requireActor(tenantID, ownerUID); err != nil {
return nil, err
}
if strings.TrimSpace(brandID) == "" {
return nil, app.For(code.Brand).InputMissingRequired("brand id is required")
}
return u.repo.FindByID(ctx, tenantID, ownerUID, brandID)
}
func requireActor(tenantID, ownerUID string) error {
if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
return app.For(code.Brand).InputMissingRequired("tenant_id and uid are required")
}
return nil
}
func toSummary(item *entity.Brand) domusecase.BrandSummary {
if item == nil {
return domusecase.BrandSummary{}
}
products := make([]domusecase.ProductSummary, 0, len(item.Products))
for _, product := range item.Products {
products = append(products, toProductSummary(product))
}
return domusecase.BrandSummary{
ID: item.ID,
DisplayName: item.DisplayName,
TopicName: item.TopicName,
SeedQuery: item.SeedQuery,
Brief: item.Brief,
ProductBrief: item.ProductBrief,
ProductContext: item.ProductContext,
ProductID: item.ProductID,
Products: products,
TargetAudience: item.TargetAudience,
Goals: item.Goals,
ResearchMap: item.ResearchMap,
CreateAt: item.CreateAt,
UpdateAt: item.UpdateAt,
}
}
func patchToMap(patch domusecase.BrandPatch) map[string]interface{} {
out := map[string]interface{}{}
if patch.DisplayName != nil {
out["display_name"] = strings.TrimSpace(*patch.DisplayName)
}
if patch.TopicName != nil {
out["topic_name"] = strings.TrimSpace(*patch.TopicName)
}
if patch.SeedQuery != nil {
out["seed_query"] = strings.TrimSpace(*patch.SeedQuery)
}
if patch.Brief != nil {
out["brief"] = strings.TrimSpace(*patch.Brief)
}
if patch.ProductBrief != nil {
out["product_brief"] = strings.TrimSpace(*patch.ProductBrief)
}
if patch.ProductContext != nil {
out["product_context"] = strings.TrimSpace(*patch.ProductContext)
}
if patch.ProductID != nil {
out["product_id"] = strings.TrimSpace(*patch.ProductID)
}
if patch.TargetAudience != nil {
out["target_audience"] = strings.TrimSpace(*patch.TargetAudience)
}
if patch.Goals != nil {
out["goals"] = strings.TrimSpace(*patch.Goals)
}
if patch.AudienceSummary != nil {
out["research_map.audience_summary"] = strings.TrimSpace(*patch.AudienceSummary)
}
if patch.ContentGoal != nil {
out["research_map.content_goal"] = strings.TrimSpace(*patch.ContentGoal)
}
if patch.QuestionsSet {
out["research_map.questions"] = cleanStringList(patch.Questions)
}
if patch.PillarsSet {
out["research_map.pillars"] = cleanStringList(patch.Pillars)
}
if patch.ExclusionsSet {
out["research_map.exclusions"] = cleanStringList(patch.Exclusions)
}
if patch.ResearchMap != nil {
out["research_map"] = *patch.ResearchMap
}
if patch.PatrolKeywordsSet {
out["research_map.patrol_keywords"] = libkg.NormalizePatrolKeywordList(patch.PatrolKeywords)
}
return out
}
func cleanStringList(items []string) []string {
out := make([]string, 0, len(items))
seen := make(map[string]struct{}, len(items))
for _, item := range items {
trimmed := strings.TrimSpace(item)
if trimmed == "" {
continue
}
if _, ok := seen[trimmed]; ok {
continue
}
seen[trimmed] = struct{}{}
out = append(out, trimmed)
}
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)
}