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 { 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 { 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 { 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, })); } }