Skip to content

Conversation

@jtrv
Copy link
Contributor

@jtrv jtrv commented Dec 29, 2025

Implements top poster api as an additional option for posters alongside RPDB

Closes #555

Summary by CodeRabbit

  • New Features

    • Poster service selector in Settings to choose between RPDB and Top Poster; Top Poster API key input and a new server endpoint to fetch posters.
  • Improvements

    • Unified poster handling and redirect URLs across services; catalogue UI, labels and tooltips updated to "Poster Services".
    • Poster retrieval now supports both services and avoids redundant fetches for already-derived posters.
    • Template inputs now include Top Poster API key.
  • Compatibility

    • Backwards-compatible migrations convert existing RPDB settings to the new poster-service fields.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 29, 2025

Walkthrough

Adds TopPoster as an alternative poster provider alongside RPDB: new TopPoster utility, server /top-poster route, UI selector and inputs, schema/config fields and migrations, unified poster-service resolution and fallback while preserving RPDB compatibility.

Changes

Cohort / File(s) Summary
Schemas & Migrations
packages/core/src/db/schemas.ts, packages/core/src/utils/config.ts, packages/frontend/src/context/userData.tsx
Added topPosterApiKey, posterService, usePosterRedirectApi; replaced rpdb with usePosterService on catalog mods (with migration); migrated rpdbUseRedirectApiusePosterRedirectApi.
Core Poster Integration
packages/core/src/main.ts
Unified poster flow: renamed param to applyPosterService, select posterApi (RPDB or TopPoster) based on posterService/API keys, use posterApi.getPosterUrl and service-specific redirect URL construction.
TopPoster Utility
packages/core/src/utils/top-poster.ts, packages/core/src/utils/index.ts, packages/core/src/utils/constants.ts
New TopPoster class (API key validation, id parsing, poster URL retrieval with existence checks and caching); exported via utils/index; added topPosterApiKey option detail.
Server API
packages/server/src/routes/api/top-poster.ts, packages/server/src/routes/api/index.ts, packages/server/src/app.ts
New /top-poster API route that validates params, constructs TopPoster client, resolves poster URL (with fallback), redirects (301) or returns 404; route registered and re-exported.
Frontend — Service UI & Templates
packages/frontend/src/components/menu/services.tsx, packages/frontend/src/components/menu/addons.tsx, packages/frontend/src/components/shared/config-templates-modal.tsx
Replaced RPDB-only UI with Poster Service selector (rpdb

Sequence Diagram(s)

%%{init: {"themeVariables":{"noteFill":"#f6fbff","actorBorder":"#6b7280","actorBkg":"#eef2ff"}}}%%
sequenceDiagram
    participant User as User / Client
    participant Frontend as Frontend UI
    participant Server as API Server
    participant TopPoster as TOP Poster API
    participant RPDB as RPDB Backend
    participant Cache as Cache (24h)

    rect rgb(245,250,255)
    Note over Frontend: Configure poster service & API key
    User->>Frontend: Select posterService and enter API key
    Frontend->>Server: Persist userData (posterService + key)
    end

    rect rgb(240,255,240)
    Note over User,Frontend: Poster retrieval
    User->>Frontend: Request item with poster
    Frontend->>Server: GET /api/top-poster (or unified poster endpoint)

    Server->>Cache: Check cached poster URL
    alt Cache hit
        Cache-->>Server: Return cached URL
    else Cache miss
        alt posterService == "top-poster"
            Server->>TopPoster: Request poster URL (with API key)
            TopPoster-->>Server: Poster URL or null
        else posterService == "rpdb"
            Server->>RPDB: Request poster URL
            RPDB-->>Server: Poster URL or null
        end
        Server->>Cache: Store result on success
    end

    alt URL found
        Server-->>Frontend: 301 redirect or poster URL
        Frontend->>User: Render poster
    else URL missing
        Server-->>Frontend: 404 structured error
        Frontend->>User: Render fallback poster
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

"I nibble keys and chase each trace,
Two poster paths now share the place,
RPDB or TOP, both in tune,
Caches hum beneath the moon,
A rabbit hops — the features race." 🐰

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing the TOP Poster API option as a feature.
Linked Issues check ✅ Passed The PR successfully implements all primary objectives: adds TOP Posters API as alternative poster service, integrates with parity to RPDB, enables user configuration choice, and follows existing RPDB integration patterns.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing TOP Poster API support; no unrelated or extraneous modifications detected beyond the stated objectives.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adb1830 and 3ba719c.

📒 Files selected for processing (3)
  • packages/core/src/db/schemas.ts
  • packages/core/src/utils/config.ts
  • packages/frontend/src/context/userData.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/utils/config.ts
🔇 Additional comments (5)
packages/core/src/db/schemas.ts (3)

301-301: LGTM! Field additions align with poster service generalisation.

The rename from rpdb to usePosterService correctly generalises the catalog modification schema to support multiple poster services. The addition of addonName provides useful context for identifying which addon provides the catalog.

Also applies to: 305-305


480-482: LGTM! New poster service fields are well-structured.

The addition of topPosterApiKey, posterService enum, and usePosterRedirectApi correctly implements support for multiple poster services whilst maintaining backward compatibility with RPDB.


1120-1123: LGTM! Consistent validation response schema.

The TopPosterIsValidResponse schema mirrors the existing RPDBIsValidResponse pattern, maintaining consistency across poster service validation endpoints.

packages/frontend/src/context/userData.tsx (2)

146-152: LGTM! Proper migration of redirect API field.

The migration logic correctly handles the rename from rpdbUseRedirectApi to usePosterRedirectApi, checking for the presence of the old field before copying and properly cleaning up afterwards.


154-162: Verify migration behavior for rpdb: false case.

The migration logic only sets usePosterService = true when rpdb === true, but unconditionally deletes the rpdb field. This means configurations with rpdb: false would lose that information (the field becomes undefined rather than false).

Please confirm this is the intended behaviour. If rpdb: false should explicitly disable the poster service, consider:

🔎 Alternative migration logic if false should be preserved
   // migrate 'rpdb' to 'usePosterService' in all catalog modifications
   if (Array.isArray(config.catalogModifications)) {
     for (const mod of config.catalogModifications) {
-      if (mod.usePosterService === undefined && mod.rpdb === true) {
-        mod.usePosterService = true;
+      if (mod.usePosterService === undefined && mod.rpdb !== undefined) {
+        mod.usePosterService = mod.rpdb;
       }
       delete mod.rpdb;
     }
   }

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
packages/frontend/src/components/menu/addons.tsx (1)

2016-2017: UI copy now matches generalised enhanced-poster behaviour

Renaming the RPDB toggle/tooltip to “Enhanced Posters” and explicitly mentioning “RPDB or Top Poster” brings the catalogue settings UI in line with the new dual‑service poster flow, while continuing to drive the existing rpdb boolean flag. That’s a sensible way to evolve the feature without breaking existing configs.

Also applies to: 2175-2178

packages/server/src/routes/api/top-poster.ts (4)

13-13: Unused logger declaration.

The logger is instantiated but never used in this route. Either remove it or add appropriate logging for debugging/monitoring.

🔎 Suggested fix
-const logger = createLogger('server');

Or add logging for successful/failed requests:

+    logger.info(`Top Poster request`, { id, type, season, episode });
     const topPoster = new TopPoster(apiKey);

43-52: Partial season/episode parameters may cause unexpected behaviour.

When only season is provided without episode (or vice versa), the condition season && episode evaluates to false, falling through to getPosterUrl instead of returning an error. This could be confusing for API consumers expecting episode thumbnail functionality.

🔎 Suggested fix
     const { id, type, fallback, apiKey, season, episode } = data;
     const topPoster = new TopPoster(apiKey);
     let posterUrl: string | null;
-    if (season && episode) {
+    if (season !== undefined || episode !== undefined) {
+      if (season === undefined || episode === undefined) {
+        res.status(400).json(
+          createResponse({
+            success: false,
+            detail: 'Both season and episode are required for episode thumbnails',
+            error: {
+              code: constants.ErrorCode.BAD_REQUEST,
+              message: 'Both season and episode must be provided together',
+            },
+          })
+        );
+        return;
+      }
       posterUrl = await topPoster.getEpisodeThumbnailUrl(
         type,
         id,
         season,
         episode
       );
     } else {
       posterUrl = await topPoster.getPosterUrl(type, id);
     }

64-64: Redundant non-null assertion.

The posterUrl! assertion is unnecessary since the preceding if (!posterUrl) block returns early, guaranteeing posterUrl is non-null at this point.

🔎 Suggested fix
-    res.redirect(301, posterUrl!);
+    res.redirect(301, posterUrl);

15-22: Consider adding stricter validation for type parameter.

The type field accepts any string, but the TopPoster utility likely only supports specific values (e.g., 'movie', 'series'). Adding enum validation would provide better error messages.

🔎 Suggested fix
 const searchParams = z.object({
   id: z.string(),
-  type: z.string(),
+  type: z.enum(['movie', 'series']),
   fallback: z.string().optional(),
   apiKey: z.string(),
   season: z.coerce.number().optional(),
   episode: z.coerce.number().optional(),
 });
packages/core/src/main.ts (1)

405-408: Consider renaming the rpdb property in catalog modifications.

The condition modification?.rpdb is misleading now that the feature supports multiple poster services. Consider renaming to posterServiceEnabled or similar in a follow-up change for clarity.

-    modification?.rpdb && activePosterService
+    modification?.posterServiceEnabled && activePosterService

This would require updating the schema and existing configurations.

top_poster_api_openapi.json (2)

1-1: OpenAPI specification lacks security definitions.

Static analysis flagged missing security configuration. While this documents an external API (Top Poster), the specification should include security schemes for completeness:

  • CKV_OPENAPI_4: No global security field defined
  • CKV_OPENAPI_5: No security operations defined

The API uses API keys in the path ({api_key}), which should be documented in the securitySchemes section.

Consider adding security scheme documentation:

"components": {
  "securitySchemes": {
    "ApiKeyPath": {
      "type": "apiKey",
      "in": "path",
      "name": "api_key",
      "description": "API key passed in URL path"
    }
  }
}

And applying it globally:

"security": [{ "ApiKeyPath": [] }]

1-1: Consider relocating OpenAPI specification.

The OpenAPI JSON file is placed at the repository root. Consider moving it to a more appropriate location such as docs/ or packages/server/openapi/ to maintain a cleaner project structure.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 689b8a2 and a82c483.

📒 Files selected for processing (12)
  • packages/core/src/db/schemas.ts
  • packages/core/src/main.ts
  • packages/core/src/utils/constants.ts
  • packages/core/src/utils/index.ts
  • packages/core/src/utils/top-poster.ts
  • packages/frontend/src/components/menu/addons.tsx
  • packages/frontend/src/components/menu/services.tsx
  • packages/frontend/src/components/shared/config-templates-modal.tsx
  • packages/server/src/app.ts
  • packages/server/src/routes/api/index.ts
  • packages/server/src/routes/api/top-poster.ts
  • top_poster_api_openapi.json
🧰 Additional context used
🧬 Code graph analysis (3)
packages/server/src/routes/api/top-poster.ts (4)
packages/server/src/utils/responses.ts (1)
  • createResponse (26-35)
packages/core/src/utils/config.ts (1)
  • formatZodError (37-39)
packages/core/src/utils/top-poster.ts (1)
  • TopPoster (11-175)
packages/core/src/utils/constants.ts (1)
  • APIError (104-113)
packages/frontend/src/components/menu/services.tsx (2)
packages/core/src/main.ts (1)
  • setUserData (116-118)
packages/frontend/src/components/ui/password-input/password-input.tsx (1)
  • PasswordInput (40-146)
packages/core/src/main.ts (3)
packages/core/src/utils/rpdb.ts (1)
  • RPDB (11-150)
packages/core/src/utils/top-poster.ts (1)
  • TopPoster (11-175)
packages/core/src/utils/env.ts (1)
  • Env (318-2071)
🪛 Checkov (3.2.334)
top_poster_api_openapi.json

[high] 1: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)


[high] 1: Ensure that security operations is not empty.

(CKV_OPENAPI_5)


[medium] 1: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

🔇 Additional comments (10)
packages/core/src/utils/constants.ts (1)

717-747: TopPoster top-level option is wired consistently

topPosterApiKey is correctly added to both the type union and TOP_LEVEL_OPTION_DETAILS, so consumers (e.g. templates UI) can surface the new key without type gaps. No issues from my side here.

packages/core/src/db/schemas.ts (1)

431-435: Poster service wiring and validation schema look coherent

Adding topPosterApiKey and a constrained posterService enum to UserDataSchema, plus the TopPosterIsValidResponse zod schema/type, lines up with the new Top Poster flow and is type-safe. Just ensure all callers use the exact literals 'rpdb' and 'top-poster' when reading/writing posterService, to avoid subtle mismatches.

Also applies to: 1067-1070

packages/server/src/routes/api/index.ts (1)

7-7: Server API barrel cleanly exposes Top Poster route

Re‑exporting topPosterApi alongside the existing route modules keeps the API surface consistent and makes wiring in app.ts straightforward. Looks good.

packages/frontend/src/components/shared/config-templates-modal.tsx (1)

862-887: Template input support for Top Poster API key is consistent

Adding topPosterApiKey to topLevelFields and driving its label/description from TOP_LEVEL_OPTION_DETAILS matches the existing pattern for TMDB/RPDB/TVDB keys, so imported templates can now prompt for the Top Poster key without extra bespoke logic.

packages/core/src/utils/index.ts (1)

16-16: TopPoster utility correctly re-exported from utils barrel

Re‑exporting ./top-poster.js here is the right move so callers can access TopPoster via the central utils entrypoint, mirroring the existing RPDB helper.

packages/server/src/app.ts (1)

9-9: Top Poster API route is wired consistently with existing APIs

Importing topPosterApi and mounting it at /top-poster on the versioned apiRouter mirrors the existing RPDB route pattern, so the new endpoint will be reachable at /api/v{API_VERSION}/top-poster without special handling. No concerns here.

Also applies to: 95-95

packages/frontend/src/components/menu/services.tsx (2)

303-326: LGTM! Clean implementation of the poster service selector.

The TemplateOption component is appropriately configured with the select type and the two service options. The default value of 'rpdb' correctly aligns with the backward compatibility logic in the core layer.


327-378: LGTM! Conditional API key inputs are well-structured.

Both PasswordInput components:

  • Use autoComplete="new-password" to prevent browser autofill issues
  • Include helpful links to obtain API keys
  • Follow the existing input patterns in the codebase
packages/core/src/main.ts (2)

400-421: LGTM! Good backward compatibility handling.

The logic correctly:

  1. Falls back to 'rpdb' when posterService is not set but rpdbApiKey exists
  2. Selects the appropriate API key based on the active service
  3. Instantiates the correct poster service class

428-454: LGTM! Unified poster URL construction.

The code correctly:

  1. Detects existing poster service URLs to avoid double-processing
  2. Constructs the appropriate API path based on the selected service
  3. Falls back to direct API calls when BASE_URL is not available

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/core/src/utils/top-poster.ts (2)

19-49: Cache check doesn't handle false values, and error differentiation is missing.

The issues identified in the previous review are still present:

  1. Line 21: if (cached) won't work if false is cached. Use if (cached !== undefined).
  2. Lines 32-36: All non-OK HTTP responses are treated as "Invalid Top Poster API key", including 5xx server errors and 429 rate limits. This makes diagnosis difficult—users can't tell if their key is wrong or if the service is temporarily unavailable.
  3. Line 38: No try-catch around response.json() or the schema parse—malformed responses will throw unhandled errors.

As noted in the previous review, you should:

  • Treat 4xx auth errors (400/401/403) or valid: false as an invalid key.
  • Treat 5xx/429/network/parse failures as "service unavailable" with a distinct error message.

54-104: Remove @ts-ignore by properly initialising variables.

The // @ts-ignore on line 101 suppresses TypeScript's legitimate concern about potentially uninitialised variables. As highlighted in the previous review, you should:

  1. Initialise idType and idValue as null on lines 61-62
  2. Update the check on line 102 to explicitly narrow the types
  3. Remove the @ts-ignore directive

This ensures type safety without suppressing compiler warnings.

Recommended fix
-    let idType: 'tmdb' | 'imdb' | 'tvdb';
-    let idValue: string;
+    let idType: 'tmdb' | 'imdb' | 'tvdb' | null = null;
+    let idValue: string | null = null;

     switch (parsedId.type) {
       // ... switch cases ...
     }
-    // @ts-ignore
     if (!idType || !idValue) return null;
     return { idType, idValue };
🧹 Nitpick comments (1)
packages/core/src/utils/top-poster.ts (1)

139-174: LGTM with minor observation.

The episode thumbnail URL retrieval follows the same sound pattern as getPosterUrl. While there's some code duplication between the two methods, the different URL construction and cache keys justify keeping them separate rather than abstracting.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a82c483 and f034956.

📒 Files selected for processing (11)
  • packages/core/src/db/schemas.ts
  • packages/core/src/main.ts
  • packages/core/src/utils/constants.ts
  • packages/core/src/utils/index.ts
  • packages/core/src/utils/top-poster.ts
  • packages/frontend/src/components/menu/addons.tsx
  • packages/frontend/src/components/menu/services.tsx
  • packages/frontend/src/components/shared/config-templates-modal.tsx
  • packages/server/src/app.ts
  • packages/server/src/routes/api/index.ts
  • packages/server/src/routes/api/top-poster.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/server/src/routes/api/index.ts
  • packages/frontend/src/components/shared/config-templates-modal.tsx
  • packages/server/src/routes/api/top-poster.ts
  • packages/frontend/src/components/menu/services.tsx
  • packages/frontend/src/components/menu/addons.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/core/src/utils/top-poster.ts (5)
packages/core/src/utils/cache.ts (1)
  • Cache (24-306)
packages/core/src/db/schemas.ts (2)
  • TopPosterIsValidResponse (1067-1069)
  • TopPosterIsValidResponse (1070-1070)
packages/core/src/utils/env.ts (1)
  • Env (318-2071)
packages/core/src/utils/id-parser.ts (1)
  • IdParser (66-199)
packages/core/src/utils/anime-database.ts (1)
  • AnimeDatabase (502-1054)
🔇 Additional comments (11)
packages/core/src/utils/index.ts (1)

16-16: LGTM!

The export follows the established pattern and is appropriately positioned alongside the RPDB export.

packages/core/src/utils/constants.ts (1)

717-747: LGTM!

The Top Poster API key configuration follows the established pattern for RPDB and provides clear guidance to users.

packages/server/src/app.ts (2)

9-9: LGTM!

The Top Poster API import and route registration follow the established pattern for API routes.


95-95: LGTM!

Route registration is consistent with other API routes like RPDB.

packages/core/src/db/schemas.ts (2)

433-434: LGTM!

The schema additions for Top Poster support are well-structured and follow Zod v4 conventions. Making both fields optional ensures backward compatibility.


1067-1070: LGTM!

The TopPosterIsValidResponse schema correctly mirrors the RPDBIsValidResponse pattern and provides proper type safety for API validation responses.

packages/core/src/main.ts (3)

34-34: LGTM!

The TopPoster import is correctly placed alongside the RPDB import.


400-421: Good backward compatibility handling.

The logic properly defaults to RPDB when posterService isn't explicitly set but rpdbApiKey is present, ensuring existing configurations continue to work. The conditional selection of API key and instantiation of the appropriate poster service is correctly implemented.


426-455: LGTM!

The poster modification logic correctly:

  • Detects existing poster service URLs (both RPDB and Top Poster)
  • Routes requests through the appropriate API endpoint based on posterService
  • Falls back to direct API calls when BASE_URL is not configured
packages/core/src/utils/top-poster.ts (2)

11-18: LGTM!

The constructor provides essential validation that the API key is set before allowing instantiation.


105-138: LGTM!

The poster URL retrieval logic is well-structured:

  • Properly caches results to reduce API calls
  • Optional existence check via HEAD request
  • Gracefully handles errors by returning null when poster doesn't exist
  • Cache key includes API key for proper multi-tenant support

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
packages/core/src/utils/top-poster.ts (3)

19-49: Address error handling and cache checking issues from previous review.

The issues flagged in the previous review remain unaddressed:

  1. Cache checking (line 21): Using if (cached) will incorrectly skip cached false values. Use if (cached !== undefined) instead.
  2. Error differentiation (lines 32-36): All HTTP errors are treated as "Invalid API key", including transient 5xx and 429 errors. This misleads users when the service is temporarily unavailable.
  3. JSON parsing (line 38): No error handling for malformed responses.

Please refer to the detailed fix suggestions in the previous review comment.


54-104: Remove @ts-ignore by initializing variables explicitly.

As noted in the previous review, the @ts-ignore at lines 101-102 should be removed by initializing idType and idValue to null and narrowing their types explicitly. This avoids suppressing TypeScript's type checking.

Refer to the previous review comment for the suggested fix.


105-174: Fix cache checking in poster URL methods.

Both getPosterUrl (line 116) and getEpisodeThumbnailUrl (line 152) use if (cached) which will fail to return cached empty strings or other falsy values. Use if (cached !== undefined) instead.

🔎 Proposed fix
  public async getPosterUrl(
    type: string,
    id: string,
    checkExists: boolean = true
  ): Promise<string | null> {
    const parsed = this.parseId(type, id);
    if (!parsed) return null;
    const { idType, idValue } = parsed;

    const cacheKey = `${type}-${id}-${this.apiKey}`;
    const cached = await posterCheckCache.get(cacheKey);
-   if (cached) {
+   if (cached !== undefined) {
      return cached;
    }
    // ... rest of method

  public async getEpisodeThumbnailUrl(
    type: string,
    id: string,
    season: number,
    episode: number,
    checkExists: boolean = true
  ): Promise<string | null> {
    const parsed = this.parseId(type, id);
    if (!parsed) return null;
    const { idType, idValue } = parsed;

    const cacheKey = `${type}-${id}-S${season}E${episode}-${this.apiKey}`;
    const cached = await posterCheckCache.get(cacheKey);
-   if (cached) {
+   if (cached !== undefined) {
      return cached;
    }
    // ... rest of method
🧹 Nitpick comments (2)
packages/core/src/utils/top-poster.ts (1)

13-17: Consider trimming the API key input.

The empty string check is good, but it doesn't catch whitespace-only API keys. Consider adding .trim() to handle accidental whitespace.

🔎 Suggested enhancement
  constructor(apiKey: string) {
-   this.apiKey = apiKey;
+   this.apiKey = apiKey.trim();
    if (!this.apiKey) {
      throw new Error('Top Poster API key is not set');
    }
  }
packages/core/src/main.ts (1)

400-408: Backward compatibility logic is sound.

The default to 'rpdb' when rpdbApiKey is present but posterService is not set ensures existing users' configurations continue to work. However, note that modification?.rpdb is now a misnomer since it applies to both RPDB and Top Poster services.

Consider renaming modification.rpdb to modification.enhancePoster or modification.usePosterService in a future refactor to better reflect its dual purpose.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f034956 and f220973.

📒 Files selected for processing (11)
  • packages/core/src/db/schemas.ts
  • packages/core/src/main.ts
  • packages/core/src/utils/constants.ts
  • packages/core/src/utils/index.ts
  • packages/core/src/utils/top-poster.ts
  • packages/frontend/src/components/menu/addons.tsx
  • packages/frontend/src/components/menu/services.tsx
  • packages/frontend/src/components/shared/config-templates-modal.tsx
  • packages/server/src/app.ts
  • packages/server/src/routes/api/index.ts
  • packages/server/src/routes/api/top-poster.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/server/src/routes/api/top-poster.ts
  • packages/frontend/src/components/menu/services.tsx
  • packages/server/src/routes/api/index.ts
  • packages/frontend/src/components/menu/addons.tsx
  • packages/frontend/src/components/shared/config-templates-modal.tsx
  • packages/core/src/utils/index.ts
🔇 Additional comments (6)
packages/core/src/db/schemas.ts (2)

446-447: LGTM! Schema additions follow existing patterns.

The new fields integrate well with the existing RPDB fields and maintain backward compatibility through optional typing.


1081-1084: LGTM! Validation schema mirrors RPDB pattern.

The TopPosterIsValidResponse schema is correctly defined and follows the established pattern for API key validation responses.

packages/server/src/app.ts (1)

9-9: LGTM! Route registration follows established patterns.

The Top Poster API route is correctly registered alongside the RPDB route, maintaining consistency with the existing API structure.

Also applies to: 95-95

packages/core/src/utils/constants.ts (1)

717-747: LGTM! Constant definition follows existing patterns.

The topPosterApiKey entry is well-structured and includes a helpful registration URL in the description, following the same pattern as other API key constants.

packages/core/src/main.ts (2)

410-421: API instance creation correctly handles both poster services.

The logic properly maps the poster service to the corresponding API key and instantiates the correct class. The code correctly handles the case where an API key might be undefined (posterApi will be undefined, and the subsequent check at line 426 will skip poster modification).


426-455: Poster URL modification logic correctly handles multiple scenarios.

The logic properly:

  1. Skips modification for posters already from rating or top-streaming services
  2. Routes through server API when BASE_URL is available (enabling caching/fallback)
  3. Calls poster API directly when BASE_URL is not configured
  4. Uses checkExists: false as an optimisation when calling the API directly

The non-null assertion on posterApiKey at line 441 is safe because we're inside the if (posterApi...) block, and posterApi can only be defined if posterApiKey is defined.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/frontend/src/components/menu/addons.tsx (1)

1570-1576: Consider both poster services when initialising usePosterService for new catalogs.

Currently, usePosterService defaults to true only when userData.rpdbApiKey is present. However, users may configure topPosterApiKey with posterService: 'top-poster' instead. New catalogs should default to usePosterService: true when either poster service is configured.

🔎 Proposed fix
               modifications.push({
                 id: catalog.id,
                 name: catalog.name,
                 type: catalog.type,
                 enabled: true,
                 shuffle: false,
-                usePosterService: userData.rpdbApiKey ? true : false,
+                usePosterService: !!(userData.rpdbApiKey || userData.topPosterApiKey),
                 hideable: catalog.hideable,
                 searchable: catalog.searchable,
                 addonName: catalog.addonName,
               });
🧹 Nitpick comments (1)
packages/core/src/utils/top-poster.ts (1)

59-68: Consider caching invalid API key results to prevent repeated validation requests.

When data.valid is false, an error is thrown before the result is cached. This means repeated calls with the same invalid key will continue hitting the API. If intentional (to allow retries after fixing the key), this is acceptable, but caching false could reduce unnecessary API load.

🔎 Proposed fix to cache invalid keys
+    apiKeyValidationCache.set(
+      this.apiKey,
+      data.valid,
+      Env.RPDB_API_KEY_VALIDITY_CACHE_TTL
+    );
+
     if (!data.valid) {
       throw new Error('Invalid Top Poster API key');
     }

-   apiKeyValidationCache.set(
-     this.apiKey,
-     data.valid,
-     Env.RPDB_API_KEY_VALIDITY_CACHE_TTL
-   );
    return data.valid;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f220973 and fea9f7e.

📒 Files selected for processing (4)
  • packages/core/src/db/schemas.ts
  • packages/core/src/main.ts
  • packages/core/src/utils/top-poster.ts
  • packages/frontend/src/components/menu/addons.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/main.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/frontend/src/components/menu/addons.tsx (1)
packages/core/src/main.ts (1)
  • setUserData (116-118)
🔇 Additional comments (10)
packages/core/src/utils/top-poster.ts (4)

11-18: LGTM!

Constructor properly validates and trims the API key, throwing a clear error when empty.


74-123: LGTM!

The parseId method properly handles type narrowing with explicit null initialisation, and the ID mapping logic covers all expected cases with appropriate fallbacks via AnimeDatabase.


124-157: LGTM!

The getPosterUrl method follows a sensible pattern with proper caching, timeout handling, and optional existence checking.


158-193: LGTM!

The getEpisodeThumbnailUrl method is consistent with getPosterUrl and properly includes season/episode in the cache key.

packages/frontend/src/components/menu/addons.tsx (3)

91-91: LGTM!

The interface field rename from rpdb to usePosterService aligns with the schema changes and provides a more generic name for supporting multiple poster services.


1998-2025: LGTM!

The icon button toggle and tooltip correctly use the renamed usePosterService field with service-agnostic terminology.


2184-2199: LGTM!

The switch control correctly binds to usePosterService with appropriate label and help text updates describing both RPDB and Top Poster support.

packages/core/src/db/schemas.ts (3)

296-305: LGTM!

The schema transform provides excellent backward compatibility by migrating the legacy rpdb field to usePosterService. The nullish coalescing (??) ensures existing configurations continue working whilst new ones use the renamed field.


450-451: LGTM!

The new topPosterApiKey and posterService fields are correctly defined as optional, allowing users to configure either RPDB or Top Poster without breaking existing configurations.


1085-1088: LGTM!

The TopPosterIsValidResponse schema is consistent with the existing RPDBIsValidResponse pattern, maintaining API parity between the two poster services.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/core/src/main.ts (1)

925-941: Consider simplifying nested ternary operators for readability.

The poster service and API resolution logic is correct, including the fallback to RPDB when posterService is undefined but rpdbApiKey exists. However, the deeply nested ternaries make this harder to maintain.

🔎 Suggested refactor for clarity
-    const posterService = applyPosterService
-      ? this.userData.posterService ||
-        (this.userData.rpdbApiKey ? 'rpdb' : undefined)
-      : undefined;
-    const posterApiKey =
-      posterService === 'rpdb'
-        ? this.userData.rpdbApiKey
-        : posterService === 'top-poster'
-          ? this.userData.topPosterApiKey
-          : undefined;
-    const posterApi = posterApiKey
-      ? posterService === 'rpdb'
-        ? new RPDB(posterApiKey)
-        : posterService === 'top-poster'
-          ? new TopPoster(posterApiKey)
-          : undefined
-      : undefined;
+    let posterService: 'rpdb' | 'top-poster' | undefined;
+    if (applyPosterService) {
+      posterService = this.userData.posterService ||
+        (this.userData.rpdbApiKey ? 'rpdb' : undefined);
+    }
+
+    const posterApiKey = posterService === 'rpdb'
+      ? this.userData.rpdbApiKey
+      : posterService === 'top-poster'
+        ? this.userData.topPosterApiKey
+        : undefined;
+
+    let posterApi: RPDB | TopPoster | undefined;
+    if (posterApiKey) {
+      posterApi = posterService === 'rpdb'
+        ? new RPDB(posterApiKey)
+        : new TopPoster(posterApiKey);
+    }
packages/core/src/utils/top-poster.ts (1)

67-71: Consider using a dedicated TTL constant for Top Poster.

The code reuses Env.RPDB_API_KEY_VALIDITY_CACHE_TTL for Top Poster API key caching. While functionally equivalent, consider adding a TOP_POSTER_API_KEY_VALIDITY_CACHE_TTL constant for clarity and independent configuration.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37e0706 and b8fd229.

📒 Files selected for processing (13)
  • packages/core/src/db/schemas.ts
  • packages/core/src/main.ts
  • packages/core/src/utils/config.ts
  • packages/core/src/utils/constants.ts
  • packages/core/src/utils/index.ts
  • packages/core/src/utils/top-poster.ts
  • packages/frontend/src/components/menu/addons.tsx
  • packages/frontend/src/components/menu/services.tsx
  • packages/frontend/src/components/shared/config-templates-modal.tsx
  • packages/frontend/src/context/userData.tsx
  • packages/server/src/app.ts
  • packages/server/src/routes/api/index.ts
  • packages/server/src/routes/api/top-poster.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/server/src/routes/api/top-poster.ts
  • packages/core/src/utils/index.ts
  • packages/server/src/app.ts
  • packages/server/src/routes/api/index.ts
  • packages/frontend/src/components/shared/config-templates-modal.tsx
  • packages/core/src/utils/constants.ts
🧰 Additional context used
🧬 Code graph analysis (2)
packages/frontend/src/components/menu/addons.tsx (1)
packages/core/src/main.ts (1)
  • setUserData (125-127)
packages/core/src/main.ts (4)
packages/core/src/db/schemas.ts (1)
  • MetaPreview (936-936)
packages/core/src/utils/rpdb.ts (1)
  • RPDB (11-150)
packages/core/src/utils/top-poster.ts (1)
  • TopPoster (13-162)
packages/core/src/utils/env.ts (1)
  • Env (318-2096)
🔇 Additional comments (17)
packages/core/src/utils/config.ts (1)

662-669: LGTM! Migration logic for poster redirect API is correct.

The migration safely transfers the legacy rpdbUseRedirectApi value to the new usePosterRedirectApi field and cleans up the old property. This aligns with the frontend migration in userData.tsx.

packages/frontend/src/context/userData.tsx (1)

146-152: LGTM! Frontend migration mirrors backend logic.

The migration is consistent with packages/core/src/utils/config.ts, ensuring both client-side and server-side handle the legacy rpdbUseRedirectApi field identically.

packages/frontend/src/components/menu/services.tsx (2)

303-326: LGTM! Poster service selector implementation is clean.

The TemplateOption-based selector and conditional API key inputs work well together. Consider whether both API keys should coexist when a user switches services, or if the unused key should be cleared for a cleaner user experience.


381-401: Redirect toggle correctly unified for both poster services.

The disabled condition properly checks for the presence of either API key, ensuring the toggle is only active when a poster service is configured.

packages/core/src/main.ts (3)

35-36: LGTM! TopPoster import added correctly.


463-469: LGTM! Poster service flag correctly derived from catalog modification.

The renamed parameter applyPosterService properly reflects the broader scope of the feature.


945-971: LGTM! Poster URL construction handles both services correctly.

The logic properly:

  • Skips items already using poster service URLs
  • Constructs redirect URLs with the correct service endpoint
  • Falls back to direct poster API calls when redirect is disabled
packages/core/src/utils/top-poster.ts (4)

13-20: LGTM! Constructor validates API key presence.

The constructor correctly trims and validates the API key before use.


22-73: LGTM! API key validation now differentiates error types.

The implementation correctly distinguishes between:

  • Network/connection errors
  • Invalid API keys (401)
  • Rate limiting (429)
  • Unexpected responses
  • Malformed JSON

This addresses the concerns from the previous review.


78-127: LGTM! ID parsing handles various formats correctly.

The method properly:

  • Parses TMDB, IMDB, and TVDB IDs
  • Falls back to AnimeDatabase mappings for unsupported ID types
  • Correctly excludes TVDB for movies (line 98)
  • Uses null initialisation for idType and idValue addressing the previous review's TypeScript concern

128-161: LGTM! Poster URL retrieval with appropriate caching.

The implementation correctly:

  • Caches results using a composite key including the API key
  • Performs HEAD requests for existence checking when required
  • Returns early without checking existence when checkExists is false
packages/core/src/db/schemas.ts (2)

290-312: LGTM! CatalogModification schema correctly handles backward compatibility.

The transform merges the legacy rpdb field into usePosterService, ensuring existing configurations continue to work whilst enabling the new dual-service approach.


1126-1129: LGTM! TopPosterIsValidResponse schema added.

The schema correctly mirrors the RPDBIsValidResponse structure for consistency.

packages/frontend/src/components/menu/addons.tsx (4)

1563-1565: LGTM! Backward-compatible initialization logic.

The initialization correctly enables usePosterService when either rpdbApiKey or topPosterApiKey is present, ensuring smooth migration for existing RPDB users whilst supporting the new Top Poster service.


2722-2749: LGTM! Consistent UI updates for poster service toggle.

The icon rendering and tooltip text have been correctly updated to reflect the new usePosterService field. The generic "Poster Services" label appropriately accommodates both RPDB and Top Poster.


2935-2950: LGTM! Clear and consistent switch control implementation.

The label, help text, and state management have been properly updated. The help text clearly mentions both RPDB and Top Poster, making it easy for users to understand the available options.


1563-1565: All references to the old rpdb field have been successfully updated.

Verification confirms that the renaming from rpdb to usePosterService is consistent across the frontend. The code at lines 1563-1565, 2722-2749, and 2935-2950 correctly uses usePosterService. The legacy rpdb field is intentionally preserved in the type schema (packages/core/src/db/schemas.ts) for backward compatibility, with a migration layer that maps the old field to the new one. Other references to "rpdb" in the codebase are legitimate and refer to the RPDB poster service class, API keys, or configuration—not the renamed field.

@Viren070 Viren070 merged commit fd62d9b into Viren070:main Jan 2, 2026
3 checks passed
@Viren070 Viren070 mentioned this pull request Jan 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: option to use "TOP Posters API" instead of RPDB

2 participants