Skip to content

Store media assets with versioned metadata, folders and tags #2542

Closed

Description

@pournasserian

Part of #2427 (F-MED). Next slice after the storage (#2540): media assets with versioned metadata, including folders, tags, alt text and caption, and their use through the reference index (MED-01, MED-02, CNT-05). The bytes go to the configured storage, and every upload and metadata change makes a new version, as entries do (CNT-02).

Scope

  • Core (FluentCMS.Core.Media):
    • MediaAsset: id, site, latest version, published version and the latest version's metadata.
    • MediaAssetVersion: file name, content type, length, SHA-256, storage name and key, folder, tags, alt text, caption, and when it was written.
    • AssetMetadata, checked all at once by field (UX-03):
      • a file name without path separators;
      • a folder path of lowercase slug segments, such as products/drills, or none;
      • up to 32 distinct tags of lowercase words;
      • alt text and a caption, each blank-free when given.
  • Core: IMediaLibrary, returning Result:
    • upload a stream with its content type and metadata;
    • update the metadata from the version the caller read;
    • read an asset or any of its versions;
    • list assets by folder (with or without subfolders) and by tag;
    • list the folders in use;
    • open an asset's bytes.
  • Uploads: the bytes go to the storage Media__Storage names, and the version records that storage. An empty file, or one larger than Media__MaxBytes (50 MB by default), is refused and its bytes are deleted again. A new version made by a metadata change keeps the same bytes.
  • Infrastructure: MediaAssets and MediaAssetVersions, scoped to the site. A trigger refuses any update of a version row on both providers, as for entry versions. Concurrent updates from one version get a conflict. media.uploaded and media.updated are appended in the same unit of work.
  • Usage (CNT-05): an entry that references a media asset that does not exist now fails with reference_not_found, as a missing entry does. Where used for an asset (Index entry references and answer where used #2492) lists the entries that show it.
  • Migrations for SQLite and PostgreSQL, each with a working Down.

Out of scope

  • Media operations in change sets and publishing them, the upload and attach tools, and media in agent scopes: the next slice.
  • The "alt text required" check: the slice after.
  • Soft delete and restore: the last slice.
  • The library screen: after the desk's layout exists (F-DSK F-DSK: Review desk as the home screen #2422).
  • Image processing and variants (M1 out of scope).

Acceptance

  • An upload stores the bytes in the configured storage and version 1 with its metadata, and the bytes read back. media.uploaded names the asset.
  • A metadata update stores the next version with the same bytes. A stale base version gets version_conflict, and invalid metadata comes back field by field, each storing nothing.
  • An empty or oversized upload is refused and leaves no bytes behind.
  • Assets list by folder, with or without subfolders, and by tag, and the folders in use list once each.
  • An entry that references a missing asset is refused. An entry that references an asset appears in where used for it.
  • An update of a version row fails on both providers.
  • Everything above passes on SQLite and PostgreSQL, the new migration's Down works on both, and ProjectReferenceRulesTests pass with no new package.

Its tests define content types with media fields, so it merges after SCH-05 (#2520).

Activity

  1. added 2 commits that reference this issue on Oct 19, 2026
    a58d620
    eda58a1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:featureNew capabilityv2FluentCMS v2 (v2 branch)

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions