Documentation Index
Fetch the complete documentation index at: https://docs.wellapp.ai/llms.txt
Use this file to discover all available pages before exploring further.
Media is an atomic, reusable binary-asset record that stores logos, avatars, and banners for companies and people in the Well platform. Each row carries a media_type enum, an optional human label, and the GCS asset location expressed either as a legacy url (deprecated) or a modern GCS path (preferred). Media rows are workspace-scoped via a nullable ManyToOne to Workspace. CompanyMedia and PersonMedia are independent pivot entities that each hold a ManyToOne reference to Media — the Media entity itself declares no back-reference collections.
| Naming | Value |
|---|
| Object | Media |
Resource type (JSON:API type) | media |
| Collection / records root | media |
| REST base | /v1/medias |
| Entity class | Media |
API operations
| Operation | Method & path | Status |
|---|
| List | GET /v1/medias | ✅ Implemented |
| Retrieve | GET /v1/medias/{id} | ✅ Implemented |
| Create | POST /v1/medias | 🟡 Planned |
| Update | PATCH /v1/medias/{id} | 🟡 Planned |
| Delete | DELETE /v1/medias/{id} | 🟡 Planned |
Data model
Attributes
| Field | Type | Required | Constraints | Allowed values | Description |
|---|
| media_id | string, UUID, 🔒 system | ✅ Yes | unique; default gen_random_uuid() | — | Public identifier for the media asset. Generated by PostgreSQL on insert via gen_random_uuid(). This is the value exposed in the JSON:API id field. |
| media_type | string (enum) | ✅ Yes | NOT NULL; native PG enum media_type_enum in core_api schema | avatar, logo, banner | Category of the binary asset. avatar is used for person profile pictures; logo for company brand marks; banner for wide header images. Controls how the asset is rendered by cell components in the records table. |
| label | string | ⚪ No | varchar(255) by MikroORM default, nullable | — | Human-readable label for the asset, e.g. “Acme Corp primary logo” or “Profile photo – Q1 2026”. Optional free-text annotation; not used programmatically. |
| url | string | ⚪ No | text, nullable; DEPRECATED — NULL for all assets stored after the GCS migration | — | Legacy absolute URL to the asset. Kept for backward compatibility with records created before the GCS storage migration. For all new assets this column is NULL; the asset is stored at path instead. The formatter’s resolveMediaUrl() falls through to this field only when path is absent. |
| path | string | ⚪ No | text, nullable | — | GCS object path for the asset, e.g. workspaces/<workspace_id>/media/<media_id>.png. The API formatter (resolveMediaUrl() in media.formatter.ts) converts this path to a CDN URL via buildMediaCdnUrl(). Takes priority over the legacy url field when both are present. NULL on pre-migration records. |
| created_at | Date, 🔒 system | ✅ Yes | timestamptz NOT NULL; initialized to new Date() as TypeScript default and also set by MikroORM onCreate lifecycle hook | — | Timestamp when the media record was created. Immutable after insert. |
| updated_at | Date, 🔒 system | ⚪ No | timestamptz, nullable; set by MikroORM onCreate hook on insert AND onUpdate hook on every subsequent write; no TypeScript default initializer (unlike created_at) | — | Timestamp of the last attribute update on this record. Set automatically by MikroORM on both create and update operations. |
| deleted_at | Date | ⚪ No | timestamptz, nullable; NULL = active record | — | Soft-delete timestamp. When non-NULL the record is considered logically deleted. All queries must filter deleted_at IS NULL. The composite index idx_media_workspace_deleted on (workspace_pk, deleted_at) is the hot path for Hasura permission filters. |
Relationships
| Name | Type | Required | Description |
|---|
| workspace | to-one (workspace) | ⚪ No | The workspace this media asset belongs to. Nullable — set to NULL on historic records predating the workspace-scoping migration. The FK carries ON DELETE SET NULL so workspace deletion orphans the asset rather than cascading a hard delete. Composite index idx_media_workspace_deleted on (workspace, deleted_at) accelerates the Hasura permission filter (workspace_pk = $1 AND deleted_at IS NULL). |
System-computed
- media_id: generated by PostgreSQL
gen_random_uuid() at INSERT time via @Property({ defaultRaw: 'gen_random_uuid()' }). Never set by application code.
- created_at: initialized to
new Date() as a TypeScript class field default AND set by MikroORM onCreate: () => new Date() lifecycle hook. Always populated.
- updated_at: set by MikroORM
onCreate and onUpdate lifecycle hooks. No TypeScript default initializer — the field is updated_at?: Date (optional). NULL on records that have never been updated after initial creation.
- deleted_at: soft-delete sentinel. NULL = active. Application must never issue hard DELETEs against the media table; set
deleted_at = NOW() instead.
- URL resolution: the public-facing
url attribute in the JSON:API response is NOT the raw media.url column. The formatter calls resolveMediaUrl(media) which returns buildMediaCdnUrl(media.path) when path is present, and falls back to media.url otherwise. Consumers must never read media.url or media.path directly — always use the resolved url from the formatted response.
- Workspace scoping: added retroactively by Migration20260105120000. Existing unscoped media was reassigned to workspaces via a data migration that de-duplicated assets shared across multiple workspaces by inserting new media rows per workspace.
- Deprecated url column: the
url column was originally NOT NULL (see Migration20250919154301 DDL). It was later made nullable by the GCS migration; all new records store the asset in GCS and leave url NULL.
- Composite records-table field
composite_media_list: built from company_media.media.media_id, company_media.media.label, and company_media.media.media_type (for companies) and the analogous person_media.* paths (for people). Defined in composites.yml; rendered by the records table as a media-list composite cell.
- Primary media computed field: the PostgreSQL function
core_api.company_primary_media(company_row, hasura_session) selects the most-recently-created non-deleted CompanyMedia row for a company and returns a JSONB projection including media_id, media_type, label, url, and path. The function was updated by Migration20260527120000 to include path alongside the legacy url so the data-views layer can derive a CDN URL for logos stored in GCS.
- Pivot relationships: CompanyMedia and PersonMedia each declare a @ManyToOne to Media on their own entity classes. The Media entity itself declares NO @OneToMany back-references — navigation from Media to its pivot rows is done via Hasura array relationships (metadata-level), not via MikroORM collections.
Example
{"type":"media","id":"b2f7c3d1-09e4-4a7b-8f56-3c2d1e0a9b7f","attributes":{"media_type":"logo","label":"Acme Corp primary logo","url":null,"path":"workspaces/4e9d7c2b-11aa-4b38-9e1f-7a3f5d6c8b20/media/b2f7c3d1-09e4-4a7b-8f56-3c2d1e0a9b7f.png","created_at":"2026-03-14T10:22:00.000Z","updated_at":"2026-04-01T08:45:00.000Z","deleted_at":null},"relationships":{"workspace":{"data":{"type":"workspace","id":"4e9d7c2b-11aa-4b38-9e1f-7a3f5d6c8b20"}}}}
Source: apps/api/src/database/entities/Media.ts · domain: financial-graph · tier: Supporting