Skip to content

perf(profile): resolve follower list targets without loading the full profile - #215

Merged
aquie00t merged 1 commit into
mainfrom
perf/follow-list-target-lookup
Aug 25, 2026
Merged

perf(profile): resolve follower list targets without loading the full profile#215
aquie00t merged 1 commit into
mainfrom
perf/follow-list-target-lookup

Conversation

@aquie00t

Copy link
Copy Markdown
Collaborator

What does this PR do?

GET /profiles/:username/followers and /following called GetProfileUseCase purely to turn a username into a user id:

const { profile } = await this.getProfileUseCase.execute(username);
const followers = await this.getFollowersUseCase.execute({
    targetId: profile.userId, ...
});

That use case also counts the user's posts — and after #214 it counts their published articles too. So every page of a follower list ran two aggregate queries whose results were discarded.

Username resolution moves into GetFollowersUseCase / GetFollowingUseCase, which now take a username and resolve it through IProfileRepository. Two fewer queries per page, and the use cases become self-contained rather than depending on the controller to have done a lookup first.

The contract this could have broken silently

An unknown username currently returns 404, because GetProfileUseCase throws NotFoundError. Move the lookup without moving the error and the endpoint quietly starts answering an empty 200 instead — a wrong answer, not a failure, and nothing in the suite would have noticed.

The existing e2e covers item shape, pagination and the empty-list case but not the unknown-username 404. It does now, on both endpoints, and there are unit tests asserting the use cases throw NotFoundError and never reach the follow repository.

Verification

lint, format:check, build clean; 759 unit tests pass.

New unit tests: the username is resolved before listing and the resolved id is what reaches getFollowers/getFollowing; an unknown username throws NotFoundError and short-circuits.

While updating those tests I also fixed a pre-existing type error in get-following.usecase.test.ts — its baseInput was missing the required currentUserId. It ran fine because vitest strips types without checking; see the note below.

The response envelope { data, meta: { limit, offset, count } } is byte-identical. No DI change — profileRepository is already registered and awilix CLASSIC resolves it by name.

Note

Third of three, and the optional one. #213 (list payload) and #214 (articleCount) are both merged-ready and independent of this.

Note

Separate finding worth its own issue: test files are not type-checked anywhere. pnpm build uses tsconfig.build.json, which excludes tests/; vitest strips types via esbuild without checking; eslint ignores tests/**. Running tsc -p tsconfig.json --noEmit surfaces 7 files with type errors that all pass at runtime — some predating the article work, some introduced by it. A typecheck script wired into the Quality Checks job would close that gap.


Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Chore

Checklist

  • My branch follows the naming convention (feature/, fix/, chore/, docs/)
  • My commits follow Conventional Commits
  • I have tested my changes locally
  • I have not introduced any breaking changes
  • I have updated relevant documentation if needed

🤖 Generated with Claude Code

… profile

GET /profiles/:username/followers and /following called GetProfileUseCase
purely to turn a username into a user id. That use case also counts the
user's posts, and after the articleCount change it counts their published
articles too - so every page of a follower list ran two aggregate queries
whose results were thrown away.

Username resolution moves into GetFollowersUseCase and GetFollowingUseCase,
which now take a username and resolve it through the profile repository. The
HTTP contract is unchanged, including the 404 for an unknown username: the
use cases throw the same NotFoundError the controller's profile lookup used
to raise. Without that, the endpoint would have started answering an empty
200 instead.

The existing e2e suite covered shape, pagination and the empty case but not
the unknown-username 404, which is precisely the contract this change could
have broken silently. It does now.

The controller keeps getProfileUseCase - getProfile still uses it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@aquie00t
aquie00t merged commit b855269 into main Aug 25, 2026
10 checks passed
@aquie00t
aquie00t deleted the perf/follow-list-target-lookup branch August 25, 2026 05:13
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))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.7.0 🎉

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