Live Dating Platform
A members-only subscription dating product — two clients on one backend, with real in-app video and voice calling. Signup and moderated photo onboarding, a swipe deck with distance and preference filtering, race-safe matching, realtime chat, WebRTC calls and recurring billing — wrapped in a trust-&-safety stack most dating apps bolt on far too late.
01Overview
A members-only dating platform built as two clients over a single backend: a full-featured Next.js web app (members and the admin panel) and an Expo / React Native app that talks to the same API over Bearer tokens. One member journey — sign up, build a profile, get the primary photo through moderation, browse, match, chat, and call face to face — runs identically on the web and in your pocket, and the same recurring subscription gates the paid surface on both.
The interesting part isn't the swipe deck. It's that a consumer product handling private user photos, payments, stranger-to-stranger messaging and live peer-to-peer calls was built with the trust-&-safety and abuse controls — AI moderation, private media, ban/block enforcement, rate limiting, verification — designed in from the schema up, rather than retrofitted after the first incident.
02The problem it solves
A dating app is deceptively deep. Behind the deck sit identity and session security, payments, a matching engine that can't double-fire under load, realtime messaging, live calling that has to punch through NAT, and — the part that sinks most consumer-social launches — a safety surface: moderation before content goes live, private media that never leaks, blocking and reporting that actually hold, rate limits that blunt abuse, and a path to real age and identity verification. Build that as an afterthought and you ship a liability.
Result a subscription dating product that launches with the calling, moderation, safety and payment machinery normally deferred — web and native from day one, on one backend.
03What we built
Accounts & identity
Hand-rolled credentials auth — bcrypt plus a signed JWT (jose) in an httpOnly cookie — that fails closed if the auth secret is unset. Tokens carry a tokenVersion so "log out everywhere" and bans revoke every live session instantly. Single-use, hashed, expiring tokens drive email verification and enumeration-safe password reset. OAuth (Google, Facebook, Instagram) rides the authorization-code flow with state-cookie CSRF and links identities by email — no SDK. Account deletion is a soft-delete grace window: hidden and signed out immediately, restorable by signing back in, hard-purged later by a daily cron.
Profiles, discovery & matching
Rich profiles — pronouns, height, body type, orientation, goals, interests and prompt cards — with a moderated primary photo gated PENDING / APPROVED / REJECTED and a member-facing banner explaining visibility. Discovery filters mutually on gender and age range, honours preferences (goals, body types, height, smokers) and geocodes each city to coordinates for distance filtering. The deck supports like / pass / super-like and rewind; matches are formed on a canonical, race-safe pair key so a simultaneous mutual like can never create two matches. Around it: who-liked-you, who-viewed-you, daily picks (a deterministic per-day shuffle), unmatch and a vacation/pause mode.
Realtime chat & live calling
Per-match conversations stream over Server-Sent Events on web (the native app polls), with typing indicators, unread badges, read markers and voice and video messages alongside text. On top of the same per-match channel sits real WebRTC video and voice calling: a signaling endpoint relays SDP offer/answer and ICE candidates between the two peers, an ICE-config endpoint hands out STUN plus time-limited HMAC TURN credentials (so calls connect even behind symmetric NAT), and an incoming call wakes the other device through push. The native client runs a full RTCPeerConnection with mute, camera on/off and front/back switching; either side can hang up with a typed reason (declined / busy / ended / timeout).
Moderation & media pipeline
Every uploaded image runs through pluggable AI moderation (OpenAI's omni-moderation model): hard policy violations are refused outright, anything else is risk-scored and falls through to a human review queue — and the system fails open to human review rather than blocking legitimate uploads. Accepted images are re-encoded with sharp — auto-rotated from EXIF, stripped of EXIF for privacy, and written as a compact main image plus a thumbnail.
Subscriptions & billing
Stripe Checkout for the recurring monthly plan, a billing portal, and a webhook that syncs subscription state — including PAST_DUE — back onto the member. Three tiers (FREE / PLUS / PREMIUM) drive a capability matrix: super-likes per day, who-viewed-you, daily picks, rewind, read receipts, advanced filters and an incognito browsing mode. Tier is tracked independently of subscription status, so a canceled member keeps their tier until period end. A dev bypass and local promo codes let the whole flow run without live Stripe keys.
04Trust, safety & the admin panel
Member photos live outside the web root and are served only through an authenticated route that re-checks the session (banned/revoked users are rejected) and authorizes per-photo: the owner always, otherwise only an active member who isn't blocked or banned — private, nosniff, never world-readable, path-traversal-safe. Uploads are validated by magic bytes, not the client-declared MIME, across images, audio and video, killing content-type spoofing and SVG-script tricks. CSRF is enforced by a same-origin check on every mutating route on top of SameSite=Lax; rate limits cover login, signup, upload, like, message, block, report and call signaling. A single guard enforces blocks and bans everywhere — browse, likes, matches, chat, calls and media. Members can block (both ways) and report; KYC-ready age and selfie photo verification surface as profile badges. Admins get a separate, 404-on-non-admin, CSRF-protected area — dashboard, a moderation queue for primary photos, photo-verification review, report triage (dismiss / resolve / resolve&ban) and user tools (ban / unban / revoke sessions) — and every admin action lands in an immutable audit log.
05Tech
06Highlights
- In-app WebRTC video & voice calling — SDP/ICE signaling over the match channel, time-limited HMAC TURN credentials, push-woken incoming calls, native peer connection with mute and camera switching.
- Two clients, one backend — full member journey on Next.js web and an Expo/React Native app over a Bearer-token API.
- AI image moderation on every upload (hard-reject + risk-scored human-review queue), failing open to review rather than blocking.
- Private, access-controlled media: stored outside the web root, per-photo authorization, EXIF-stripped
sharppipeline, magic-byte validation for image/audio/video. - Race-safe matching on a canonical pair key; like / pass / super-like, rewind, who-liked-you, who-viewed-you and daily picks.
- Realtime chat over SSE with typing indicators, read markers and voice/video messages; graceful block/ban/expiry handling.
- Stripe subscriptions with webhook state sync and PAST_DUE handling; FREE/PLUS/PREMIUM capability matrix incl. incognito mode.
- Credentials auth that fails closed;
tokenVersionsession revocation; admin panel with an immutable audit log; soft-delete with restore + purge cron.