Skip to content

feat(gaming): operator-set custom thumbnail per game - #219

Open
okapitula wants to merge 3 commits into
devfrom
feat/game-custom-thumbnail
Open

okapitula wants to merge 3 commits into
devfrom
feat/game-custom-thumbnail

Conversation

@okapitula

Copy link
Copy Markdown
Collaborator

Summary

Adds an operator-set customThumbnailUrl to games: a new nullable game.custom_thumbnail_url column, settable through PATCH /backoffice/gaming/games/{id} and returned next to thumbnailUrl on every game output (admin and public game lists and detail, category games, rule preview, GAME_CATALOG_READER's CatalogGame, lobby GameSummary and FeaturedSlot). Core does not choose between the two; a consumer renders customThumbnailUrl ?? thumbnailUrl.

Why

thumbnailUrl is the aggregator's image and a catalogue sync owns it, so an operator could not replace a game's artwork without the next sync overwriting it. A separate column keeps the operator's choice out of the sync's reach, and clearing it falls back to the aggregator image with nothing to restore.

  • null clears the value, and omitting the key leaves it untouched. Neither thumbnail field ever changes the other.
  • Only https: URLs without embedded credentials are accepted. The stored and returned value is the normalized URL (new URL(v).href), not the raw input, so quotes, angle brackets and control characters never reach the public routes unescaped. The 512-character cap applies after normalization.
  • The host must be on the new PlatformConfig.gaming.allowedThumbnailHosts list, which matches hosts the same way the CMS banner allowlist does (exact host or a subdomain). The URL is loaded by anonymous players' browsers, so an unlisted host would receive their IPs. A disallowed host returns 400 GameThumbnailHostNotAllowedError. The isAllowedHost matcher moved from cms into @openora/core/contracts, so the CMS banner check and gaming share it.
  • The before/after audit snapshot and the gaming.game.updated before/after carry the field. The change shows in the audit log under resourceType: 'game', and older events replay it as null.
  • The permission is unchanged: game-config:update.

Alternatives considered

  • Overwriting thumbnailUrl from the backoffice: the next catalogue sync would revert it, and clearing it would lose the aggregator image.
  • Resolving precedence in core with a single displayThumbnailUrl: this hides which image is the operator's, and the backoffice needs both to show "custom vs. default".

Risks

  • gaming.allowedThumbnailHosts defaults to an empty list, which rejects every custom thumbnail. An operator must list their thumbnail host(s) before the backoffice can set one.
  • CatalogGame gains a required customThumbnailUrl: string | null. Code that implements GameCatalogReader itself, or builds a CatalogGame by hand, must now supply it. Code that only reads through the port is unaffected.
  • Lobby outputs stay cached for up to 30s, the same as for thumbnailUrl, so a change can take up to 30s to appear there.
  • The migration is additive: one nullable column, no backfill.
  • Deferred: the existing thumbnailUrl input still accepts any string, and chat keeps its own copy of the host matcher.

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.

1 participant