harden media
This commit is contained in:
221
plans/media-avatars-team-league-logos-streamlining-plan.md
Normal file
221
plans/media-avatars-team-league-logos-streamlining-plan.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Media Streamlining Plan: Driver Avatars, Team Logos, League Logos
|
||||
|
||||
## Goal
|
||||
Create one clean, conflict-free way to represent and deliver:
|
||||
|
||||
- Driver avatars (defaults now; user uploads later)
|
||||
- Team logos (seeded)
|
||||
- League logos (seeded)
|
||||
|
||||
So that:
|
||||
|
||||
- Seeding never produces conflicting behavior across environments.
|
||||
- The UI never has to guess whether a value is a file path, an API route, a generated asset, or an uploaded asset.
|
||||
- There is exactly one place that decides the final image URL for each entity.
|
||||
|
||||
## What exists today (inventory, by responsibility)
|
||||
|
||||
### Driver avatars
|
||||
|
||||
Where they surface:
|
||||
|
||||
- Driver lists, driver leaderboards, race entry lists, dashboard summaries, and social/friend UI elements.
|
||||
- API payloads sometimes include an avatar URL; other times the client constructs a URL from the driver id.
|
||||
- There are multiple fallback strategies: empty string, null, or client-side default image.
|
||||
|
||||
Where they come from:
|
||||
|
||||
- A “default avatar set” of three files exists in the website public assets.
|
||||
- There is also a server route that can generate an avatar image for a driver id.
|
||||
- Some parts of the system treat driver avatar as a user-uploadable media setting.
|
||||
|
||||
Observed problems:
|
||||
|
||||
- Mixed meaning of the avatar field: sometimes it is an absolute URL, sometimes a relative path, sometimes a server route string.
|
||||
- Multiple fallbacks implemented in multiple places leads to inconsistent UI and hard-to-debug “missing image” bugs.
|
||||
- Multiple “demo/fake” avatar generators exist, creating divergent behavior between environments.
|
||||
|
||||
### Team logos
|
||||
|
||||
Where they surface:
|
||||
|
||||
- Team cards, team leaderboards, recruiting/featured team sections.
|
||||
- Sometimes the UI uses `logoUrl` from the API payload; other times it falls back to a server route based on id.
|
||||
|
||||
Where they come from:
|
||||
|
||||
- A server route can generate a team logo image for a team id.
|
||||
- Seed logic also “pre-seeds” team logos by writing route strings into an in-memory store.
|
||||
|
||||
Observed problems:
|
||||
|
||||
- Team “logoUrl” may be an actual URL, or it may be a placeholder, or it may be a server route string stored as data.
|
||||
- Storing route strings as if they were media values creates conflicts when routes change.
|
||||
- In some persistence modes the “seeded logo store” is not truly persisted, so bootstrapping may re-trigger reseeding or create inconsistent results.
|
||||
|
||||
### League logos
|
||||
|
||||
Where they surface:
|
||||
|
||||
- League cards, league headers, league pages.
|
||||
- UI tends to call a client-side helper that builds a league-logo URL from id.
|
||||
|
||||
Where they come from:
|
||||
|
||||
- A server route can generate a league logo image for a league id.
|
||||
- Seed logic also “pre-seeds” league logos by writing route strings into an in-memory store.
|
||||
|
||||
Observed problems:
|
||||
|
||||
- Same class of conflicts as team logos.
|
||||
- There is no single authoritative rule for when a league has a “real” logo versus a generated one.
|
||||
|
||||
## Proposed streamlined model (single canonical representation)
|
||||
|
||||
### Canonical concept: Media Reference (not a URL)
|
||||
Instead of treating stored values as “final URLs”, define a single canonical *media reference* for each entity image.
|
||||
|
||||
Media reference types:
|
||||
|
||||
- **System default**: a fixed asset shipped with the website (driver defaults: male/female/neutral).
|
||||
- **Generated**: deterministically generated from an entity id and a seeded pseudo-random source (team/league logos).
|
||||
- **Uploaded**: a user-uploaded object managed by the media subsystem.
|
||||
- **None**: intentionally unset.
|
||||
|
||||
Key rule: **only one layer resolves media references into URLs**.
|
||||
|
||||
### URL resolution responsibilities
|
||||
|
||||
- **Backend** resolves *references* into *final URLs* for API payloads.
|
||||
- **Backend** also serves the image bytes for generated assets and uploaded assets.
|
||||
- **Frontend** treats received URLs as ready-to-render and does not invent additional fallbacks beyond a single last-resort placeholder.
|
||||
|
||||
## Seeding strategy (cleanest route)
|
||||
|
||||
### Teams and leagues: seeded via faker, but without storing URLs
|
||||
|
||||
Requirement: “seed team and league logos via faker”.
|
||||
|
||||
Clean approach:
|
||||
|
||||
- During seed, assign each team/league a **Generated** media reference.
|
||||
- The generator uses faker with a seed derived from the entity id to produce a deterministic “logo identity” (colors, initials, shapes, etc.).
|
||||
- The stored value is **only the reference** (type + seed key), not a route string and not a URL.
|
||||
- When the UI needs to show the logo, it either receives a resolved URL in the API payload or uses a single, standardized media URL builder.
|
||||
|
||||
Benefits:
|
||||
|
||||
- Deterministic results: same team id always yields the same logo.
|
||||
- No conflicts when URLs/routes change.
|
||||
- No need to persist binary files for seeded logos.
|
||||
|
||||
### Drivers: seeded from the 3 default avatar images
|
||||
|
||||
Requirement: “seed driver logos from these defaults” and later “normally these would be user uploads”.
|
||||
|
||||
Clean approach:
|
||||
|
||||
- During seed, assign each driver a **System default** media reference selecting one of:
|
||||
- male-default-avatar
|
||||
- female-default-avatar
|
||||
- neutral-default-avatar
|
||||
- Selection is deterministic (based on driver id) so reseeding does not change faces randomly.
|
||||
- Later, if a user uploads an avatar, the reference switches to **Uploaded** and overrides the default.
|
||||
|
||||
Benefits:
|
||||
|
||||
- No dependency on generated avatars for baseline.
|
||||
- No ambiguous meaning of the avatar field.
|
||||
|
||||
## Contract rules (what the UI can rely on)
|
||||
|
||||
### Field semantics
|
||||
|
||||
- Every API payload that includes a driver/team/league image should provide a **single resolved URL field** for that image.
|
||||
- Resolved URL is either:
|
||||
- a valid URL string the UI can render immediately, or
|
||||
- null (meaning: show a generic placeholder).
|
||||
- Never send empty strings.
|
||||
- Never send “sometimes relative file path, sometimes server route” style mixed values.
|
||||
|
||||
### Fallback rules
|
||||
|
||||
- The backend resolver must guarantee a valid URL whenever it can (system default or generated).
|
||||
- The frontend uses exactly one last-resort placeholder if it receives null.
|
||||
- No per-component bespoke fallbacks.
|
||||
|
||||
## Streamlining work items (what changes where)
|
||||
|
||||
### 1) Centralize media reference resolution
|
||||
|
||||
Create one “media resolver” concept used by:
|
||||
|
||||
- API payload assembly for all places that include avatars/logos.
|
||||
- Image-serving routes for generated assets and uploaded assets.
|
||||
|
||||
This resolver is the only place that knows:
|
||||
|
||||
- how to map media references to a concrete image URL
|
||||
- what the fallback is when no uploaded media exists
|
||||
|
||||
### 2) Stop storing server route strings as data
|
||||
|
||||
Remove the pattern where seed logic writes values like “/api/media/.../logo” into an in-memory media store.
|
||||
|
||||
Replace it with:
|
||||
|
||||
- stored media references (generated/system-default/uploaded)
|
||||
- consistent URL resolution at response time
|
||||
|
||||
### 3) Normalize route prefixes and caching behavior
|
||||
|
||||
- Choose one public URL shape for these images and apply it universally.
|
||||
- Add consistent cache headers for generated assets (deterministic) so the browser and CDN can cache safely.
|
||||
|
||||
### 4) Align frontend consumption
|
||||
|
||||
- Ensure the UI always prefers the resolved URL from the API payload.
|
||||
- Where the UI only has an id (e.g. very lightweight list items), use a single shared “URL builder” instead of ad-hoc string concatenation.
|
||||
- Remove duplicate “if missing then fallback to …” logic sprinkled across components.
|
||||
|
||||
### 5) Align tests and demo fakes
|
||||
|
||||
- Eliminate competing fake avatar/logo generators.
|
||||
- Ensure all test fixtures use the same deterministic rules as seed and runtime generation.
|
||||
- Ensure snapshot/contract tests treat empty string as invalid and expect null instead.
|
||||
|
||||
### 6) Make bootstrapping/reseeding conflict-proof
|
||||
|
||||
- Reseed decision should be based on durable data correctness (presence of required entities) rather than transient “in-memory media store” state.
|
||||
- Ensure “missing avatar/logo” checks are aligned with the new media reference model.
|
||||
|
||||
### 7) Migration and cleanup
|
||||
|
||||
- Define how existing seeded databases are handled:
|
||||
- either a one-time cleanup that rewrites old stored values into the new reference model, or
|
||||
- a documented wipe-and-reseed path for local/dev environments.
|
||||
- Ensure the migration path eliminates stored route strings.
|
||||
|
||||
## Mermaid: Target flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
UI[Website UI] --> API[API payloads include resolved image URLs]
|
||||
API --> RES[Media resolver]
|
||||
RES --> UP[Uploaded media storage]
|
||||
RES --> GEN[Deterministic generator]
|
||||
RES --> DEF[System default assets]
|
||||
GEN --> IMG[Image response with cache headers]
|
||||
UP --> IMG
|
||||
DEF --> UI
|
||||
IMG --> UI
|
||||
```
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Driver avatars always render with one of the three defaults unless an upload exists.
|
||||
- Team and league logos always render deterministically in dev/test seed, without persisting URLs.
|
||||
- No API payload returns empty string for avatar/logo.
|
||||
- No UI component constructs its own bespoke fallback logic.
|
||||
- No bootstrapping loop caused by “missing media” when media is generated or defaults are available.
|
||||
|
||||
Reference in New Issue
Block a user