Skip to main content

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.
NamingValue
ObjectMedia
Resource type (JSON:API type)media
Collection / records rootmedia
REST base/v1/medias
Entity classMedia

API operations

OperationMethod & pathStatus
ListGET /v1/medias✅ Implemented
RetrieveGET /v1/medias/{id}✅ Implemented
CreatePOST /v1/medias🟡 Planned
UpdatePATCH /v1/medias/{id}🟡 Planned
DeleteDELETE /v1/medias/{id}🟡 Planned

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
media_idstring, UUID, 🔒 system✅ Yesunique; 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_typestring (enum)✅ YesNOT NULL; native PG enum media_type_enum in core_api schemaavatar, logo, bannerCategory 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.
labelstring⚪ Novarchar(255) by MikroORM default, nullableHuman-readable label for the asset, e.g. “Acme Corp primary logo” or “Profile photo – Q1 2026”. Optional free-text annotation; not used programmatically.
urlstring⚪ Notext, nullable; DEPRECATED — NULL for all assets stored after the GCS migrationLegacy 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.
pathstring⚪ Notext, nullableGCS 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_atDate, 🔒 system✅ Yestimestamptz NOT NULL; initialized to new Date() as TypeScript default and also set by MikroORM onCreate lifecycle hookTimestamp when the media record was created. Immutable after insert.
updated_atDate, 🔒 system⚪ Notimestamptz, 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_atDate⚪ Notimestamptz, nullable; NULL = active recordSoft-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

NameTypeRequiredDescription
workspaceto-one (workspace)⚪ NoThe 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