diff --git a/.changeset/seeded-media-usage-ready.md b/.changeset/seeded-media-usage-ready.md new file mode 100644 index 0000000000..d1f50aa558 --- /dev/null +++ b/.changeset/seeded-media-usage-ready.md @@ -0,0 +1,5 @@ +--- +"emdash": patch +--- + +Fixes seeded media references using a different ID from their media-library item and prepares Used in data for newly seeded development sites. diff --git a/packages/core/src/astro/routes/api/setup/dev-bypass.ts b/packages/core/src/astro/routes/api/setup/dev-bypass.ts index ab1e257257..bbaf8ff67e 100644 --- a/packages/core/src/astro/routes/api/setup/dev-bypass.ts +++ b/packages/core/src/astro/routes/api/setup/dev-bypass.ts @@ -4,6 +4,7 @@ * * Development-only endpoint to bypass the setup wizard. * Runs migrations, creates a dev admin user, and marks setup complete. + * Newly applied sample content has its Media Usage index prepared before redirecting. * * ONLY available when import.meta.env.DEV is true. * @@ -30,6 +31,7 @@ import { getPublicOrigin } from "#api/public-url.js"; import { isSafeRedirect } from "#api/redirect.js"; import { runMigrations } from "#db/migrations/runner.js"; import { OptionsRepository } from "#db/repositories/options.js"; +import { repairContentMediaUsageCollection } from "#media/usage/content-repair.js"; import { applySeed } from "#seed/apply.js"; import { loadSeed } from "#seed/load.js"; import { validateSeed } from "#seed/validate.js"; @@ -74,6 +76,16 @@ async function handleDevBypass(context: Parameters[0]): Promise 0) { + for (const collectionSlug of Object.keys(seed.content ?? {})) { + const repair = await repairContentMediaUsageCollection(emdash.db, { + collectionSlug, + }); + if (repair.status !== "complete") { + throw new Error(`Seeded Media Usage indexing failed for ${collectionSlug}`); + } + } + } } const options = new OptionsRepository(emdash.db); diff --git a/packages/core/src/seed/apply.ts b/packages/core/src/seed/apply.ts index 21f983181b..67af2c83a6 100644 --- a/packages/core/src/seed/apply.ts +++ b/packages/core/src/seed/apply.ts @@ -1366,9 +1366,9 @@ async function resolveMedia( const ext = getExtensionFromContentType(contentType) || getExtensionFromUrl(url) || ".bin"; // Generate filename and storage key - const id = ulid(); + const storageId = ulid(); const finalFilename = filename || generateFilename(url, ext); - const storageKey = `${id}${ext}`; + const storageKey = `${storageId}${ext}`; // Get the body as buffer const arrayBuffer = await response.arrayBuffer(); @@ -1392,7 +1392,7 @@ async function resolveMedia( // Create media record const mediaRepo = new MediaRepository(ctx.db); - await mediaRepo.create({ + const media = await mediaRepo.create({ filename: finalFilename, mimeType: contentType, size: body.length, @@ -1407,7 +1407,7 @@ async function resolveMedia( // Create the MediaValue - only store id, URL is built at runtime by EmDashMedia const mediaValue: MediaValue = { provider: "local", - id, + id: media.id, alt: alt ?? undefined, width, height, diff --git a/packages/core/tests/unit/astro/setup-dev-bypass.test.ts b/packages/core/tests/unit/astro/setup-dev-bypass.test.ts index b791aaefff..c205852ef5 100644 --- a/packages/core/tests/unit/astro/setup-dev-bypass.test.ts +++ b/packages/core/tests/unit/astro/setup-dev-bypass.test.ts @@ -22,7 +22,10 @@ const fixtureSeed: SeedFile = { { slug: "posts", label: "Posts", - fields: [{ slug: "title", label: "Title", type: "string" }], + fields: [ + { slug: "title", label: "Title", type: "string" }, + { slug: "featured_image", label: "Featured image", type: "image" }, + ], }, ], taxonomies: [ @@ -36,7 +39,21 @@ const fixtureSeed: SeedFile = { ], bylines: [{ id: "sample", slug: "sample-author", displayName: "Sample Author" }], content: { - posts: [{ id: "post-1", slug: "sample-post", data: { title: "Sample Post" } }], + posts: [ + { + id: "post-1", + slug: "sample-post", + data: { + title: "Sample Post", + featured_image: { + id: "seed-media-1", + provider: "local", + filename: "sample.jpg", + mimeType: "image/jpeg", + }, + }, + }, + ], }, menus: [ { @@ -89,6 +106,34 @@ describe("setup dev-bypass seed gating", () => { expect(await countRows(db, "_emdash_bylines")).toBe(1); }); + it("makes seeded media usage ready without activating incremental capture", async () => { + const response = await GET(makeContext(db)); + expect(response.status).toBe(200); + + const usage = await db + .selectFrom("_emdash_media_usage") + .select(["media_id", "field_slug"]) + .where("media_id", "=", "seed-media-1") + .execute(); + expect(usage).toEqual([{ media_id: "seed-media-1", field_slug: "featured_image" }]); + + const coverage = await db + .selectFrom("_emdash_media_usage_index_status") + .select(["status", "indexed_source_count"]) + .where("adapter_id", "=", "content-media") + .where("scope_type", "=", "collection") + .where("scope_key", "=", "posts") + .executeTakeFirstOrThrow(); + expect(coverage).toEqual({ status: "complete", indexed_source_count: 1 }); + + const activation = await db + .selectFrom("_emdash_media_usage_activation") + .select("state") + .where("task_key", "=", "incremental_capture") + .executeTakeFirstOrThrow(); + expect(activation.state).toBe("expanded"); + }); + it("applies schema only with ?content=0", async () => { const response = await GET(makeContext(db, "?content=0")); expect(response.status).toBe(200); diff --git a/packages/core/tests/unit/seed/media.test.ts b/packages/core/tests/unit/seed/media.test.ts index 224de9c9d2..4dcc3b21fc 100644 --- a/packages/core/tests/unit/seed/media.test.ts +++ b/packages/core/tests/unit/seed/media.test.ts @@ -202,10 +202,11 @@ describe("$media seed resolution", () => { // Check the content has resolved ImageValue const contentRepo = new ContentRepository(db); const entry = await contentRepo.findBySlug("posts", "hello"); + const media = await db.selectFrom("media").select("id").executeTakeFirstOrThrow(); // ImageValue stores id (URL is built at runtime by EmDashImage) expect(entry?.data.featured_image).toMatchObject({ - id: expect.any(String), + id: media.id, alt: "Test image", }); });