Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/seeded-media-usage-ready.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions packages/core/src/astro/routes/api/setup/dev-bypass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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";
Expand Down Expand Up @@ -74,6 +76,16 @@ async function handleDevBypass(context: Parameters<APIRoute>[0]): Promise<Respon
console.log(
`[setup-dev-bypass] Seed applied: ${seedResult.collections.created} collections, ${seedResult.fields.created} fields`,
);
if (includeContent && seedResult.content.created + seedResult.content.updated > 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);
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/seed/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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,
Expand All @@ -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,
Expand Down
49 changes: 47 additions & 2 deletions packages/core/tests/unit/astro/setup-dev-bypass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -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: [
{
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/tests/unit/seed/media.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});
});
Expand Down
Loading