Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, Get } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { ApiTags, ApiOperation, ApiOkResponse } from '@nestjs/swagger'
import { AppService } from './app.service'
import { Public } from './auth/decorator'

Expand All @@ -9,6 +9,8 @@ export class AppController {
constructor(private readonly appService: AppService) {}

@Public()
@ApiOperation({ summary: 'Hello world endpoint' })
@ApiOkResponse({ description: 'Returns a greeting message' })
@Get()
getHello(): string {
return this.appService.getHello()
Expand Down
10 changes: 10 additions & 0 deletions src/audit-log/audit-log.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { Controller, Get, Query } from '@nestjs/common'
import {
ApiBearerAuth,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger'
import { Role } from 'generated/prisma/enums'
import { Roles, User } from 'src/auth/decorator'
import { AuditLogService } from './audit-log.service'
import { GetAuditLogsQueryDto } from './dto'

@ApiTags('Audit Logs')
@ApiBearerAuth()
@Controller('audit-logs')
export class AuditLogController {
constructor(private readonly auditLogService: AuditLogService) {}

@Roles(Role.SUPER_ADMIN, Role.ADMIN)
@ApiOperation({ summary: 'Get all audit logs' })
@ApiOkResponse({ description: 'List of audit logs retrieved successfully' })
@Get()
getAuditLogs(
@User('role') userRole: string,
Expand Down
24 changes: 24 additions & 0 deletions src/audit-log/dto/getAuditLogs.dto.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,57 @@
import { ApiPropertyOptional } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsDateString, IsEmail, IsIn, IsInt, IsOptional } from 'class-validator'
import { PaginationQueryDto } from 'src/common/pagination/dto'

export class GetAuditLogsQueryDto extends PaginationQueryDto {
@ApiPropertyOptional({
example: 'createdAt',
description: 'Field to order by',
})
@IsOptional()
@IsIn(['createdAt', 'type', 'requestUrl', 'requestMethod'])
orderBy?: string

@ApiPropertyOptional({
example: 'desc',
enum: ['asc', 'desc'],
description: 'Order direction',
})
@IsOptional()
@IsIn(['asc', 'desc'])
order?: 'asc' | 'desc'

@ApiPropertyOptional({
example: '2024-01-01',
description: 'From date',
})
@IsOptional()
@IsDateString()
from?: string

@ApiPropertyOptional({
example: '2024-12-31',
description: 'To date',
})
@IsOptional()
@IsDateString()
to?: string

@ApiPropertyOptional({ example: 'CREATE', description: 'Audit log type' })
@IsOptional()
@IsIn(['CREATE', 'UPDATE', 'DELETE', 'ERROR'])
type?: string

@ApiPropertyOptional({ example: 1, description: 'User ID' })
@Type(() => Number)
@IsOptional()
@IsInt()
userId?: string

@ApiPropertyOptional({
example: 'user@example.com',
description: 'User email',
})
@IsOptional()
@IsEmail()
userEmail?: string
Expand Down
32 changes: 31 additions & 1 deletion src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import {
Post,
UseGuards,
} from '@nestjs/common'
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'
import {
ApiBearerAuth,
ApiCreatedResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger'
import { OtpService } from 'src/otp/otp.service'
import { TokenService } from 'src/token/token.service'
import { CreateUserDto } from 'src/user/dto'
Expand All @@ -33,20 +39,28 @@ export class AuthController {
) {}

@Public()
@ApiOperation({ summary: 'Sign up new user' })
@ApiCreatedResponse({ description: 'User signed up successfully' })
@Post('signup')
signup(@Body() createUserDto: CreateUserDto) {
return this.authService.signup(createUserDto)
}

@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Login user' })
@ApiOkResponse({ description: 'User logged in successfully' })
@Post('login')
login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto)
}

@ApiBearerAuth()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Logout from all devices' })
@ApiOkResponse({
description: 'User logged out successfully from all devices',
})
@Post('logout-all')
logoutAll(@User('sub') userId: number) {
return this.authService.logoutFromAllDevices(userId)
Expand All @@ -56,6 +70,8 @@ export class AuthController {
@ApiBearerAuth()
@UseGuards(RefreshTokenGuard)
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Refresh access token' })
@ApiOkResponse({ description: 'Access token refreshed successfully' })
@Post('refresh-token')
refreshToken(
@User('sub') userId: number,
Expand All @@ -68,12 +84,16 @@ export class AuthController {
@ApiBearerAuth()
@UseGuards(RefreshTokenGuard)
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Revoke refresh token' })
@ApiOkResponse({ description: 'Refresh token revoked successfully' })
@Post('revoke-refresh-token')
revokeRefreshToken(@User('rtid') refreshTokenId: string) {
return this.tokenService.revokeRefreshToken(refreshTokenId)
}

@ApiBearerAuth()
@ApiOperation({ summary: 'Change user email' })
@ApiOkResponse({ description: 'Email changed successfully' })
@Patch('change-email')
changeEmail(
@User('email') oldEmail: string,
Expand All @@ -83,6 +103,8 @@ export class AuthController {
}

@ApiBearerAuth()
@ApiOperation({ summary: 'Change user password' })
@ApiOkResponse({ description: 'Password changed successfully' })
@Patch('change-password')
changePassword(
@User('email') email: string,
Expand All @@ -92,27 +114,35 @@ export class AuthController {
}

@Public()
@ApiOperation({ summary: 'Forgot password' })
@ApiOkResponse({ description: 'Password reset code sent to email' })
@Patch('forgot-password')
forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto) {
return this.authService.forgotPassword(forgotPasswordDto)
}

@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Send OTP to email' })
@ApiOkResponse({ description: 'OTP sent to email successfully' })
@Post('email-otp')
emailOtp(@Body() emailOtpDto: EmailOtpDto) {
return this.otpService.emailOtp(emailOtpDto.email)
}

@ApiBearerAuth()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Send OTP to authenticated user email' })
@ApiOkResponse({ description: 'OTP sent successfully' })
@Post('guarded-email-otp')
guardedEmailOtp(@User('email') email: string) {
return this.otpService.emailOtp(email, true)
}

@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Verify OTP' })
@ApiOkResponse({ description: 'OTP verified successfully' })
@Post('verify-otp')
verifyOtp(@Body() verifyOtpDto: VerifyOtpDto) {
return this.otpService.verifyOtp(verifyOtpDto)
Expand Down
3 changes: 3 additions & 0 deletions src/common/pagination/dto/pagination-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { ApiPropertyOptional } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsOptional, IsPositive } from 'class-validator'

export class PaginationQueryDto {
@ApiPropertyOptional({ example: 10, description: 'Number of items per page' })
@Type(() => Number)
@IsOptional()
@IsPositive()
limit?: number

@ApiPropertyOptional({ example: 1, description: 'Page number' })
@Type(() => Number)
@IsOptional()
@IsPositive()
Expand Down
10 changes: 10 additions & 0 deletions src/user/dto/get-users.dto.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { ApiPropertyOptional } from '@nestjs/swagger'
import { IsIn, IsOptional } from 'class-validator'
import { PaginationQueryDto } from 'src/common/pagination/dto'

export class GetUsersQueryDto extends PaginationQueryDto {
@ApiPropertyOptional({
example: 'firstName',
description: 'Field to order by',
})
@IsOptional()
@IsIn(['firstName', 'lastName'])
orderBy?: string

@ApiPropertyOptional({
example: 'asc',
enum: ['asc', 'desc'],
description: 'Order direction',
})
@IsOptional()
@IsIn(['asc', 'desc'])
order?: 'asc' | 'desc'
Expand Down
23 changes: 22 additions & 1 deletion src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
Post,
Query,
} from '@nestjs/common'
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'
import {
ApiBearerAuth,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger'
import { Role } from 'generated/prisma/enums'
import { Public, Roles, User } from 'src/auth/decorator'
import {
Expand All @@ -30,21 +35,29 @@ export class UserController {
constructor(private readonly userService: UserService) {}

@Roles(Role.MODERATOR, Role.ADMIN, Role.SUPER_ADMIN)
@ApiOperation({ summary: 'Get all users' })
@ApiOkResponse({ description: 'List of users retrieved successfully' })
@Get()
getAllUsers(@Query() getUsersQueryDto: GetUsersQueryDto) {
return this.userService.findAllUsers(getUsersQueryDto)
}

@ApiOperation({ summary: 'Get current user profile' })
@ApiOkResponse({ description: 'User profile retrieved successfully' })
@Get('me')
getMe(@User('sub') userId: number) {
return this.userService.findUserByUserId(userId)
}

@ApiOperation({ summary: 'Get user by ID' })
@ApiOkResponse({ description: 'User retrieved successfully' })
@Get(':id')
getUser(@Param('id', ParseIntPipe) id: number) {
return this.userService.findUserByUserId(id)
}

@ApiOperation({ summary: 'Update user profile' })
@ApiOkResponse({ description: 'User updated successfully' })
@Patch()
updateUser(
@User('sub') userId: number,
Expand All @@ -53,6 +66,8 @@ export class UserController {
return this.userService.updateUser(userId, updateUserDto)
}

@ApiOperation({ summary: 'Soft delete user' })
@ApiOkResponse({ description: 'User soft deleted successfully' })
@Delete()
deleteUser(
@User('email') email: string,
Expand All @@ -63,12 +78,16 @@ export class UserController {

@Public()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Reactivate user account' })
@ApiOkResponse({ description: 'User reactivated successfully' })
@Post('reactivate-user')
reactivateUser(@Body() reactivateUserDto: ReactivateUserDto) {
return this.userService.reactivateUser(reactivateUserDto)
}

// #remove this route (hard-delete), instead create user block and unblock routes (allow only super_admin/admin)
@ApiOperation({ summary: 'Hard delete user' })
@ApiOkResponse({ description: 'User permanently deleted successfully' })
@Delete('hard-delete')
hardDeleteUser(
@User('email') email: string,
Expand All @@ -78,6 +97,8 @@ export class UserController {
}

@Roles(Role.SUPER_ADMIN)
@ApiOperation({ summary: 'Change user role' })
@ApiOkResponse({ description: 'User role changed successfully' })
@Patch(':id/change-role')
changeUserRole(
@Param('id', ParseIntPipe) id: number,
Expand Down
Loading