Skip to content

Feat/113 team invitation system - #208

Merged
mijinummi merged 2 commits into
MD-Creative-Production:mainfrom
ThatCodeBabe:feat/113-team-invitation-system
Jul 20, 2026
Merged

Feat/113 team invitation system#208
mijinummi merged 2 commits into
MD-Creative-Production:mainfrom
ThatCodeBabe:feat/113-team-invitation-system

Conversation

@ThatCodeBabe

Copy link
Copy Markdown
Contributor

Description

Implements the Team Invitation System described in #113: organization admins can invite new members by email with an assigned role, invitees accept via a secure single-use token, and invitations support expiration, revocation, resend, listing (paginated/filtered/sorted), and full audit logging.

The codebase had no Organizations Module, Users Module, or RBAC integration to build on top of (roles only ever lived in JWT claims, and organizationId elsewhere in the app was an unvalidated loose string with no backing table). Since the issue's own acceptance criteria require "accepted users are added to the correct organization with the assigned role," this PR adds minimal Organization and Membership Prisma models as a prerequisite. Organization CRUD itself stays out of scope, matching the issue — organizations/memberships are seeded directly via Prisma in tests, same as they'd need to be seeded in production until a dedicated Organizations module lands.

Key decisions, open to discussion in review:

  • Invitation/Membership roles reuse the existing global Role enum rather than introducing a second role vocabulary.
  • Expiration is derived and opportunistically persisted on read/write instead of a cron job, since @nestjs/schedule isn't a dependency in this repo yet.
  • POST /invitations/accept is public and creates a User record if the invitee doesn't have one yet; no session/JWT is issued, since this repo has no login/token-issuance code to hook into.
  • A new OrgAdminGuard enforces org-scoped ADMIN membership, since the existing RolesGuard only checks the platform-wide JWT role, not per-organization membership.

Design doc: docs/superpowers/specs/2026-07-20-team-invitation-system-design.md

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Related Issues

Fixes #113
Closes #113

Changes Made

  • Added Organization, Membership, Invitation, and InvitationAuditLog Prisma models, plus a migration
  • Added apps/backend/src/modules/invitations/ module: controller, services (InvitationsService, InvitationTokenService, InvitationAcceptanceService), repository, DTOs, entities, enums, and a new OrgAdminGuard
  • Endpoints: POST /invitations, GET /invitations, GET /invitations/:id, POST /invitations/accept, POST /invitations/:id/resend, PATCH /invitations/:id/revoke, DELETE /invitations/:id
  • Secure single-use invitation tokens (SHA-256 hash persisted, raw token returned once), mirroring the existing ApiKey key/keyHash pattern
  • Lazy/derived expiration handling driven by INVITATION_EXPIRATION_DAYS (default 7, documented in .env.example), with no new scheduler dependency
  • Delivery payload (org name, inviter name, role, invite link, expiry) assembled on create/resend, ready for a future email integration
  • Per-invitation audit log covering created/resent/accepted/revoked/expired events
  • Registered InvitationsModule in app.module.ts
  • Swagger/OpenAPI documentation on all endpoints
  • 51 new unit tests across DTO validation, token service, repository, both services, the controller, and the new guard

Testing

  • I have tested these changes locally
  • Tests pass locally (npm run test)
  • Linting passes (npm run lint)
  • Code is formatted (npm run format)
  • TypeScript builds successfully (npm run build)

Verified against a local Postgres instance: applied existing migrations, then applied and confirmed the new add_team_invitation_system migration runs cleanly and matches the generated Prisma client. Full backend suite: 292/292 tests passing, repo-wide lint 0 errors, format:check clean, build clean.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings or errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests passed with my changes

Screenshots (if applicable)

N/A — backend-only change.

Additional Context

This PR intentionally scopes down from the issue's original framing, which assumed pre-existing Organizations/Users/RBAC modules — none exist in this codebase. Rather than silently faking organizationId as another unvalidated string (as Incident currently does), I added the minimal real data model needed to make the acceptance criteria meaningful. Happy to adjust the Organization/Membership shape if maintainers are already planning a dedicated Organizations module with a different design.

…#113)

Implements invitation creation, secure single-use tokens, expiration
handling, revocation, resend, listing with pagination/filter/sort, and
audit logging, following the module/repository/service layout suggested
in the issue.

Since the codebase had no Organizations Module, Users Module, or RBAC
integration to build on top of (roles live only in JWT claims, and
"organizationId" elsewhere in the app is an unvalidated loose string
with no table behind it), this adds minimal Organization and
Membership Prisma models as a prerequisite so invitation acceptance
can actually associate a user with an org and role. Organization CRUD
itself stays out of scope, matching the issue - orgs/memberships used
in tests are seeded directly via Prisma, same as production usage
would be until a dedicated Organizations module lands.

Key decisions (open to discussion in review):
- Invitation/Membership roles reuse the existing global Role enum
  rather than introducing a parallel vocabulary.
- Expiration is derived and opportunistically persisted on read/write
  (no cron job), since @nestjs/schedule isn't a dependency here yet.
- POST /invitations/accept is public and creates a User record if the
  invitee doesn't have one yet; no session/JWT is issued since this
  repo has no login/token-issuance code to hook into.
- A new OrgAdminGuard enforces org-scoped ADMIN membership, since the
  existing RolesGuard only checks the platform-wide JWT role.

Design doc: docs/superpowers/specs/2026-07-20-team-invitation-system-design.md
@mijinummi
mijinummi merged commit 3080b1d into MD-Creative-Production:main Jul 20, 2026
13 checks passed
@grantfox-oss grantfox-oss Bot mentioned this pull request Jul 20, 2026
12 tasks
@mijinummi

Copy link
Copy Markdown
Collaborator

LGTM

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.

Create Team Invitation System

2 participants