# Catalog Relay Backend for Android TV Apps

Use this when an Android TV streaming/catalog app needs out-of-the-box catalogs without embedding provider API keys in the APK.

## Pattern

Preferred v1 architecture:

```text
Android app
  -> user/system Stremio-compatible catalog addons
  -> first-party catalog relay (Cloudflare Worker or edge API)
  -> local/direct TMDB fallback only for dev or user-provided key
```

The relay is not a replacement for custom catalogs. It is just a first-party fallback source with a stable HTTP contract.

## Backend shape

For a Cloudflare Worker, keep Worker-specific code thin and isolate provider/catalog logic in portable modules:

```text
services/catalog-worker/
  src/index.ts              # Worker adapter: routes, Cache API, env bindings
  src/core/catalogs.ts      # declarative catalog registry
  src/core/tmdb.ts          # provider client + response mapping
  src/core/keyRing.ts       # multi-key parse/order/retry helpers
  src/core/models.ts        # service/domain response types
```

Only `index.ts` should depend on Worker runtime objects such as `Request`, `ExecutionContext`, `caches.default`, or `env`. Core files should be dependency-light so they can move later to Node/FastAPI/Debian.

## Endpoint contract

Use app-friendly JSON rather than exposing provider payloads directly:

```text
GET /v1/health
GET /v1/catalog/{type}/{catalogId}?skip=0&limit=20
```

Response shape:

```json
{
  "items": [
    {
      "id": "tmdb:12345",
      "type": "movie",
      "name": "Example",
      "poster": "https://image.tmdb.org/t/p/w500/...",
      "background": "https://image.tmdb.org/t/p/w1280/...",
      "releaseInfo": "2026",
      "imdbRating": "7.8"
    }
  ],
  "hasMore": true,
  "source": "nexstream-catalog",
  "cacheTtlSeconds": 3600
}
```

## Catalog registry

Mirror the Android catalog registry where possible:

- `tmdb.trending_week`, `tmdb.trending_day`
- `tmdb.popular`, `tmdb.top_rated`
- `tmdb.now_playing`, `tmdb.upcoming`
- `tmdb.airing_today`, `tmdb.on_the_air`
- `tmdb.recent_releases`, `tmdb.this_month`, `tmdb.box_office`
- `tmdb.ott.netflix`, `tmdb.ott.hbo`, `tmdb.ott.disney`, `tmdb.ott.prime`, `tmdb.ott.apple`
- genre rows using `tmdb.top` + `genre` extra

Keep row ids/titles consistent so Home and See All routes stay aligned.

## Secrets and multiple API keys

Do not commit keys and do not put provider keys in APK source.

Backend env examples:

```text
TMDB_API_KEYS=key1,key2,key3
TRAKT_CLIENT_IDS=id1,id2
TRAKT_CLIENT_SECRETS=secret1,secret2
MDBLIST_API_KEYS=key1,key2
```

Selection strategy:

1. Parse comma-separated keys.
2. Deterministically rotate by hashing `type/catalogId/page` so cache/load distribution is stable.
3. Retry once with the next key for `429`, `401`, or `403`.
4. Return only normalized catalog JSON — never keys or raw credential-bearing URLs.

Cloudflare setup:

```bash
wrangler secret put TMDB_API_KEYS
wrangler deploy
```

Local Android config should use Gradle properties or env vars:

```properties
NEXSTREAM_CATALOG_BASE_URL=https://your-worker-url/
```

## Android integration

Add a relay API/repository/module, then wire fallback order:

```text
AddonRepository.getCatalogRows(...)
NexStreamCatalogRepository.getCatalogRow(...)
TmdbCatalogRepository.getCatalogRow(...)
```

Repository behavior:

- If `NEXSTREAM_CATALOG_BASE_URL` is blank, return empty list so the next fallback runs.
- Catch only recoverable network/protocol/schema errors: `IOException`, `HttpException`, serialization exceptions.
- Re-throw `CancellationException`.
- Map relay DTOs to existing `MetaPreview`/domain models.

Home and See All must use the same chain or pagination will diverge.

## Verification

Run both backend and Android checks:

```bash
cd services/catalog-worker
npm install
npm run typecheck

cd ../..
./gradlew compileDebugKotlin --console=plain
./gradlew assembleDebug testDebugUnitTest --console=plain
```

Secret safety checks:

- Search source/service files, not generated build artifacts, for accidental key leaks.
- Android debug builds generate `BuildConfig` containing local Gradle secrets; do not include `app/build`, `.gradle`, or generated Hilt/KSP outputs in diffs or commits.
- If `git diff` includes generated `BuildConfig.java`, clean generated build outputs before reviewing/committing.

## Pitfalls

- `URLSearchParams.entries()` and `caches.default` typings may differ by TypeScript/lib config. For Worker projects, using `url.searchParams.forEach(...)` and a narrow cast for `caches.default` can keep `tsc --noEmit` happy while staying Worker-compatible.
- Do not make the relay the only catalog path. Users still need custom Stremio manifest support.
- Do not treat copied/open-source catalog taxonomies as proof a reference is API-less. Many rich catalogs are backed by TMDB/Trakt/MDBList/default keys/scraping. Reimplement taxonomy and caching, not scraper/provider internals or bundled keys.
