A collaborative Progressive Web App (PWA) to build, share, and explore movie & TV show watchlists with friends, featuring an AI-powered semantic search.
Users can create shared watchlists, invite members, track movies and TV shows as seen or not seen, and search for content either by title or by describing what they feel like watching in natural language.
- Shared watchlists with member management and invitations by username
- Movie and TV tracking with seen / not seen status, filterable by content type
- Classic search with debounced live results from the TMDB API
- Magic search (AI-powered)
- User accounts with authentication, profile, and viewing history
- Installable PWA with custom icons and splash screens
TMDB's search API only matches exact titles and can't interpret intent — there's no way to ask for "something melancholic with great cinematography." Magic Search adds that layer through a dedicated pipeline:
The flow:
- The user describes what they're in the mood for, in any language
- A Cloud Function sends the query to Gemini, which returns a list of relevant titles
- Each title is resolved against the TMDB API in parallel to fetch real metadata (poster, rating, overview...)
- Results are returned to the client and displayed like any other search
Gemini and TMDB API keys are never exposed to the client — every request goes through the Cloud Function, with both keys stored in Secret Manager.
-
Frontend: Angular 19
-
Backend / Infrastructure: Firebase (Authentication, Firestore, Cloud Functions), Google Gemini API, TMDB API
-
Test & Workflow: Vitest, GitHub Actions
src/app/
├── core/
│ ├── guards/ # authGuard, watchlistGuard
│ └── interceptors/ # TMDB auth header injection
├── features/
│ ├── pages/ # Watchlist, Search, Trendings, Profile, Access...
│ └── popups/ # Members, new watchlist, media details...
└── shared/
├── components/ # Reusable UI (card, navbar, popup, button...)
├── services/ # Auth, Watchlist, Filter, Navbar, TMDB
├── pipes/ # Date, runtime, genre, rating formatting
├── models/ # Interfaces and enums
└── utils/
functions/
└── src/index.ts # Cloud Function: AI semantic search (Gemini + TMDB)
A few notable choices:
- Guards —
authGuardprotects private routes,watchlistGuardensures a valid active watchlist before accessing protected pages. - Interceptors —
tmdbInterceptorautomatically attaches the TMDB auth header to outgoing requests, keeping that logic out of services. - Services — business logic and Firestore/HTTP access live in dedicated injectable services, exposing signals and observables consumed by components.
- Routing — every route is lazy-loaded via
loadComponentto keep the initial bundle small.
Tests focus on business logic, conditional rendering, and edge cases rather than a uniform global percentage.
| Metric | Coverage | Detail |
|---|---|---|
| Statements | 98.05% | 1108 / 1130 |
| Branches | 94.47% | 325 / 344 |
| Functions | 97.08% | 300 / 309 |
| Lines | 98.48% | 975 / 990 |
Full report on Codecov.
Two GitHub Actions workflows:
tests.yml— runs on every push todevelop. Generates a placeholderenvironments.ts(needed for builds, but containing no real credentials since Firebase/TMDB are mocked in tests), runs the Vitest suite with coverage, and uploads results to Codecov.deploy.yml— builds and deploys the production app, generating the realenvironments.tsat build time from GitHub Secrets.
Thibaud Wajrock
Personal portfolio project. Feel free to explore the code, but please don't reuse it commercially without permission.
