MishkaCmsCore.Dev.SeedMedia (Mishka CMS Core v0.0.1)

Copy Markdown View Source

The files the dev seeds put in a site's media library, and where any other seed gets a picture.

A media row used to be written with an invented path — /uploads/images/3.jpg, which nothing had ever written a file to — so every thumbnail in the library fell back to its type glyph, the builder's media sheet handed pages an <img src> that answered 404, and a blog cover was either nothing or one of the marketing site's own photographs. The set under priv/dev_seed_media is drawn from primitives rather than photographed: nobody's copyright, nothing downloaded, and small enough (well under 40 KB each) to live in the repository.

A FILE'S NAME SAYS WHAT IT IS FOR. cover-* is a wide banner, card-* a listing picture, avatar-* a portrait and doc-* a document; that prefix is the whole registration, so adding one is dropping it in the directory. An entry whose prefix is none of those — or that is not a file at all — is left alone rather than guessed at: files/0 is what the seed writes, and it only names what it can describe.

Storing goes through the resource's own upload path — Media.validate_and_process_file/1 then Media.save_file_to_storage/3 — so a seeded row lands exactly where an uploaded one does, under /uploads/<site>/media/<category>/<uuid>.<ext>, and replacing, archiving or deleting it from the admin works on it like any other file. The copy is staged through a temporary file first: storing removes the source it is given, and the source here is a file the repository ships.

There are no videos in the set. A real .mp4 needs an encoder nothing in this toolchain has, and a row pointing at one we could not write is the 404 this module exists to remove. That costs the dev database the one :videos row the old seed invented, so a seeded library no longer has anything for the media screen's type filter, its by_type read or the film glyph the thumbnail column draws in place of a picture — ground the resource's own tests and the stress seed still cover, and that a real .webm should take back the day something here can write one.

Summary

Functions

The pixel size of path in the shape the dimensions attribute stores, or %{} for a file whose header this cannot read.

Where the shipped files live.

Every shipped file as %{filename: ..., path: ..., shape: ..., title: ...}, ordered by name.

The paths of the images site_id already has, grouped by shape — %{cover: [...], card: [...], avatar: [...]}.

Writes whatever of the set site_id does not have yet into its library, one category per shape the set has, and returns the media rows it wrote.

Functions

dimensions(path)

@spec dimensions(String.t()) :: map()

The pixel size of path in the shape the dimensions attribute stores, or %{} for a file whose header this cannot read.

Read from the file rather than declared beside it, so a replaced picture cannot leave a row describing the one before it. Every picture in the set is a lossy WebP, which keeps its canvas size in the first chunk (VP8) 26 bytes in, as two 14-bit values; that is the one header this reads. Anything else — a document, or a lossless (VP8L) or extended (VP8X) WebP somebody adds later — answers %{} rather than a guess, and the suite says which file it was, so the clause for that chunk gets written the day a file needs it.

directory()

@spec directory() :: String.t()

Where the shipped files live.

Configurable, the way MishkaCmsCore.Runtime.PageUpload.directory/1 is, and for a reason the shipped path cannot cover: a file's presence in this directory IS its registration, and under :test the path below resolves through Mix's priv symlink to the repository's own source. A suite proving that a file dropped in between two runs is picked up would be dropping it into the set the package ships — one killed VM away from a stray picture nothing ignores and git add -A commits. Pointed somewhere else, it writes where it can afford to.

files()

@spec files() :: [
  %{filename: String.t(), path: String.t(), shape: atom(), title: String.t()}
]

Every shipped file as %{filename: ..., path: ..., shape: ..., title: ...}, ordered by name.

Answers [] when the directory is not there — a package that did not ship it seeds a library with nothing in it rather than failing the whole run.

images(site_id)

@spec images(String.t()) :: %{required(atom()) => [String.t()]}

The paths of the images site_id already has, grouped by shape — %{cover: [...], card: [...], avatar: [...]}.

This is how a seed that needs a picture asks for one: the blog's covers and avatars are rows in the library rather than strings pointing at whatever happens to be under priv/static. A site that has not been media-seeded answers %{}, and every caller has to read that as "no picture", because that is also what a fresh install looks like.

install(site_id)

@spec install(String.t()) :: [MishkaCmsCore.Runtime.Media.t()]

Writes whatever of the set site_id does not have yet into its library, one category per shape the set has, and returns the media rows it wrote.

A file that cannot be stored is reported and skipped: a library short one picture is worth more to whoever is seeding than a run that stops on it.

RUN AGAIN, THIS ADDS ONLY WHAT IS NEW. Reseeding is how a developer gets the pictures a later version of this set ships without dropping their database, so a category that is there is reused and a file that is there is passed over — which is also what makes dropping one more file into priv/dev_seed_media and reseeding the way to add it. It never updates: the row an admin renamed, re-described or moved to another category is theirs.