Skip to content

Conversation

@sagzy
Copy link
Contributor

@sagzy sagzy commented Jan 12, 2026

ref https://linear.app/ghost/issue/BER-3012

  • in dispatchers.ts, we currently make 3 database query to load the host site, account and user
  • however, in most cases, this host data already exists in the Hono request context, via the host-data-context middleware: https://github.com/TryGhost/ActivityPub/blob/main/src/http/middleware/host-data-context.ts
  • with this change, we pass the host data to the Fedify context instead of making additional database queries in dispatchers
  • we have also added the host data database query to ensureCorrectContext to make sure the host data is available to all dispatchers code
  • the one exception is keypairDispatcher, for which we still do an additional database query. The reason is that we don't want to expose the public/private key pair to the Account entity, as there is a risk we might inadvertently expose the private key

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds account keypair retrieval: KnexAccountRepository.getKeyPair and AccountService.getKeyPair (returns Result with errors for missing or non-internal accounts) and integration tests for those cases. Extends ContextData with hostSite and hostAccount, updates middleware, Fedify context creation, API controllers, pubsub handler, and many dispatcher factories/handlers to consume hostSite/hostAccount from context instead of SiteService-based lookups. Adjusts dispatcher function signatures and unit tests accordingly. Also adds pubsub test helpers and docker env vars for the emulator.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: removing unnecessary database queries in dispatchers by passing host data through context instead of querying.
Description check ✅ Passed The description clearly explains the motivation, changes made, and the rationale for the exception to the general pattern (keypairDispatcher).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sagzy sagzy force-pushed the remove-account-site-queries-in-dispatchers branch 2 times, most recently from 694e215 to 67a200d Compare January 12, 2026 11:54
@sagzy sagzy marked this pull request as ready for review January 13, 2026 14:24
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@sagzy sagzy force-pushed the remove-account-site-queries-in-dispatchers branch 3 times, most recently from dfeac87 to f5bb0c6 Compare January 13, 2026 16:23
@sagzy sagzy force-pushed the remove-account-site-queries-in-dispatchers branch 2 times, most recently from c65b17f to 16ddfdf Compare January 14, 2026 21:41
@sagzy sagzy force-pushed the remove-account-site-queries-in-dispatchers branch from 16ddfdf to af965d8 Compare January 14, 2026 22:02
Base automatically changed from add-missing-fields-to-account-entity to main January 15, 2026 09:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/dispatchers.ts`:
- Around line 599-602: The code in createAnnounceHandler fetches
ctx.data.hostSite into hostSite and throws if missing, but hostSite is never
used; remove the unused retrieval and validation by deleting the const hostSite
= ctx.data.hostSite; and the if (!hostSite) { throw new Error(`Site not found
for host: ${ctx.host`); } lines from createAnnounceHandler so the function no
longer performs this redundant check.
♻️ Duplicate comments (1)
src/app.ts (1)

256-267: Consider logging when host data loading fails.

When loadDataForHost returns an error, the code silently continues without setting hostSite or hostAccount. This could make debugging difficult if dispatchers later fail due to missing host data.

Proposed logging addition
         if (!isError(result)) {
             const { site, account } = getValue(result);
             ctx.data.hostSite = site;
             ctx.data.hostAccount = account;
+        } else {
+            ctx.data.logger.warn(
+                'Failed to load host data for {host}: {error}',
+                { host: ctx.host, error: getError(result) },
+            );
         }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f10b87 and 40ddf7a.

📒 Files selected for processing (8)
  • src/app.ts
  • src/dispatchers.ts
  • src/dispatchers.unit.test.ts
  • src/events/pubsub-http.ts
  • src/helpers/fedify.ts
  • src/http/api/follow.controller.ts
  • src/http/api/like.controller.ts
  • src/http/api/post.controller.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/http/api/like.controller.ts
  • src/dispatchers.unit.test.ts
  • src/http/api/post.controller.ts
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{ts,tsx}: Never use direct string comparisons for ActivityPub IDs; use SHA256 hash lookups instead - see ADR-0009
Always use the Result helper functions (isError, getError, getValue) with Result types instead of destructuring directly
Dependency injection parameter names must match registration names in Awilix CLASSIC injection mode
Routes should be defined using decorators (@APIRoute, @RequireRoles) rather than direct registration
Prefer class-based architecture with dependency injection over function factories for handlers
Repositories should not be used directly; they should be used through services
Controllers should be lean and delegate to services where appropriate
Views can talk directly to the database if necessary but should not be responsible for any business logic
Prefer immutable entities that generate domain events over mutable entities with dirty flags
Prefer error objects with context over string literal errors in Result types
Avoid mutable entities with dirty flags; use immutable entities with domain events instead

Files:

  • src/http/api/follow.controller.ts
  • src/events/pubsub-http.ts
  • src/helpers/fedify.ts
  • src/app.ts
  • src/dispatchers.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 1010
File: src/app.ts:744-744
Timestamp: 2025-07-09T06:47:50.064Z
Learning: In the TryGhost/ActivityPub repository, allouis prefers to keep refactoring PRs focused on code restructuring only, without adding new functionality or changing behavior. When moving existing code that has issues, the issues should be preserved in the refactoring and addressed separately.
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1136
File: src/instrumentation.ts:36-48
Timestamp: 2025-07-31T16:35:35.813Z
Learning: In the TryGhost/ActivityPub repository, the SQL queries captured by extractQueryInfoFromError from Knex's query-error event are already normalized with placeholders, so additional SQL normalization is not needed for Sentry fingerprinting.
📚 Learning: 2025-08-21T13:32:58.582Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1223
File: src/http/api/bluesky.controller.ts:0-0
Timestamp: 2025-08-21T13:32:58.582Z
Learning: In the Ghost ActivityPub codebase, `ctx.get('account')` in controller methods should always return an account because the middleware guarantees this. Guards for missing account are unnecessary in this system.

Applied to files:

  • src/http/api/follow.controller.ts
📚 Learning: 2025-11-25T10:54:15.904Z
Learnt from: CR
Repo: TryGhost/ActivityPub PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T10:54:15.904Z
Learning: Applies to src/activity-handlers/**/*.{ts,tsx} : Legacy code in `dispatchers.ts` should not be used as a reference; new handlers should follow the class-based pattern in `/activity-handlers/` - see ADR-0006

Applied to files:

  • src/dispatchers.ts
📚 Learning: 2025-06-16T07:48:37.253Z
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 851
File: src/http/api/views/reply.chain.view.ts:89-94
Timestamp: 2025-06-16T07:48:37.253Z
Learning: In the ActivityPub codebase, the team prefers to let the code throw errors for invalid data (like malformed URLs) rather than adding defensive handling, as it indicates the system is in an undefined state that should be addressed at the source.

Applied to files:

  • src/dispatchers.ts
📚 Learning: 2025-07-31T16:35:35.813Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1136
File: src/instrumentation.ts:36-48
Timestamp: 2025-07-31T16:35:35.813Z
Learning: In the TryGhost/ActivityPub repository, the SQL queries captured by extractQueryInfoFromError from Knex's query-error event are already normalized with placeholders, so additional SQL normalization is not needed for Sentry fingerprinting.

Applied to files:

  • src/dispatchers.ts
🧬 Code graph analysis (1)
src/app.ts (4)
src/site/site.service.ts (1)
  • Site (9-14)
src/http/host-data-context-loader.ts (1)
  • HostDataContextLoader (9-96)
src/core/result.ts (2)
  • isError (22-24)
  • getValue (26-28)
src/activitypub/fedify-context.factory.ts (1)
  • FedifyContextFactory (5-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: build / Build Docker Images (linux/amd64, amd64)
🔇 Additional comments (14)
src/app.ts (3)

172-177: LGTM!

The ContextData type extension is well-designed. Making hostSite and hostAccount nullable allows for contexts where host data isn't available (like queue processing), while enabling the optimization in request contexts.


778-795: LGTM!

This middleware correctly propagates the host data already loaded by createHostDataContextMiddleware into the Fedify context. The ordering ensures site and account are available on the Hono context before this middleware runs.


908-923: LGTM!

The federation wiring correctly passes hostSite and hostAccount from the Hono context into the Fedify ContextData, completing the data flow for federation requests.

src/http/api/follow.controller.ts (2)

33-38: LGTM!

The Fedify context creation correctly includes hostSite and hostAccount, consistent with the broader refactoring to pass host data through context rather than querying the database in dispatchers.


152-157: LGTM!

Consistent with the changes in handleFollow.

src/helpers/fedify.ts (1)

5-19: LGTM!

Simplifying to pass ctxData directly is cleaner since the ContextData type now includes all required fields (globaldb, logger, hostSite, hostAccount).

src/events/pubsub-http.ts (1)

56-68: LGTM!

The explicit null values for hostSite and hostAccount with the explanatory comment are a good approach. This documents that host data could be loaded here if needed in the future, while avoiding unnecessary DB queries for current use cases.

src/dispatchers.ts (7)

44-83: LGTM!

The actorDispatcher correctly retrieves hostAccount from context data and properly handles nullable URL fields with conditional checks, avoiding the previous new URL('') crash risk.


85-134: LGTM!

The keypairDispatcher correctly uses !hostAccount to handle both null and undefined cases. The intentional DB query for key pairs (via accountService.getKeyPair) aligns with the PR objective of not exposing private keys on the Account entity. The exhaustive error handling is well-implemented.


214-230: LGTM!

The handleAnnouncedCreate function correctly retrieves hostSite from context data and uses it for the follower check. Throwing an error when hostSite is missing is appropriate since the function cannot proceed without it.


784-851: LGTM!

Both createFollowersDispatcher and createFollowingDispatcher correctly retrieve host data from context and use hostAccount.id for their respective queries. The consistent error handling pattern is appropriate.


853-889: LGTM!

Both counters consistently retrieve host data from context and use hostAccount.id for counting operations.


895-944: LGTM!

The createOutboxDispatcher is well-refactored: the factory now only requires postService, with host data derived from context. Both hostAccount.id and the full hostAccount object are correctly used for their respective purposes.


946-960: LGTM!

Consistent with the other counter implementations.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
features/step_definitions/index.js (1)

99-109: Fix the duplicate await keyword.

Line 103 has await await Promise.all([...]) which contains a redundant await. While this doesn't cause a runtime error (the second await just awaits the resolved value), it's unnecessary and likely a typo.

🐛 Proposed fix
 Before(async function reset() {
     await resetWiremock();
     await purgeSubscriptions();
     await resetDatabase();
-    await await Promise.all([
+    await Promise.all([
         setupSelfSite(),
         fetchActivityPub('https://alice.test/.ghost/activitypub/v1/site'),
         fetchActivityPub('https://bob.test/.ghost/activitypub/v1/site'),
         fetchActivityPub('https://charlie.test/.ghost/activitypub/v1/site'),
     ]);
 });
🧹 Nitpick comments (1)
features/support/pubsub.js (1)

6-10: Consider filtering out undefined subscription names.

If any of the environment variables are not set, the SUBSCRIPTIONS array will contain undefined values. While this is handled by the catch block, filtering them out explicitly would be cleaner and avoid unnecessary API calls.

♻️ Suggested improvement
 const SUBSCRIPTIONS = [
     process.env.MQ_PUBSUB_SUBSCRIPTION_NAME,
     process.env.MQ_PUBSUB_RETRY_SUBSCRIPTION_NAME,
     process.env.MQ_PUBSUB_GHOST_SUBSCRIPTION_NAME,
-];
+].filter(Boolean);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05fd29e and b869c28.

📒 Files selected for processing (3)
  • docker-compose.yml
  • features/step_definitions/index.js
  • features/support/pubsub.js
🧰 Additional context used
📓 Path-based instructions (1)
features/step_definitions/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

Step definitions should be grouped together by the high-level feature they are testing, not necessarily in 1-to-1 mapping with feature files

Files:

  • features/step_definitions/index.js
🧠 Learnings (3)
📓 Common learnings
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1136
File: src/instrumentation.ts:36-48
Timestamp: 2025-07-31T16:35:35.813Z
Learning: In the TryGhost/ActivityPub repository, the SQL queries captured by extractQueryInfoFromError from Knex's query-error event are already normalized with placeholders, so additional SQL normalization is not needed for Sentry fingerprinting.
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 1010
File: src/app.ts:744-744
Timestamp: 2025-07-09T06:47:50.064Z
Learning: In the TryGhost/ActivityPub repository, allouis prefers to keep refactoring PRs focused on code restructuring only, without adding new functionality or changing behavior. When moving existing code that has issues, the issues should be preserved in the refactoring and addressed separately.
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1136
File: src/instrumentation.ts:36-48
Timestamp: 2025-07-31T16:35:35.813Z
Learning: In the TryGhost/ActivityPub repository, the SQL queries captured by extractQueryInfoFromError from Knex's query-error event are already normalized with placeholders (?), so additional SQL normalization is not needed for Sentry fingerprinting. The actual parameter values are stored separately in the bindings array.
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1223
File: src/http/api/bluesky.controller.ts:0-0
Timestamp: 2025-08-21T13:32:58.582Z
Learning: In the Ghost ActivityPub codebase, `ctx.get('account')` in controller methods should always return an account because the middleware guarantees this. Guards for missing account are unnecessary in this system.
📚 Learning: 2025-06-10T10:07:58.527Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 811
File: src/app.ts:249-250
Timestamp: 2025-06-10T10:07:58.527Z
Learning: The MQ_PUBSUB_GHOST_TOPIC_NAME and MQ_PUBSUB_GHOST_SUBSCRIPTION_NAME environment variables were temporarily removed from validation checks in src/app.ts to allow the app to boot, but they are planned to be re-introduced later. The docker-compose.yml entries for these variables should be preserved for future use.

Applied to files:

  • features/support/pubsub.js
  • docker-compose.yml
📚 Learning: 2025-06-11T12:57:36.707Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 826
File: src/app.ts:228-234
Timestamp: 2025-06-11T12:57:36.707Z
Learning: The ActivityPub project prefers to use default placeholder values for missing environment variables (e.g., MQ_PUBSUB_*), relying on runtime errors rather than explicit fail-fast checks during application startup.

Applied to files:

  • docker-compose.yml
🧬 Code graph analysis (1)
features/step_definitions/index.js (1)
features/support/pubsub.js (1)
  • purgeSubscriptions (32-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (4)
docker-compose.yml (1)

210-214: LGTM!

The environment variables are correctly configured and consistent with the pubsub-testing service definitions (lines 256-265). The PUBSUB_EMULATOR_HOST format is appropriate for the @google-cloud/pubsub client's emulator mode.

features/support/pubsub.js (1)

32-46: LGTM!

The seek-to-future-timestamp pattern is a valid approach for purging Pub/Sub messages in test environments. The parallel execution and error suppression are appropriate for a test utility where subscriptions may not always exist.

features/step_definitions/index.js (2)

1-7: LGTM!

The imports for After and purgeSubscriptions are correctly added to support the new test lifecycle hooks.

Also applies to: 17-17


111-113: LGTM!

The After hook correctly purges subscriptions after each test scenario, ensuring test isolation by cleaning up any messages generated during the test. This complements the Before hook purge for robust message queue cleanup.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants