feat(profile): expose published articleCount on the profile response - #214
Merged
Conversation
The profile carried followersCount, followingCount and postCount but said nothing about articles, even though the article feature has been merged for several releases. The count is published-only, and identical for every viewer including the owner. A total that included drafts would leak both the existence and the volume of unpublished work, and a viewer-dependent number would be unstable and would fork a code path that is otherwise the same for everyone. An author's own draft count is the meta of GET /articles/me?status=DRAFT. countPublishedByAuthorId is a single count query covered exactly by the existing (author_id, status) index, rather than reusing findAll with limit 1 and reading total - that would run a findMany with the full relation include to produce a number. It joins the Promise.all that already fetches the follow status and the post count, so the profile still makes one round of parallel queries. Note for review: adding a required property to ProfileItemSchema is not type-checked anywhere. Controller methods take a bare FastifyReply, so send() is never compared against the route's response generic, and fast-json-stringify fails serialization on a missing required property. The e2e test is the only thing standing between that and a 500, which is why this change adds four. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 tasks
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 25, 2026
# [1.7.0](v1.6.0...v1.7.0) (2026-08-25) ### Features * **article:** drop the markdown body from list responses ([#213](#213)) ([628d897](628d897)) * **profile:** expose published articleCount on the profile response ([#214](#214)) ([1b030bf](1b030bf)) ### Performance Improvements * **profile:** resolve follower list targets without loading the full profile ([#215](#215)) ([b855269](b855269))
|
🎉 This PR is included in version 1.7.0 🎉 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.
What does this PR do?
The profile carried
followersCount,followingCountandpostCountbut said nothing about articles, even though the article feature has been merged for several releases.GET /api/v1/profiles/:usernamenow also returnsarticleCount.Published-only, and the same for everyone
The count excludes drafts and archived articles — including when the viewer is the owner. Two reasons:
An author's own draft count is available as the
meta.totalofGET /articles/me?status=DRAFT.E2E pins all of this: a draft does not move the number, publishing does, archiving moves it back down, and owner / stranger / guest all see the same value.
The query
countPublishedByAuthorIdis a singleprisma.article.count({ where: { authorId, status: PUBLISHED } }), covered exactly by the existing@@index([authorId, status]). It joins thePromise.allthat already fetches the follow status and the post count, so the profile still makes one round of parallel queries.Deliberately not
findAll({ authorId, limit: 1 })readingtotal— that would run afindManywith the full relation include just to produce a number.Note for review: this class of change is not type-checked
Adding a required property to
ProfileItemSchemawithout populating it in the controller makes fast-json-stringify fail serialization → 500. TypeScript does not catch it: controller methods take a bareFastifyReply, sosend()is never compared against the route's response generic.The e2e test is the only thing between that and a production 500, which is why this PR adds four of them.
Verification
lint,format:check,buildclean; 759 unit tests pass (755 + 4 new).articleCountcomes from the repository, is called withprofile.userId, is identical for owner/stranger/guest, is reported alongsidepostCountrather than instead of it, and is not queried at all when the profile does not exist.countPublishedByAuthorIdcounts published only, ignores drafts, drops back when an article is archived, ignores another author's articles, and returns 0 for an unknown id.No DI, Cradle, route or
app.tschange:GetProfileUseCaseisasClass(...).singleton()and awilix CLASSIC resolves the newarticleRepositoryparameter by name from the key already registered inpersistence.di.ts.Client contract for the profile Articles tab
No new endpoint. The existing filter already does this job, with cache invalidation wired into publish/archive/update/delete:
GET /api/v1/articles?authorUsername=<username>&page=&limit=GET /api/v1/articles/me?status=DRAFTSecond of three. Stacks on #213, which slims the list payload those tabs will be reading.
Type of Change
Checklist
feature/,fix/,chore/,docs/)🤖 Generated with Claude Code