Skip to content

fix(article): match the tag filter case-insensitively - #238

Merged
aquie00t merged 1 commit into
mainfrom
fix/article-tag-filter-casing
Aug 30, 2026
Merged

fix(article): match the tag filter case-insensitively#238
aquie00t merged 1 commit into
mainfrom
fix/article-tag-filter-casing

Conversation

@aquie00t

Copy link
Copy Markdown
Collaborator

Problem

GET /api/v1/articles?tag=… matched the tag exactly, while GET /api/v1/posts?tag=… lowercased it first. Verified against production:

GET /articles?tag=nodejs  → 1 article
GET /articles?tag=NodeJS  → total: 0     ❌
GET /posts?tag=nodejs     → total: 20
GET /posts?tag=NodeJS     → total: 20    ✅

Tags are stored lowercased on both write paths (normalizeTags() for articles, the hashtag regex in PrismaPostRepository.create for posts), so any filter that is not lowercased matches nothing. A client that carries a tag's display casing into the URL — the trends list renders names as stored, but a hand-written or shared link need not — loses every article on that tag page while the posts beside them still appear.

Fix

  • New normalizeTagFilter() in article-input.ts, next to the write-path normalizeTags(). Unlike that one it never throws: a malformed filter is a filter that matches no article, not a 400. A blank or whitespace-only filter becomes no filter, rather than a lookup for the empty tag.
  • GetArticlesUseCase normalizes once and feeds the same value to both the repository call and the cache key.

Normalization sits in the use case rather than PrismaArticleRepository on purpose: the cache key is built before the repository is reached, so lowercasing at the repository would leave NodeJS and nodejs selecting the same articles under two separate cache entries.

get-my-articles carries no tag filter, so this is the only affected read path.

Tests

Six unit cases added — three on the helper (lowercase/trim, blank → undefined, malformed does not throw) and three on the use case (normalized tag reaches the repository, blank filter is dropped, two casings share one cache key).

pnpm vitest run --config vitest.unit.config.ts tests/unit/core/use-cases/article → 114 passed. Lint, tsc --noEmit and prettier clean. Integration and e2e left to CI.

Not in this PR

The client's /explore?tag=… page queries only /posts?tag=… and never /articles?tag=…, so tagged articles do not show there at all — that is a client-side gap, not an API one. Both endpoints support the filter, and /tags/trends and /tags/search already return postCount and articleCount separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XJ4fR79WAs2pxEYxY9uNY9

Tags are stored lowercased on both write paths, but the article list
passed the query string tag through untouched, so /articles?tag=NodeJS
returned nothing while /posts?tag=NodeJS returned the full page. A
client carrying a tag's display casing into the URL lost every article
on the tag page.

Normalize the filter in the use case rather than the repository so the
cache key is built from the same value: otherwise "NodeJS" and "nodejs"
would select the same articles under two separate cache entries. A blank
filter now means no filter instead of a lookup for the empty tag.

Unlike the write path the filter never throws — a malformed tag matches
no article, which is not a bad request.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJ4fR79WAs2pxEYxY9uNY9
@aquie00t
aquie00t merged commit 8acad07 into main Aug 30, 2026
10 checks passed
@aquie00t
aquie00t deleted the fix/article-tag-filter-casing branch August 30, 2026 04:36
github-actions Bot pushed a commit that referenced this pull request Aug 30, 2026
## [1.12.3](v1.12.2...v1.12.3) (2026-08-30)

### Bug Fixes

* **article:** match the tag filter case-insensitively ([#238](#238)) ([8acad07](8acad07))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.12.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant