Skip to content

fix(core): resolve entry slug to canonical id when reading content terms#1163

Open
officialasishkumar wants to merge 2 commits into
emdash-cms:mainfrom
officialasishkumar:fix/1045-terms-get-slug-resolution
Open

fix(core): resolve entry slug to canonical id when reading content terms#1163
officialasishkumar wants to merge 2 commits into
emdash-cms:mainfrom
officialasishkumar:fix/1045-terms-get-slug-resolution

Conversation

@officialasishkumar
Copy link
Copy Markdown

What does this PR do?

Fixes GET /_emdash/api/content/{collection}/{id}/terms/{taxonomy} returning an empty term list when the content entry is addressed by its slug instead of its ULID.

Term assignments (content_taxonomies rows) are keyed by the canonical content ULID. The POST handler on this route already resolves the URL id segment (which may be a slug) to that ULID via handleContentGet before writing. The GET handler did not — it looked up assignments under the raw slug, found none, and returned { "terms": [] }, even though the terms were assigned and a request by ULID returned them correctly.

Closes #1045

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool:

Root cause

In packages/core/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts:

  • POST resolves the slug to the canonical id before writing:
    const existing = await emdash.handleContentGet(collection, id);
    const canonicalId = typeof existingItem?.id === "string" ? existingItem.id : id;
    await repo.setTermsForEntry(collection, canonicalId, taxonomy, termIds);
  • GET passed the raw URL id straight through:
    const terms = await repo.getTermsForEntry(collection, id, taxonomy);

getTermsForEntry joins content_taxonomies on entry_id, which stores the canonical ULID. A slug never matches, so a slug-addressed read returns an empty list. It is a read/write asymmetry: POST stores under the ULID, GET read under whatever was in the URL.

Fix

Resolve the slug to the canonical id in the GET handler before querying, mirroring POST. When the entry can't be resolved (for example, it doesn't exist), the handler falls back to the raw id, preserving the previous "no such entry → empty list" behaviour instead of introducing a new 404 on a read.

const existing = await emdash.handleContentGet(collection, id);
const canonicalId = existing.success ? (existing.data?.item.id ?? id) : id;
const terms = await repo.getTermsForEntry(collection, canonicalId, taxonomy);

How to test

Added packages/core/tests/integration/astro/content-terms-slug-resolution.test.ts, which:

  1. creates a tag term and a post with slug esim-pakistan,
  2. assigns the term via POST .../terms/tag addressed by slug (POST already resolves it),
  3. asserts GET .../terms/tag returns the term both when addressed by ULID (control) and by slug (the regression).

The test reproduces the bug — on main the slug-addressed GET returns []; with this change it returns the assigned term.

Manual check against a running instance:

curl "$BASE/_emdash/api/content/posts/esim-pakistan/terms/tag" -H "Authorization: Bearer $TOK"
# before: {"data":{"terms":[]}}
# after:  {"data":{"terms":[{"slug":"pakistan", ...}]}}

GET /_emdash/api/content/{collection}/{id}/terms/{taxonomy} passed the raw
URL `id` straight to the taxonomy repository, but content_taxonomies rows
are keyed by the canonical content ULID. The POST handler already resolves a
slug to that ULID via handleContentGet before writing, so a slug-addressed
read matched no rows and returned an empty list even when terms were
assigned — only requests using the ULID returned the assignments.

Resolve the slug the same way in the GET handler before querying, falling
back to the raw id when the entry can't be resolved so the existing
'no such entry -> empty list' behaviour is preserved.

Closes emdash-cms#1045
Copilot AI review requested due to automatic review settings May 25, 2026 02:03
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 25, 2026

🦋 Changeset detected

Latest commit: a1cde57

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a read/write asymmetry in the content-terms API so that GET /_emdash/api/content/{collection}/{id}/terms/{taxonomy} resolves a slug {id} to the entry’s canonical ULID before querying term assignments (matching the existing POST behavior), and adds a regression test + changeset.

Changes:

  • Update the terms GET route to resolve {id} via handleContentGet() and query by the canonical entry ID.
  • Add an integration test covering GET-by-slug vs GET-by-ULID behavior.
  • Add a patch changeset for the emdash package.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/core/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts Resolve slug → canonical ULID in GET before querying content_taxonomies.
packages/core/tests/integration/astro/content-terms-slug-resolution.test.ts Regression test ensuring GET returns assigned terms when addressed by slug.
.changeset/fix-terms-get-slug-resolution.md Patch release note for the bugfix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +38 to +45
// The URL `id` may be a slug. Term rows are keyed by the canonical
// content ULID — the POST handler resolves the slug and stores under
// that ULID, so the read must resolve it too. Without this, a request
// addressed by slug looks up assignments under the slug, finds none,
// and returns an empty list even though the term is assigned (#1045).
const existing = await emdash.handleContentGet(collection, id);
const canonicalId = existing.success ? (existing.data?.item.id ?? id) : id;
const terms = await repo.getTermsForEntry(collection, canonicalId, taxonomy);
@officialasishkumar
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request May 25, 2026
@github-actions github-actions Bot added cla: signed review/awaiting-author Reviewed; waiting on the author to respond and removed cla: needed labels May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core cla: signed review/awaiting-author Reviewed; waiting on the author to respond size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GET /content/{c}/{id}/terms/{tax} does not resolve slug → canonical ULID

2 participants