Summary
Publishing a news (or any collection) entry whose staged slug collides with
another entry's already-published (slug, locale) fails with a raw D1/SQLite
error and no indication of what failed or how to fix it.
Repro
-
Create/edit a draft entry and set its slug to a value that another
already-published entry in the same locale already uses.
-
Click Publish (or POST /_emdash/api/content/{collection}/{id}/publish).
-
The request 500s. The only feedback surfaced (both in the admin UI toast
and in the Worker logs) is:
Content publish error: Error: D1_ERROR: UNIQUE constraint failed:
ec_news.slug, ec_news.locale: SQLITE_CONSTRAINT (extended: SQLITE_CONSTRAINT_UNIQUE)
There is no mention of the slug value, the locale, or which existing entry
already holds it. As an editor, all you see is "failed to publish" — you
have to go spelunking in the database to find out that a totally unrelated
article (created via an earlier CSV/legacy import with a numeric slug like
11) happens to already own the slug you typed.
Where this happens
ContentRepository.publish() writes the staged _slug from the draft
revision straight to the live slug column with a raw UPDATE ... SET slug = ... (see packages/core/src/database/repositories/content.ts, inside
publish(), the if (typeof revision.data._slug === "string") block). This
bypasses whatever slug-uniqueness pre-check exists for handleContentUpdate
(the comment right above handleContentPublish even calls this out: "a slug
edit is staged as _slug in the draft revision and only lands on the live
slug column here, inside repo.publish() — it never passes through
handleContentUpdate, where slug-change auto-redirects are normally
created"). So the uniqueness check that presumably guards normal slug edits
never runs on this path, and the DB constraint is the only thing that catches
it — as a generic 500.
Suggested fix
Before writing _slug to the live column in publish(), check for an
existing published row with the same (slug, locale) (excluding the current
id) and return a proper EmDashValidationError / VALIDATION_ERROR result
naming the conflicting slug — the same shape already used elsewhere in this
function for other failure modes. That turns an opaque 500 + raw SQLite
message into an actionable "this slug is already used by entry X" error the
admin UI can show inline on the slug field.
Environment
emdash 0.28.1
- Cloudflare Workers + D1
Summary
Publishing a
news(or any collection) entry whose staged slug collides withanother entry's already-published
(slug, locale)fails with a raw D1/SQLiteerror and no indication of what failed or how to fix it.
Repro
Create/edit a draft entry and set its slug to a value that another
already-published entry in the same locale already uses.
Click Publish (or
POST /_emdash/api/content/{collection}/{id}/publish).The request 500s. The only feedback surfaced (both in the admin UI toast
and in the Worker logs) is:
There is no mention of the slug value, the locale, or which existing entry
already holds it. As an editor, all you see is "failed to publish" — you
have to go spelunking in the database to find out that a totally unrelated
article (created via an earlier CSV/legacy import with a numeric slug like
11) happens to already own the slug you typed.Where this happens
ContentRepository.publish()writes the staged_slugfrom the draftrevision straight to the live
slugcolumn with a rawUPDATE ... SET slug = ...(seepackages/core/src/database/repositories/content.ts, insidepublish(), theif (typeof revision.data._slug === "string")block). Thisbypasses whatever slug-uniqueness pre-check exists for
handleContentUpdate(the comment right above
handleContentPublisheven calls this out: "a slugedit is staged as
_slugin the draft revision and only lands on the liveslugcolumn here, insiderepo.publish()— it never passes throughhandleContentUpdate, where slug-change auto-redirects are normallycreated"). So the uniqueness check that presumably guards normal slug edits
never runs on this path, and the DB constraint is the only thing that catches
it — as a generic 500.
Suggested fix
Before writing
_slugto the live column inpublish(), check for anexisting published row with the same
(slug, locale)(excluding the currentid) and return a proper
EmDashValidationError/VALIDATION_ERRORresultnaming the conflicting slug — the same shape already used elsewhere in this
function for other failure modes. That turns an opaque 500 + raw SQLite
message into an actionable "this slug is already used by entry X" error the
admin UI can show inline on the slug field.
Environment
emdash0.28.1