fix(article): match the tag filter case-insensitively - #238
Merged
Conversation
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
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))
|
🎉 This PR is included in version 1.12.3 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GET /api/v1/articles?tag=…matched the tag exactly, whileGET /api/v1/posts?tag=…lowercased it first. Verified against production:Tags are stored lowercased on both write paths (
normalizeTags()for articles, the hashtag regex inPrismaPostRepository.createfor 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
normalizeTagFilter()inarticle-input.ts, next to the write-pathnormalizeTags(). 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.GetArticlesUseCasenormalizes once and feeds the same value to both the repository call and the cache key.Normalization sits in the use case rather than
PrismaArticleRepositoryon purpose: the cache key is built before the repository is reached, so lowercasing at the repository would leaveNodeJSandnodejsselecting the same articles under two separate cache entries.get-my-articlescarries 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 --noEmitand 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/trendsand/tags/searchalready returnpostCountandarticleCountseparately.🤖 Generated with Claude Code
https://claude.ai/code/session_01XJ4fR79WAs2pxEYxY9uNY9