carlos@cesaints: ~/projects/media-catalog.md — zsh

Command: cat projects/media-catalog.md

projects/media-catalog.md · 1.2 KB

Media Archive: a catalog and personal list of films, series and anime

A personal prototype built in one night: a catalog that searches films, series and anime across four public metadata sources at once, shows each title's page, official trailers and where to watch legally, and keeps each person's list with a magic-link sign-in. It stores and streams no video, and it was never deployed.

open the demoa simulation of the system with fictional data · 5 screens

Role
Personal project, from concept to code
Period
Sep 2026 – Sep 2026
What I did

Me: Concept, architecture, the connectors for the four sources, caching and rate limiting, the data model and access rules, the interface, the tests and the recorded decisions.

Private project, described without identifying the client, product or company.

Diagram

Federated search on the server, a personal list guarded by the database

The browser has the palette and the search, with no API key at all. The own API validates the term, limits requests per IP, uses an LRU cache with a TTL and deduplicates in-flight calls; it queries the public metadata sources in parallel, with a fallback for anime, and normalizes everything into a checked contract. The personal list lives in Supabase: magic-link login and Postgres with row policies, where only the owner reads and changes the list, and the app never uses the service key. Where to watch points only to licensed services, trailers come only through the official player, and no video is stored or streamed.

Context

A personal catalog of films, series and anime, with a terminal look. It is a prototype built in one night on my machine: it was never deployed and has no users, so there are no usage results to show.

What I built

  • Federated search. A command palette (Ctrl/Cmd+K or “/”) and a search page that query the four sources at once and merge the results; the previous request is cancelled on every new keystroke.
  • Title page. A rating that also reads as text, synopsis, trailers that only load on click, where to watch legally (subscription, rental, purchase, free with ads), cast, technical details and each source’s attribution.
  • My list. Want to watch, watching, watched and dropped, with optimistic status changes announced to screen readers.
  • Failing gracefully. Each home section loads and fails on its own; when a source is not configured yet, the interface says what is missing and the rest keeps working.
  • Process. Six ADRs, five written gates and project documentation generated from the code itself by a deterministic script.

Limits

A prototype with no deployment, no users and no remote CI. The test and coverage figures come from reports generated by the project itself and were not re-run.

Constraints

  • Zero recurring cost and no credentials in the browser.
  • Metadata only, from sources whose terms allow it; no video stored or streamed.
  • Each external source can fail on its own without taking the page down.
  • One night of work.

Decisions

Metadata only, with the rejected sources written down

Context
A film and series catalog invites shortcuts: piracy APIs, anonymous mirrors and unmaintained wrappers.
Choice
Four public metadata sources, trailers only through the official player, where-to-watch only with licensed services, and a document that rejects the other sources with legal, cost, reliability and security reasons.
Gains
  • No legal risk from content, and zero cost.
Costs
  • The catalog depends on the quotas and availability of free sources.

Federated search with caching and limits on the server

Context
Querying four sources on every keystroke would be slow, burn through quotas and break whenever one of them went down.
Choice
An API of its own validates the term, queries the sources in parallel, with a fallback source for anime, and normalizes everything into a validated contract; an LRU cache with TTL, deduplication of in-flight calls and a per-IP limit.
Gains
  • An unstable source does not take the search down.
  • No API key reaches the browser.
Costs
  • The limit is per instance; with several instances the real ceiling multiplies, a limitation accepted in an ADR.

Authorization in the database, with no service key in the app

Context
The personal list is the only user data, and nobody may read another person's list.
Choice
Magic-link sign-in and row policies in Postgres, where only the owner reads, creates, changes and removes; the app never uses the service key.
Gains
  • A bug in the application does not expose another person's list.
Costs
  • It depends on a free managed database that pauses when idle, hence a health check.

Stack and why

Next.js 15 and React 19
App Router, with the home page revalidated every hour and title pages every 12 hours.
Strict TypeScript and Zod
A domain contract validated at every external boundary.
Tailwind v4
Theme tokens with measured contrast.
Supabase (Postgres and Auth)
Magic links and row policies.
Vitest
Connectors, caching, rate limiting and validation.

Results

  • 90 test cases in 10 files, with 61.95% line coverage and 82.57% branch coverage.

    self-reportedCoverage report generated by the project
  • Six ADRs and five written gates: requirements, experience, security, performance and cost, and tests.

    audited private repositoryThe repository's documentation(Audit of the private repository, Sep 2026)

Security angle

Attack surface

  • The public search, which calls the external sources from the server.
  • External links coming from the sources (official sites and services).
  • Each user's personal list.
  • The magic-link sign-in.

Controls in place

  • The user always taken from the session validated on the server.
  • External URLs sanitized, with a defense against domain-suffix tricks, and open-redirect protection.
  • A lint rule that forbids raw HTML, a strict CSP with media blocked, and HSTS.
  • Search terms validated (at least 2 characters, a page ceiling, control characters stripped) and a per-IP limit that answers 429.
  • Errors sent to the client without a stack.

What I would test today

  • Exceeding the rate limit by spreading calls across several instances.
  • A source response with a forged service URL.
  • Reading or changing another person's list by swapping identifiers.

Evidence

  • audited private repositoryAudited local repository(Audit of the private repository, Sep 2026)