Skip to content

PRD: Asset & media management

ModulePlatform (CORE-16)PRD IDPRD-AST-001
StatusShippedOwnerPhát Nguyễn
Date2026-06-15Versionv1.0
CapabilityMedia storage · reference dataURDAST · MTL · BNK

TL;DR

One shared way for every BANA service to store, address, and serve binary media - product photos, organizer logos, documents - on object storage: an authenticated upload lands each file under a unique, URL-safe name and writes a durable media record (storage coordinates, content type, size, integrity fingerprint, plus an optional binding to the entity it belongs to), so any object is findable by its owner and streams inline or downloads by name. The same backbone also serves a shared localization bundle and a read-only Vietnamese banks registry with logos, so the storefront and back office render bank choices and translations from one source.

1. Context & Problem

Every product module needs to attach media - photos on products and variants, logos on organizers, supporting documents on ledgers and tickets - and the storefront needs bank logos and a shared translation bundle. Without a shared backbone, each service would invent its own upload path, object-naming scheme, and way of remembering which file belongs to which record, and bank reference data would be copy-pasted until it drifted.

The gap is twofold. First, a store-and-remember primitive: put a binary down, get back a stable link, and keep a durable record tying the object to the entity it illustrates - so the catalogue finds "the photo for this variant" without scanning storage. Second, shared reference media: one localization bundle and one Vietnamese banks registry (with logos) the whole platform reads, instead of each app shipping its own copy.

This PRD specifies that backbone: authenticated upload to object storage with a media record per object, owner binding and a media-record query API, public serving and listing, the localization bundle, and the banks registry plus logo endpoints.

2. Goals & Non-Goals

Goals

  • An authenticated multipart upload to a configured storage bucket that assigns each file a unique, URL-safe object name and returns an addressable link (AST).
  • A durable media record per stored object capturing its storage coordinates, content type, size, integrity fingerprint, metadata, and storage type (MTL).
  • Owner binding at upload time - an object can carry an owner type / owner id / variant so it is findable by the entity it belongs to (MTL).
  • Public serving: stream an object inline by name, download it as an attachment, and list a bucket by prefix; plus delete that removes object and media record together (AST).
  • A shared localization bundle served inline and downloadable from the configured bucket (AST).
  • A read-only Vietnamese banks registry as structured data - each entry with names, capability flags, and an absolute logo URL - plus a per-bank logo image endpoint (BNK).
  • Safe addressing: validated object names / folder depth, strict logo filenames, whitelisted response headers, and content-type-sniffing disabled on every stream (AST, BNK).

Non-Goals

  • Image transformation, thumbnailing, or on-the-fly resizing - objects are served as stored.
  • Per-merchant bucket isolation or tenant-scoped storage - a single configured bucket backs the host service.
  • A managed media-library browsing UI - this PRD delivers the API surface, not a gallery screen.
  • Authoring or editing the banks registry - it is read-only reference data bundled with the service.
  • The realtime notification pipeline (ACT, WSS) - specified in PRD-ACT-001.

3. Success Metrics

MetricTarget / signal
Upload integrityEvery successfully stored object has a matching media record with its size, content type, and integrity fingerprint
FindabilityAn object uploaded with an owner binding is retrievable by that owner's type + id
Naming safetyNo stored object name collides or carries an unsafe path; every served stream disables content-type sniffing
Reference reuseStorefront and back office render bank choices and logos from the one registry, not local copies
Cleanup integrityDeleting an object leaves no orphan media record for that bucket + object

4. Personas & Use Cases

PersonaGoal in this feature
Owner / ManagerAttach a photo to a product or a logo to an organizer and have it shown everywhere that entity appears
Producing serviceUpload an object, bind it to a record, and later resolve the record's media by its media record
Storefront / clientStream an image by name, render bank choices and logos, and load the shared translation bundle
Platform operatorTrust that object names are safe and that deletes clean up both storage and metadata

Core scenario: a manager adds a photo while editing a variant. The client uploads the file with the variant's owner type and id; the service stores it under a unique name, returns the link, and writes a media record capturing the object and its binding. Wherever that variant later appears, its media resolves through the record. Separately, the checkout screen fetches the banks registry once and renders each provider with its absolute logo URL.

5. User Stories

  • As a manager, I attach an image to a product and it appears wherever the product is shown, so the catalogue looks complete.
  • As a producing service, I bind an uploaded object to the record it illustrates, so I can find that record's media later without scanning storage.
  • As a client, I stream an image by its object name and download a document as an attachment, so media just works in the UI.
  • As a client, I read one banks registry with absolute logo URLs, so I render payment choices without joining paths myself.
  • As an operator, I delete an object and trust that its media record is cleaned up too, so nothing dangles.
  • As a client, I load one shared localization bundle, so every surface translates from the same source.

6. Functional Requirements

#RequirementURD ref
FR-1Authenticated multipart upload stores one or more files in the configured bucket, each under a unique URL-safe object name, returning its object name and addressable linkURD-AST-001
FR-2An upload may carry a folder path (validated, max two levels) and an owner binding (owner type / owner id / variant)URD-AST-002 · URD-MTL-002
FR-3After a successful store, a media record is created per object capturing bucket, object name, link, content type, size, integrity fingerprint, metadata, storage type, and sync flagURD-MTL-001
FR-4A stored object streams inline by object name; nested object paths up to two folder levels are supportedURD-AST-003
FR-5A stored object downloads as an attachment by object nameURD-AST-004
FR-6An authenticated delete removes the object from storage and clears its media-record entries for that bucket + objectURD-AST-005 · URD-MTL-004
FR-7An authenticated list returns a bucket's objects filtered by name prefix, depth, and a result capURD-AST-006
FR-8A shared localization bundle is served inline and downloadable as an attachment from the configured bucketURD-AST-007
FR-9Media records are exposed as a full create/read/update/delete management resource (list, get-by-id, get-one, count, create, update, delete), authenticatedURD-MTL-003
FR-10The Vietnamese banks registry is served as structured data - each entry with short name, full name, capability flags (VietQR, disburse, NAPAS), and an absolute logo URLURD-BNK-001..002
FR-11Each bank logo is served as an image by a strict per-bank logo filename, validated against that pattern, with long-lived immutable cachingURD-BNK-003
FR-12Object names, folder paths, and logo filenames are validated; only whitelisted metadata headers are echoed and content-type sniffing is disabled on every streamURD-AST-008 · URD-BNK-004

Full requirement text and acceptance criteria live in the Platform URD - AST · MTL · BNK. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Naming safetyObject names are unique and URL-safe; folder depth is capped and every path segment validated before a store or fetch
Header hygieneOnly whitelisted metadata headers are reflected onto responses; content-type sniffing is always disabled; header values are stripped of line breaks
Storage abstractionStorage is reached through a single storage abstraction configured from the environment (endpoint, access key, secret key, bucket)
ResilienceA failed media-record write does not lose the stored object - the object is reported with its per-file media-record error; media-record cleanup on delete is best-effort and logged
CachingThe banks registry is cacheable; bank logos are served immutable with a long cache lifetime
AuthUpload, delete, list, and the media-record management API require authentication; object reads, the localization bundle, and the banks registry / logos are public reads
i18nBank display names and the shared localization bundle are the localization source; bank entries carry both short and full names

8. UX & Flows

The upload surface accepts one or more files with an optional folder path and an owner binding, returning each object's name, link, and media record (or its media-record error). Reads stream by name inline or as a download; a bucket can be listed by prefix. The banks registry and per-bank logos are fetched directly into the UI.

9. Data & Domain

EntityRole
Media recordThe durable record of a stored object - bucket, object name, link, content type, size, integrity fingerprint, metadata, storage type, sync flag - plus an optional owner binding (owner type, owner id, variant)
Stored objectThe binary in the configured bucket, addressed by its object name and reachable through its link
Bank registry entryA read-only reference record - short name, full name, capability flags (VietQR, disburse, NAPAS), and a logo - bundled with the service

Conceptual only - full schema and invariants live in the asset domain model. Owner relations are soft references resolved by type + id, not database foreign keys.

10. Dependencies & Assumptions

Depends on

  • Object storage - an endpoint, access key, secret key, and a default bucket are configured in the environment.
  • Platform core - owns the media-record definition, the owner-type registry, and the shared environment keys.
  • A configured public base URL - used to project relative bank-logo paths into absolute URLs for the client.

Assumptions

  • The producing service knows the owner type / owner id of the record it is attaching media to.
  • The localization bundle and the Vietnamese bank logo assets are present in the configured bucket / bundled with the service.
  • A single configured bucket is sufficient for the host service; multi-bucket / per-tenant isolation is out of scope here.

11. Risks & Open Questions

Risk / questionMitigation / status
Object stored but media-record write failsObject is not lost - it is returned with a per-file media-record error and logged for reconciliation
Unsafe or colliding object namesNames are generated unique and URL-safe; folder depth capped and segments validated; reads reject invalid paths
Media-record management authorization is permissive todayKnown follow-up - tightening the media-record API to its permission set is tracked (URD-CON-005)
Bank logo path traversalLogo filenames must match a strict per-bank pattern; anything else is refused before disk access
Banks registry drift across appsSingle read-only registry served from one source; apps stop shipping local copies

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - AST, MTL, BNK in the URD feature catalog
RolloutMounted by host services that need media; no feature flag
MigrationNone - the media-record store and the bundled banks assets ship with the service
Launch criteriaUpload stores objects with a media record and optional binding; objects stream inline and download by name; delete clears both storage and media record; the banks registry returns entries with absolute logo URLs and logos serve as images
MonitoringUpload error rate, media-record write-failure rate, object-vs-record consistency, registry / logo cache hit behaviour

13. FAQ

Where do uploaded files go? Into the configured storage bucket, each under a unique, URL-safe name, with an addressable link returned to the caller.

How do I find the media for a specific product or variant? Bind the object at upload with its owner type and id; the media record then makes it findable by that entity through the query API.

What happens if the media record can't be written after a file is stored? The object is not lost - the upload result flags that file with a media-record error for reconciliation; other files in the same upload are unaffected.

Are uploaded objects public? Object reads, the localization bundle, and the banks registry / logos are public; uploading, deleting, listing, and the media-record management API require authentication.

Can I edit the banks registry? No - it is read-only reference data bundled with the service, each entry projected with an absolute logo URL the client uses directly.

Does deleting an object leave its record behind? No - a delete removes the object from storage and clears its media-record entries for that bucket + object.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.