3.3 KiB
3.3 KiB
System Overview
YouTube Studio Flow is a multi-tenant SaaS tool for YouTube channel management. Teams connect their YouTube channels, import their video library, manage video metadata using a description template and block system, and push changes back to YouTube via the API.
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | Next.js 15 App Router, TypeScript, CSS Modules |
| Data fetching | TanStack Query v5 |
| Auth state | Zustand |
| Backend | NestJS 10, TypeScript |
| ORM | Prisma (PostgreSQL) |
| Queue | BullMQ + Redis |
| Auth | Google OAuth → JWT (access + refresh tokens) |
| YouTube integration | googleapis package, quota-tracked |
Process Architecture
The backend runs as two separate Node.js processes from the same build:
| Process | Entry point | Role |
|---|---|---|
| API server | src/main.ts |
HTTP API on port 3001 |
| Queue worker | src/worker.ts |
BullMQ job processor (no HTTP) |
Both must be running for full functionality. The API enqueues jobs; the worker executes them.
System Diagram
Browser (Next.js)
│
│ HTTP/REST (NEXT_PUBLIC_API_URL)
▼
NestJS API (:3001)
│
├──── PostgreSQL (Prisma)
├──── Redis (BullMQ queues)
│
└──── BullMQ worker process
│
├──── PostgreSQL (reads/writes)
└──── YouTube API (googleapis)
Request Scoping
Every authenticated request is scoped to a team. The JWT payload contains userId and teamId. All database queries must filter by channel: { teamId } or direct teamId field. See 02 - Backend for the auth pattern.
Key Design Decisions
- Denormalized
lintStatus:Video.lintStatusis a cached column. Any code path that deletesLintResultrows must recompute it manually — it is not computed on-the-fly. - YouTube token encryption: OAuth tokens are AES-256 encrypted in the DB using
TOKEN_ENCRYPTION_KEY. If this key changes all channels need re-authentication. - Queue deduplication: BullMQ job IDs are used for deduplication. The pattern is
lint-{videoId}for lint-once andlint-{videoId}-{timestamp}for forced reruns. - No shared code: Backend and frontend are fully independent — no shared packages, no shared types. The frontend maintains its own API interface definitions in
api.ts. hasPendingChangesnull-hash fallback:Video.lastSyncedHashis set at import time to establish the YouTube baseline. For videos that existed before hashing was introduced (i.e.lastSyncedHash IS NULL),findAllfalls back to computing a synthetic baseline fromyoutubeDescriptionandrecordingDate: nulland compares that against the current state hash. This means pre-existing videos correctly show "push pending" only when local changes diverge from the YouTube state, rather than always appearing as pending. Re-importing a channel resets the hash baseline and will clear any locally-pending changes that have not yet been pushed.
Related
- 02 - Backend — Module structure, patterns, shared services
- 03 - Frontend — Routing, state management, API layer
- 04 - Database Schema — All Prisma models
- 05 - Queue System — BullMQ queues and processors
- 06 - Authentication — OAuth flow, JWT, guards