From d9788abe4c33033aa894ee20d9e648fa91945c68 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Sat, 13 Jun 2026 13:14:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(plugin):=20media=20deposit=20SDK=20surface?= =?UTF-8?q?=20=E2=80=94=20Media,=20MediaDeposit,=20EnsureMedia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the SDK surface for plugins to deposit images into the CMS media library. MediaDeposit / MediaResult types and a Media interface on CoreServices for runtime deposits; EnsureMedia on the Provisioner interface for idempotent seed-time deposits under a plugin-chosen, template-referable UUID. The CMS implements both against one internal depositor; seeded templates reference the chosen UUID directly via {% img "" %}. Co-Authored-By: Claude Opus 4.8 (1M context) --- plugin/deps.go | 42 ++++++++++++++++++++++++++++++++++++++++++ plugin/provisioner.go | 5 +++++ 2 files changed, 47 insertions(+) diff --git a/plugin/deps.go b/plugin/deps.go index 6a5478c..5d1d517 100644 --- a/plugin/deps.go +++ b/plugin/deps.go @@ -38,6 +38,9 @@ type CoreServices struct { MediaPath string AppURL string + // Media library — deposit images through the full upload pipeline + Media Media + // AI ToolRegistry ai.ToolRegistry AITextCall func(ctx context.Context, taskKey, systemPrompt, userMessage string) (string, error) @@ -60,6 +63,45 @@ type CoreServices struct { RAGService RAGService } +// MediaDeposit describes an image a plugin deposits into the CMS media library. +// The bytes flow through the same pipeline as an admin upload (optimize, WebP, +// thumbnails, LQIP, dimensions); responsive variants are generated on demand. +type MediaDeposit struct { + // ID is the media row's primary key. REQUIRED for Provisioner.EnsureMedia — + // it is the deterministic, template-referable key a seeded {% img %} tag + // resolves by. Optional for Media.Deposit, where a zero value is generated. + ID uuid.UUID + // Filename is the original filename, e.g. "hero.jpg". + Filename string + // Data is the raw image bytes, typically from go:embed. + Data []byte + // AltText is optional accessibility text. + AltText string + // Folder optionally groups the media under a named library folder, + // created if absent. + Folder string + // Source is an optional provenance label, e.g. the plugin name. + Source string +} + +// MediaResult reports the outcome of a deposit. +type MediaResult struct { + // ID is the media row's primary key (the supplied ID, or a generated one). + ID uuid.UUID + // Ref is a ready-to-use reference ("media:") for a block src or a + // {% img %} tag. + Ref string + // Created is false when an existing row with the supplied ID was reused. + Created bool +} + +// Media lets a plugin deposit images into the CMS media library at runtime. +// Seed-time deposits use Provisioner.EnsureMedia instead — it is idempotent +// (skip if the ID exists; on changed bytes it warns rather than overwriting). +type Media interface { + Deposit(ctx context.Context, deposit MediaDeposit) (MediaResult, error) +} + // JobRunner submits background jobs for async processing. type JobRunner interface { Submit(ctx context.Context, jobType string, config []byte) error diff --git a/plugin/provisioner.go b/plugin/provisioner.go index d343e9b..91f5eb0 100644 --- a/plugin/provisioner.go +++ b/plugin/provisioner.go @@ -23,6 +23,11 @@ type Provisioner interface { DisableOrphanedJobSchedules(registeredTypes []string) error EnsurePlugin(name string) error EnsureCustomColor(config CustomColorConfig) error + // EnsureMedia deposits an image under the plugin-chosen MediaDeposit.ID if a + // row with that ID does not already exist. Idempotent: an existing ID is a + // no-op; if the bytes differ from what was first deposited it logs a warning + // rather than overwriting (seeded media is immutable once placed). + EnsureMedia(deposit MediaDeposit) error } // DataTableConfig defines a data table to provision.