Appearance
Historical snapshot archived 2026-09-25. This records an earlier review or plan, not current implementation or live ticket state. For current work, follow root AGENTS.md, the relevant BloxClips skill, and owning repository source/tests. Preserve approved decisions as evidence; verify their present authority before acting.
Data Model
Overview
The backend uses Prisma against PostgreSQL. prisma/schema.prisma is the model source and prisma/migrations/ adds important database-only constraints such as case-insensitive uniqueness, external-table RLS, and partial uniqueness for open payouts. Application statuses are mostly strings rather than Prisma enums; callers must preserve exact spellings.
mermaid
erDiagram
WebUser ||--o{ AuthAccount : authenticates_with
WebUser ||--o{ Submission : creates
WebUser ||--o{ LinkedSocialAccount : proves_ownership
Campaign ||--o{ Submission : receives
Submission ||--o{ ViewSnapshot : measured_by
Submission ||--o{ PayoutItem : paid_through
WebUser ||--o{ Payout : requests
Payout ||--o{ PayoutItem : contains
WebUser ||--o| TaxForm : current_form
TaxForm ||--o{ TaxFormSubmission : retains
TaxForm ||--o{ TaxFormStateTransition : audits
TaxFormSubmission ||--o{ Payout : snapshotted_by
WebUser ||--o{ Referral : referrer
WebUser ||--o| Referral : referred_user
Payout ||--o| ReferralCommission : triggers
WebUser ||--o{ ReferralCommission : earnsCore entities
WebUser and AuthAccount
WebUser is the application identity. Important fields include legacy discordId, normalized email, profile/onboarding fields, jwtVersion, TOS acceptance, preferred payout method, tax/address data, and referral linkage. AuthAccount represents Discord or Google provider accounts and stores encrypted provider tokens. Provider/account ID is unique; a user can have multiple providers.
Important constraints exist in migrations for case-insensitive handles, normalized phone, and normalized email. Major mutations are owned by src/api/routes/auth.ts, onboarding.ts, users.ts, moderation admin routes, and tax recollection utilities.
Campaign
Represents an operational paid creator campaign—not a public marketing case study. Key fields are name/game, string-formatted budget/rates, platform/video-type gates, minimum views, caps, deadline, submission toggles, tracking duration, fee rate, and budget-freeze state. A campaign has submissions and legacy Discord user channels.
active, acceptingSubmissions, paused, isDeleted, and viewsFrozen are separate switches. Their combinations matter and are not represented by one status enum.
Precise campaign rates are append-only CampaignRateVersion rows effective at a timestamp. CPM and creator RPM are independent DECIMAL(20,6) USD-per-1,000-view values, with nullable long-form variants. API values are canonical decimal strings. The rate migration creates an initial version for every existing campaign: safely parsed payout text becomes RPM, CPM receives the same neutral break-even value, and malformed payout text receives a $0.30 CPM/RPM default. Runtime APIs have no legacy migration state or fallback path.
Submission
Links a user/video to a campaign. It retains both userId (historically Discord) and nullable webUserId. Metrics have several layers: initial/current counts, optional manual override, optional frozen count, polling fields, and snapshots. customRate/customViewCap override campaign rules. Legacy one-shot payout fields coexist with cumulative delta-payout fields.
Lifecycle: PENDING → ACCEPTED | DENIED | FLAGGED. Tracking is independent of moderation status: every submitted clip remains eligible until its submission window expires or its campaign finishes. Polling becomes less frequent as the clip ages. Scrape failures may still create operational stop/flag outcomes, and payout-item review may deny/flag the submission without itself ending metric collection.
New individual RPM overrides are append-only SubmissionRateVersion rows. A version with null RPM explicitly removes the override prospectively. The legacy Float customRate is retained for historical reads but is not written by the precise-rate endpoint.
LinkedSocialAccount and verification challenges
LinkedSocialAccount proves one WebUser owns a platform account; (platform, platformAccountId) is globally unique. PendingVerification stores a ten-minute bio code and is unique per user/platform/account. VerificationAttempt provides the rolling failure/lockout audit. These are mutated by src/api/routes/verifications.ts after platform profile scraping.
Payout and PayoutItem
Payout is both a legacy transfer record and the aggregate for the current review/send workflow. Current lifecycle:
text
REQUESTED → SCRAPING → READY_FOR_REVIEW → AWAITING_SEND → PROCESSING → COMPLETED
↘ BELOW_THRESHOLD / FAILED
READY_FOR_REVIEW → REJECTEDLegacy rows use PENDING, PROCESSING, COMPLETED, and FAILED. Migrations enforce at most one open current-flow payout per creator, including AWAITING_SEND.
PayoutItem snapshots views, caps, prior paid views, rate, gross/net amount, scrape failure reason, badges, and admin decision (PENDING, APPROVED, REJECTED, FLAGGED). Approval increments cumulative submission payout fields before the separate send step; terminal rail failure includes reversal logic.
Payment methods
StripeAccount, PayPalAccount, and UsdtPayoutMethod are one-per-user destinations. Legacy Discord IDs still appear in Stripe/PayPal. PayPal schema retains OAuth token columns, while current route comments describe an email-based flow. PaymentSystemConfig is a singleton for PayPal cycle volume and circuit-breaker state.
Tax entities
TaxForm is mutable current state; TaxFormSubmission is an immutable submission/PDF/TIN-verification record; TaxFormStateTransition is append-only history. Important states are:
text
pending_submission, submitted, pending_verification, verified,
mismatch, verification_error, manually_verified, manually_failed,
manual_review_required, invalidated, expiredverified and manually_verified are the accepted form states. W-8BEN expiration and retention timestamps are explicit. FtinCountryConfig controls country-specific FTIN guidance.
Referrals
Referral is one immutable attribution per referred user and records campaign-program cohort plus anti-fraud evidence. ReferralCommission is a payout-triggered accrual with PENDING → PAID; unique sourcePayoutId prevents duplicate accrual. ReferralLinkAlias maps admin-created slugs to a canonical user referral code.
Communication and operations
Announcementfans out toUserNotification; preferences are one per user.WhopIdentityis the one-to-one BloxClipsWebUserto Whop connected-account owner mapping. Support conversation data belongs to Whop and is not stored locally.BookingRequest,BookingAvailabilityConfig,ContactAttempt,FeaturedGame, andFeaturedServicesupport marketing operations.AdminAuditLogand newerAuditLogare distinct ledgers.ExternalLeaderboardPaymentrepresents off-platform payout history matched optionally to a user.GuildConfigstores one Discord guild/channel configuration row.
Tables intentionally outside the diagram
Discord tickets/bans, notifications, marketing records, and operational singleton tables matter but do not define the main campaign-to-payout path. PV tracker records are not database entities at all; they live in JSON state under assets/.
Difficult-to-infer or drifted fields
- Monetary campaign values are strings (
budget,payout,payoutLong) and parsed in utilities; their accepted formats are an application contract. Submission.userIdand several notification/paymentuserIdfields historically mean Discord ID, while newer code preferswebUserId.Payout.amountmeans net on current rows but may equal gross on legacy rows; nullable gross/fee fields distinguish them.PayPalAccountretains token fields even though current comments describe no OAuth.UserChannelandTicketbelong to Discord-era workflows; their current production relevance is unknown.- Many state fields are unvalidated database strings, so mutation code—not the schema—owns allowed transitions.