Skip to content

Comments

feat (clan): allow co leaders to view/accept/reject clan requests#1137

Open
ammarjai wants to merge 6 commits intostudie-tech:mainfrom
ammarjai:feature-clan-coleader-accept-reject-request
Open

feat (clan): allow co leaders to view/accept/reject clan requests#1137
ammarjai wants to merge 6 commits intostudie-tech:mainfrom
ammarjai:feature-clan-coleader-accept-reject-request

Conversation

@ammarjai
Copy link

@ammarjai ammarjai commented Feb 17, 2026

Pull Request

Allow co leaders to view/accept/reject clan requests. No breaking changes. Not really sure this is the best way to do this, but I implement this hack(?) somewhat due to limitation in DB schema and I don't want to change that.

Output

  1. Image showing a clan co leader
Screenshot 2026-02-17 111250
  1. Image showing a clan co leader view of clan requests page
Screenshot 2026-02-17 111306
  1. Image showing a non clan user view of clan requests page after sending request
Screenshot 2026-02-17 111326
  1. Image showing a clan co leader accepting request and getting successful message
Screenshot 2026-02-17 111340
  1. Image showing the requester now being in the clan
Screenshot 2026-02-17 111353

License

By making this pull request, I confirm that I have the right to waive copyright and related rights to my contribution, and agree that all copyright and related rights in my contributions are waived, and I acknowledge that the Studie-Tech ApS organization has the copyright to use and modify my contribution for perpetuity.

Summary by CodeRabbit

  • New Features

    • Requests UI now passes the clan leader ID and a leader/co‑leader flag so leaders and co‑leaders see the same request view and the Requests tab renders correctly.
    • Backend request fetching now accepts a leader identifier to scope requests to that leader.
  • Bug Fixes

    • Authorization and filtering tightened so only leaders or co‑leaders can view/act on clan requests; accept/reject flows return clearer error messages.

@vercel
Copy link

vercel bot commented Feb 17, 2026

@ammarjai is attempting to deploy a commit to the TheNinja-RPG Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Walkthrough

Frontend props and backend clan routes were changed to enforce leader/co‑leader authorization: ClanRequests now receives clanLeaderId and isLeaderOrColeader; getRequests (and accept/reject) accept/derive leader info and perform additional fetches and leadership checks before returning or mutating requests.

Changes

Cohort / File(s) Summary
Frontend: Clan UI & props
app/src/layout/Clan.tsx
Replaced isLeader prop with clanLeaderId: string and isLeaderOrColeader: boolean. ClanRequests now calls getRequests({ clanLeaderId }), derives request visibility from isLeaderOrColeader, computes userId for UserRequestSystem, and call sites (ClanProfile/ClanInfo/tab) updated to pass new props.
Backend: clan router auth & inputs
app/src/server/api/routers/clan.ts
getRequests now takes .input(z.object({ clanLeaderId: z.string() })). getRequests, acceptRequest, and rejectRequest perform extra fetches (clan-by-leader, user lookups), compute isLeader/isColeader, enforce leader/co‑leader guards, and adjust error messages and response shape accordingly.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Frontend as "Frontend\n(app/src/layout/Clan.tsx)"
  participant Server as "Server API\n(app/src/server/api/routers/clan.ts)"
  participant DB as "DB / Services"

  Client->>Frontend: open Requests tab / click accept/reject
  Frontend->>Server: getRequests({ clanLeaderId })
  Server->>DB: fetch clan by leader, fetch users (leader/requesters)
  DB-->>Server: clan & user records
  Server-->>Frontend: filtered requests based on isLeader/isColeader
  Frontend->>Server: acceptRequest / rejectRequest (target userId)
  Server->>DB: validate leadership (leader/co‑leader) via fetched records
  DB-->>Server: mutation result
  Server-->>Frontend: success or error (with groupLabel)
  Frontend->>Client: render updated requests or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Priority

Suggested reviewers

  • MathiasGruber

Poem

🐰 A hop, a rule, a leader's call,
Props rearranged to guard the hall.
Requests now checked with careful cheer,
Co‑leaders counted, roles made clear.
I nibble code and whisper: onward, all!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature: enabling co-leaders to view/accept/reject clan requests, matching the core change across both modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main
Description check ✅ Passed The PR description clearly explains the feature implementation, indicates no breaking changes, includes visual evidence via screenshots, and acknowledges the use of a workaround approach.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@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 (1)
app/src/layout/Clan.tsx (1)

722-726: The userId override to clanLeaderId is fragile — consider a dedicated prop instead.

Passing clanLeaderId as userId to UserRequestSystem tricks it into showing accept/reject buttons (since the requests have receiverId === leaderId). This works but couples the behavior to an implementation detail of UserRequestSystem's logic. If that component ever uses userId for authorization or display purposes beyond button visibility, this will break silently.

Consider adding an explicit canManageRequests boolean prop to UserRequestSystem to decouple this, or at minimum add a comment explaining why the leader's ID is passed here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/layout/Clan.tsx` around lines 722 - 726, The current hack of passing
clanLeaderId into UserRequestSystem via the userId prop is fragile; change the
call site in Clan.tsx to pass the real current user (userData.userId) as userId
and introduce a new explicit boolean prop (e.g., canManageRequests) on
UserRequestSystem, setting it to isLeaderOrColeader so the component can show
accept/reject controls without overloading userId; update the UserRequestSystem
prop usage (and its props type/interface) to accept canManageRequests and use
that flag for button visibility/management logic instead of comparing receiverId
to userId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/server/api/routers/clan.ts`:
- Around line 445-455: The error message "Only clan leaders can reject requests"
is misleading because co-leaders (determined via checkCoLeader and the
isColeader flag) are also allowed; update the errorResponse call that follows
the isLeader/isColeader check to use a wording that includes co-leaders (e.g.,
"Only clan leaders or co-leaders can reject requests"), and make the identical
wording change in the corresponding accept path where the same
isLeader/isColeader authorization check and errorResponse are used; reference
the isLeader, isColeader, checkCoLeader, and errorResponse symbols when locating
the two spots to update.
- Around line 376-381: The getRequests query currently trusts the
client-supplied clanLeaderId and returns pending requests via fetchRequests,
allowing any authenticated user to enumerate requests; update getRequests to
perform an authorization guard before calling fetchRequests by verifying
ctx.userId is the clan leader or has a leader/co-leader role for that clan
(e.g., call an existing lookup helper or query the DB using ctx.drizzle to fetch
the clan/role for input.clanLeaderId and ensure it matches ctx.userId or a
co-leader role), and throw an unauthorized error if the check fails; only after
this guard succeeds should you call fetchRequests(ctx.drizzle, ["CLAN"], 3600 *
12, input.clanLeaderId).

---

Nitpick comments:
In `@app/src/layout/Clan.tsx`:
- Around line 722-726: The current hack of passing clanLeaderId into
UserRequestSystem via the userId prop is fragile; change the call site in
Clan.tsx to pass the real current user (userData.userId) as userId and introduce
a new explicit boolean prop (e.g., canManageRequests) on UserRequestSystem,
setting it to isLeaderOrColeader so the component can show accept/reject
controls without overloading userId; update the UserRequestSystem prop usage
(and its props type/interface) to accept canManageRequests and use that flag for
button visibility/management logic instead of comparing receiverId to userId.

@greptile-apps
Copy link

greptile-apps bot commented Feb 17, 2026

Confidence Score: 3/5

  • This PR is functional but has outstanding issues from previous review threads (authorization, null-safety, error messages) that should be resolved before merging.
  • The core feature logic is sound — co-leaders can now view/accept/reject clan requests. However, previous review threads identified multiple issues including a missing server-side authorization check in getRequests (client-provided clanLeaderId not verified), null-safety ordering in acceptRequest/rejectRequest, and hardcoded error messages. These are addressable but should be fixed first.
  • app/src/server/api/routers/clan.ts needs attention for the authorization, null-safety, and error message issues identified in previous review threads.

Important Files Changed

Filename Overview
app/src/layout/Clan.tsx Updated ClanRequests to accept clanLeaderId and isLeaderOrColeader props, correctly wiring up the request system for co-leaders. Changes are straightforward and logically sound.
app/src/server/api/routers/clan.ts Added co-leader authorization to getRequests, acceptRequest, and rejectRequest. Previous review threads identified authorization gaps in getRequests, null-safety ordering issues, and hardcoded error messages that should be addressed.

Sequence Diagram

sequenceDiagram
    participant CL as Co-Leader/Leader (Client)
    participant FE as ClanRequests Component
    participant API as tRPC API
    participant DB as Database

    CL->>FE: View Requests Tab
    FE->>API: getRequests({ clanLeaderId })
    API->>DB: fetchUser(ctx.userId) || fetchClanByLeader(clanLeaderId)
    DB-->>API: user, fetchedClan
    alt User has no clan
        API->>DB: fetchRequests(ctx.userId)
        DB-->>API: user's own requests
    else User is leader/co-leader
        API->>DB: fetchRequests(clanLeaderId)
        DB-->>API: clan requests (scoped to leader)
    else User is not leader/co-leader
        API-->>FE: empty array []
    end
    API-->>FE: requests[]
    FE-->>CL: Display requests with Accept/Reject buttons

    CL->>FE: Accept/Reject Request
    FE->>API: acceptRequest/rejectRequest({ id })
    API->>DB: fetchRequest(id)
    API->>DB: fetchClanByLeader || fetchUser(ctx.userId)
    DB-->>API: clan, user
    API->>API: Check isLeader or isColeader
    alt Authorized
        API->>DB: updateRequestState
        DB-->>API: success
        API-->>FE: { success: true }
    else Not Authorized
        API-->>FE: error response
    end
Loading

Last reviewed commit: cec1f5a

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 376 to 381
getRequests: protectedProcedure
.meta({ mcp: { enabled: true, description: "Get pending clan join requests" } })
.query(async ({ ctx }) => {
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
.input(z.object({ clanLeaderId: z.string() }))
.query(async ({ ctx, input }) => {
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, input.clanLeaderId);
}),
Copy link

Choose a reason for hiding this comment

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

Missing server-side authorization check

The getRequests endpoint now accepts clanLeaderId as client-provided input and passes it directly to fetchRequests, with no verification that the calling user (ctx.userId) is actually the leader or a co-leader of that clan. Previously, ctx.userId was used directly, which was inherently secure since it came from the authenticated session.

Any authenticated user can now call this endpoint with any clanLeaderId value and retrieve all clan join requests for that clan. While the data exposed (usernames, levels, ranks) is relatively low-sensitivity, this still violates the principle of least privilege.

Consider adding an authorization check:

Suggested change
getRequests: protectedProcedure
.meta({ mcp: { enabled: true, description: "Get pending clan join requests" } })
.query(async ({ ctx }) => {
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
.input(z.object({ clanLeaderId: z.string() }))
.query(async ({ ctx, input }) => {
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, input.clanLeaderId);
}),
getRequests: protectedProcedure
.meta({ mcp: { enabled: true, description: "Get pending clan join requests" } })
.input(z.object({ clanLeaderId: z.string() }))
.query(async ({ ctx, input }) => {
const fetchedClan = await fetchClanByLeader(ctx.drizzle, input.clanLeaderId);
const isLeader = ctx.userId === fetchedClan?.leaderId;
const isColeader = checkCoLeader(ctx.userId, fetchedClan);
if (!isLeader && !isColeader) {
throw new Error("Only clan leaders or co-leaders can view requests");
}
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, input.clanLeaderId);
}),

This does add an extra DB query. An alternative approach that avoids the extra roundtrip: look up the clan by ctx.userId's membership and verify the leader ID matches, or restructure the query to derive clanLeaderId server-side from ctx.userId.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/server/api/routers/clan.ts
Line: 376:381

Comment:
**Missing server-side authorization check**

The `getRequests` endpoint now accepts `clanLeaderId` as client-provided input and passes it directly to `fetchRequests`, with no verification that the calling user (`ctx.userId`) is actually the leader or a co-leader of that clan. Previously, `ctx.userId` was used directly, which was inherently secure since it came from the authenticated session.

Any authenticated user can now call this endpoint with any `clanLeaderId` value and retrieve all clan join requests for that clan. While the data exposed (usernames, levels, ranks) is relatively low-sensitivity, this still violates the principle of least privilege.

Consider adding an authorization check:

```suggestion
  getRequests: protectedProcedure
    .meta({ mcp: { enabled: true, description: "Get pending clan join requests" } })
    .input(z.object({ clanLeaderId: z.string() }))
    .query(async ({ ctx, input }) => {
      const fetchedClan = await fetchClanByLeader(ctx.drizzle, input.clanLeaderId);
      const isLeader = ctx.userId === fetchedClan?.leaderId;
      const isColeader = checkCoLeader(ctx.userId, fetchedClan);
      if (!isLeader && !isColeader) {
        throw new Error("Only clan leaders or co-leaders can view requests");
      }
      return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, input.clanLeaderId);
    }),
```

This does add an extra DB query. An alternative approach that avoids the extra roundtrip: look up the clan by `ctx.userId`'s membership and verify the leader ID matches, or restructure the query to derive `clanLeaderId` server-side from `ctx.userId`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +494 to +495
const isLeader = user.userId === fetchedClan?.leaderId;
const isColeader = checkCoLeader(user.userId, fetchedClan);
Copy link

Choose a reason for hiding this comment

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

Null safety: fetchedClan used before guard

isLeader and isColeader are derived from fetchedClan on lines 494-495, but fetchedClan can be null (it's checked on line 497: if (!fetchedClan) return errorResponse(...)). If fetchedClan is null, then fetchedClan?.leaderId is undefined, so isLeader will be false, and checkCoLeader handles null/undefined gracefully by returning false. The guard on line 501 would then return "Only clan leaders can accept requests" — which is a misleading error message when the actual issue is "clan not found."

Consider moving the !fetchedClan guard before the leader/co-leader checks to ensure the correct error message is returned:

Suggested change
const isLeader = user.userId === fetchedClan?.leaderId;
const isColeader = checkCoLeader(user.userId, fetchedClan);
const isLeader = user.userId === fetchedClan?.leaderId;
const isColeader = checkCoLeader(user.userId, fetchedClan);
// Guards
if (!fetchedClan) return errorResponse(`${groupLabel} not found`);
if (!requester) return errorResponse("Requester not found");
if (!leader) return errorResponse("Leader not found");
if (nMembers >= CLAN_MAX_MEMBERS) return errorResponse(`${groupLabel} is full`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/server/api/routers/clan.ts
Line: 494:495

Comment:
**Null safety: `fetchedClan` used before guard**

`isLeader` and `isColeader` are derived from `fetchedClan` on lines 494-495, but `fetchedClan` can be `null` (it's checked on line 497: `if (!fetchedClan) return errorResponse(...)`). If `fetchedClan` is `null`, then `fetchedClan?.leaderId` is `undefined`, so `isLeader` will be `false`, and `checkCoLeader` handles `null`/`undefined` gracefully by returning `false`. The guard on line 501 would then return `"Only clan leaders can accept requests"` — which is a misleading error message when the actual issue is "clan not found."

Consider moving the `!fetchedClan` guard before the leader/co-leader checks to ensure the correct error message is returned:

```suggestion
      const isLeader = user.userId === fetchedClan?.leaderId;
      const isColeader = checkCoLeader(user.userId, fetchedClan);
      // Guards
      if (!fetchedClan) return errorResponse(`${groupLabel} not found`);
      if (!requester) return errorResponse("Requester not found");
      if (!leader) return errorResponse("Leader not found");
      if (nMembers >= CLAN_MAX_MEMBERS) return errorResponse(`${groupLabel} is full`);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 449 to 452
]);
// Derived
const isLeader = user.userId === fetchedClan?.leaderId;
const isColeader = checkCoLeader(user.userId, fetchedClan);
Copy link

Choose a reason for hiding this comment

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

Same null-safety concern in rejectRequest

Same pattern as in acceptRequest: fetchedClan can be null from fetchClanByLeader, and if it is, both isLeader and isColeader will be false, leading to the error "Only clan leaders can reject requests" — which is misleading when the actual problem is that no clan was found for the given leader ID.

Consider adding a !fetchedClan guard before using it:

Suggested change
]);
// Derived
const isLeader = user.userId === fetchedClan?.leaderId;
const isColeader = checkCoLeader(user.userId, fetchedClan);
const isLeader = user.userId === fetchedClan?.leaderId;
const isColeader = checkCoLeader(user.userId, fetchedClan);
if (!fetchedClan) {
return errorResponse("Clan not found");
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/server/api/routers/clan.ts
Line: 449:452

Comment:
**Same null-safety concern in `rejectRequest`**

Same pattern as in `acceptRequest`: `fetchedClan` can be `null` from `fetchClanByLeader`, and if it is, both `isLeader` and `isColeader` will be `false`, leading to the error "Only clan leaders can reject requests" — which is misleading when the actual problem is that no clan was found for the given leader ID.

Consider adding a `!fetchedClan` guard before using it:

```suggestion
      const isLeader = user.userId === fetchedClan?.leaderId;
      const isColeader = checkCoLeader(user.userId, fetchedClan);
      if (!fetchedClan) {
        return errorResponse("Clan not found");
      }
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 501 to 503
if (!isLeader && !isColeader) {
return errorResponse("Only clan leaders can accept requests");
}
Copy link

Choose a reason for hiding this comment

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

Error message excludes co-leaders

The error message says "Only clan leaders can accept requests" but this PR specifically adds co-leader support. If a non-leader, non-co-leader user hits this guard, they'll see a message that doesn't mention co-leaders can also perform this action. The same issue exists in rejectRequest on line 454.

Suggested change
if (!isLeader && !isColeader) {
return errorResponse("Only clan leaders can accept requests");
}
if (!isLeader && !isColeader) {
return errorResponse("Only clan leaders or co-leaders can accept requests");
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/server/api/routers/clan.ts
Line: 501:503

Comment:
**Error message excludes co-leaders**

The error message says "Only clan leaders can accept requests" but this PR specifically adds co-leader support. If a non-leader, non-co-leader user hits this guard, they'll see a message that doesn't mention co-leaders can also perform this action. The same issue exists in `rejectRequest` on line 454.

```suggestion
      if (!isLeader && !isColeader) {
        return errorResponse("Only clan leaders or co-leaders can accept requests");
      }
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/server/api/routers/clan.ts`:
- Around line 386-400: The code performs an unnecessary DB call fetching
`leader` via `fetchUser(ctx.drizzle, input.clanLeaderId)` only to compute
`groupLabel`, which is never used; remove the unused fetch and `groupLabel`
derivation by replacing the Promise.all with a single await for
`fetchClanByLeader(ctx.drizzle, input.clanLeaderId)` (keep `fetchedClan`), and
leave the existing guards and logic that use `fetchedClan`, `isLeader`
(user.userId === fetchedClan?.leaderId), `isColeader`
(checkCoLeader(user.userId, fetchedClan)), and the final
`fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, input.clanLeaderId)` call
unchanged.

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
app/src/server/api/routers/clan.ts (1)

380-397: Sequential DB calls can be parallelized; single-item Promise.all is redundant.

Two observations:

  1. Promise.all([singlePromise]) on line 386 adds no concurrency benefit — just unwrap the await directly.
  2. The user fetch (line 381) and fetchClanByLeader (line 386) are sequential. Since fetchClanByLeader doesn't depend on user, you can run them in parallel and branch afterward, saving a DB roundtrip for clan members.
Suggested refactor
     .query(async ({ ctx, input }) => {
-      // Main fetch for non clan user
-      const user = await fetchUser(ctx.drizzle, ctx.userId);
-      if (!user.clanId) {
-        return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
-      }
-      // Fetch for clan user with guards
-      const [fetchedClan] = await Promise.all([
-        fetchClanByLeader(ctx.drizzle, input.clanLeaderId),
-      ]);
+      // Fetch user and clan in parallel
+      const [user, fetchedClan] = await Promise.all([
+        fetchUser(ctx.drizzle, ctx.userId),
+        fetchClanByLeader(ctx.drizzle, input.clanLeaderId),
+      ]);
+      // Non-clan user: return their own requests
+      if (!user.clanId) {
+        return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
+      }
       // Derived
       const isLeader = user.userId === fetchedClan?.leaderId;
       const isColeader = checkCoLeader(user.userId, fetchedClan);

As per coding guidelines: "Minimize DB roundtrips by preferring to reduce the number of database roundtrips over reducing the amount of data fetched; run queries in parallel with Promise.all()."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/server/api/routers/clan.ts` around lines 380 - 397, Run fetchUser and
fetchClanByLeader in parallel instead of sequentially and remove the redundant
Promise.all([...singlePromise]) wrapper: kick off both promises
(fetchUser(ctx.drizzle, ctx.userId) and fetchClanByLeader(ctx.drizzle,
input.clanLeaderId)) concurrently with Promise.all, then await their results
into user and fetchedClan; keep the existing guards (checkCoLeader, isLeader,
user.clanId) and return fetchRequests(...) for non-clan users (using ctx.userId)
or for authorized clan members (using input.clanLeaderId). Ensure you replace
the single-item Promise.all around fetchClanByLeader with the parallel pattern
and update references to user and fetchedClan accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/server/api/routers/clan.ts`:
- Around line 380-397: Run fetchUser and fetchClanByLeader in parallel instead
of sequentially and remove the redundant Promise.all([...singlePromise])
wrapper: kick off both promises (fetchUser(ctx.drizzle, ctx.userId) and
fetchClanByLeader(ctx.drizzle, input.clanLeaderId)) concurrently with
Promise.all, then await their results into user and fetchedClan; keep the
existing guards (checkCoLeader, isLeader, user.clanId) and return
fetchRequests(...) for non-clan users (using ctx.userId) or for authorized clan
members (using input.clanLeaderId). Ensure you replace the single-item
Promise.all around fetchClanByLeader with the parallel pattern and update
references to user and fetchedClan accordingly.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 380 to 386
// Main fetch for non clan user
const user = await fetchUser(ctx.drizzle, ctx.userId);
if (!user.clanId) {
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
}
// Fetch for clan user with guards
const fetchedClan = await fetchClanByLeader(ctx.drizzle, input.clanLeaderId);
Copy link

Choose a reason for hiding this comment

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

Sequential DB queries can be parallelized

For clan users, fetchUser and fetchClanByLeader are called sequentially but don't depend on each other — input.clanLeaderId comes from the client, not from user. Per the project's "Minimize DB Roundtrips" guideline, these can be parallelized with Promise.all to save a roundtrip. The trade-off is that fetchClanByLeader would also run for non-clan users (wasted query), but you could restructure to handle that:

Suggested change
// Main fetch for non clan user
const user = await fetchUser(ctx.drizzle, ctx.userId);
if (!user.clanId) {
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
}
// Fetch for clan user with guards
const fetchedClan = await fetchClanByLeader(ctx.drizzle, input.clanLeaderId);
const [user, fetchedClan] = await Promise.all([
fetchUser(ctx.drizzle, ctx.userId),
fetchClanByLeader(ctx.drizzle, input.clanLeaderId),
]);
// Main fetch for non clan user
if (!user.clanId) {
return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
}

Context Used: Context from dashboard - CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/server/api/routers/clan.ts
Line: 380:386

Comment:
**Sequential DB queries can be parallelized**

For clan users, `fetchUser` and `fetchClanByLeader` are called sequentially but don't depend on each other — `input.clanLeaderId` comes from the client, not from `user`. Per the project's "Minimize DB Roundtrips" guideline, these can be parallelized with `Promise.all` to save a roundtrip. The trade-off is that `fetchClanByLeader` would also run for non-clan users (wasted query), but you could restructure to handle that:

```suggestion
      const [user, fetchedClan] = await Promise.all([
        fetchUser(ctx.drizzle, ctx.userId),
        fetchClanByLeader(ctx.drizzle, input.clanLeaderId),
      ]);
      // Main fetch for non clan user
      if (!user.clanId) {
        return await fetchRequests(ctx.drizzle, ["CLAN"], 3600 * 12, ctx.userId);
      }
```

**Context Used:** Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=0827434e-cdb7-4415-b790-c54f1a8e8feb))

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 522 to 523
if (!isLeader && !isColeader) {
return errorResponse("Only clan leader or co-leaders can accept requests");
Copy link

Choose a reason for hiding this comment

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

Hardcoded "clan" ignores outlaw factions

The groupLabel variable is already computed on line 513 to differentiate between "clan" and "faction" for outlaws, and it's used in other error messages in this block (e.g., lines 518, 521, 526). However, this error message hardcodes "clan" instead of using groupLabel. The same issue exists in rejectRequest on line 475.

Suggested change
if (!isLeader && !isColeader) {
return errorResponse("Only clan leader or co-leaders can accept requests");
if (!isLeader && !isColeader) {
return errorResponse(`Only ${groupLabel} leader or co-leaders can accept requests`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/server/api/routers/clan.ts
Line: 522:523

Comment:
**Hardcoded "clan" ignores outlaw factions**

The `groupLabel` variable is already computed on line 513 to differentiate between "clan" and "faction" for outlaws, and it's used in other error messages in this block (e.g., lines 518, 521, 526). However, this error message hardcodes "clan" instead of using `groupLabel`. The same issue exists in `rejectRequest` on line 475.

```suggestion
      if (!isLeader && !isColeader) {
        return errorResponse(`Only ${groupLabel} leader or co-leaders can accept requests`);
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 474 to 475
if (!isLeader && !isColeader) {
return errorResponse("Only clan leader or co-leaders can reject requests");
Copy link

Choose a reason for hiding this comment

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

Same hardcoded "clan" issue here

Same as in acceptRequestgroupLabel is available on line 468 but not used in this error message. For outlaw faction users, this would incorrectly say "clan" instead of "faction".

Suggested change
if (!isLeader && !isColeader) {
return errorResponse("Only clan leader or co-leaders can reject requests");
if (!isLeader && !isColeader) {
return errorResponse(`Only ${groupLabel} leader or co-leaders can reject requests`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/src/server/api/routers/clan.ts
Line: 474:475

Comment:
**Same hardcoded "clan" issue here**

Same as in `acceptRequest``groupLabel` is available on line 468 but not used in this error message. For outlaw faction users, this would incorrectly say "clan" instead of "faction".

```suggestion
      if (!isLeader && !isColeader) {
        return errorResponse(`Only ${groupLabel} leader or co-leaders can reject requests`);
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
app/src/server/api/routers/clan.ts (1)

461-478: leader fetch is an unnecessary DB roundtrip in rejectRequest.

leader (line 464) is used solely for groupLabel derivation (line 468) and a guard that can never trigger (fetchUser throws on not-found, so if (!leader) at line 473 is unreachable). You can derive groupLabel from user.isOutlaw instead — all members of an outlaw faction share the same flag — and drop the leader fetch entirely.

This saves one DB call per reject invocation. The same pattern is already used in other endpoints (e.g., createRequest at line 423 uses the acting user's isOutlaw).

Proposed fix
     const request = await fetchRequest(ctx.drizzle, input.id, "CLAN");
     // Secondary fetches
-    const [fetchedClan, leader, user] = await Promise.all([
+    const [fetchedClan, user] = await Promise.all([
       fetchClanByLeader(ctx.drizzle, request.receiverId),
-      fetchUser(ctx.drizzle, request.receiverId),
       fetchUser(ctx.drizzle, ctx.userId),
     ]);
     // Derived
-    const groupLabel = leader?.isOutlaw ? "faction" : "clan";
+    const groupLabel = user?.isOutlaw ? "faction" : "clan";
     const isLeader = user.userId === fetchedClan?.leaderId;
     const isColeader = checkCoLeader(user.userId, fetchedClan);
     // Guards
     if (!fetchedClan) return errorResponse(`${groupLabel} not found`);
-    if (!leader) return errorResponse("Leader not found");
     if (!isLeader && !isColeader) {
       return errorResponse(
         `Only ${groupLabel} leader or co-leaders can reject requests`,
       );
     }

As per coding guidelines: "Minimize DB roundtrips by preferring to reduce the number of database roundtrips over reducing the amount of data fetched."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/server/api/routers/clan.ts` around lines 461 - 478, The extra
fetchUser call for leader in rejectRequest is unnecessary and causes an extra DB
roundtrip; remove the leader variable and its fetchUser call (drop
fetchUser(ctx.drizzle, request.receiverId) from the Promise.all alongside
fetchClanByLeader/fetchedClan and fetchUser(ctx.drizzle, ctx.userId)), derive
groupLabel from the acting user's isOutlaw (use user.isOutlaw instead of
leader?.isOutlaw), and delete the now-unreachable guard if (!leader); keep
existing checks using fetchedClan, user, isLeader (user.userId ===
fetchedClan?.leaderId) and isColeader (checkCoLeader) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/server/api/routers/clan.ts`:
- Around line 461-478: The extra fetchUser call for leader in rejectRequest is
unnecessary and causes an extra DB roundtrip; remove the leader variable and its
fetchUser call (drop fetchUser(ctx.drizzle, request.receiverId) from the
Promise.all alongside fetchClanByLeader/fetchedClan and fetchUser(ctx.drizzle,
ctx.userId)), derive groupLabel from the acting user's isOutlaw (use
user.isOutlaw instead of leader?.isOutlaw), and delete the now-unreachable guard
if (!leader); keep existing checks using fetchedClan, user, isLeader
(user.userId === fetchedClan?.leaderId) and isColeader (checkCoLeader) intact.

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.

1 participant