import { Controller, Post, Get, Patch, Body, Req, Res, HttpCode, HttpStatus } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiCreatedResponse, ApiOkResponse, } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { AuthService } from './auth.service'; import { RegisterDto } from './dto/register.dto'; import { LoginDto } from './dto/login.dto'; import { UpdateProfileDto } from './dto/update-profile.dto'; import { AuthLogoutResponseDto, AuthProfileResponseDto, AuthTokenResponseDto, } from './dto/auth-response.dto'; import { CurrentUser } from './decorators/current-user.decorator'; import { Public } from './decorators/public.decorator'; import { JwtPayload } from './interfaces/jwt-payload.interface'; const REFRESH_COOKIE = 'refresh_token'; const COOKIE_OPTIONS = { httpOnly: true, sameSite: 'lax' as const, secure: process.env.NODE_ENV === 'production', path: '/api/v1/auth', maxAge: 7 * 24 * 60 * 60 * 1000, }; @ApiTags('Auth') @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} @Public() @Post('register') @ApiOperation({ summary: 'Register new user' }) @ApiCreatedResponse({ type: AuthTokenResponseDto }) async register(@Body() dto: RegisterDto, @Res({ passthrough: true }) res: Response) { const result = await this.authService.register(dto); res.cookie(REFRESH_COOKIE, result.refreshToken, COOKIE_OPTIONS); return { user: result.user, accessToken: result.accessToken }; } @Public() @Post('login') @ApiOperation({ summary: 'Login with email and password' }) @ApiCreatedResponse({ type: AuthTokenResponseDto }) async login(@Body() dto: LoginDto, @Res({ passthrough: true }) res: Response) { const result = await this.authService.login(dto); res.cookie(REFRESH_COOKIE, result.refreshToken, COOKIE_OPTIONS); return { user: result.user, accessToken: result.accessToken }; } @Public() @Post('refresh') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Refresh access token' }) @ApiOkResponse({ type: AuthTokenResponseDto }) async refresh(@Req() req: Request, @Res({ passthrough: true }) res: Response) { const token = req.cookies?.[REFRESH_COOKIE]; const result = await this.authService.refresh(token); res.cookie(REFRESH_COOKIE, result.refreshToken, COOKIE_OPTIONS); return { user: result.user, accessToken: result.accessToken }; } @Post('logout') @HttpCode(HttpStatus.OK) @ApiBearerAuth() @ApiOperation({ summary: 'Logout user' }) @ApiOkResponse({ type: AuthLogoutResponseDto }) async logout(@CurrentUser() user: JwtPayload, @Res({ passthrough: true }) res: Response) { await this.authService.logout(user.sub); res.clearCookie(REFRESH_COOKIE, { path: '/api/v1/auth' }); return { message: 'Logged out successfully' }; } @Get('me') @ApiBearerAuth() @ApiOperation({ summary: 'Get current user profile' }) @ApiOkResponse({ type: AuthProfileResponseDto }) async getProfile(@CurrentUser() user: JwtPayload) { return this.authService.getProfile(user.sub); } @Patch('me') @ApiBearerAuth() @ApiOperation({ summary: 'Update current user profile' }) @ApiOkResponse({ type: AuthProfileResponseDto }) async updateProfile(@CurrentUser() user: JwtPayload, @Body() dto: UpdateProfileDto) { return this.authService.updateProfile(user.sub, dto); } }