Matthew Gisi

Fastify TypeScript Prisma BullMQ FFmpeg WebSocket

Recall — Private
Coaching Platform

Edgewood High School Esports · Built independently

A fully self-hosted coaching platform built for a competitive Overwatch team. Six integrated modules — VOD library with HLS adaptive streaming, async strategy playbook, interactive slide-deck lessons, real-time collaborative whiteboard, competitive draft helper, and a central admin panel — all locked behind Google SSO with role-based access. No public routes. No third-party video hosting.

Built around
three constraints.

🔒

Private by Design

Every route requires an authenticated session. Google OAuth is the only entry point — only @edgewoodhs.org addresses and individually whitelisted emails can log in. New accounts land on a pending screen until the coach approves them. There is no public-facing content anywhere on the platform.

⏱️

Async-First Coaching

The coach prepares content once — VODs, playbook entries, lessons — and players consume it on their own schedule. Review tracking, unread badges, and comment threads keep the feedback loop intact without requiring everyone to be online at the same time.

🗄️

Self-Hosted Infrastructure

Video files are stored in MinIO (S3-compatible object storage) and transcoded locally via FFmpeg into multi-quality HLS. No footage leaves the server. The entire platform — API, worker, web, database, storage — runs in Docker on private hardware.

Six tools,
one platform.

Auth

Authentication & Roles

Google OAuth via openid-client. Sessions are issued as httpOnly JWT cookies — 8-hour active tokens, 30-day refresh. Two roles: Coach (full access) and Player (read-only consumer). The coach account is hardcoded; players are approved individually through the admin panel.

VOD Library

Video-on-Demand

Recordings are organized in a collapsible Match → Map → POV hierarchy. Videos are uploaded through the admin panel, transcoded to HLS via a BullMQ background job, and served via adaptive streaming with HLS.js. The coach can lock individual videos to specific players, add timestamp bookmarks that appear as chapter markers, and toggle per-video visibility.

Playbook

Strategy Distribution

Async strategy entries the coach publishes for players to review on their own time. Each entry supports text, images, and embedded video clips. Players can mark entries reviewed, post comments, and reply to coach feedback. Unread badges surface new replies. The coach tracks review completion per player and can draft entries before publishing.

Lessons

Interactive Slide Decks

Structured learning modules built as fullscreen slide presentations. Eight slide types: Title, Content (bullets + highlight box), Two-column, Three-column, Video (YouTube / Google Drive / internal VOD), Activity, Takeaways, Stats. A [[word]] syntax renders inline orange highlights throughout. Lessons can be locked to specific players and toggled between draft and published.

Whiteboard

Real-Time Collaborative Canvas

A live strategy drawing tool synced in real time via WebSocket — multiple users see the same board state simultaneously. The coach places and labels hero tokens, draws with an adjustable pen tool, and drops payload markers on a map background or plain canvas. Boards support named snapshots, a multi-frame timeline for building positioning sequences, and export to MP4 or GIF.

Draft Helper

Competitive Draft Enforcement

Enforces Overwatch competitive ban rules across a multi-game series. The coach defines reusable Week Rotations (ordered game modes), then steps through map selection → Ban 1 → Ban 2 per game. Back-to-back ban enforcement grays out heroes banned in adjacent games. Sessions export as a printable PDF or a full JSON audit log with teams, maps, bans, roles, and timestamps.

What runs
under the hood.

Transcoding

FFmpeg + BullMQ Pipeline

Uploaded video files are queued in BullMQ (backed by Redis) and processed by a separate worker container. FFmpeg transcodes each file into a multi-quality HLS stream with a .m3u8 manifest. The original file and all HLS segments are stored in MinIO. The VOD entry is marked ready once transcoding completes.

WebSocket

Real-Time Sync

The whiteboard uses @fastify/websocket to broadcast canvas state changes to all connected clients in real time. Hero token positions, pen strokes, and timeline scrubs are synced across sessions without polling — each event is emitted over the persistent WebSocket connection and applied immediately on all connected clients.

Sessions

JWT Cookie Auth

After Google OAuth completes, the server issues a signed JWT stored in an httpOnly cookie — inaccessible to JavaScript. Short-lived access tokens (8hr) are paired with long-lived refresh tokens (30 days). Role and identity claims are embedded in the token payload and verified on every request before any handler runs.

Storage

MinIO Object Storage

All binary assets — raw video uploads, HLS segments, map images, hero icons — are stored in MinIO, an S3-compatible self-hosted object store. Presigned URLs are generated per-request for secure, time-limited access. Nothing is served from the filesystem directly; all asset delivery goes through the MinIO API.

Built on
modern primitives.

Fastify

API server. Handles all routes, JWT verification, Google OAuth callback, file upload via multipart, and WebSocket connections for the real-time whiteboard.

🌐

Astro + Tailwind

Frontend. Server-rendered pages with Astro's island architecture. Tailwind for styling. Sessions are validated server-side on each page request before any HTML is returned.

🗃️

Prisma + PostgreSQL

Database layer. Prisma ORM manages the schema and provides type-safe queries. PostgreSQL stores all structured data: users, roles, matches, entries, lessons, boards, drafts.

⚙️

BullMQ + Redis

Background job queue for video transcoding. Upload requests are enqueued immediately; a dedicated worker container processes them asynchronously so the API stays responsive.

🎬

FFmpeg + HLS.js

FFmpeg runs in the worker container to transcode uploads into adaptive HLS streams. HLS.js handles playback in the browser, automatically selecting quality based on available bandwidth.

📦

MinIO

S3-compatible self-hosted object storage for all binary assets. Videos, HLS segments, map images, and hero icons are stored and retrieved via presigned URLs — no direct filesystem exposure.

🔑

Google OAuth

The only login path. openid-client handles the OAuth 2.0 / OIDC flow. Domain-restricted to @edgewoodhs.org plus a coach-managed whitelist for external accounts.

🐳

Docker

The full platform — API, worker, web, PostgreSQL, Redis, MinIO — runs as a Docker Compose stack on private hardware. No cloud dependencies; the entire system is self-contained.

🔁

WebSocket

Persistent connections via @fastify/websocket power the real-time whiteboard. Canvas events — token moves, strokes, timeline scrubs — are broadcast to all connected clients instantly.

FastifyTypeScriptAstroTailwind CSSPrismaPostgreSQLBullMQRedisFFmpegHLS StreamingHLS.jsMinIOGoogle OAuthJWTWebSocketDockerSystems DesignRole-Based Access ControlVideo TranscodingReal-Time Collaboration