package usecase import ( "context" "strings" "haixun-backend/internal/library/clock" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" "haixun-backend/internal/model/content_formula/domain/entity" domrepo "haixun-backend/internal/model/content_formula/domain/repository" domusecase "haixun-backend/internal/model/content_formula/domain/usecase" "github.com/google/uuid" ) type useCase struct { repo domrepo.Repository } func NewUseCase(repo domrepo.Repository) domusecase.UseCase { return &useCase{repo: repo} } func (u *useCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.FormulaSummary, error) { if err := requireActor(req.TenantID, req.OwnerUID, req.AccountID); err != nil { return nil, err } label := strings.TrimSpace(req.Label) if label == "" { label = "未命名公式" } sourceType := strings.TrimSpace(req.SourceType) if sourceType == "" { sourceType = entity.SourcePaste } formulaText := strings.TrimSpace(req.Formula) if formulaText == "" && strings.TrimSpace(req.PostTemplate) == "" { return nil, app.For(code.ThreadsAccount).InputMissingRequired("formula or post_template is required") } now := clock.NowUnixNano() item := &entity.ContentFormula{ ID: uuid.NewString(), TenantID: req.TenantID, OwnerUID: req.OwnerUID, AccountID: req.AccountID, Label: label, SourceType: sourceType, SourceRef: strings.TrimSpace(req.SourceRef), SourcePostText: strings.TrimSpace(req.SourcePostText), SourceAuthor: strings.TrimSpace(req.SourceAuthor), SourcePermalink: strings.TrimSpace(req.SourcePermalink), SourceMetrics: req.SourceMetrics, Summary: strings.TrimSpace(req.Summary), Wins: req.Wins, Improvements: req.Improvements, Formula: formulaText, PostTemplate: strings.TrimSpace(req.PostTemplate), HookPattern: strings.TrimSpace(req.HookPattern), Structure: strings.TrimSpace(req.Structure), ReplicationTips: req.ReplicationTips, Avoid: req.Avoid, Tags: req.Tags, CreateAt: now, UpdateAt: now, } if err := u.repo.Create(ctx, item); err != nil { return nil, err } summary := toSummary(*item) return &summary, nil } func (u *useCase) Get(ctx context.Context, tenantID, ownerUID, accountID, formulaID string) (*domusecase.FormulaSummary, error) { if err := requireActor(tenantID, ownerUID, accountID); err != nil { return nil, err } item, err := u.repo.Get(ctx, tenantID, ownerUID, accountID, formulaID) if err != nil { return nil, err } summary := toSummary(*item) return &summary, nil } func (u *useCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.FormulaSummary, error) { if err := requireActor(req.TenantID, req.OwnerUID, req.AccountID); err != nil { return nil, err } patch := map[string]interface{}{"update_at": clock.NowUnixNano()} if req.Label != nil { patch["label"] = strings.TrimSpace(*req.Label) } if req.Formula != nil { patch["formula"] = strings.TrimSpace(*req.Formula) } if req.PostTemplate != nil { patch["post_template"] = strings.TrimSpace(*req.PostTemplate) } if req.HookPattern != nil { patch["hook_pattern"] = strings.TrimSpace(*req.HookPattern) } if req.Structure != nil { patch["structure"] = strings.TrimSpace(*req.Structure) } if req.Tags != nil { patch["tags"] = req.Tags } if len(patch) == 1 { return nil, app.For(code.ThreadsAccount).InputMissingRequired("no fields to update") } item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.AccountID, req.FormulaID, patch) if err != nil { return nil, err } summary := toSummary(*item) return &summary, nil } func (u *useCase) Delete(ctx context.Context, tenantID, ownerUID, accountID, formulaID string) error { if err := requireActor(tenantID, ownerUID, accountID); err != nil { return err } return u.repo.Delete(ctx, tenantID, ownerUID, accountID, formulaID) } func (u *useCase) List(ctx context.Context, req domusecase.ListRequest) (*domusecase.ListResult, error) { if err := requireActor(req.TenantID, req.OwnerUID, req.AccountID); err != nil { return nil, err } result, err := u.repo.List(ctx, domrepo.ListFilter{ TenantID: req.TenantID, OwnerUID: req.OwnerUID, AccountID: req.AccountID, SourceType: req.SourceType, Tag: req.Tag, Page: req.Page, PageSize: req.PageSize, }) if err != nil { return nil, err } out := make([]domusecase.FormulaSummary, 0, len(result.Items)) for _, item := range result.Items { out = append(out, toSummary(item)) } return &domusecase.ListResult{Items: out, Total: result.Total}, nil } func requireActor(tenantID, ownerUID, accountID string) error { if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" { return app.For(code.Auth).AuthUnauthorized("missing actor") } if strings.TrimSpace(accountID) == "" { return app.For(code.ThreadsAccount).InputMissingRequired("account_id is required") } return nil } func toSummary(item entity.ContentFormula) domusecase.FormulaSummary { return domusecase.FormulaSummary{ ID: item.ID, AccountID: item.AccountID, Label: item.Label, SourceType: item.SourceType, SourceRef: item.SourceRef, SourcePostText: item.SourcePostText, SourceAuthor: item.SourceAuthor, SourcePermalink: item.SourcePermalink, SourceMetrics: item.SourceMetrics, Summary: item.Summary, Wins: item.Wins, Improvements: item.Improvements, Formula: item.Formula, PostTemplate: item.PostTemplate, HookPattern: item.HookPattern, Structure: item.Structure, ReplicationTips: item.ReplicationTips, Avoid: item.Avoid, Tags: item.Tags, CreateAt: item.CreateAt, UpdateAt: item.UpdateAt, } }