Initial commit: YouTube Studio Flow (backend, frontend, infrastructure, docs)

This commit is contained in:
2026-08-11 12:27:44 +02:00
commit d5af006443
304 changed files with 74604 additions and 0 deletions
@@ -0,0 +1,65 @@
# 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.lintStatus` is a cached column. Any code path that deletes `LintResult` rows 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 and `lint-{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`.
- **`hasPendingChanges` null-hash fallback**: `Video.lastSyncedHash` is set at import time to establish the YouTube baseline. For videos that existed before hashing was introduced (i.e. `lastSyncedHash IS NULL`), `findAll` falls back to computing a synthetic baseline from `youtubeDescription` and `recordingDate: null` and 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