Skip to content

Refactored account retrieval to use AccountView#505

Merged
mike182uk merged 15 commits intomainfrom
refactor-get-account-to-use-view
Apr 23, 2025
Merged

Refactored account retrieval to use AccountView#505
mike182uk merged 15 commits intomainfrom
refactor-get-account-to-use-view

Conversation

@mike182uk
Copy link
Member

no ref

Refactored account retrieval to use AccountView

@coderabbitai
Copy link

coderabbitai bot commented Apr 9, 2025

"""

Walkthrough

The changes update several components related to account handling and lookup processes. The expected HTTP status for non-existent accounts is corrected from 500 to 404, and an assertion ensuring that error codes are below 500 has been added in the test step definitions. The logic for retrieving account details is refactored to use a newly introduced AccountView class, replacing the previous accountService in both the route handler and API endpoint. The AccountView class consolidates methods for fetching account data by internal ID, handle, or ActivityPub ID, integrating both local database and Fediverse remote lookups with simplified control flow and error handling. Additionally, the lookupAPIdByHandle helper function now accepts an options parameter to conditionally allow private address lookups based on environment variables, with corresponding updates to its unit tests. New snapshot files and integration tests for AccountView have been added to verify expected output and behavior.

Possibly related PRs

  • Added local lookup for Ghost sites in the profile view #470: The main PR refactors the account retrieval logic by replacing the previous service-based handler with a new AccountView abstraction and simplifies the createGetAccountHandler function, while the retrieved PR introduces the initial local lookup for Ghost sites in the profile view and removes the profile endpoint in favor of the account endpoint; both PRs modify the same createGetAccountHandler function and related account retrieval code, indicating a direct and strong connection at the code level.

Suggested labels

ricardo.ghost.is, princi-vershwal-12.ghost.is, main.ghost.is
"""

✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Collaborator

@allouis allouis left a comment

Choose a reason for hiding this comment

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

Overall structure good, except I think the ViewContext should change from Site -> Account

* Create a handler to handle a request for an account
*
* @param accountService Account service instance
* @param db Database client
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these param comments useful to you?

Copy link
Member Author

Choose a reason for hiding this comment

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

not particularly no, just habit and i get a nice typescript description. Maybe we only include them when extra context is needed? (i.e @param db Database client that can only read and not write)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I agree - that's a nice rule

}

// Return response
return new Response(JSON.stringify(accountDto), {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't check for the accountDto being null here - I think we wanna 404 in that case probs

Copy link
Member Author

Choose a reason for hiding this comment

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

updated in e67df95

* and determine if that account is following / followed by the account
* being viewed
*/
site?: Site;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we want context to be an Account rather than Site - otherwise we're leaking the idea of the default account for a site into this view.

If we pass in an account rather than a site, then when we eventually support multiple accounts for a site, the controller can just pass in like ctx.get('authenticateAccount') or something like this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeh i went with this so we don't have to double query the default account - If you are requesting your own profile, we cant just return this account, we still need to go via the view to then get the rest of the data needed (so 2 queries for similar data). Maybe not a big deal?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah I see - that's a good point - but I do think we're going to want to be attaching the current account to the context pretty soon, as we have calls to getDefaultAccount everywhere and it would be nice to move that to middleware so we have an easy time migrating to multiple accounts.

Copy link
Member Author

Choose a reason for hiding this comment

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

updated in 731f134

Comment on lines +222 to +230
const actor = await lookupObject(fedifyContext, apId);

if (actor === null) {
throw new Error(`Could not find Actor ${apId}`);
}

if (!isActor(actor)) {
return null;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this might just be a refactor/following some existing pattern - but I still think it's good to confirm, and change if we need to.

Why do we throw if actor is null, but we return null if it's not an actor? In this context I think I would expect a 404 in both cases 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeh that was existing code, but I agree, in both cases it should 404

Copy link
Member Author

@mike182uk mike182uk Apr 10, 2025

Choose a reason for hiding this comment

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

updated in ae8be27

accountRepository: KnexAccountRepository,
fedify: Federation<ContextData>,
db: Knex,
fedifyContextFactory: FedifyContextFactory,
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we are passing db and fedifyContextFactory to this controller and then passing these two to the AccountView?
Instead of passing db and fedifyContextFactory to the controller and then into AccountView, can we just create the AccountView instance in app.ts and inject it into the controller?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm, i guess i saw the view as something tied closely to the API request, so the controller has control over how this is constructed. Do you think it should just be passed in? cc @allouis

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, my thinking was — since we’re not using db or fedifyContextFactory directly in the controller, it felt a bit cleaner to move their usage up into app.ts and just pass in the AccountView.
But open to thoughts — happy to go either way depending on how we see it fitting into the overall structure.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree with @vershwal here, keeping the DI in app.ts makes the signature simpler, and doesn't expose details to the controller - also should be easier to unit test?

Copy link
Member Author

Choose a reason for hiding this comment

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

🫡

updated in b153afa

no ref

Refactored account retrieval to use `AccountView`
@mike182uk mike182uk force-pushed the refactor-get-account-to-use-view branch from 419d03d to aebe9fc Compare April 10, 2025 13:56
@mike182uk mike182uk force-pushed the refactor-get-account-to-use-view branch from ae8be27 to fe3d513 Compare April 10, 2025 15:18

Scenario: Get non-existent account
When an authenticated "get" request is made to "/.ghost/activitypub/account/@nonexistent@fake-external-activitypub"
Then the request is rejected with a 500
Copy link
Collaborator

@allouis allouis Apr 14, 2025

Choose a reason for hiding this comment

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

😬 Yeah I think we should never have an explicit 500 check here.

I think we should update that stepdef to throw an error if a 5xx error is passed as the expected status code - what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeh I don't think it makes sense to expect an internal server error 😅 I'll add a check into the stepdef 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

updated in 0a05e1b

@mike182uk mike182uk force-pushed the refactor-get-account-to-use-view branch from 7475290 to 4e57973 Compare April 14, 2025 15:17
@mike182uk mike182uk marked this pull request as ready for review April 14, 2025 16:03
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

🧹 Nitpick comments (4)
src/lookup-helpers.ts (1)

75-79: Consider a more robust check for environment variable
Right now, the code strictly checks for process.env.ALLOW_PRIVATE_ADDRESS === 'true'. Many deployments represent boolean environment variables using uppercase 'TRUE' or '1'. You may want to ensure a case-insensitive or more general check.

Example update:

- process.env.ALLOW_PRIVATE_ADDRESS === 'true'
+ process.env.ALLOW_PRIVATE_ADDRESS?.toLowerCase() === 'true'
src/http/api/account.ts (1)

27-30: Optional: centralize the 'me' keyword
If the CURRENT_USER_KEYWORD is used in more than one place in the codebase, consider defining it in a central constants file or config to avoid duplication and promote consistency.

src/http/api/views/account.view.ts (2)

21-78: Review of AccountView constructor & viewById method

  • The constructor parameters db and fedifyContextFactory are well-defined for database and Fediverse lookups.
  • viewById correctly fetches the account and determines follow relationships.
  • You’ve provisioned attachment: [] with a TODO comment. If not truly needed, consider removing it to simplify the AccountDTO.
- attachment: [], // TODO: I don't think we need this
+ // remove attachment if it’s no longer used

303-333: getActorCollectionCount method
Nicely handles each collection type, returning zero on error. Consider logging the error at a debug or info level if you need to troubleshoot Fediverse issues later.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9668045 and 556b54a.

📒 Files selected for processing (7)
  • features/account.feature (1 hunks)
  • features/step_definitions/stepdefs.js (1 hunks)
  • src/app.ts (3 hunks)
  • src/http/api/account.ts (2 hunks)
  • src/http/api/views/account.view.ts (1 hunks)
  • src/lookup-helpers.ts (1 hunks)
  • src/lookup-helpers.unit.test.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/http/api/views/account.view.ts (6)
src/activitypub/fedify-context.factory.ts (1)
  • FedifyContextFactory (4-24)
src/http/api/types.ts (1)
  • AccountDTO (7-77)
src/account/utils.ts (1)
  • getAccountHandle (77-79)
src/helpers/html.ts (1)
  • sanitizeHtml (3-212)
src/lookup-helpers.ts (1)
  • lookupAPIdByHandle (64-108)
src/helpers/activitypub/actor.ts (1)
  • getHandle (60-64)
🔇 Additional comments (15)
features/account.feature (1)

23-23: Good update to expected HTTP status code!

Changing from 500 to 404 for non-existent accounts is appropriate - a 404 status code correctly indicates "not found" rather than suggesting a server error occurred.

src/lookup-helpers.unit.test.ts (1)

43-45: LGTM: Test updated to match new function signature.

The test now properly checks that lookupWebFinger is called with the new options parameter that includes allowPrivateAddress: true.

features/step_definitions/stepdefs.js (1)

1485-1488: Excellent enhancement to test validation!

Adding an assertion to check that expected status codes are less than 500 ensures tests never expect server errors. This is a good practice that aligns with the conversation in the previous reviews and the changes in the feature file.

src/app.ts (3)

116-116: LGTM: Adding the new AccountView import.

This is the first step in the refactoring to use AccountView for account-related operations.


271-271: LGTM: Initializing the AccountView class.

Creating the instance with the required dependencies looks good.


964-964: Properly updated handler to use AccountView.

The account handler now uses the new accountView instead of accountService and fedify, streamlining the dependency chain for account retrieval operations.

src/http/api/account.ts (3)

36-36: No issues with parameter replacement
Replacing accountService with accountView in the handler signature is consistent with the new approach of using the AccountView class for account retrieval.


45-45: Validate handle parameter
The logic checks if the handle is 'me' or a valid handle. Returning 404 for invalid handles makes sense from a user perspective. If you need to distinguish between an invalid handle vs. a missing resource, you might also consider logging or returning a 400 in certain scenarios, but 404 is acceptable if that’s your desired behavior.

Also applies to: 47-49


57-72: Clean separation of concerns
• Creating a viewContext with requestUserAccount clarifies who the requesting user is for the subsequent retrieval.
• Splitting logic between handling 'me' and a specific handle is neat and straightforward.
• Returning 404 when accountDto is null is a clear indication of a non-existent account.

src/http/api/views/account.view.ts (6)

1-20: Interface documentation and import structure look good
Having a dedicated ViewContext interface is clean and adaptable for future expansions. Marking requestUserAccount as optional is acceptable, as long as you’re ready to handle the case when it's not present.


80-100: viewByHandle method
Clean approach to first get the AP ID via WebFinger, then calling viewByApId. If you see repeated patterns in these low-level lookups across the codebase, consider unifying them in a small helper routine.


102-157: viewByApId method
Gracefully handles local versus remote accounts by delegating to viewByApIdRemote if the local DB query is empty. This separation helps keep the code simpler.


159-236: viewByApIdRemote method

  • Correct to ensure actor is non-null and isActor(actor) is true before proceeding.
  • Uses sanitizeHtml to protect from HTML injection in remote bios.
  • The fallback returns null if the remote actor can’t be fetched or is invalid, which is consistent.

238-272: getAccountByQuery method
The joins and subqueries for counts are straightforward and read clearly. For large-scale deployments, watch for potential performance bottlenecks with multiple subselects. Some DB vendors can optimize these effectively, but consider verifying with an EXPLAIN plan if performance becomes an issue.

If you suspect performance issues in production, you can run a query analyzer or sample load testing on these queries.


274-301: getRequestUserContextData method
Implementing follow checks in separate queries is acceptable, but you could unify it in a single query if you find performance concerns. For now, readability is fine.

@mike182uk mike182uk force-pushed the refactor-get-account-to-use-view branch from b9cba7c to b931ec9 Compare April 17, 2025 12:18
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

🧹 Nitpick comments (4)
src/http/api/views/account.view.ts (4)

30-78: Consider extracting common account formatting logic.

There's significant duplication between viewById and viewByApId methods. Both methods have nearly identical logic for checking relationships and returning an AccountDTO.

 async viewById(
     id: number,
     context: ViewContext,
 ): Promise<AccountDTO | null> {
     const accountData = await this.getAccountByQuery(
         (qb: Knex.QueryBuilder) => qb.where('accounts.id', id),
     );

     if (!accountData) {
         return null;
     }

-    let followedByMe = false;
-    let followsMe = false;
-
-    if (
-        context.requestUserAccount?.id &&
-        // Don't check if the request user is following / followed by themselves
-        accountData.id !== context.requestUserAccount.id
-    ) {
-        ({ followedByMe, followsMe } = await this.getRequestUserContextData(
-            context.requestUserAccount.id,
-            accountData.id,
-        ));
-    }
-
-    return {
-        id: accountData.id,
-        name: accountData.name,
-        handle: getAccountHandle(
-            new URL(accountData.ap_id).host,
-            accountData.username,
-        ),
-        bio: sanitizeHtml(accountData.bio || ''),
-        url: accountData.url,
-        avatarUrl: accountData.avatar_url || '',
-        bannerImageUrl: accountData.banner_image_url || '',
-        customFields: accountData.custom_fields
-            ? JSON.parse(accountData.custom_fields)
-            : {},
-        postCount: accountData.post_count,
-        likedCount: accountData.like_count,
-        followingCount: accountData.following_count,
-        followerCount: accountData.follower_count,
-        followedByMe,
-        followsMe,
-        attachment: [], // TODO: I don't think we need this
-    };
+    return this.formatAccountDTO(accountData, context);
 }

+/**
+ * Format account data into a consistent AccountDTO object
+ */
+private async formatAccountDTO(
+    accountData: any,
+    context: ViewContext
+): Promise<AccountDTO> {
+    let followedByMe = false;
+    let followsMe = false;
+
+    if (
+        context.requestUserAccount?.id &&
+        // Don't check if the request user is following / followed by themselves
+        accountData.id !== context.requestUserAccount.id
+    ) {
+        ({ followedByMe, followsMe } = await this.getRequestUserContextData(
+            context.requestUserAccount.id,
+            accountData.id,
+        ));
+    }
+
+    return {
+        id: accountData.id,
+        name: accountData.name,
+        handle: getAccountHandle(
+            new URL(accountData.ap_id).host,
+            accountData.username,
+        ),
+        bio: sanitizeHtml(accountData.bio || ''),
+        url: accountData.url,
+        avatarUrl: accountData.avatar_url || '',
+        bannerImageUrl: accountData.banner_image_url || '',
+        customFields: accountData.custom_fields
+            ? JSON.parse(accountData.custom_fields)
+            : {},
+        postCount: accountData.post_count,
+        likedCount: accountData.like_count,
+        followingCount: accountData.following_count,
+        followerCount: accountData.follower_count,
+        followedByMe,
+        followsMe,
+        attachment: [], // TODO: I don't think we need this
+    };
+}

Similar changes would be needed in the viewByApId method.

Also applies to: 109-157


76-76: Address or remove the TODO comments.

There are several TODO comments indicating attachments may not be needed. Either implement the feature, remove the property, or add a more detailed comment explaining why it's being kept as a placeholder.

Also applies to: 155-155, 234-234


274-301: Consider optimizing the follow relationship queries.

The method makes two separate database queries to check follow relationships. These could be combined into a single query for better performance.

 private async getRequestUserContextData(
     requestUserAccountId: number,
     retrievedAccountId: number,
 ) {
-    let followedByMe = false;
-    let followsMe = false;
-
-    followedByMe =
-        (
-            await this.db('follows')
-                .where('follower_id', requestUserAccountId)
-                .where('following_id', retrievedAccountId)
-                .first()
-        )?.id !== undefined;
-
-    followsMe =
-        (
-            await this.db('follows')
-                .where('following_id', requestUserAccountId)
-                .where('follower_id', retrievedAccountId)
-                .first()
-        )?.id !== undefined;
+    const [followResult, followedResult] = await Promise.all([
+        this.db('follows')
+            .where('follower_id', requestUserAccountId)
+            .where('following_id', retrievedAccountId)
+            .first(),
+        this.db('follows')
+            .where('following_id', requestUserAccountId)
+            .where('follower_id', retrievedAccountId)
+            .first()
+    ]);
+
+    const followedByMe = followResult?.id !== undefined;
+    const followsMe = followedResult?.id !== undefined;

     return {
         followedByMe,
         followsMe,
     };
 }

303-331: Error handling masks potential issues in collection retrieval.

While it's good to gracefully handle errors when retrieving collection counts, the current implementation silently converts all errors to zero counts. Consider adding logging to capture and monitor these errors.

 try {
     const collection = await getCollection.bind(actor)();

     return collection?.totalItems ?? 0;
 } catch (error) {
+    // Log error for monitoring but still gracefully continue
+    console.error(`Failed to get ${collectionName} count for actor ${actor.id}:`, error);
     return 0;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9cba7c and b931ec9.

📒 Files selected for processing (7)
  • features/account.feature (1 hunks)
  • features/step_definitions/stepdefs.js (1 hunks)
  • src/app.ts (3 hunks)
  • src/http/api/account.ts (2 hunks)
  • src/http/api/views/account.view.ts (1 hunks)
  • src/lookup-helpers.ts (1 hunks)
  • src/lookup-helpers.unit.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • features/account.feature
  • src/lookup-helpers.unit.test.ts
  • features/step_definitions/stepdefs.js
  • src/lookup-helpers.ts
  • src/http/api/account.ts
  • src/app.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build, Test and Push
🔇 Additional comments (7)
src/http/api/views/account.view.ts (7)

14-19: Good implementation of ViewContext interface.

The interface clearly defines the purpose of the request user account context, which aligns with the previous PR discussions about using Account instead of Site context.


21-25: Clean constructor with appropriate dependencies.

The class follows dependency injection principles by accepting database and federation context dependencies through the constructor, which is good for testability.


87-100: Well-structured handle resolution method.

The viewByHandle method correctly leverages lookup helpers and has appropriate error handling by returning null when no matching account is found, aligning with the consistent pattern discussed in previous reviews.


171-177: Consistent null handling for not-found cases.

The implementation now correctly returns null in both cases when an actor isn't found or isn't an actor type, which matches the approach discussed in previous reviews for consistent error handling.


216-227: Good sanitization of user-generated content.

The implementation properly sanitizes all user-generated content, including custom fields from external sources, which is important for security.


238-272: Well-structured database query with efficient joins.

The getAccountByQuery method properly joins the users table and uses subqueries to efficiently gather all the required data in a single database call. The use of raw SQL for aggregate counts is appropriate here for performance reasons.


1-332: Overall excellent implementation of the AccountView.

The AccountView class successfully centralizes account retrieval logic, providing a consistent interface for both local and remote account data. The implementation aligns with the PR objective to refactor account retrieval, and the code handles errors appropriately, sanitizes user data, and maintains consistent return types as discussed in previous reviews.

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: 2

🧹 Nitpick comments (2)
src/http/api/views/account.view.integration.test.ts (2)

307-321: Use mockImplementationOnce to prevent test‑to‑test bleed‑through

vi.spyOn(AccountView.prototype, 'viewByApId').mockImplementation(...) installs a permanent stub until vi.restoreAllMocks() in the next beforeEach.
If a new test is inserted before that call, the stub state may unexpectedly persist.
Limit the scope to this assertion by using mockImplementationOnce:

-            const spy = vi
-                .spyOn(AccountView.prototype, 'viewByApId')
-                .mockImplementation(async (apId) => {
+            const spy = vi
+                .spyOn(AccountView.prototype, 'viewByApId')
+                .mockImplementationOnce(async (apId) => {

17-21: Expose both mocked helpers to the tests

The factory passed to the top‑level vi.mock() only creates stubs for lookupAPIdByHandle and lookupObject.
lookupObject is never used in this suite; if future tests rely on its behaviour you’ll need to supply an implementation.
Consider exporting typed helpers in a shared __mocks__/lookup-helpers.ts file so the intent is explicit and DRY across test files.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b931ec9 and f275ae0.

📒 Files selected for processing (4)
  • src/http/api/__snapshots__/views/AccountView.viewByApId.internal-no-context.json (1 hunks)
  • src/http/api/__snapshots__/views/AccountView.viewById.no-context.json (1 hunks)
  • src/http/api/views/account.view.integration.test.ts (1 hunks)
  • src/http/api/views/account.view.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/http/api/snapshots/views/AccountView.viewById.no-context.json
  • src/http/api/snapshots/views/AccountView.viewByApId.internal-no-context.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/http/api/views/account.view.ts

Comment on lines +63 to +77
beforeEach(async () => {
await db.raw('SET FOREIGN_KEY_CHECKS = 0');
await Promise.all([
db('reposts').truncate(),
db('likes').truncate(),
db('posts').truncate(),
db('follows').truncate(),
db('accounts').truncate(),
db('users').truncate(),
db('sites').truncate(),
]);
await db.raw('SET FOREIGN_KEY_CHECKS = 1');

vi.restoreAllMocks();
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Serialise truncation to reduce the risk of deadlocks

Running six TRUNCATE statements in parallel (Promise.all([...])) while foreign‑key checks are disabled is usually safe, but MySQL occasionally deadlocks on parallel DDL in rapid test loops.
It’s safer (and fast enough) to execute them sequentially:

-        await Promise.all([
-            db('reposts').truncate(),
-            db('likes').truncate(),
-            db('posts').truncate(),
-            db('follows').truncate(),
-            db('accounts').truncate(),
-            db('users').truncate(),
-            db('sites').truncate(),
-        ]);
+        for (const table of [
+            'reposts',
+            'likes',
+            'posts',
+            'follows',
+            'accounts',
+            'users',
+            'sites',
+        ]) {
+            // sequential truncate avoids occasional DDL deadlocks
+            // and keeps the intent crystal‑clear
+            await db(table).truncate();
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(async () => {
await db.raw('SET FOREIGN_KEY_CHECKS = 0');
await Promise.all([
db('reposts').truncate(),
db('likes').truncate(),
db('posts').truncate(),
db('follows').truncate(),
db('accounts').truncate(),
db('users').truncate(),
db('sites').truncate(),
]);
await db.raw('SET FOREIGN_KEY_CHECKS = 1');
vi.restoreAllMocks();
});
beforeEach(async () => {
await db.raw('SET FOREIGN_KEY_CHECKS = 0');
- await Promise.all([
- db('reposts').truncate(),
- db('likes').truncate(),
- db('posts').truncate(),
- db('follows').truncate(),
- db('accounts').truncate(),
- db('users').truncate(),
- db('sites').truncate(),
- ]);
+ for (const table of [
+ 'reposts',
+ 'likes',
+ 'posts',
+ 'follows',
+ 'accounts',
+ 'users',
+ 'sites',
+ ]) {
+ // sequential truncate avoids occasional DDL deadlocks
+ // and keeps the intent crystal‑clear
+ await db(table).truncate();
+ }
await db.raw('SET FOREIGN_KEY_CHECKS = 1');
vi.restoreAllMocks();
});

Comment on lines +60 to +62
accountView = new AccountView(db, fedifyContextFactory);
});

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Close the Knex connection after the test suite to avoid open‑handle leaks

beforeAll creates a real Knex instance but the suite never tears it down. Vitest keeps the process alive when there are un‑closed DB handles, which can make the test runner hang or report “open handles” warnings on CI.

+    afterAll(async () => {
+        await db.destroy(); // closes pool & frees resources
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
accountView = new AccountView(db, fedifyContextFactory);
});
accountView = new AccountView(db, fedifyContextFactory);
});
afterAll(async () => {
await db.destroy(); // closes pool & frees resources
});

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

🧹 Nitpick comments (1)
src/http/api/account.ts (1)

122-129: Suggestion: Reuse the CURRENT_USER_KEYWORD constant

For consistency, consider using the CURRENT_USER_KEYWORD constant in other handlers instead of hardcoded 'me' strings.

-        if (handle === 'me') {
+        if (handle === CURRENT_USER_KEYWORD) {

This applies to both places where 'me' is used as a hardcoded string.

Also applies to: 224-232

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f275ae0 and f5f5617.

📒 Files selected for processing (3)
  • features/step_definitions/stepdefs.js (1 hunks)
  • src/app.ts (3 hunks)
  • src/http/api/account.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • features/step_definitions/stepdefs.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app.ts
🔇 Additional comments (6)
src/http/api/account.ts (6)

31-34: Good extraction of 'me' into a constant

Extracting the 'me' value into a named constant CURRENT_USER_KEYWORD improves readability and maintainability.


39-42: Clean dependency injection with AccountView

The refactored signature is simpler and follows the agreed-upon approach from previous discussions, injecting AccountView directly rather than passing its dependencies through the controller.


49-53: Improved error handling for invalid handles

Proper validation of the handle parameter with a clear 404 response for invalid values.


61-63: Good use of view context pattern

Creating a view context object with the request user account improves code structure and makes dependencies explicit.


65-72: Simplified account lookup logic

The refactored code elegantly handles both the current user case and regular handle lookups using the AccountView, resulting in cleaner and more maintainable code.


74-76: Proper null checking for account

The null check for accountDto with a 404 response addresses the previous review comment correctly.

Copy link
Collaborator

@allouis allouis left a comment

Choose a reason for hiding this comment

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

I think we should return Result instead of AccountDTO | null - but seeing as we're not throwing errors, this is fine for now, and we can use result when we need to throw/handle errors

@mike182uk mike182uk merged commit 959010f into main Apr 23, 2025
7 checks passed
@mike182uk mike182uk deleted the refactor-get-account-to-use-view branch April 23, 2025 10:09
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