301 lines
12 KiB
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)
|
|
}
|