Pulse
/ Architecture & build plan
Back to index
Build plan MVP Last updated Apr 24, 2026

Pulse - from front-end
to back-end

Full stack plan for a campaign reporting dashboard that stays simple to operate. Built to ingest public post metrics through n8n and ScrapeCreators, track committed-vs-actual KPIs, and ship a polished read-only view for clients.

See the designs Open Campaign Detail

Product goals

Every architectural choice below serves one of these five outcomes.

1 · Truth
Committed vs actual, always fresh
The agency and client see the same campaign-level numbers after each scheduled sync - no "let me get back to you" emails.
2 · Speed
Dashboard opens in <1.5s
Pre-aggregated snapshots, not live SQL - influencer counts stay fast even at 100+ KOLs per campaign.
3 · Signal
Alerts surface before the client asks
Underperforming campaigns auto-flag at 50%, 80%, and 95% timeline so the agency can follow up proactively.
4 · Trust
Client view feels premium
Co-branded read-only links, PDF export, no Pulse "marketing" - the agency looks like the hero.

Screens delivered

Seven fully designed screens, all linked in the navigation hub.

ScreenPurposeRoute
Campaign ListAll campaigns at a glance, select 2+ to compare/campaigns
Campaign DetailHero screen - KPIs, daily growth, platform split, influencer grid/campaigns/:id
Influencer DetailPer-KOL deep dive - performance, real public posts, contract/influencers/:id
Alerts CenterTriage under-KPI / engagement-drop / deadline alerts/alerts
CompareSide-by-side for 2-3 campaigns with winners & CPM/compare?ids=...
Client ViewRead-only shareable link - no sidebar, no edit actions/share/:token
Architecture DocThis page/docs/architecture

Tech stack

Frontend
Static React front-end now
Phase này dùng HTML + React/Babel CDN để test UI nhanh. PulseStore mặc định đọc/ghi localStorage, nhưng đã có API mode cho pilot: mở app với ?api=1 để đọc/ghi qua backend tối thiểu.
Backend
Backend API + n8n trên VPS
Backend trả JSON đã chuẩn hoá cho PULSE. n8n chạy lịch 5-6 lần/ngày, ghi execution log, retry, alert khi job fail. Browser không gọi ScrapeCreators trực tiếp và không giữ API key.
Database
PostgreSQL hoặc backend store của bạn
Core relational store. Có thể thêm TimescaleDB khi metric history lớn. PULSE chỉ cần API contract ổn định.
Infra
ScrapeCreators API + n8n
n8n gọi ScrapeCreators cho TikTok, Instagram, Facebook public metrics. n8n giữ lịch chạy, retry, log và validate dữ liệu trước khi upsert vào backend.

Why this stack


System architecture

Three loops run continuously. The dashboard is just a view over the state they produce.

The three loops

TikTok posts via ScrapeCreators Instagram posts via ScrapeCreators Facebook posts via ScrapeCreators Manual upload CSV import 01 · INGESTION n8n + ScrapeCreators Schedule: 5-6/day ScrapeCreators API Execution logs, retries LOOP 1 · REFRESH RAW METRICS 02 · AGGREGATION SQL views + cron campaign_daily, kpi_snap LOOP 2 · ROLL UP EVERY 5 MIN 03 · ALERTING Rule engine 50 / 80 / 95 timeline KPI LOOP 3 · EVALUATE RULES Postgres + Timescale campaigns · influencers · posts metrics_hourly · alerts 04 · UI PULSE UI Agency /app Client /share/:token API ADAPTER READY

Loop 1 · Ingestion

Loop 2 · Aggregation

Loop 3 · Alerting


PostgreSQL schema

Only the essentials - expand as you go.

-- CORE ENTITIES
CREATE TABLE organizations (
  id UUID PRIMARY KEY, name TEXT, created_at TIMESTAMPTZ
);

CREATE TABLE users (
  id UUID PRIMARY KEY, org_id UUID REFERENCES organizations,
  email CITEXT UNIQUE, role TEXT -- owner | manager | analyst
);

CREATE TABLE clients (
  id UUID PRIMARY KEY, org_id UUID,
  name TEXT, brand_color TEXT, logo_url TEXT
);

CREATE TABLE campaigns (
  id UUID PRIMARY KEY, org_id UUID, client_id UUID,
  name TEXT, status TEXT, -- draft | live | ended
  start_date DATE, end_date DATE, budget_cents BIGINT,
  committed_views BIGINT, committed_engagement BIGINT
);

CREATE TABLE influencers (
  id UUID PRIMARY KEY, display_name TEXT, avatar_url TEXT,
  tier TEXT -- nano | micro | mid | macro | mega
);

CREATE TABLE influencer_handles (
  id UUID PRIMARY KEY, influencer_id UUID,
  platform TEXT, -- tiktok | instagram | facebook
  handle TEXT, platform_user_id TEXT, followers BIGINT
);

-- THE BOOKING - a KOL committed to a campaign
CREATE TABLE bookings (
  id UUID PRIMARY KEY,
  campaign_id UUID, influencer_id UUID,
  fee_cents BIGINT,
  committed_views BIGINT, committed_engagement BIGINT,
  committed_posts INT, min_engagement_rate NUMERIC(5,2),
  exclusivity_days INT, usage_rights_days INT
);

-- POSTS - each booked piece of content
CREATE TABLE posts (
  id UUID PRIMARY KEY,
  booking_id UUID, platform TEXT,
  platform_post_id TEXT, url TEXT, thumb_url TEXT,
  type TEXT, -- video | reel | feed | live | short
  caption TEXT, pull_status TEXT,
  error_message TEXT, posted_at TIMESTAMPTZ,
  last_pulled_at TIMESTAMPTZ, source TEXT
);

-- TIME-SERIES (Timescale hypertable)
CREATE TABLE metrics_hourly (
  post_id UUID, ts TIMESTAMPTZ,
  views BIGINT, likes BIGINT, comments BIGINT,
  shares BIGINT, saves BIGINT, reach BIGINT
);
SELECT create_hypertable('metrics_hourly', 'ts');

-- DERIVED SNAPSHOTS (updated by Loop 2)
CREATE TABLE kpi_snapshots (
  booking_id UUID PRIMARY KEY,
  actual_views BIGINT, actual_engagement BIGINT,
  actual_posts INT, kpi_pct NUMERIC(5,2),
  engagement_rate NUMERIC(5,2), updated_at TIMESTAMPTZ
);

-- ALERTS
CREATE TABLE alerts (
  id UUID PRIMARY KEY, campaign_id UUID, booking_id UUID,
  type TEXT, -- under_kpi | engagement_drop | deadline | sentiment
  severity TEXT, message TEXT, status TEXT,
  raised_at TIMESTAMPTZ, resolved_at TIMESTAMPTZ
);

-- SHARE LINKS
CREATE TABLE share_links (
  token TEXT PRIMARY KEY, -- 32-char random
  campaign_id UUID, created_by UUID,
  expires_at TIMESTAMPTZ, password_hash TEXT,
  last_viewed_at TIMESTAMPTZ, view_count INT DEFAULT 0
);

API surface

Current REST shape for the pilot backend. Without ?api=1, the front-end still uses localStorage as a safe demo fallback. Admin CRUD currently persists through the full snapshot endpoint; granular create/update endpoints can be added after the pilot schema is stable.

MethodRouteNotes
GET/api/healthBackend readiness and store mode
GET/api/pulseFull PULSE snapshot for the front-end API adapter
PUT/api/pulsePersist Admin changes from API mode into the pilot backend store
GET/api/campaignsCampaign list for dashboard views
GET/api/campaigns/:idSingle campaign record
GET/api/campaigns/:id/bookingsBookings for campaign detail and influencer detail
GET/api/campaigns/:id/postsPublic post metrics pulled through n8n/ScrapeCreators
GET/api/campaigns/:id/dailyDaily growth series
GET/api/alertsCurrent stale-data / KPI alert rows
POST/api/alerts/:id/resolveMark resolved
POST/api/auth/email-linkSend a Firebase email sign-in link through the backend mailer
POST/api/ingest/postsn8n sends normalized ScrapeCreators payloads to backend
GET/api/share/:tokenClient read-only campaign payload; hides fees/costs/internal fields
POST/api/client-invitesCreate hashed client share token and send invite email when auth/email are configured
GET/api/media-proxyServer-side thumbnail proxy for API/production mode

Real-time strategy

Three layers - each cheaper than the last.

Layer 1
Webhook push (seconds)
Platforms that support it (IG comments, TikTok creator events) push straight into the pipeline.
Layer 2
n8n schedule (5-6/day)
The steady beat. Most metrics come from these runs - cheaper and easier to debug than heavy scraping inside the dashboard.
Layer 3
Dashboard refresh
After each sync, the API/local adapter refreshes the dashboard with normalized metrics. No API key lives in the UI.

"Real-time" is a spectrum - here's what the client actually sees

EventDelay to dashboard
Post URL added to a bookingNext n8n scheduled run or manual run
View count update on TikTok postNext ScrapeCreators/n8n pull window (5-6/day)
Campaign crosses 80% timelineAfter next payload sync + aggregation
New alert raisedImmediately after aggregation in the UI

Security & client sharing


Build roadmap

6 weeks to v1 with one full-stack engineer + one designer.

WeekShipKey risk
1Auth, org/client/campaign CRUD, CSV import for metricsNone - scaffolding
2Campaign Detail v1 with mocked metrics, design system finalizedDesign decisions
3n8n workflow family, ScrapeCreators payload import, provider error loggingQuota and missing post URLs
4TikTok, Instagram, Facebook post metrics + alerting engineRate limits and missing post URLs
5Client share links, PDF export, compare viewPDF rendering fidelity
6Polish, internal QA, beta with 1 friendly clientClient expectations - under-promise real-time

What I'd NOT build in v1


Agency decisions for v1

  1. Conversions. Hidden for now; add them later when the integration picture is clear.
  2. Influencer KPIs. Based on committed public posts/videos for the brand. No influencer login, account connection, or creator API token is needed in v1.
  3. Client workflow. 1990 staff set up each campaign in the backend/admin, then share a read-only URL for the client to follow progress.
  4. Historical backfill. Not required right now; v1 focuses on live and upcoming campaigns.
  5. Multi-tenant / white-label. Not required right now; build for 1990 Agency first.
  6. Notifications. Use Google Chat webhook later, after the core system is fully functional.

- End of plan -
Hand off to engineering. Start shipping.
Back to the screens