Skip to content

Conversation

@sagzy
Copy link
Contributor

@sagzy sagzy commented Jan 12, 2026

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

  • we want to remove the multiple calls to siteService.getSiteByHost and accountService.getDefaultAccountForSite in dispatchers.js, as they create 3 database queries for each dispatcher call
  • instead, we want to load the account data in the Fedify context directly, so that we make a single database query to retrieve host account data per request
  • dispatchers however currently use the Account type, not the Account entity. This change adds the missing fields to Account entity so it can be used by dispatchers code

@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

This PR adds three ActivityPub URL fields—apOutbox, apFollowing, and apLiked—to Account types and workflows. The Account and AccountDraft interfaces and AccountEntity constructor/create/fromDraft flows now include these fields. Database mappings and SQL selects (account and post repositories, HostDataContextLoader) persist and read the new columns. Tests across multiple modules were updated to use a new createTestInternalAccount test helper instead of the draft+create flow; several tests also switched AccountEntity imports to type-only or use shared test fixtures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 'Added more fields to Account Entity' directly and accurately summarizes the main change across the changeset, where ActivityPub fields are added to Account, AccountDraft, and AccountEntity interfaces/classes.
Description check ✅ Passed The description relates to the changeset by explaining the context for adding fields: enabling dispatchers to use Account entity directly to reduce database queries, which aligns with the fields being added throughout the codebase.

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

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79f9f1f and 7ec7734.

📒 Files selected for processing (1)
  • src/account/account.repository.knex.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/account/account.repository.knex.ts
⏰ 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: build / Build Docker Images (linux/amd64, amd64)
  • GitHub Check: Cursor Bugbot

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


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 marked this pull request as ready for review January 14, 2026 09:59
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 (4)
src/account/account.repository.knex.ts (4)

253-268: Missing new fields in SELECT clause.

The getBySite query doesn't select the new fields (ap_outbox_url, ap_following_url, ap_liked_url, ap_public_key, ap_private_key), but mapRowToAccountEntity expects them. This will result in undefined values for these fields on returned entities.

🔧 Proposed fix
             const accountRow = await this.db('accounts')
                 .where('id', user.account_id)
                 .select(
                     'accounts.id',
                     'accounts.uuid',
                     'accounts.username',
                     'accounts.name',
                     'accounts.bio',
                     'accounts.url',
                     'accounts.avatar_url',
                     'accounts.banner_image_url',
                     'accounts.ap_id',
                     'accounts.ap_followers_url',
                     'accounts.ap_inbox_url',
+                    'accounts.ap_outbox_url',
+                    'accounts.ap_following_url',
+                    'accounts.ap_liked_url',
+                    'accounts.ap_public_key',
+                    'accounts.ap_private_key',
                     'accounts.custom_fields',
                 )
                 .first();

282-295: Missing new fields in SELECT clause.

Same issue as getBySite - the getByApId query doesn't select the new fields required by mapRowToAccountEntity.

🔧 Proposed fix
             .select(
                 'accounts.id',
                 'accounts.uuid',
                 'accounts.username',
                 'accounts.name',
                 'accounts.bio',
                 'accounts.url',
                 'accounts.avatar_url',
                 'accounts.banner_image_url',
                 'accounts.ap_id',
                 'accounts.ap_followers_url',
                 'accounts.ap_inbox_url',
+                'accounts.ap_outbox_url',
+                'accounts.ap_following_url',
+                'accounts.ap_liked_url',
+                'accounts.ap_public_key',
+                'accounts.ap_private_key',
                 'accounts.custom_fields',
                 'users.site_id',
             )

310-324: Missing new fields in SELECT clause.

Same issue - getById doesn't select the new fields.

🔧 Proposed fix
             .select(
                 'accounts.id',
                 'accounts.uuid',
                 'accounts.username',
                 'accounts.name',
                 'accounts.bio',
                 'accounts.url',
                 'accounts.avatar_url',
                 'accounts.banner_image_url',
                 'accounts.custom_fields',
                 'accounts.ap_id',
                 'accounts.ap_followers_url',
                 'accounts.ap_inbox_url',
+                'accounts.ap_outbox_url',
+                'accounts.ap_following_url',
+                'accounts.ap_liked_url',
+                'accounts.ap_public_key',
+                'accounts.ap_private_key',
                 'users.site_id',
             )

341-355: Missing new fields in SELECT clause.

Same issue - getByInboxUrl doesn't select the new fields.

🔧 Proposed fix
             .select(
                 'accounts.id',
                 'accounts.uuid',
                 'accounts.username',
                 'accounts.name',
                 'accounts.bio',
                 'accounts.url',
                 'accounts.avatar_url',
                 'accounts.banner_image_url',
                 'accounts.ap_id',
                 'accounts.ap_followers_url',
                 'accounts.ap_inbox_url',
+                'accounts.ap_outbox_url',
+                'accounts.ap_following_url',
+                'accounts.ap_liked_url',
+                'accounts.ap_public_key',
+                'accounts.ap_private_key',
                 'accounts.custom_fields',
                 'users.site_id',
             )
♻️ Duplicate comments (3)
src/account/account.entity.ts (1)

373-375: Consider keeping AccountDraftData unexported.

A past review noted this type may not be used externally. If it's only consumed internally (e.g., by AccountEntity.draft), keeping it unexported reduces the public API surface.

#!/bin/bash
# Check if AccountDraftData is imported/used outside of account.entity.ts
rg -n "AccountDraftData" --type=ts -g '!src/account/account.entity.ts'
src/post/content.unit.test.ts (1)

264-280: Tests don't need to be async.

These test functions are marked async but don't contain any await expressions. The preparer.prepare() call is synchronous. While the beforeEach correctly uses async/await for account creation, the individual test functions don't need to be async.

src/post/post.repository.knex.ts (1)

137-141: Private key loading concern already raised.

The security concern about loading ap_private_key into the entity for all post queries has been raised in the past review comments. As noted there, the risk is accidentally leaking private keys through API responses. Consider whether the private key is actually needed in these post-fetching contexts, or if it could be loaded separately only when required (e.g., in the keypair dispatcher).

🧹 Nitpick comments (1)
src/helpers/activitypub/activity.unit.test.ts (1)

64-101: Consider refactoring remaining tests to use test helpers for consistency.

The buildCreateActivityAndObjectFromPost and buildUpdateActivityAndObjectFromPost test blocks still use the Object.create pattern while buildAnnounceActivityForPost now uses the test helpers. For long-term maintainability and consistency, consider migrating these tests to use createTestInternalAccount/createTestExternalAccount as well.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f9f068c and 3398518.

📒 Files selected for processing (12)
  • src/account/account.entity.ts
  • src/account/account.entity.unit.test.ts
  • src/account/account.repository.knex.ts
  • src/feed/feed-update.service.unit.test.ts
  • src/helpers/activitypub/activity.unit.test.ts
  • src/http/api/feed.unit.test.ts
  • src/http/api/helpers/post.unit.test.ts
  • src/http/api/post.controller.unit.test.ts
  • src/http/host-data-context-loader.ts
  • src/post/content.unit.test.ts
  • src/post/post.repository.knex.ts
  • src/test/account-entity-test-helpers.ts
🧰 Additional context used
📓 Path-based instructions (3)
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/host-data-context-loader.ts
  • src/helpers/activitypub/activity.unit.test.ts
  • src/test/account-entity-test-helpers.ts
  • src/account/account.repository.knex.ts
  • src/post/post.repository.knex.ts
  • src/post/content.unit.test.ts
  • src/http/api/helpers/post.unit.test.ts
  • src/account/account.entity.unit.test.ts
  • src/http/api/post.controller.unit.test.ts
  • src/http/api/feed.unit.test.ts
  • src/account/account.entity.ts
  • src/feed/feed-update.service.unit.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

All unit & integration test files should have the prefix .test.ts

Files:

  • src/helpers/activitypub/activity.unit.test.ts
  • src/post/content.unit.test.ts
  • src/http/api/helpers/post.unit.test.ts
  • src/account/account.entity.unit.test.ts
  • src/http/api/post.controller.unit.test.ts
  • src/http/api/feed.unit.test.ts
  • src/feed/feed-update.service.unit.test.ts
**/*.unit.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Unit test files should use the file extension .unit.test.ts to indicate the test type

Files:

  • src/helpers/activitypub/activity.unit.test.ts
  • src/post/content.unit.test.ts
  • src/http/api/helpers/post.unit.test.ts
  • src/account/account.entity.unit.test.ts
  • src/http/api/post.controller.unit.test.ts
  • src/http/api/feed.unit.test.ts
  • src/feed/feed-update.service.unit.test.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 737
File: migrate/migrations/000054_populate-outboxes-table.up.sql:56-63
Timestamp: 2025-05-27T09:09:22.110Z
Learning: In the outboxes table migration for Ghost ActivityPub, the author_id field always refers to the author of the original post, not the person performing the outbox action. For reposts (outbox_type = 1), author_id should be posts.author_id (original author), while user_id captures who made the repost via the joins through accounts and users tables.
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 602
File: src/http/api/follow.ts:80-88
Timestamp: 2025-05-01T07:06:59.816Z
Learning: In the ActivityPub codebase, Account entities use the `apId` property when referencing ActivityPub IDs, not `ap_id`.
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1329
File: .github/renovate.json5:31-34
Timestamp: 2025-09-25T12:39:45.755Z
Learning: The jobs/update-external-accounts directory was removed from the TryGhost/ActivityPub repository and no longer exists, so Renovate configuration exclusions referencing this path are no longer needed.
📚 Learning: 2025-09-25T12:39:45.755Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1329
File: .github/renovate.json5:31-34
Timestamp: 2025-09-25T12:39:45.755Z
Learning: The jobs/update-external-accounts directory was removed from the TryGhost/ActivityPub repository and no longer exists, so Renovate configuration exclusions referencing this path are no longer needed.

Applied to files:

  • src/helpers/activitypub/activity.unit.test.ts
  • src/post/post.repository.knex.ts
  • src/account/account.entity.ts
📚 Learning: 2025-06-16T15:43:24.787Z
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 858
File: features/support/content.js:24-27
Timestamp: 2025-06-16T15:43:24.787Z
Learning: In features/support/content.js, the publishArticle() and publishNote() helper functions handle endpoints with different return types: the webhook endpoint returns the post object directly, while the note action endpoint returns a wrapper object with a `post` property. Both helpers are designed to normalize these differences by returning the full post object.

Applied to files:

  • src/helpers/activitypub/activity.unit.test.ts
📚 Learning: 2025-04-24T10:41:15.124Z
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 566
File: src/account/account.entity.ts:110-123
Timestamp: 2025-04-24T10:41:15.124Z
Learning: In the Account entity's `updateProfile` method, the current implementation using a helper function that checks for undefined values is type-safe at runtime, even though TypeScript might not perfectly infer this due to the function's return type declaration.

Applied to files:

  • src/test/account-entity-test-helpers.ts
  • src/post/content.unit.test.ts
  • src/http/api/helpers/post.unit.test.ts
  • src/account/account.entity.unit.test.ts
  • src/http/api/post.controller.unit.test.ts
  • src/http/api/feed.unit.test.ts
  • src/feed/feed-update.service.unit.test.ts
📚 Learning: 2025-05-27T09:09:22.110Z
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 737
File: migrate/migrations/000054_populate-outboxes-table.up.sql:56-63
Timestamp: 2025-05-27T09:09:22.110Z
Learning: In the outboxes table migration for Ghost ActivityPub, the author_id field always refers to the author of the original post, not the person performing the outbox action. For reposts (outbox_type = 1), author_id should be posts.author_id (original author), while user_id captures who made the repost via the joins through accounts and users tables.

Applied to files:

  • src/post/post.repository.knex.ts
📚 Learning: 2025-05-01T07:06:59.816Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 602
File: src/http/api/follow.ts:80-88
Timestamp: 2025-05-01T07:06:59.816Z
Learning: In the ActivityPub codebase, Account entities use the `apId` property when referencing ActivityPub IDs, not `ap_id`.

Applied to files:

  • src/post/post.repository.knex.ts
  • src/account/account.entity.ts
📚 Learning: 2025-07-16T05:20:47.031Z
Learnt from: vershwal
Repo: TryGhost/ActivityPub PR: 1050
File: migrate/migrations/000061_add-ghost-ap-post-mappings-table.up.sql:8-10
Timestamp: 2025-07-16T05:20:47.031Z
Learning: In the TryGhost/ActivityPub codebase, the established pattern for large string fields like ap_id is to use hash-based lookups via generated SHA-256 hash columns with UNIQUE constraints. This approach provides fast fixed-size binary lookups instead of slower string comparisons, while the unique constraint on the hash effectively ensures uniqueness of the original string field due to SHA-256's cryptographic properties. This pattern is used in both the posts table and ghost_ap_post_mappings table.

Applied to files:

  • src/post/post.repository.knex.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/**/*.{ts,tsx} : Avoid mutable entities with dirty flags; use immutable entities with domain events instead

Applied to files:

  • src/account/account.entity.unit.test.ts
  • src/feed/feed-update.service.unit.test.ts
🧬 Code graph analysis (9)
src/helpers/activitypub/activity.unit.test.ts (2)
src/post/post.entity.ts (1)
  • Post (143-687)
src/test/account-entity-test-helpers.ts (2)
  • createTestExternalAccount (101-131)
  • createTestInternalAccount (72-99)
src/test/account-entity-test-helpers.ts (1)
src/account/account.entity.ts (2)
  • draft (165-202)
  • AccountEntity (79-322)
src/account/account.repository.knex.ts (2)
src/account/account.entity.ts (2)
  • AccountEntity (79-322)
  • draft (165-202)
src/core/url.ts (1)
  • parseURL (1-7)
src/post/post.repository.knex.ts (1)
src/core/url.ts (1)
  • parseURL (1-7)
src/http/api/helpers/post.unit.test.ts (1)
src/test/account-entity-test-helpers.ts (1)
  • createTestInternalAccount (72-99)
src/account/account.entity.unit.test.ts (2)
src/test/account-entity-test-helpers.ts (1)
  • createTestInternalAccount (72-99)
src/account/account.entity.ts (2)
  • AccountEntity (79-322)
  • draft (165-202)
src/http/api/post.controller.unit.test.ts (1)
src/test/account-entity-test-helpers.ts (1)
  • createTestInternalAccount (72-99)
src/http/api/feed.unit.test.ts (1)
src/test/account-entity-test-helpers.ts (1)
  • createTestInternalAccount (72-99)
src/feed/feed-update.service.unit.test.ts (1)
src/test/account-entity-test-helpers.ts (1)
  • createTestInternalAccount (72-99)
⏰ 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 (31)
src/account/account.entity.ts (4)

3-4: LGTM!

The import of exportJwk from @fedify/fedify is appropriate for serializing CryptoKey objects to JWK format for storage.


30-34: LGTM!

The new ActivityPub fields (apOutbox, apFollowing, apLiked, apPublicKey, apPrivateKey) are well-typed and appropriately nullable where external accounts may not have private keys.


92-96: LGTM!

Constructor parameters correctly extended with the new ActivityPub fields, maintaining consistency with the interface.


135-163: LGTM!

The fromDraft method is correctly converted to async to accommodate the async exportJwk calls. The key serialization pattern using JSON.stringify(await exportJwk(...)) is consistent and properly handles the nullable apPrivateKey.

src/account/account.repository.knex.ts (4)

35-39: LGTM!

The AccountRow interface is correctly extended with the new ActivityPub fields.


64-72: LGTM!

The insert statement correctly includes the new fields and properly serializes the keys using exportJwk.


95-95: LGTM!

Correctly awaiting the now-async AccountEntity.fromDraft method.


385-389: LGTM!

The mapping correctly includes the new fields. However, this depends on the queries actually selecting these columns, which is addressed in the previous comments.

src/test/account-entity-test-helpers.ts (3)

1-1: LGTM!

Import added correctly for key serialization.


88-98: LGTM!

The key serialization pattern using exportJwk is consistent with AccountEntity.fromDraft. This helper effectively reduces boilerplate across tests.


123-130: LGTM!

External accounts correctly serialize only the public key and set apPrivateKey to null, matching the expected behavior that external accounts don't have private keys.

src/http/api/post.controller.unit.test.ts (3)

5-5: LGTM!

Appropriate use of type-only import since AccountEntity is only used as a type annotation in this file.


21-21: LGTM!

Switching to createTestInternalAccount simplifies the test setup by encapsulating the draft creation and key serialization logic.


96-106: LGTM!

Clean refactor using the new async test helper. The beforeEach correctly awaits the async createTestInternalAccount call.

src/feed/feed-update.service.unit.test.ts (4)

5-5: LGTM!

Type-only import is appropriate since AccountEntity is used for type annotations and casting.


18-18: LGTM!

Import of the new test helper aligns with the PR-wide refactoring pattern.


41-50: LGTM!

Test setup correctly uses the async helper and awaits the result.


218-229: LGTM!

Blocked account test correctly uses the async helper for creating test accounts.

src/helpers/activitypub/activity.unit.test.ts (1)

328-364: Good adoption of test helpers for the Announce activity tests.

The refactored setup using createTestExternalAccount and createTestInternalAccount with Post.createFromData is cleaner and aligns with the PR's goal of centralizing test fixture creation. The async beforeEach correctly awaits the helper functions.

src/post/content.unit.test.ts (1)

247-262: Test helper setup is correctly implemented.

The beforeEach properly awaits the async createTestExternalAccount helper and provides appropriate test data. The type-only import of AccountEntity is appropriate since the entity is not directly instantiated here.

src/http/api/feed.unit.test.ts (1)

125-144: Clean migration to test helper pattern.

The refactored beforeEach correctly uses async/await with createTestInternalAccount. The type-only import of AccountEntity is appropriate since the entity is only used for type annotation on line 25.

src/account/account.entity.unit.test.ts (2)

74-104: Appropriate use of lower-level API for testing fromDraft.

These tests correctly use createInternalAccountDraftData and AccountEntity.draft to test the entity creation path directly, rather than using the higher-level createTestInternalAccount helper. This is the right approach for unit testing the fromDraft method itself. The await on AccountEntity.fromDraft correctly handles the now-async method.


516-547: Block/unblock tests correctly create separate account instances.

The tests properly create distinct account entities with different IDs (1 and 2) to test blocking/unblocking another account. This ensures the domain logic is tested correctly.

src/http/host-data-context-loader.ts (3)

44-48: New ActivityPub fields correctly added to the query.

The SQL selection includes all the new ActivityPub-related fields (ap_outbox_url, ap_following_url, ap_liked_url, ap_public_key, ap_private_key) following the existing naming convention.


86-91: Field mapping to createFromRow is consistent.

The new fields are correctly passed to createFromRow with the appropriate snake_case naming convention matching the database columns.


89-90: No action required. The code correctly ensures private key handling aligns with account type expectations. The HostDataContextLoader loads accounts joined with users and sites, which guarantees isInternal = true (computed as site_id !== null). Private keys are present only for these internal accounts, as expected.

src/post/post.repository.knex.ts (3)

68-72: Interface additions are consistent with query selections.

The new fields follow the established naming pattern (prefixing author fields with author_ap_*). The types are appropriate: URLs are string | null and will be parsed with parseURL, while keys use string and string | null.


957-961: Field mapping is consistent with the new interface.

The mapping correctly uses parseURL for URL fields (apOutbox, apFollowing, apLiked) and passes string values directly for keys (apPublicKey, apPrivateKey). This aligns with the AccountEntity.create signature updates.


1053-1057: Outbox query mirrors getByQuery changes.

The same fields are selected in getOutboxForAccount as in getByQuery, maintaining consistency. The same private key concern applies here.

src/http/api/helpers/post.unit.test.ts (2)

5-18: Clean refactoring to use the new test helper.

The migration from manual AccountEntity.draft + AccountEntity.create to createTestInternalAccount reduces test boilerplate and centralizes account creation logic. The function correctly returns the Promise without redundant async/await, and all callers properly await it.


20-32: Tests properly adapted to async account creation.

All test cases correctly use async functions and await createAuthor(), ensuring the Promise resolves before proceeding with assertions.

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

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 is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on February 3

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.

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/http/host-data-context-loader.ts`:
- Around line 84-86: Repository query methods getByApId, getById, and
getByInboxUrl don't select the newly added AP URL columns, so createFromRow /
mapRowToAccountEntity receive nulls; update each method's SELECT clause to
include ap_outbox_url, ap_following_url, and ap_liked_url (the same column names
used when mapping to createFromRow/mapRowToAccountEntity) so those fields are
populated when accounts are fetched.
🧹 Nitpick comments (1)
src/account/account.entity.unit.test.ts (1)

21-785: Consider extracting common test fixtures to reduce duplication.

The test data setup is highly repetitive across tests (same host, URL patterns, etc.). Consider creating a shared default configuration object that tests can spread and override only when needed:

const defaultAccountOverrides = {
    host: new URL('http://example.com'),
    username: 'testuser',
    name: 'Test User',
    bio: 'Test Bio',
    url: new URL('http://example.com/url'),
    avatarUrl: new URL('http://example.com/avatar.png'),
    bannerImageUrl: new URL('http://example.com/banner.png'),
    customFields: null,
};

// Usage in tests:
const account = await createTestInternalAccount(1, {
    ...defaultAccountOverrides,
    name: 'Custom Name', // only override what's relevant
});

This would reduce boilerplate while keeping tests explicit about what matters for each case.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3398518 and 79f9f1f.

📒 Files selected for processing (6)
  • src/account/account.entity.ts
  • src/account/account.entity.unit.test.ts
  • src/account/account.repository.knex.ts
  • src/http/host-data-context-loader.ts
  • src/post/content.unit.test.ts
  • src/post/post.repository.knex.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/account/account.repository.knex.ts
  • src/post/content.unit.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
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/account/account.entity.ts
  • src/account/account.entity.unit.test.ts
  • src/http/host-data-context-loader.ts
  • src/post/post.repository.knex.ts
**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

All unit & integration test files should have the prefix .test.ts

Files:

  • src/account/account.entity.unit.test.ts
**/*.unit.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Unit test files should use the file extension .unit.test.ts to indicate the test type

Files:

  • src/account/account.entity.unit.test.ts
🧠 Learnings (6)
📓 Common learnings
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 602
File: src/http/api/follow.ts:80-88
Timestamp: 2025-05-01T07:06:59.816Z
Learning: In the ActivityPub codebase, Account entities use the `apId` property when referencing ActivityPub IDs, not `ap_id`.
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 737
File: migrate/migrations/000054_populate-outboxes-table.up.sql:56-63
Timestamp: 2025-05-27T09:09:22.110Z
Learning: In the outboxes table migration for Ghost ActivityPub, the author_id field always refers to the author of the original post, not the person performing the outbox action. For reposts (outbox_type = 1), author_id should be posts.author_id (original author), while user_id captures who made the repost via the joins through accounts and users tables.
📚 Learning: 2025-05-01T07:06:59.816Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 602
File: src/http/api/follow.ts:80-88
Timestamp: 2025-05-01T07:06:59.816Z
Learning: In the ActivityPub codebase, Account entities use the `apId` property when referencing ActivityPub IDs, not `ap_id`.

Applied to files:

  • src/account/account.entity.ts
  • src/post/post.repository.knex.ts
📚 Learning: 2025-04-24T10:41:15.124Z
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 566
File: src/account/account.entity.ts:110-123
Timestamp: 2025-04-24T10:41:15.124Z
Learning: In the Account entity's `updateProfile` method, the current implementation using a helper function that checks for undefined values is type-safe at runtime, even though TypeScript might not perfectly infer this due to the function's return type declaration.

Applied to files:

  • src/account/account.entity.ts
  • src/account/account.entity.unit.test.ts
📚 Learning: 2025-05-27T09:09:22.110Z
Learnt from: allouis
Repo: TryGhost/ActivityPub PR: 737
File: migrate/migrations/000054_populate-outboxes-table.up.sql:56-63
Timestamp: 2025-05-27T09:09:22.110Z
Learning: In the outboxes table migration for Ghost ActivityPub, the author_id field always refers to the author of the original post, not the person performing the outbox action. For reposts (outbox_type = 1), author_id should be posts.author_id (original author), while user_id captures who made the repost via the joins through accounts and users tables.

Applied to files:

  • src/post/post.repository.knex.ts
📚 Learning: 2025-07-16T05:20:47.031Z
Learnt from: vershwal
Repo: TryGhost/ActivityPub PR: 1050
File: migrate/migrations/000061_add-ghost-ap-post-mappings-table.up.sql:8-10
Timestamp: 2025-07-16T05:20:47.031Z
Learning: In the TryGhost/ActivityPub codebase, the established pattern for large string fields like ap_id is to use hash-based lookups via generated SHA-256 hash columns with UNIQUE constraints. This approach provides fast fixed-size binary lookups instead of slower string comparisons, while the unique constraint on the hash effectively ensures uniqueness of the original string field due to SHA-256's cryptographic properties. This pattern is used in both the posts table and ghost_ap_post_mappings table.

Applied to files:

  • src/post/post.repository.knex.ts
📚 Learning: 2025-09-25T12:39:45.755Z
Learnt from: mike182uk
Repo: TryGhost/ActivityPub PR: 1329
File: .github/renovate.json5:31-34
Timestamp: 2025-09-25T12:39:45.755Z
Learning: The jobs/update-external-accounts directory was removed from the TryGhost/ActivityPub repository and no longer exists, so Renovate configuration exclusions referencing this path are no longer needed.

Applied to files:

  • src/post/post.repository.knex.ts
🧬 Code graph analysis (2)
src/account/account.entity.unit.test.ts (1)
src/test/account-entity-test-helpers.ts (1)
  • createTestInternalAccount (70-90)
src/post/post.repository.knex.ts (1)
src/core/url.ts (1)
  • parseURL (1-7)
⏰ 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 (21)
src/account/account.entity.unit.test.ts (9)

16-19: LGTM!

The import structure is clean, correctly importing both helpers: createInternalAccountDraftData for tests that specifically verify draft behavior, and createTestInternalAccount for all other test cases.


22-72: LGTM!

The refactored tests correctly use async/await with createTestInternalAccount and maintain the same test coverage for apId and apFollowers URL generation.


74-147: LGTM!

The fromDraft tests appropriately continue using the draft-based approach (createInternalAccountDraftDataAccountEntity.draftAccountEntity.fromDraft) since they specifically verify that workflow. This is the correct design choice.


149-193: LGTM!

Tests for getApIdForPost correctly verify URL generation for both Article and Note post types using the new helper.


195-493: LGTM!

The updateProfile tests provide comprehensive coverage including individual property updates, multiple simultaneous updates, null value handling, and proper event emission verification. The refactoring to use createTestInternalAccount is cleanly applied.


496-600: LGTM!

Block and unblock tests correctly verify self-operation prevention (empty events) and proper event emission for cross-account operations. The two-account setup with distinct IDs is correctly implemented.


602-656: LGTM!

Domain block and unblock tests correctly verify event emission with proper domain URL and account ID verification through the event getters.


658-762: LGTM!

Follow and unfollow tests mirror the block/unblock pattern correctly, verifying self-operation prevention and proper event emission for cross-account operations.


764-785: LGTM!

The readAllNotifications test correctly verifies event emission with the expected account ID.

src/account/account.entity.ts (7)

28-30: LGTM! New ActivityPub fields added consistently to Account interface.

The three new fields (apOutbox, apFollowing, apLiked) follow the existing pattern for nullable URL fields like apFollowers and apInbox.


50-69: LGTM! AccountDraft updated with new AP fields.

The draft interface correctly includes all three new fields with the appropriate URL | null type.


76-94: LGTM! Constructor updated with new fields.

The immutable entity pattern is maintained with the three new readonly properties.


105-125: LGTM! Static create method propagates new fields correctly.

The create method properly passes through the new AP fields from the data object to the constructor.


127-148: LGTM! fromDraft method correctly initializes new fields from draft.

The draft-to-entity construction path properly maps the new AP fields.


150-187: LGTM! Draft computation logic follows established URL patterns.

The URL construction for internal accounts (/.ghost/activitypub/outbox/index, etc.) is consistent with existing endpoints like apFollowers and apInbox. External accounts correctly pass through the provided URLs.


339-358: LGTM! ExternalAccountDraftData type updated with new fields.

The type correctly requires the new AP URL fields for external accounts.

src/http/host-data-context-loader.ts (1)

44-46: LGTM! Database query extended to select new AP URL fields.

The new columns (account_ap_outbox_url, account_ap_following_url, account_ap_liked_url) follow the existing naming convention.

src/post/post.repository.knex.ts (4)

68-70: LGTM! PostRow interface extended with new author AP URL fields.

The interface correctly adds the three new string | null fields matching the database column types.


135-137: LGTM! Query selects new AP URL columns.

The getByQuery method correctly selects the new account AP URL fields with appropriate aliases.


940-957: LGTM! AccountEntity creation includes new AP URL fields.

The mapping correctly uses parseURL for the new fields, consistent with the existing pattern for apFollowers and apInbox. The parseURL utility safely handles null values.


1047-1049: LGTM! Outbox query selects new AP URL columns.

The getOutboxForAccount method correctly includes the new author AP URL fields in its select list, consistent with the getByQuery method.

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

@sagzy sagzy merged commit 0968b1f into main Jan 15, 2026
12 checks passed
@sagzy sagzy deleted the add-missing-fields-to-account-entity branch January 15, 2026 09:48
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