thread-master/apps/backend/internal/module/growth/domain/domain.go

301 lines
12 KiB
Go

package domain
import (
"context"
"errors"
"time"
"github.com/google/uuid"
)
var (
ErrNotFound = errors.New("growth not found")
ErrForbidden = errors.New("growth access denied")
ErrValidation = errors.New("growth validation")
ErrThrottle = errors.New("account health throttle")
ErrReviewPending = errors.New("draft review required")
ErrRateLimited = errors.New("checkup regenerate limit")
)
const (
SourceScoutOutreach = "scout_outreach"
SourceOutboxStep = "outbox_step"
KindReach = "reach"
KindReply = "reply"
KindFollow = "follow"
KindConversion = "conversion"
KindInteraction = "interaction"
ConfidenceConfirmed = "confirmed"
ConfidencePossible = "possible"
StatusObserving = "observing"
StatusConfirmed = "confirmed"
StatusPossible = "possible"
StatusExpired = "expired"
StatusAmended = "amended"
CheckupGenerating = "generating"
CheckupReady = "ready"
CheckupFailed = "failed"
HealthNormal = "normal"
HealthWarn = "warn"
HealthThrottle = "throttle"
ReviewNone = "none"
ReviewPending = "pending"
ReviewApproved = "approved"
ReviewRejected = "rejected"
RewardCredited = "credited"
RewardCapped = "capped"
RewardSkipped = "skipped"
ObserveWindow = 72 * time.Hour
)
func NowNano() int64 { return time.Now().UTC().UnixNano() }
func NewID() string { return uuid.NewString() }
type OutcomeEvent struct {
ID string `bson:"_id" json:"id"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
SourceType string `bson:"source_type" json:"source_type"`
SourceID string `bson:"source_id" json:"source_id"`
ThreadsAccountID string `bson:"threads_account_id,omitempty" json:"threads_account_id,omitempty"`
Kind string `bson:"kind" json:"kind"`
Confidence string `bson:"confidence" json:"confidence"`
Status string `bson:"status" json:"status"`
ReplyCountDelta int `bson:"reply_count_delta,omitempty" json:"reply_count_delta,omitempty"`
LikeCountDelta int `bson:"like_count_delta,omitempty" json:"like_count_delta,omitempty"`
FollowersDelta int `bson:"followers_delta,omitempty" json:"followers_delta,omitempty"`
ConversionAmount float64 `bson:"conversion_amount,omitempty" json:"conversion_amount,omitempty"`
ConversionNote string `bson:"conversion_note,omitempty" json:"conversion_note,omitempty"`
ConversionCurrency string `bson:"conversion_currency,omitempty" json:"conversion_currency,omitempty"`
HasConversion bool `bson:"has_conversion" json:"has_conversion"`
ClickCount int `bson:"click_count,omitempty" json:"click_count,omitempty"`
WindowEndsAt int64 `bson:"window_ends_at" json:"window_ends_at"`
SentAt int64 `bson:"sent_at" json:"sent_at"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
const (
KindClick = "click"
PlaybookBrief = "brief"
PlaybookPersona = "persona"
PlaybookPlay = "play"
WSRoleOwner = "owner"
WSRoleEditor = "editor"
WSRoleReviewer = "reviewer"
)
type Playbook struct {
ID string `bson:"_id" json:"id"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
Kind string `bson:"kind" json:"kind"`
Title string `bson:"title" json:"title"`
Niche string `bson:"niche" json:"niche"`
Body string `bson:"body" json:"body"`
Anonymous bool `bson:"anonymous" json:"anonymous"`
ImportCount int `bson:"import_count" json:"import_count"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
type UtmLink struct {
ID string `bson:"_id" json:"id"`
Code string `bson:"code" json:"code"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
DestinationURL string `bson:"destination_url" json:"destination_url"`
OutcomeID string `bson:"outcome_id,omitempty" json:"outcome_id,omitempty"`
Label string `bson:"label,omitempty" json:"label,omitempty"`
Clicks int `bson:"clicks" json:"clicks"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
}
type WorkspaceMember struct {
ID string `bson:"_id" json:"id"`
WorkspaceID string `bson:"workspace_id" json:"workspace_id"`
UID int64 `bson:"uid" json:"uid"`
Role string `bson:"role" json:"role"`
JoinedAt int64 `bson:"joined_at" json:"joined_at"`
}
// BenchmarkSample — anonymized contribution for niche medians
type BenchmarkSample struct {
ID string `bson:"_id" json:"id"`
Niche string `bson:"niche" json:"niche"`
EngRate float64 `bson:"eng_rate" json:"eng_rate"`
AvgViews float64 `bson:"avg_views" json:"avg_views"`
OwnerHash string `bson:"owner_hash" json:"owner_hash"` // one sample per owner per niche
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
type OutcomeSummary struct {
From int64
To int64
Reach int
Conversations int
FollowsPossible int
FollowsConfirmed int
Conversions int
ConversionAmount float64
}
type CheckupFinding struct {
Claim string `bson:"claim" json:"claim"`
Evidence []string `bson:"evidence" json:"evidence"`
}
type CheckupAction struct {
Title string `bson:"title" json:"title"`
Reason string `bson:"reason" json:"reason"`
Deeplink string `bson:"deeplink" json:"deeplink"`
}
type WeeklyCheckup struct {
ID string `bson:"_id" json:"id"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
WeekKey string `bson:"week_key" json:"week_key"`
Status string `bson:"status" json:"status"`
Summary string `bson:"summary" json:"summary"`
Findings []CheckupFinding `bson:"findings" json:"findings"`
Actions []CheckupAction `bson:"actions" json:"actions"`
Error string `bson:"error,omitempty" json:"error,omitempty"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
type AccountHealth struct {
ThreadsAccountID string `bson:"_id" json:"threads_account_id"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
Score int `bson:"score" json:"score"`
Level string `bson:"level" json:"level"`
Factors []string `bson:"factors" json:"factors"`
Advice string `bson:"advice" json:"advice"`
ComputedAt int64 `bson:"computed_at" json:"computed_at"`
}
type Workspace struct {
ID string `bson:"_id" json:"id"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
Name string `bson:"name" json:"name"`
ReviewRequired bool `bson:"review_required" json:"review_required"`
IsDefault bool `bson:"is_default" json:"is_default"`
Archived bool `bson:"archived" json:"archived"`
FooterText string `bson:"footer_text,omitempty" json:"footer_text,omitempty"`
HidePoweredBy bool `bson:"hide_powered_by,omitempty" json:"hide_powered_by,omitempty"`
BrandName string `bson:"brand_name,omitempty" json:"brand_name,omitempty"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
type MemberWorkspaceState struct {
OwnerUID int64 `bson:"_id" json:"owner_uid"`
CurrentWorkspaceID string `bson:"current_workspace_id" json:"current_workspace_id"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
type DraftReview struct {
ID string `bson:"_id" json:"id"`
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
WorkspaceID string `bson:"workspace_id" json:"workspace_id"`
RefType string `bson:"ref_type" json:"ref_type"`
RefID string `bson:"ref_id" json:"ref_id"`
Status string `bson:"status" json:"status"`
Reason string `bson:"reason,omitempty" json:"reason,omitempty"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
}
type InviteReward struct {
ID string `bson:"_id" json:"id"`
InviterUID int64 `bson:"inviter_uid" json:"inviter_uid"`
InviteeUID int64 `bson:"invitee_uid" json:"invitee_uid"`
PlanID string `bson:"plan_id" json:"plan_id"`
Points int `bson:"points" json:"points"`
Status string `bson:"status" json:"status"`
CreatedAt int64 `bson:"created_at" json:"created_at"`
}
type OutcomeListFilter struct {
Kind string
Confidence string
SourceType string
From int64
To int64
Page int
PageSize int
}
type Repository interface {
// Outcomes
UpsertOutcomeBySource(ctx context.Context, e *OutcomeEvent) error
GetOutcome(ctx context.Context, id string) (*OutcomeEvent, error)
ListOutcomes(ctx context.Context, ownerUID int64, f OutcomeListFilter) ([]*OutcomeEvent, int64, error)
SaveOutcome(ctx context.Context, e *OutcomeEvent) error
ListObservingDue(ctx context.Context, now int64, limit int) ([]*OutcomeEvent, error)
// Checkups
SaveCheckup(ctx context.Context, c *WeeklyCheckup) error
GetCheckup(ctx context.Context, id string) (*WeeklyCheckup, error)
LatestCheckup(ctx context.Context, ownerUID int64) (*WeeklyCheckup, error)
ListCheckups(ctx context.Context, ownerUID int64, page, pageSize int) ([]*WeeklyCheckup, int64, error)
CountCheckupGenerations(ctx context.Context, ownerUID int64, sinceNano int64) (int64, error)
// Health
SaveHealth(ctx context.Context, h *AccountHealth) error
GetHealth(ctx context.Context, accountID string) (*AccountHealth, error)
ListHealth(ctx context.Context, ownerUID int64) ([]*AccountHealth, error)
// Workspace
SaveWorkspace(ctx context.Context, w *Workspace) error
GetWorkspace(ctx context.Context, id string) (*Workspace, error)
ListWorkspaces(ctx context.Context, ownerUID int64, includeArchived bool) ([]*Workspace, error)
CountActiveWorkspaces(ctx context.Context, ownerUID int64) (int64, error)
GetMemberWSState(ctx context.Context, ownerUID int64) (*MemberWorkspaceState, error)
SaveMemberWSState(ctx context.Context, s *MemberWorkspaceState) error
// Reviews
SaveReview(ctx context.Context, r *DraftReview) error
GetReview(ctx context.Context, id string) (*DraftReview, error)
FindReviewByRef(ctx context.Context, ownerUID int64, refType, refID string) (*DraftReview, error)
ListReviews(ctx context.Context, ownerUID int64, status string, page, pageSize int) ([]*DraftReview, int64, error)
// Invite rewards
SaveInviteReward(ctx context.Context, r *InviteReward) error
ListInviteRewards(ctx context.Context, inviterUID int64, limit int) ([]*InviteReward, error)
SumInviteRewardPointsMonth(ctx context.Context, inviterUID int64, monthStartNano int64) (int, error)
SumInviteRewardPointsTotal(ctx context.Context, inviterUID int64) (int, error)
HasInviteRewardForInvitee(ctx context.Context, inviteeUID int64) (bool, error)
// P2 Playbooks
SavePlaybook(ctx context.Context, p *Playbook) error
GetPlaybook(ctx context.Context, id string) (*Playbook, error)
ListPlaybooks(ctx context.Context, kind, niche string, ownerOnlyUID int64, page, pageSize int) ([]*Playbook, int64, error)
DeletePlaybook(ctx context.Context, id string) error
IncPlaybookImport(ctx context.Context, id string) error
// P2 UTM
SaveUtmLink(ctx context.Context, u *UtmLink) error
GetUtmByCode(ctx context.Context, code string) (*UtmLink, error)
ListUtmLinks(ctx context.Context, ownerUID int64) ([]*UtmLink, error)
IncUtmClick(ctx context.Context, code string) (*UtmLink, error)
// P2 Workspace members
SaveWorkspaceMember(ctx context.Context, m *WorkspaceMember) error
ListWorkspaceMembers(ctx context.Context, workspaceID string) ([]*WorkspaceMember, error)
RemoveWorkspaceMember(ctx context.Context, workspaceID string, uid int64) error
IsWorkspaceMember(ctx context.Context, workspaceID string, uid int64) (bool, string, error)
// P2 Benchmark
UpsertBenchmarkSample(ctx context.Context, s *BenchmarkSample) error
ListBenchmarkSamples(ctx context.Context, niche string, limit int) ([]*BenchmarkSample, error)
}