Some checks failed
CI/CD Pipeline / Discord Notification (Failure) (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Deployment Summary (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Portainer (push) Blocked by required conditions
CI/CD Pipeline / Discord Notification (Success) (push) Blocked by required conditions
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 1m20s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Has been cancelled
181 lines
5.1 KiB
TypeScript
181 lines
5.1 KiB
TypeScript
import {
|
|
Controller,
|
|
Post,
|
|
Get,
|
|
Body,
|
|
UseGuards,
|
|
HttpCode,
|
|
HttpStatus,
|
|
Logger,
|
|
Param,
|
|
} from '@nestjs/common';
|
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam } from '@nestjs/swagger';
|
|
import { InvitationService } from '../services/invitation.service';
|
|
import { CreateInvitationDto, InvitationResponseDto } from '../dto/invitation.dto';
|
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
|
import { RolesGuard } from '../guards/roles.guard';
|
|
import { Roles } from '../decorators/roles.decorator';
|
|
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
|
|
import { Public } from '../decorators/public.decorator';
|
|
import { UserRole } from '@domain/entities/user.entity';
|
|
|
|
/**
|
|
* Invitations Controller
|
|
*
|
|
* Handles user invitation endpoints:
|
|
* - POST /invitations - Create invitation (admin/manager)
|
|
* - GET /invitations/verify/:token - Verify invitation (public)
|
|
* - GET /invitations - List organization invitations (admin/manager)
|
|
*/
|
|
@ApiTags('Invitations')
|
|
@Controller('invitations')
|
|
export class InvitationsController {
|
|
private readonly logger = new Logger(InvitationsController.name);
|
|
|
|
constructor(private readonly invitationService: InvitationService) {}
|
|
|
|
/**
|
|
* Create invitation and send email
|
|
*/
|
|
@Post()
|
|
@HttpCode(HttpStatus.CREATED)
|
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
@Roles('admin', 'manager')
|
|
@ApiBearerAuth()
|
|
@ApiOperation({
|
|
summary: 'Create invitation',
|
|
description: 'Send an invitation email to a new user. Admin/manager only.',
|
|
})
|
|
@ApiResponse({
|
|
status: 201,
|
|
description: 'Invitation created successfully',
|
|
type: InvitationResponseDto,
|
|
})
|
|
@ApiResponse({
|
|
status: 403,
|
|
description: 'Forbidden - requires admin or manager role',
|
|
})
|
|
@ApiResponse({
|
|
status: 409,
|
|
description: 'Conflict - user or active invitation already exists',
|
|
})
|
|
async createInvitation(
|
|
@Body() dto: CreateInvitationDto,
|
|
@CurrentUser() user: UserPayload
|
|
): Promise<InvitationResponseDto> {
|
|
this.logger.log(`[User: ${user.email}] Creating invitation for: ${dto.email}`);
|
|
|
|
const invitation = await this.invitationService.createInvitation(
|
|
dto.email,
|
|
dto.firstName,
|
|
dto.lastName,
|
|
dto.role as unknown as UserRole,
|
|
user.organizationId,
|
|
user.id
|
|
);
|
|
|
|
return {
|
|
id: invitation.id,
|
|
token: invitation.token,
|
|
email: invitation.email,
|
|
firstName: invitation.firstName,
|
|
lastName: invitation.lastName,
|
|
role: invitation.role,
|
|
organizationId: invitation.organizationId,
|
|
expiresAt: invitation.expiresAt,
|
|
isUsed: invitation.isUsed,
|
|
usedAt: invitation.usedAt,
|
|
createdAt: invitation.createdAt,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Verify invitation token
|
|
*/
|
|
@Get('verify/:token')
|
|
@Public()
|
|
@ApiOperation({
|
|
summary: 'Verify invitation token',
|
|
description: 'Check if an invitation token is valid and not expired. Public endpoint.',
|
|
})
|
|
@ApiParam({
|
|
name: 'token',
|
|
description: 'Invitation token',
|
|
example: 'abc123def456',
|
|
})
|
|
@ApiResponse({
|
|
status: 200,
|
|
description: 'Invitation is valid',
|
|
type: InvitationResponseDto,
|
|
})
|
|
@ApiResponse({
|
|
status: 404,
|
|
description: 'Invitation not found',
|
|
})
|
|
@ApiResponse({
|
|
status: 400,
|
|
description: 'Invitation expired or already used',
|
|
})
|
|
async verifyInvitation(@Param('token') token: string): Promise<InvitationResponseDto> {
|
|
this.logger.log(`Verifying invitation token: ${token}`);
|
|
|
|
const invitation = await this.invitationService.verifyInvitation(token);
|
|
|
|
return {
|
|
id: invitation.id,
|
|
token: invitation.token,
|
|
email: invitation.email,
|
|
firstName: invitation.firstName,
|
|
lastName: invitation.lastName,
|
|
role: invitation.role,
|
|
organizationId: invitation.organizationId,
|
|
expiresAt: invitation.expiresAt,
|
|
isUsed: invitation.isUsed,
|
|
usedAt: invitation.usedAt,
|
|
createdAt: invitation.createdAt,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* List organization invitations
|
|
*/
|
|
@Get()
|
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
|
@Roles('admin', 'manager')
|
|
@ApiBearerAuth()
|
|
@ApiOperation({
|
|
summary: 'List invitations',
|
|
description: 'Get all invitations for the current organization. Admin/manager only.',
|
|
})
|
|
@ApiResponse({
|
|
status: 200,
|
|
description: 'Invitations retrieved successfully',
|
|
type: [InvitationResponseDto],
|
|
})
|
|
@ApiResponse({
|
|
status: 403,
|
|
description: 'Forbidden - requires admin or manager role',
|
|
})
|
|
async listInvitations(@CurrentUser() user: UserPayload): Promise<InvitationResponseDto[]> {
|
|
this.logger.log(`[User: ${user.email}] Listing invitations for organization`);
|
|
|
|
const invitations = await this.invitationService.getOrganizationInvitations(
|
|
user.organizationId
|
|
);
|
|
|
|
return invitations.map(invitation => ({
|
|
id: invitation.id,
|
|
token: invitation.token,
|
|
email: invitation.email,
|
|
firstName: invitation.firstName,
|
|
lastName: invitation.lastName,
|
|
role: invitation.role,
|
|
organizationId: invitation.organizationId,
|
|
expiresAt: invitation.expiresAt,
|
|
isUsed: invitation.isUsed,
|
|
usedAt: invitation.usedAt,
|
|
createdAt: invitation.createdAt,
|
|
}));
|
|
}
|
|
}
|