Skip to content

fix(invite): fix duplicate invite insert on db#17

Merged
vitingr merged 2 commits into
mainfrom
fix/duplicate-invite
Mar 26, 2026
Merged

fix(invite): fix duplicate invite insert on db#17
vitingr merged 2 commits into
mainfrom
fix/duplicate-invite

Conversation

@medinavs

@medinavs medinavs commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Invite emails now validate that the invite exists before being sent, reducing failed or misdirected emails.
  • New Features

    • Clear error response for missing invites (returns not found).
    • Invite emails are dispatched using the invite token for more reliable delivery.

@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db189e65-dd58-4738-817b-95375022e3ec

📥 Commits

Reviewing files that changed from the base of the PR and between 88a5dec and ac9b1ed.

📒 Files selected for processing (1)
  • src/core/use-cases/email/send-invite/index.ts

📝 Walkthrough

Walkthrough

Controller and use-case refactor: the controller now passes an invite token to the send-invite use case; the use case fetches the invite by token, throws when missing, and sends the email using invite-derived fields.

Changes

Cohort / File(s) Summary
Exception Addition
src/core/domain/exceptions/invites.ts
Adds InviteNotFound domain exception extending ControllerError with default HTTP 404 status.
Use Case Refactor
src/core/use-cases/email/send-invite/index.ts, src/core/use-cases/email/send-invite/types.ts
SendInviteUseCasePayload now requires invite_token. execute loads invite via adminRepository.getInviteByToken(invite_token), throws InviteNotFound if absent, and derives email dispatch fields from the fetched invite.
Controller Update
src/adapters/inbound/http/controllers/admin/create-and-send-invite/index.ts
Controller changed email dispatch to call sendEmailUseCase.execute with { invite_token: response.inviteToken.token } instead of passing email and organization_id.

Sequence Diagram

sequenceDiagram
    participant Controller
    participant SendInviteUseCase
    participant AdminRepository
    participant EmailService

    Controller->>SendInviteUseCase: execute({ invite_token })
    SendInviteUseCase->>AdminRepository: getInviteByToken(invite_token)
    AdminRepository-->>SendInviteUseCase: invite / null
    alt invite == null
        SendInviteUseCase-->>Controller: throw InviteNotFound (404)
    else invite found
        SendInviteUseCase->>EmailService: sendEmail({ to: invite.email, organization_id: invite.organization_id, expires_at: invite.expires_at })
        EmailService-->>SendInviteUseCase: send result
        SendInviteUseCase-->>Controller: { invite_id, email, expires_at }
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I found a token, neat and small,
I hopped it to the mailer's hall.
Fetch the invite, then shout "Hooray!"
One token leads the tidy way. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title references fixing duplicate invite insertion in the database, but the actual changes refactor how invite email-sending works by switching from email/organization_id to invite_token lookup. Update the title to reflect the actual change: e.g., 'refactor(invite): use invite token for email sending' or similar, to accurately describe the refactored email dispatch mechanism.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/duplicate-invite

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/core/use-cases/email/send-invite/index.ts (1)

15-33: ⚠️ Potential issue | 🟠 Major

Inconsistent expires_at usage may mislead users.

Line 15 calculates a new expires_at (72 hours from now) and passes it to sendInviteEmail (line 27), but the return value uses invite.expires_at from the database (line 33). This inconsistency means:

  1. The email tells the user the invite expires 72 hours from the email send time
  2. The actual invite expiration is whatever was stored when the invite was created

If the invite was created earlier (or has a different expiration policy), the email will contain incorrect expiration information.

🛠️ Suggested fix: use the stored expiration consistently
 async execute({
   invite_token,
 }: SendInviteUseCasePayload): Promise<SendInviteUseCaseReturn> {
-  const expires_at = new Date(Date.now() + 1000 * 60 * 60 * 72)
-
   const invite = await this.adminRepository.getInviteByToken(invite_token)

   if (!invite) {
     throw new InviteNotFound()
   }

   await this.resendRepository.sendInviteEmail({
     to: invite.email,
     token: invite.token,
     organization_id: invite.organization_id,
-    expires_at
+    expires_at: invite.expires_at
   })

   return {
     invite_id: invite.id,
     email: invite.email,
     expires_at: invite.expires_at
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/use-cases/email/send-invite/index.ts` around lines 15 - 33, The code
computes a new expires_at and sends it in sendInviteEmail but returns
invite.expires_at, causing inconsistent expiration info; fix by using the stored
invite.expires_at from the invite returned by getInviteByToken for both the
email send and the return value (i.e., remove or stop using the newly computed
expires_at and pass invite.expires_at into resendRepository.sendInviteEmail and
the returned object), keeping references to invite, invite.expires_at,
sendInviteEmail, and getInviteByToken to locate the change.
🧹 Nitpick comments (1)
src/core/domain/exceptions/invites.ts (1)

1-7: Consider relocating ControllerError to the domain layer as part of a broader refactoring.

The domain exception InviteNotFound imports ControllerError from the adapter layer. While this follows the established pattern in the codebase (6+ other exception files use the same import), it does violate clean architecture principles where domain should not depend on adapters.

Moving ControllerError to @/core/domain/exceptions/base and updating all exception imports would be a valuable refactoring to align the architecture properly.

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

In `@src/core/domain/exceptions/invites.ts` around lines 1 - 7, The domain
exception InviteNotFound currently imports ControllerError from the adapter
layer; move the ControllerError class into the domain exceptions module (create
a new base file, e.g., a ControllerError or BaseDomainError in
`@/core/domain/exceptions/base`) and update InviteNotFound (and the other domain
exception files that import ControllerError) to import from that new domain
location; ensure the moved class preserves its API (constructor signature and
exports) so classes like InviteNotFound (and the 6+ other exception classes)
keep functioning unchanged while removing the dependency on the adapter layer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/core/use-cases/email/send-invite/index.ts`:
- Around line 15-33: The code computes a new expires_at and sends it in
sendInviteEmail but returns invite.expires_at, causing inconsistent expiration
info; fix by using the stored invite.expires_at from the invite returned by
getInviteByToken for both the email send and the return value (i.e., remove or
stop using the newly computed expires_at and pass invite.expires_at into
resendRepository.sendInviteEmail and the returned object), keeping references to
invite, invite.expires_at, sendInviteEmail, and getInviteByToken to locate the
change.

---

Nitpick comments:
In `@src/core/domain/exceptions/invites.ts`:
- Around line 1-7: The domain exception InviteNotFound currently imports
ControllerError from the adapter layer; move the ControllerError class into the
domain exceptions module (create a new base file, e.g., a ControllerError or
BaseDomainError in `@/core/domain/exceptions/base`) and update InviteNotFound (and
the other domain exception files that import ControllerError) to import from
that new domain location; ensure the moved class preserves its API (constructor
signature and exports) so classes like InviteNotFound (and the 6+ other
exception classes) keep functioning unchanged while removing the dependency on
the adapter layer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 654be0c7-ec83-45f5-a70d-80051a9dd456

📥 Commits

Reviewing files that changed from the base of the PR and between e7fc47a and 88a5dec.

📒 Files selected for processing (4)
  • src/adapters/inbound/http/controllers/admin/create-and-send-invite/index.ts
  • src/core/domain/exceptions/invites.ts
  • src/core/use-cases/email/send-invite/index.ts
  • src/core/use-cases/email/send-invite/types.ts

@vitingr vitingr merged commit daf3a91 into main Mar 26, 2026
2 checks passed
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.

2 participants