haixunMaster/haixun-backend/internal/model/copy_draft/usecase/usecase.go

288 lines
9.3 KiB
Go
Raw Permalink Normal View History

2026-06-24 10:02:42 +00:00
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/copy_draft/domain/entity"
domrepo "haixun-backend/internal/model/copy_draft/domain/repository"
domusecase "haixun-backend/internal/model/copy_draft/domain/usecase"
"github.com/google/uuid"
)
type copyDraftUseCase struct {
repo domrepo.Repository
}
func NewUseCase(repo domrepo.Repository) domusecase.UseCase {
return &copyDraftUseCase{repo: repo}
}
func (u *copyDraftUseCase) Create(ctx context.Context, req domusecase.CreateRequest) (*domusecase.CopyDraftSummary, error) {
if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
return nil, err
}
text := strings.TrimSpace(req.Text)
if text == "" {
return nil, app.For(code.Persona).InputMissingRequired("draft text is required")
}
draftType := strings.TrimSpace(req.DraftType)
if draftType == "" {
draftType = entity.DraftTypeViralReplica
}
item := &entity.CopyDraft{
ID: uuid.NewString(),
TenantID: req.TenantID,
OwnerUID: req.OwnerUID,
PersonaID: req.PersonaID,
2026-06-25 08:20:03 +00:00
CopyMissionID: strings.TrimSpace(req.CopyMissionID),
2026-06-24 10:02:42 +00:00
ScanPostID: strings.TrimSpace(req.ScanPostID),
DraftType: draftType,
2026-06-25 08:20:03 +00:00
SortOrder: req.SortOrder,
2026-06-24 10:02:42 +00:00
Text: text,
Angle: strings.TrimSpace(req.Angle),
Hook: strings.TrimSpace(req.Hook),
Rationale: strings.TrimSpace(req.Rationale),
ReferenceNotes: strings.TrimSpace(req.ReferenceNotes),
Sources: req.Sources,
Status: "pending",
CreateAt: clock.NowUnixNano(),
}
if err := u.repo.Create(ctx, item); err != nil {
return nil, err
}
summary := toSummary(*item)
return &summary, nil
}
2026-06-25 08:20:03 +00:00
func (u *copyDraftUseCase) CreateMany(ctx context.Context, req domusecase.CreateManyRequest) ([]domusecase.CopyDraftSummary, error) {
if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
return nil, err
}
if len(req.Drafts) == 0 {
return nil, app.For(code.Persona).InputMissingRequired("drafts is required")
}
items := make([]*entity.CopyDraft, 0, len(req.Drafts))
for idx, draftReq := range req.Drafts {
text := strings.TrimSpace(draftReq.Text)
if text == "" {
continue
}
draftType := strings.TrimSpace(draftReq.DraftType)
if draftType == "" {
draftType = entity.DraftTypeMatrix
}
sortOrder := draftReq.SortOrder
if sortOrder == 0 {
sortOrder = idx + 1
}
items = append(items, &entity.CopyDraft{
ID: uuid.NewString(),
TenantID: req.TenantID,
OwnerUID: req.OwnerUID,
PersonaID: req.PersonaID,
CopyMissionID: strings.TrimSpace(draftReq.CopyMissionID),
ScanPostID: strings.TrimSpace(draftReq.ScanPostID),
DraftType: draftType,
SortOrder: sortOrder,
Text: text,
Angle: strings.TrimSpace(draftReq.Angle),
Hook: strings.TrimSpace(draftReq.Hook),
Rationale: strings.TrimSpace(draftReq.Rationale),
ReferenceNotes: strings.TrimSpace(draftReq.ReferenceNotes),
Sources: draftReq.Sources,
Status: "pending",
CreateAt: clock.NowUnixNano(),
})
}
if len(items) == 0 {
return nil, app.For(code.Persona).InputMissingRequired("draft text is required")
}
if err := u.repo.CreateMany(ctx, items); err != nil {
return nil, err
}
out := make([]domusecase.CopyDraftSummary, 0, len(items))
for _, item := range items {
out = append(out, toSummary(*item))
}
return out, nil
}
func (u *copyDraftUseCase) Get(ctx context.Context, tenantID, ownerUID, personaID, draftID string) (*domusecase.CopyDraftSummary, error) {
if err := requireActor(tenantID, ownerUID, personaID); err != nil {
return nil, err
}
item, err := u.repo.Get(ctx, tenantID, ownerUID, personaID, draftID)
if err != nil {
return nil, err
}
summary := toSummary(*item)
return &summary, nil
}
func (u *copyDraftUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.CopyDraftSummary, error) {
if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
return nil, err
}
draftID := strings.TrimSpace(req.DraftID)
if draftID == "" {
return nil, app.For(code.Persona).InputMissingRequired("draft_id is required")
}
patch := map[string]interface{}{}
if req.Patch.Text != nil {
text := strings.TrimSpace(*req.Patch.Text)
if text == "" {
return nil, app.For(code.Persona).InputMissingRequired("draft text cannot be empty")
}
patch["text"] = text
}
if req.Patch.Hook != nil {
patch["hook"] = strings.TrimSpace(*req.Patch.Hook)
}
if req.Patch.Angle != nil {
patch["angle"] = strings.TrimSpace(*req.Patch.Angle)
}
if req.Patch.Status != nil {
status := strings.TrimSpace(*req.Patch.Status)
if status != "" && status != "pending" && status != "ready" {
return nil, app.For(code.Persona).InputMissingRequired("status must be pending or ready")
}
if status != "" {
patch["status"] = status
}
}
if len(patch) == 0 {
return nil, app.For(code.Persona).InputMissingRequired("no fields to update")
}
item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.PersonaID, draftID, patch)
if err != nil {
return nil, err
}
summary := toSummary(*item)
return &summary, nil
}
func (u *copyDraftUseCase) MarkPublished(ctx context.Context, req domusecase.MarkPublishedRequest) (*domusecase.CopyDraftSummary, error) {
if err := requireActor(req.TenantID, req.OwnerUID, req.PersonaID); err != nil {
return nil, err
}
draftID := strings.TrimSpace(req.DraftID)
mediaID := strings.TrimSpace(req.MediaID)
if draftID == "" || mediaID == "" {
return nil, app.For(code.Persona).InputMissingRequired("draft_id and media_id are required")
}
now := clock.NowUnixNano()
patch := map[string]interface{}{
"status": "published",
"published_media_id": mediaID,
"published_permalink": strings.TrimSpace(req.Permalink),
"published_at": now,
}
item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, req.PersonaID, draftID, patch)
if err != nil {
return nil, err
}
summary := toSummary(*item)
return &summary, nil
}
func (u *copyDraftUseCase) ReplaceMissionMatrix(
ctx context.Context,
tenantID, ownerUID, personaID, missionID string,
drafts []domusecase.CreateRequest,
) ([]domusecase.CopyDraftSummary, error) {
if err := requireActor(tenantID, ownerUID, personaID); err != nil {
return nil, err
}
missionID = strings.TrimSpace(missionID)
if missionID == "" {
return nil, app.For(code.Persona).InputMissingRequired("copy_mission_id is required")
}
if err := u.repo.DeleteByMissionAndType(ctx, tenantID, ownerUID, personaID, missionID, entity.DraftTypeMatrix); err != nil {
return nil, err
}
return u.CreateMany(ctx, domusecase.CreateManyRequest{
TenantID: tenantID,
OwnerUID: ownerUID,
PersonaID: personaID,
Drafts: drafts,
})
}
func (u *copyDraftUseCase) ClearByMission(ctx context.Context, tenantID, ownerUID, personaID, missionID string) error {
if err := requireActor(tenantID, ownerUID, personaID); err != nil {
return err
}
missionID = strings.TrimSpace(missionID)
if missionID == "" {
return app.For(code.Persona).InputMissingRequired("copy_mission_id is required")
}
return u.repo.DeleteByMission(ctx, tenantID, ownerUID, personaID, missionID)
}
func (u *copyDraftUseCase) ListByMission(ctx context.Context, tenantID, ownerUID, personaID, missionID string, limit int) ([]domusecase.CopyDraftSummary, error) {
if err := requireActor(tenantID, ownerUID, personaID); err != nil {
return nil, err
}
items, err := u.repo.ListByMission(ctx, tenantID, ownerUID, personaID, missionID, limit)
if err != nil {
return nil, err
}
out := make([]domusecase.CopyDraftSummary, 0, len(items))
for _, item := range items {
out = append(out, toSummary(item))
}
return out, nil
}
2026-06-24 10:02:42 +00:00
func (u *copyDraftUseCase) List(ctx context.Context, tenantID, ownerUID, personaID string, limit int) ([]domusecase.CopyDraftSummary, error) {
if err := requireActor(tenantID, ownerUID, personaID); err != nil {
return nil, err
}
items, err := u.repo.List(ctx, tenantID, ownerUID, personaID, limit)
if err != nil {
return nil, err
}
out := make([]domusecase.CopyDraftSummary, 0, len(items))
for _, item := range items {
out = append(out, toSummary(item))
}
return out, nil
}
func requireActor(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")
}
return nil
}
func toSummary(item entity.CopyDraft) domusecase.CopyDraftSummary {
return domusecase.CopyDraftSummary{
2026-06-25 08:20:03 +00:00
ID: item.ID,
PersonaID: item.PersonaID,
CopyMissionID: item.CopyMissionID,
ScanPostID: item.ScanPostID,
DraftType: item.DraftType,
SortOrder: item.SortOrder,
Text: item.Text,
Angle: item.Angle,
Hook: item.Hook,
Rationale: item.Rationale,
ReferenceNotes: item.ReferenceNotes,
Sources: item.Sources,
Status: item.Status,
PublishedMediaID: item.PublishedMediaID,
PublishedPermalink: item.PublishedPermalink,
PublishedAt: item.PublishedAt,
CreateAt: item.CreateAt,
2026-06-24 10:02:42 +00:00
}
}