Skip to content

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.

System Architecture ​

Overview ​

The submission system spans frontend (Next.js), backend API (Express), background scheduler, Discord bot (legacy), and external scrapers (Apify, YouTube Data API).

┌─────────────┐     ┌─────────────┐     ┌──────────────────┐
│   Frontend  │────▶│  Backend    │────▶│   PostgreSQL     │
│  (Next.js)  │     │  (Express)  │     │    (Prisma)      │
└─────────────┘     └──────┬──────┘     └──────────────────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Scheduler│ │  Apify   │ │ YouTube  │
        │ (30 min) │ │ Scrapers │ │ Data API │
        └──────────┘ └──────────┘ └──────────┘
              │
              ▼
        ┌──────────┐
        │ Discord  │  (notifications only; legacy approval removed)
        └──────────┘

Major Components ​

1. Frontend (Next.js 15, App Router) ​

Routes:

  • /dashboard/submit — Clipper submission modal (SubmitVideoScreen.tsx)
  • /dashboard/history — Clipper submission history (SubmissionHistoryScreen.tsx)
  • /dashboard/admin/submissions — Admin review queue (AdminSubmissionsScreen.tsx)
  • /dashboard/admin/campaigns/[id] — Campaign-scoped review (AdminCampaignDetailScreen.tsx)

State/Hooks:

  • useSubmissionHistory — Fetches paginated user submissions
  • useAdminSubmissions / useAdminCampaignDetail — Admin review with filters, snapshots, actions

2. Backend API (Express) ​

Public Routes (src/api/routes/submissions.ts):

  • GET /api/submissions — User's submissions with budget-aware earnings
  • POST /api/submissions — Create submission (scrape, verify account, create row)

Admin Routes (src/api/routes/admin.ts):

  • GET /api/admin/submissions/review — Paginated queue with filters
  • PUT /api/admin/submissions/:id/status — Accept/Deny/Flag (triggers tracking, budget clamp, notifications)
  • PUT /api/admin/submissions/:id/views — Manual view override
  • PUT /api/api/admin/submissions/:id/rate — Custom RPM override
  • PUT /api/admin/submissions/:id/cap — Custom view cap override
  • GET /api/admin/submissions/:id/snapshots — View history for chart

3. Background Scheduler (src/scheduler.ts) ​

Runs on Express server startup via setInterval:

JobIntervalPurpose
Tracking tick30 minPoll due ACCEPTED submissions, update views, budget clamp
Campaign expiry60 secClose expired campaigns, send Discord reports

Tracking tick detail (src/utils/tracking/runTrackingTick.ts):

  • Queries Submission where status=ACCEPTED, nextPollAt <= now, trackingStoppedAt=null, frozenViewCount=null
  • Groups by platform: YouTube (Data API, batch 50), TikTok/Instagram (Apify actors)
  • On success: writes currentViews, ViewSnapshot, resets consecutiveScrapeFailures, sets nextPollAt via pollScheduler
  • On failure: increments consecutiveScrapeFailures; at 3 → FLAGGED, trackingStoppedAt
  • Budget clamp: computeScrapeBudgetClamp (first-come-first-earned) → may set frozenViewCount and campaign.viewsFrozen=true

4. External Scrapers ​

PlatformProviderAuthCost
YouTubeYouTube Data API v3YOUTUBE_API_KEYFree (quota)
TikTokApify clockworks/tiktok-scraperAPIFY_TOKENPer result
InstagramApify apify/instagram-scraperAPIFY_TOKENPer result

Cost tracking: src/utils/scrapers/costTracker.ts records rows per user/platform.

5. Discord Integration ​

Current: In-app UserNotification only (created on accept, payout sent/rejected, campaign launch)

Legacy (removed): Discord message with Approve/Deny buttons in requestLogChannel — code commented in submissions.ts:37-124. Button handler (buttonHandler.ts) now returns "use dashboard" for submission actions.


Data Flow Summary ​

Submission Creation ​

POST /api/submissions
  → validate URL (Zod: HTTPS, allowed domains)
  → detect platform (YouTube/TikTok/Instagram)
  → check campaign active, platform allowed
  → duplicate check: same videoLink + campaignId
  → cross-campaign cooldown (10 min)
  → scrape metadata (YouTube API / Apify)
  → verify LinkedSocialAccount ownership
    → if missing: return 412 ACCOUNT_VERIFICATION_REQUIRED
    → if owned by another: 403
  → create Submission (PENDING, initialViews=scraped views)
  → create ViewSnapshot (initial data point)
  → return submission

Review → Tracking ​

PUT /api/admin/submissions/:id/status {status: ACCEPTED}
  → budget pre-check (getCampaignSpend ≥ 100% → 409)
  → update status=ACCEPTED, acceptedAt, nextPollAt=now
  → budget clamp on accept (may freeze campaign)
  → checkAndCloseCampaign (95% threshold)
  → create UserNotification (SUBMISSION_ACCEPTED)
  → audit log (SUBMISSION_ACCEPTED)

Tracking Tick (every 30 min) ​

runTrackingTick()
  → find due ACCEPTED submissions
  → filter expired (acceptedAt + trackingDurationDays)
  → group by platform, scrape batch
  → per submission: computeScrapeBudgetClamp
    → if clamp.didFreezeCampaign → markCampaignFrozen
  → update currentViews, ViewSnapshot, nextPollAt
  → on 3 failures → FLAGGED, trackingStoppedAt

Payout Request ​

POST /api/payouts/request
  → cooldown check (24h since last COMPLETED)
  → daily rescrape cap (200)
  → open payout check
  → payment method connected?
  → balance check (clipper net + affiliate ≥ $100)
  → tax form gate (assertCanPayout)
  → create Payout (REQUESTED)
  → async processPayoutRequest()
    → rescrape all eligible submissions
    → build PayoutItems (delta math: payableViews - priorPaidViews)
    → if net ≥ $100 → READY_FOR_REVIEW
      else → BELOW_THRESHOLD

Payout Review → Send ​

Admin: GET /api/admin/payouts/review → READY_FOR_REVIEW queue
Admin: POST /api/admin/payouts/review/:id/items/:itemId {decision: APPROVED}
Admin: POST /api/admin/payouts/review/:id/approve
  → gate check (tax, method, amount)
  → sticky status: REJECTED→DENIED, FLAGGED→FLAGGED (stops tracking)
  → bump paidViewsTotal / paidAmountTotal on submissions
  → tax form snapshot
  → affiliate sweep + accrual
  → status → AWAITING_SEND
Admin (Pending Payouts tab): POST /api/admin/payouts/review/:id/send (TOTP)
  → re-verify gate
  → dispatchPayout (Stripe/PayPal/USDT)
  → COMPLETED or FAILED (reverts paid-totals, reverses affiliate)

Key Services / Utilities ​

FileResponsibility
src/utils/campaignBudget.tsgetCampaignSpend, computeScrapeBudgetClamp, checkAndCloseCampaign, markCampaignFrozen
src/utils/calculateSubmissionEarnings.tscalculateCampaignEarnings (budget-aware, first-come-first-earned)
src/utils/tracking/pollScheduler.tscomputeNextPollAt (12h/24h cadence), isTrackingExpired
src/utils/tracking/runTrackingTick.tsUnified polling tick
src/utils/payouts/processRequest.tsUser-initiated payout orchestrator
src/utils/payouts/rescrape.tsPayout-time rescrape with budget clamp
src/utils/payouts/badges.tsAuto-detect fraud signals (like ratio, view spike)
src/utils/fees.tsapplyClipperFee (7%), burnMultiplier (platform fee)
src/utils/payout.tsparseRate, parseBudget
src/utils/platforms.tsURL detection, ID extraction
src/utils/scrapers/apify.tsTikTok/Instagram Apify wrappers
src/utils/youtube.tsYouTube Data API batch calls

Database Relationships (Core) ​

WebUser 1──∞ Submission ∞──1 Campaign
Submission 1──∞ ViewSnapshot
Submission 1──∞ PayoutItem ∞──1 Payout
Payout ∞──1 WebUser
LinkedSocialAccount ∞──1 WebUser

Environment Variables (Submission-Relevant) ​

VariableUsed By
DATABASE_URLPrisma
YOUTUBE_API_KEYYouTube scraping
APIFY_TOKENTikTok/Instagram scraping
APIFY_TIKTOK_ACTOR_IDTikTok actor (default: clockworks/tiktok-scraper)
APIFY_INSTAGRAM_ACTOR_IDIG actor (default: apify/instagram-scraper)
STRIPE_SECRET_KEYPayout dispatch
PAYPAL_CLIENT_ID/SECRETPayPal payouts
NOWPAYMENTS_API_KEYUSDT payouts
DISCORD_BOT_TOKENNotifications, campaign expiry reports
MINIMUM_PAYOUT_AMOUNTDefault $100
FRONTEND_URLReferral links, email templates