Files

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.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.
  • 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