Context
Derived from the audit in #205. Depends on #228 (schema) and #230 (force-password-change gate).
The current POST /admin/teams/invitations/accept requires password and full_name from the recipient. This is replaced with a magic link flow — no credentials in email, no password form on acceptance. The recipient is logged straight in and forced to set their password on first use.
Also removes the now-redundant create-team and delete-team endpoints — there is one implicit admin team, it is seeded (see #228), and it is never deleted.
What's needed
1. TeamRole enum + DTO validation
Create src/modules/admin/teams/enums/team-role.enum.ts:
export enum TeamRole {
ADMIN = 'admin',
SUPER_ADMIN = 'super_admin',
OWNER = 'owner',
DEV = 'dev',
DESIGNER = 'designer',
}
Apply @IsEnum(TeamRole) to InviteMembersDto.role — remove the // TODO comment.
2. Refactor POST /admin/teams/invitations/accept
AcceptInviteDto — remove password and full_name entirely. Only token remains.
acceptInvite service method:
- New user path: auto-generate a 16-char random password (
crypto.randomBytes), hash and store it, set force_password_change = true
- Existing user path: skip account creation entirely — just assign role + membership
- Both paths: issue a short-lived (15 min) one-time JWT and return it so the frontend can log the user straight in
- Return
{ accessToken, accountCreated: boolean } so the frontend knows whether to show "welcome, set your password" vs "you've been added"
3. Remove redundant endpoints
From AdminTeamsController:
- Remove
GET /admin/teams (list teams)
- Remove
POST /admin/teams (create team)
- Remove
DELETE /admin/teams/:teamId (delete team)
Remove corresponding service methods, docs, and DTOs.
Acceptance criteria
Depends on
#228, #230
Context
Derived from the audit in #205. Depends on #228 (schema) and #230 (force-password-change gate).
The current
POST /admin/teams/invitations/acceptrequirespasswordandfull_namefrom the recipient. This is replaced with a magic link flow — no credentials in email, no password form on acceptance. The recipient is logged straight in and forced to set their password on first use.Also removes the now-redundant create-team and delete-team endpoints — there is one implicit admin team, it is seeded (see #228), and it is never deleted.
What's needed
1.
TeamRoleenum + DTO validationCreate
src/modules/admin/teams/enums/team-role.enum.ts:Apply
@IsEnum(TeamRole)toInviteMembersDto.role— remove the// TODOcomment.2. Refactor
POST /admin/teams/invitations/acceptAcceptInviteDto— removepasswordandfull_nameentirely. Onlytokenremains.acceptInviteservice method:crypto.randomBytes), hash and store it, setforce_password_change = true{ accessToken, accountCreated: boolean }so the frontend knows whether to show "welcome, set your password" vs "you've been added"3. Remove redundant endpoints
From
AdminTeamsController:GET /admin/teams(list teams)POST /admin/teams(create team)DELETE /admin/teams/:teamId(delete team)Remove corresponding service methods, docs, and DTOs.
Acceptance criteria
POST /admin/teams/invitations/acceptaccepts only{ token }— no password or name fieldsforce_password_change = trueand a random internal passwordInviteMembersDto.rolerejects any value outsideTeamRoleenumDepends on
#228, #230