diff --git a/.env.example b/.env.example index 9536e88..4da90e0 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,11 @@ JWT_EXPIRATION=3600 # 1 hour in seconds XSECURITY_ENABLED=false XSECURITY_SECRET=your-XSecurity-secret +#throttle config +THROTTLE_TTL=10 +THROTTLE_LIMIT=10 + + # Database Config DB_HOST=localhost DB_PORT=5432 diff --git a/.vscode/settings.json b/.vscode/settings.json index 8d7ff77..a12e3b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "username": "postgres", "password": "" } - ] + ], + "typescript.tsdk": "node_modules\\typescript\\lib" } \ No newline at end of file diff --git a/README.md b/README.md index a90bb39..5ee4a08 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,7 @@ nest generate --help You can create custom CLI commands tailored to your specific needs using the [nestjs-command](https://www.npmjs.com/package/nestjs-command) package. This project already includes integration with [nestjs-command](https://www.npmjs.com/package/nestjs-command) package. -For reference, check out the `xsecurity` command implemented in [src/commands/xsecurity.command.ts](https://github.com/Innovix-Matrix-System/ims-nest-api-starter/blob/master/src/commands/xsecurity.command.ts). +For reference, check out the `xsecurity` command implemented in [src/commands/xsecurity.command.ts](https://github.com/Innovix-Matrix-Systems/ims-nest-api-starter/blob/main/src/commands/xsecurity.command.ts). ## Authors diff --git a/package-lock.json b/package-lock.json index 00d8f1e..271d889 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/terminus": "^10.2.3", + "@nestjs/throttler": "^6.2.1", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -34,7 +35,6 @@ "nestjs-command": "^3.1.4", "passport": "^0.7.0", "passport-jwt": "^4.0.1", - "passport-local": "^1.0.0", "pg": "^8.13.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", @@ -49,7 +49,6 @@ "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/passport-jwt": "^4.0.1", - "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.0.0", @@ -2191,6 +2190,16 @@ } } }, + "node_modules/@nestjs/throttler": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.2.1.tgz", + "integrity": "sha512-vdt6VjhKC6vcLBJRUb97IuR6Htykn5kokZzmT8+S5XFOLLjUF7rzRpr+nUOhK9pi1L0hhbzSf2v2FJl4v64EJA==", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2685,17 +2694,6 @@ "@types/passport-strategy": "*" } }, - "node_modules/@types/passport-local": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.38.tgz", - "integrity": "sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==", - "dev": true, - "dependencies": { - "@types/express": "*", - "@types/passport": "*", - "@types/passport-strategy": "*" - } - }, "node_modules/@types/passport-strategy": { "version": "0.2.38", "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", @@ -7924,17 +7922,6 @@ "passport-strategy": "^1.0.0" } }, - "node_modules/passport-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", - "dependencies": { - "passport-strategy": "1.x.x" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", diff --git a/package.json b/package.json index 679dc85..ba702b2 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/terminus": "^10.2.3", + "@nestjs/throttler": "^6.2.1", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -54,7 +55,6 @@ "nestjs-command": "^3.1.4", "passport": "^0.7.0", "passport-jwt": "^4.0.1", - "passport-local": "^1.0.0", "pg": "^8.13.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", @@ -69,7 +69,6 @@ "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/passport-jwt": "^4.0.1", - "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.0.0", diff --git a/src/app.module.ts b/src/app.module.ts index b662e58..413b260 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,17 +1,19 @@ import { MikroOrmModule } from '@mikro-orm/nestjs'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { APP_GUARD } from '@nestjs/core'; +import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; import { CommandModule } from 'nestjs-command'; import { AppController } from './app.controller'; -import { AuthModule } from './auth/auth.module'; import { XSecureInstallCommand } from './commands/xsecurity.command'; import mikroOrmConfig from './config/mikro-orm.config'; -import { HealthModule } from './health/health.module'; import { XSecurityMiddleware } from './middlewares/xsecurity.middleware'; -import { MiscModule } from './misc/misc.module'; -import { PermissionModule } from './permission/permission.module'; -import { RoleModule } from './role/role.module'; -import { UserModule } from './user/user.module'; +import { AuthModule } from './modules/auth/auth.module'; +import { HealthModule } from './modules/health/health.module'; +import { MiscModule } from './modules/misc/misc.module'; +import { PermissionModule } from './modules/permission/permission.module'; +import { RoleModule } from './modules/role/role.module'; +import { UserModule } from './modules/user/user.module'; @Module({ imports: [ @@ -22,9 +24,19 @@ import { UserModule } from './user/user.module'; }), MikroOrmModule.forRootAsync({ imports: [ConfigModule], + inject: [ConfigService], useFactory: (configService: ConfigService) => mikroOrmConfig(configService), + }), + ThrottlerModule.forRootAsync({ + imports: [ConfigModule], inject: [ConfigService], + useFactory: (configService: ConfigService) => [ + { + ttl: configService.get('THROTTLE_TTL'), + limit: configService.get('THROTTLE_LIMIT'), + }, + ], }), CommandModule, HealthModule, @@ -35,9 +47,14 @@ import { UserModule } from './user/user.module'; AuthModule, ], controllers: [AppController], - providers: [XSecureInstallCommand], + providers: [ + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, + XSecureInstallCommand, + ], }) -// export class AppModule {} export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(XSecurityMiddleware).forRoutes('*'); diff --git a/src/auth/strategies/local.strategy.ts b/src/auth/strategies/local.strategy.ts deleted file mode 100644 index 1ed5ff3..0000000 --- a/src/auth/strategies/local.strategy.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; -import { Strategy } from 'passport-local'; -import { AuthService } from '../auth.service'; -import { LoginDto } from '../dto/login.dto'; - -@Injectable() -export class LocalStrategy extends PassportStrategy(Strategy) { - constructor(private authService: AuthService) { - super({ usernameField: 'email' }); // Use 'email' instead of 'username' - } - - async validate(email: string, password: string): Promise { - const loginDto = plainToInstance(LoginDto, { email, password }); - const validationErrors = await validate(loginDto); - if (validationErrors.length > 0) { - const errorMessages = validationErrors.flatMap((error) => - Object.values(error.constraints || {}), - ); - throw new BadRequestException(errorMessages); - } - const validateUserData: ValidateUserResponse = - await this.authService.validateUser(email, password); - const { user, errors } = validateUserData; - if (!user) { - throw new BadRequestException(errors); - } - return user; - } -} diff --git a/src/common/controllers/base.controller.ts b/src/common/controllers/base.controller.ts index 0d5fe9d..882b12d 100644 --- a/src/common/controllers/base.controller.ts +++ b/src/common/controllers/base.controller.ts @@ -1,4 +1,4 @@ -import { Res } from '@nestjs/common'; +import { HttpStatus, Res } from '@nestjs/common'; import { Response } from 'express'; export class BaseController { @@ -6,7 +6,7 @@ export class BaseController { data: any[], pageData: PaginatedMeta, message = '', - statusCode = 200, + statusCode = HttpStatus.OK, res: Response, ) { const response = { @@ -31,7 +31,7 @@ export class BaseController { sendSuccessResponse( result: any, message = '', - statusCode = 200, + statusCode = HttpStatus.OK, @Res() res: Response, ) { const response = { @@ -47,7 +47,7 @@ export class BaseController { sendErrorResponse( error: string, errorMessages = [], - statusCode = 500, + statusCode = HttpStatus.INTERNAL_SERVER_ERROR, @Res() res: Response, ) { if (errorMessages.length === 0) { diff --git a/src/decorators/auth-user.decorator.ts b/src/common/decorators/auth-user.decorator.ts similarity index 100% rename from src/decorators/auth-user.decorator.ts rename to src/common/decorators/auth-user.decorator.ts diff --git a/src/decorators/confirm-password.decorator.ts b/src/common/decorators/confirm-password.decorator.ts similarity index 100% rename from src/decorators/confirm-password.decorator.ts rename to src/common/decorators/confirm-password.decorator.ts diff --git a/src/decorators/permissions.decorator.ts b/src/common/decorators/permissions.decorator.ts similarity index 70% rename from src/decorators/permissions.decorator.ts rename to src/common/decorators/permissions.decorator.ts index 638aad1..a616681 100644 --- a/src/decorators/permissions.decorator.ts +++ b/src/common/decorators/permissions.decorator.ts @@ -1,5 +1,5 @@ import { SetMetadata } from '@nestjs/common'; -import { PermissionName } from '../enum/permission.enum'; +import { PermissionName } from '../../enums/permission.enum'; export const Permissions = (...permissions: string[]) => SetMetadata(PermissionName.PERMISSIONS_KEY, permissions); diff --git a/src/decorators/user-email-unique.decorator.ts b/src/common/decorators/user-email-unique.decorator.ts similarity index 91% rename from src/decorators/user-email-unique.decorator.ts rename to src/common/decorators/user-email-unique.decorator.ts index d808360..c18bfd2 100644 --- a/src/decorators/user-email-unique.decorator.ts +++ b/src/common/decorators/user-email-unique.decorator.ts @@ -6,7 +6,7 @@ import { ValidatorConstraintInterface, registerDecorator, } from 'class-validator'; -import { UserService } from '../user/user.service'; +import { UserService } from '../../modules/user/user.service'; @Injectable() @ValidatorConstraint({ name: 'isUserEmailUnique', async: true }) diff --git a/src/decorators/valid-permission.decorator.ts b/src/common/decorators/valid-permission.decorator.ts similarity index 91% rename from src/decorators/valid-permission.decorator.ts rename to src/common/decorators/valid-permission.decorator.ts index 81bd801..2555090 100644 --- a/src/decorators/valid-permission.decorator.ts +++ b/src/common/decorators/valid-permission.decorator.ts @@ -7,7 +7,7 @@ import { ValidatorConstraintInterface, registerDecorator, } from 'class-validator'; -import { Permission } from '../entities/permission.entity'; +import { Permission } from '../../modules/permission/entities/permission.entity'; @Injectable() @ValidatorConstraint({ name: 'isValidPermissions', async: true }) diff --git a/src/decorators/valid-roles.decorator.ts b/src/common/decorators/valid-roles.decorator.ts similarity index 91% rename from src/decorators/valid-roles.decorator.ts rename to src/common/decorators/valid-roles.decorator.ts index 16d4bb6..fa02a92 100644 --- a/src/decorators/valid-roles.decorator.ts +++ b/src/common/decorators/valid-roles.decorator.ts @@ -7,7 +7,7 @@ import { ValidatorConstraintInterface, registerDecorator, } from 'class-validator'; -import { Role } from '../entities/role.entity'; +import { Role } from '../../modules/role/entities/role.entity'; @Injectable() @ValidatorConstraint({ name: 'isValidRoles', async: true }) diff --git a/src/filters/validation-exception.filter.ts b/src/common/filters/validation-exception.filter.ts similarity index 94% rename from src/filters/validation-exception.filter.ts rename to src/common/filters/validation-exception.filter.ts index 072a109..4dd4009 100644 --- a/src/filters/validation-exception.filter.ts +++ b/src/common/filters/validation-exception.filter.ts @@ -13,7 +13,6 @@ export class ValidationExceptionFilter implements ExceptionFilter { const response = ctx.getResponse(); const status = exception.getStatus(); const exceptionResponse: any = exception.getResponse(); - console.log(exceptionResponse); if (!Array.isArray(exceptionResponse?.message)) { response.status(status).json({ statusCode: status, diff --git a/src/common/guards/permission.guard.ts b/src/common/guards/permission.guard.ts index fb419a9..8ae6179 100644 --- a/src/common/guards/permission.guard.ts +++ b/src/common/guards/permission.guard.ts @@ -1,6 +1,6 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { UserService } from '../../user/user.service'; +import { UserService } from '../../modules/user/user.service'; @Injectable() export class PermissionGuard implements CanActivate { diff --git a/src/database/factories/user.factory.ts b/src/database/factories/user.factory.ts index 0222627..19c7723 100644 --- a/src/database/factories/user.factory.ts +++ b/src/database/factories/user.factory.ts @@ -1,7 +1,7 @@ import { faker } from '@faker-js/faker'; import { Factory } from '@mikro-orm/seeder'; import * as bcrypt from 'bcrypt'; -import { User } from '../../entities/user.entity'; +import { User } from '../../modules/user/entities/user.entity'; export class UserFactory extends Factory { model = User; diff --git a/src/database/seeders/PermissionSeeder.ts b/src/database/seeders/PermissionSeeder.ts index 348462f..237de15 100644 --- a/src/database/seeders/PermissionSeeder.ts +++ b/src/database/seeders/PermissionSeeder.ts @@ -1,6 +1,6 @@ import type { EntityManager } from '@mikro-orm/core'; import { Seeder } from '@mikro-orm/seeder'; -import { Permission } from '../../entities/permission.entity'; +import { Permission } from '../../modules/permission/entities/permission.entity'; import { getAllPermissions } from '../../utils/permission.helper'; export class PermissionSeeder extends Seeder { diff --git a/src/database/seeders/RoleSeeder.ts b/src/database/seeders/RoleSeeder.ts index b99eb6a..d6b76e7 100644 --- a/src/database/seeders/RoleSeeder.ts +++ b/src/database/seeders/RoleSeeder.ts @@ -1,8 +1,8 @@ import type { EntityManager, EntityRepository } from '@mikro-orm/core'; import { Seeder } from '@mikro-orm/seeder'; -import { Permission } from '../../entities/permission.entity'; -import { Role } from '../../entities/role.entity'; -import { RoleName } from '../../enum/role.enum'; +import { RoleName } from '../../enums/role.enum'; +import { Permission } from '../../modules/permission/entities/permission.entity'; +import { Role } from '../../modules/role/entities/role.entity'; export class RoleSeeder extends Seeder { async run(em: EntityManager): Promise { diff --git a/src/database/seeders/UserSeeder.ts b/src/database/seeders/UserSeeder.ts index 7f5c379..a8dc78e 100644 --- a/src/database/seeders/UserSeeder.ts +++ b/src/database/seeders/UserSeeder.ts @@ -1,8 +1,8 @@ import type { EntityManager, EntityRepository } from '@mikro-orm/core'; import { Seeder } from '@mikro-orm/seeder'; -import { Role } from '../../entities/role.entity'; -import { User } from '../../entities/user.entity'; import * as bcrypt from 'bcrypt'; +import { User } from '../../modules/user/entities/user.entity'; +import { Role } from '../../modules/role/entities/role.entity'; export class UserSeeder extends Seeder { async run(em: EntityManager): Promise { diff --git a/src/enum/permission.enum.ts b/src/enums/permission.enum.ts similarity index 100% rename from src/enum/permission.enum.ts rename to src/enums/permission.enum.ts diff --git a/src/enum/role.enum.ts b/src/enums/role.enum.ts similarity index 100% rename from src/enum/role.enum.ts rename to src/enums/role.enum.ts diff --git a/src/middlewares/xsecurity.middleware.ts b/src/middlewares/xsecurity.middleware.ts index f9fa084..6441d1f 100644 --- a/src/middlewares/xsecurity.middleware.ts +++ b/src/middlewares/xsecurity.middleware.ts @@ -4,6 +4,7 @@ import { Injectable, NestMiddleware, } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import * as crypto from 'crypto'; import { NextFunction, Request, Response } from 'express'; @@ -16,8 +17,10 @@ export class XSecurityMiddleware implements NestMiddleware { { count: number; resetTime: number } >(); + constructor(private configService: ConfigService) {} + use(req: Request, res: Response, next: NextFunction) { - if (process.env.XSECURITY_ENABLED !== 'true') { + if (this.configService.get('XSECURITY_ENABLED') !== 'true') { return next(); } @@ -66,7 +69,7 @@ export class XSecurityMiddleware implements NestMiddleware { } private isValidXSecureToken(signedToken: string): boolean { - const sharedSecretKey = process.env.XSECURITY_SECRET; + const sharedSecretKey = this.configService.get('XSECURITY_SECRET'); const parts = signedToken.split('.'); if (parts.length !== 2) return false; diff --git a/src/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts similarity index 90% rename from src/auth/auth.controller.spec.ts rename to src/modules/auth/auth.controller.spec.ts index 6c149a8..565ba2d 100644 --- a/src/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -1,4 +1,4 @@ -import { BadRequestException } from '@nestjs/common'; +import { BadRequestException, HttpStatus } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Response } from 'express'; import { AuthController } from './auth.controller'; @@ -61,11 +61,11 @@ describe('AuthController', () => { loginDto.email, loginDto.password, ); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: mockAuthData, message: 'Logged in successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); diff --git a/src/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts similarity index 64% rename from src/auth/auth.controller.ts rename to src/modules/auth/auth.controller.ts index c033ad3..ed6ace8 100644 --- a/src/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -1,6 +1,13 @@ -import { Body, Controller, Post, Res, UseFilters } from '@nestjs/common'; -import { BaseController } from '../common/controllers/base.controller'; -import { ValidationExceptionFilter } from '../filters/validation-exception.filter'; +import { + Body, + Controller, + HttpStatus, + Post, + Res, + UseFilters, +} from '@nestjs/common'; +import { BaseController } from '../../common/controllers/base.controller'; +import { ValidationExceptionFilter } from '../../common/filters/validation-exception.filter'; import { AuthService } from './auth.service'; import { LoginDto } from './dto/login.dto'; @@ -20,7 +27,7 @@ export class AuthController extends BaseController { return this.sendSuccessResponse( authData, 'Logged in successfully', - 200, + HttpStatus.OK, res, ); } diff --git a/src/auth/auth.module.ts b/src/modules/auth/auth.module.ts similarity index 86% rename from src/auth/auth.module.ts rename to src/modules/auth/auth.module.ts index dd7ae29..e35d047 100644 --- a/src/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -6,7 +6,6 @@ import { UserModule } from '../user/user.module'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { JwtStrategy } from './strategies/jwt.strategy'; -import { LocalStrategy } from './strategies/local.strategy'; @Module({ imports: [ @@ -23,7 +22,7 @@ import { LocalStrategy } from './strategies/local.strategy'; MiscModule, UserModule, ], - providers: [ConfigService, AuthService, JwtStrategy, LocalStrategy], + providers: [ConfigService, AuthService, JwtStrategy], controllers: [AuthController], }) export class AuthModule {} diff --git a/src/auth/auth.service.spec.ts b/src/modules/auth/auth.service.spec.ts similarity index 90% rename from src/auth/auth.service.spec.ts rename to src/modules/auth/auth.service.spec.ts index b7f3f2c..13b938c 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/modules/auth/auth.service.spec.ts @@ -3,13 +3,12 @@ import { getRepositoryToken } from '@mikro-orm/nestjs'; import { ConfigService } from '@nestjs/config'; import { JwtModule } from '@nestjs/jwt'; import { Test, TestingModule } from '@nestjs/testing'; -import { User } from '../entities/user.entity'; -import { PasswordService } from '../misc/password.service'; +import { MiscModule } from '../misc/misc.module'; +import { User } from '../user/entities/user.entity'; import { UserTransformer } from '../user/transformer/user.transformer'; import { UserService } from '../user/user.service'; import { AuthService } from './auth.service'; import { JwtStrategy } from './strategies/jwt.strategy'; -import { LocalStrategy } from './strategies/local.strategy'; describe('AuthService', () => { let service: AuthService; @@ -61,12 +60,11 @@ describe('AuthService', () => { secret: 'test-secret', signOptions: { expiresIn: '1h' }, }), + MiscModule, ], providers: [ AuthService, UserService, - PasswordService, - LocalStrategy, UserTransformer, { provide: JwtStrategy, @@ -75,8 +73,8 @@ describe('AuthService', () => { inject: [ConfigService], }, { provide: ConfigService, useValue: mockConfigService }, - { provide: getRepositoryToken(User), useValue: mockUserRepository }, { provide: EntityManager, useValue: mockEntityManager }, + { provide: getRepositoryToken(User), useValue: mockUserRepository }, ], }).compile(); diff --git a/src/auth/auth.service.ts b/src/modules/auth/auth.service.ts similarity index 100% rename from src/auth/auth.service.ts rename to src/modules/auth/auth.service.ts diff --git a/src/auth/dto/login.dto.ts b/src/modules/auth/dto/login.dto.ts similarity index 100% rename from src/auth/dto/login.dto.ts rename to src/modules/auth/dto/login.dto.ts diff --git a/src/auth/guards/local-auth.guard.ts b/src/modules/auth/guards/local-auth.guard.ts similarity index 100% rename from src/auth/guards/local-auth.guard.ts rename to src/modules/auth/guards/local-auth.guard.ts diff --git a/src/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts similarity index 100% rename from src/auth/strategies/jwt.strategy.ts rename to src/modules/auth/strategies/jwt.strategy.ts diff --git a/src/modules/auth/types.d.ts b/src/modules/auth/types.d.ts new file mode 100644 index 0000000..0a6f33b --- /dev/null +++ b/src/modules/auth/types.d.ts @@ -0,0 +1,13 @@ +interface JwtEncodeData { + email: string; + sub: number; +} + +interface LoginResponse extends UserResponse { + AccessToken: string; +} + +interface ValidateUserResponse { + user: Partial; + errors: string[]; +} diff --git a/src/health/health.controller.ts b/src/modules/health/health.controller.ts similarity index 100% rename from src/health/health.controller.ts rename to src/modules/health/health.controller.ts diff --git a/src/health/health.module.ts b/src/modules/health/health.module.ts similarity index 100% rename from src/health/health.module.ts rename to src/modules/health/health.module.ts diff --git a/src/misc/misc.module.ts b/src/modules/misc/misc.module.ts similarity index 100% rename from src/misc/misc.module.ts rename to src/modules/misc/misc.module.ts diff --git a/src/misc/password.service.spec.ts b/src/modules/misc/password.service.spec.ts similarity index 100% rename from src/misc/password.service.spec.ts rename to src/modules/misc/password.service.spec.ts diff --git a/src/misc/password.service.ts b/src/modules/misc/password.service.ts similarity index 100% rename from src/misc/password.service.ts rename to src/modules/misc/password.service.ts diff --git a/src/entities/permission.entity.ts b/src/modules/permission/entities/permission.entity.ts similarity index 82% rename from src/entities/permission.entity.ts rename to src/modules/permission/entities/permission.entity.ts index dc553e4..a7ae51f 100644 --- a/src/entities/permission.entity.ts +++ b/src/modules/permission/entities/permission.entity.ts @@ -7,8 +7,8 @@ import { Property, } from '@mikro-orm/core'; import { PermissionRepository } from '../repositories/permission.repository'; -import { Role } from './role.entity'; -import { User } from './user.entity'; +import { Role } from '../../role/entities/role.entity'; +import { User } from '../../user/entities/user.entity'; @Entity({ repository: () => PermissionRepository }) export class Permission { diff --git a/src/permission/permission.controller.spec.ts b/src/modules/permission/permission.controller.spec.ts similarity index 83% rename from src/permission/permission.controller.spec.ts rename to src/modules/permission/permission.controller.spec.ts index 3415d0a..b93782e 100644 --- a/src/permission/permission.controller.spec.ts +++ b/src/modules/permission/permission.controller.spec.ts @@ -1,9 +1,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Response } from 'express'; -import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; import { PermissionController } from './permission.controller'; import { PermissionService } from './permission.service'; -import { Permission } from '../entities/permission.entity'; +import { Permission } from './entities/permission.entity'; +import { HttpStatus } from '@nestjs/common'; describe('PermissionController', () => { let controller: PermissionController; @@ -55,11 +56,11 @@ describe('PermissionController', () => { await controller.findAll(mockResponse); expect(permissionService.findAll).toHaveBeenCalled(); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: mockPermissions, message: 'Permissions fetched successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); diff --git a/src/permission/permission.controller.ts b/src/modules/permission/permission.controller.ts similarity index 66% rename from src/permission/permission.controller.ts rename to src/modules/permission/permission.controller.ts index f33b752..5f6a66a 100644 --- a/src/permission/permission.controller.ts +++ b/src/modules/permission/permission.controller.ts @@ -1,8 +1,8 @@ -import { Controller, Get, Res, UseGuards } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Res, UseGuards } from '@nestjs/common'; import { Response } from 'express'; -import { BaseController } from '../common/controllers/base.controller'; +import { BaseController } from '../../common/controllers/base.controller'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; import { PermissionService } from './permission.service'; -import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; @Controller('permission') export class PermissionController extends BaseController { @@ -17,7 +17,7 @@ export class PermissionController extends BaseController { return this.sendSuccessResponse( permissions, 'Permissions fetched successfully', - 200, + HttpStatus.OK, res, ); } diff --git a/src/permission/permission.module.ts b/src/modules/permission/permission.module.ts similarity index 86% rename from src/permission/permission.module.ts rename to src/modules/permission/permission.module.ts index 6977527..bb5c27c 100644 --- a/src/permission/permission.module.ts +++ b/src/modules/permission/permission.module.ts @@ -2,7 +2,7 @@ import { MikroOrmModule } from '@mikro-orm/nestjs'; import { Module } from '@nestjs/common'; import { PermissionController } from './permission.controller'; import { PermissionService } from './permission.service'; -import { Permission } from '../entities/permission.entity'; +import { Permission } from './entities/permission.entity'; @Module({ imports: [MikroOrmModule.forFeature([Permission])], diff --git a/src/permission/permission.service.spec.ts b/src/modules/permission/permission.service.spec.ts similarity index 93% rename from src/permission/permission.service.spec.ts rename to src/modules/permission/permission.service.spec.ts index 8cfbc3f..c5bdc9d 100644 --- a/src/permission/permission.service.spec.ts +++ b/src/modules/permission/permission.service.spec.ts @@ -1,6 +1,6 @@ import { EntityManager, EntityRepository } from '@mikro-orm/core'; import { Test, TestingModule } from '@nestjs/testing'; -import { Permission } from '../entities/permission.entity'; +import { Permission } from './entities/permission.entity'; import { PermissionService } from './permission.service'; import { getRepositoryToken } from '@mikro-orm/nestjs'; diff --git a/src/permission/permission.service.ts b/src/modules/permission/permission.service.ts similarity index 86% rename from src/permission/permission.service.ts rename to src/modules/permission/permission.service.ts index 3089e4d..99d0065 100644 --- a/src/permission/permission.service.ts +++ b/src/modules/permission/permission.service.ts @@ -1,6 +1,6 @@ import { InjectRepository } from '@mikro-orm/nestjs'; import { Injectable } from '@nestjs/common'; -import { Permission } from '../entities/permission.entity'; +import { Permission } from './entities/permission.entity'; import { EntityManager, EntityRepository } from '@mikro-orm/core'; @Injectable() diff --git a/src/repositories/permission.repository.ts b/src/modules/permission/repositories/permission.repository.ts similarity index 100% rename from src/repositories/permission.repository.ts rename to src/modules/permission/repositories/permission.repository.ts diff --git a/src/role/dto/role-create.dto.ts b/src/modules/role/dto/role-create.dto.ts similarity index 100% rename from src/role/dto/role-create.dto.ts rename to src/modules/role/dto/role-create.dto.ts diff --git a/src/entities/role.entity.ts b/src/modules/role/entities/role.entity.ts similarity index 81% rename from src/entities/role.entity.ts rename to src/modules/role/entities/role.entity.ts index 19ba786..d1167c0 100644 --- a/src/entities/role.entity.ts +++ b/src/modules/role/entities/role.entity.ts @@ -7,9 +7,9 @@ import { Property, } from '@mikro-orm/core'; import { Exclude } from 'class-transformer'; +import { Permission } from '../../permission/entities/permission.entity'; +import { User } from '../../user/entities/user.entity'; import { RoleRepository } from '../repositories/role.repository'; -import { Permission } from './permission.entity'; -import { User } from './user.entity'; @Entity({ repository: () => RoleRepository }) export class Role { diff --git a/src/repositories/role.repository.ts b/src/modules/role/repositories/role.repository.ts similarity index 100% rename from src/repositories/role.repository.ts rename to src/modules/role/repositories/role.repository.ts diff --git a/src/role/role.controller.spec.ts b/src/modules/role/role.controller.spec.ts similarity index 85% rename from src/role/role.controller.spec.ts rename to src/modules/role/role.controller.spec.ts index 0c7cbe9..d4d262d 100644 --- a/src/role/role.controller.spec.ts +++ b/src/modules/role/role.controller.spec.ts @@ -2,12 +2,13 @@ import { Collection } from '@mikro-orm/core'; import { Reflector } from '@nestjs/core'; import { Test, TestingModule } from '@nestjs/testing'; import { Response } from 'express'; -import { PermissionGuard } from '../common/guards/permission.guard'; -import { Role } from '../entities/role.entity'; +import { PermissionGuard } from '../../common/guards/permission.guard'; +import { Role } from './entities/role.entity'; import { UserService } from '../user/user.service'; import { CreateRoleDto } from './dto/role-create.dto'; import { RoleController } from './role.controller'; import { RoleService } from './role.service'; +import { HttpStatus } from '@nestjs/common'; describe('RoleController', () => { let controller: RoleController; @@ -73,11 +74,11 @@ describe('RoleController', () => { await controller.findAll(mockResponse); expect(roleService.findAll).toHaveBeenCalled(); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: mockRoles, message: 'Roles fetched successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -104,11 +105,11 @@ describe('RoleController', () => { await controller.createRole(createRoleDto, mockResponse); expect(roleService.create).toHaveBeenCalledWith(createRoleDto); - expect(mockResponse.status).toHaveBeenCalledWith(201); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.CREATED); expect(mockResponse.json).toHaveBeenCalledWith({ data: createdRole, message: 'Role created successfully', - statusCode: 201, + statusCode: HttpStatus.CREATED, success: true, }); }); @@ -125,6 +126,6 @@ describe('RoleController', () => { await controller.deleteRole(roleId, mockResponse); expect(roleService.delete).toHaveBeenCalledWith(1); - expect(mockResponse.status).toHaveBeenCalledWith(204); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.NO_CONTENT); }); }); diff --git a/src/role/role.controller.ts b/src/modules/role/role.controller.ts similarity index 73% rename from src/role/role.controller.ts rename to src/modules/role/role.controller.ts index 12a600e..bb8e704 100644 --- a/src/role/role.controller.ts +++ b/src/modules/role/role.controller.ts @@ -3,19 +3,20 @@ import { Controller, Delete, Get, + HttpStatus, Param, Post, Res, UseGuards, } from '@nestjs/common'; import { Response } from 'express'; -import { BaseController } from '../common/controllers/base.controller'; -import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; -import { PermissionGuard } from '../common/guards/permission.guard'; -import { Permissions } from '../decorators/permissions.decorator'; -import { PermissionName } from '../enum/permission.enum'; +import { BaseController } from '../../common/controllers/base.controller'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { PermissionGuard } from '../../common/guards/permission.guard'; +import { Permissions } from '../../common/decorators/permissions.decorator'; import { CreateRoleDto } from './dto/role-create.dto'; import { RoleService } from './role.service'; +import { PermissionName } from '../../enums/permission.enum'; @Controller('role') export class RoleController extends BaseController { @@ -31,7 +32,7 @@ export class RoleController extends BaseController { return this.sendSuccessResponse( roles, 'Roles fetched successfully', - 200, + HttpStatus.OK, res, ); } @@ -44,7 +45,7 @@ export class RoleController extends BaseController { return this.sendSuccessResponse( role, 'Role created successfully', - 201, + HttpStatus.CREATED, res, ); } @@ -57,7 +58,7 @@ export class RoleController extends BaseController { return this.sendSuccessResponse( null, 'Role deleted successfully', - 204, + HttpStatus.NO_CONTENT, res, ); } diff --git a/src/role/role.module.ts b/src/modules/role/role.module.ts similarity index 80% rename from src/role/role.module.ts rename to src/modules/role/role.module.ts index 5671e2f..0eb880b 100644 --- a/src/role/role.module.ts +++ b/src/modules/role/role.module.ts @@ -1,10 +1,10 @@ import { MikroOrmModule } from '@mikro-orm/nestjs'; import { Module } from '@nestjs/common'; -import { Role } from '../entities/role.entity'; -import { User } from '../entities/user.entity'; import { UserModule } from '../user/user.module'; import { RoleController } from './role.controller'; import { RoleService } from './role.service'; +import { User } from '../user/entities/user.entity'; +import { Role } from './entities/role.entity'; @Module({ imports: [MikroOrmModule.forFeature([User, Role]), UserModule], diff --git a/src/role/role.service.spec.ts b/src/modules/role/role.service.spec.ts similarity index 92% rename from src/role/role.service.spec.ts rename to src/modules/role/role.service.spec.ts index 9031eb9..7d4d3ed 100644 --- a/src/role/role.service.spec.ts +++ b/src/modules/role/role.service.spec.ts @@ -2,10 +2,10 @@ import { Collection, EntityManager, EntityRepository } from '@mikro-orm/core'; import { getRepositoryToken } from '@mikro-orm/nestjs'; import { InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { Permission } from '../entities/permission.entity'; -import { Role } from '../entities/role.entity'; -import { User } from '../entities/user.entity'; +import { Role } from '../role/entities/role.entity'; +import { User } from '../user/entities/user.entity'; import { RoleService } from './role.service'; +import { Permission } from '../permission/entities/permission.entity'; jest.mock('@mikro-orm/core', () => { const actual = jest.requireActual('@mikro-orm/core'); diff --git a/src/role/role.service.ts b/src/modules/role/role.service.ts similarity index 89% rename from src/role/role.service.ts rename to src/modules/role/role.service.ts index a0c7c8f..6022b9d 100644 --- a/src/role/role.service.ts +++ b/src/modules/role/role.service.ts @@ -5,9 +5,9 @@ import { InternalServerErrorException, NotFoundException, } from '@nestjs/common'; -import { Permission } from '../entities/permission.entity'; -import { Role } from '../entities/role.entity'; +import { Role } from './entities/role.entity'; import { CreateRoleDto } from './dto/role-create.dto'; +import { Permission } from '../permission/entities/permission.entity'; @Injectable() export class RoleService { diff --git a/src/user/dto/change-password.dto.ts b/src/modules/user/dto/change-password.dto.ts similarity index 75% rename from src/user/dto/change-password.dto.ts rename to src/modules/user/dto/change-password.dto.ts index 6440ad3..1044bee 100644 --- a/src/user/dto/change-password.dto.ts +++ b/src/modules/user/dto/change-password.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty, IsString, MinLength } from 'class-validator'; -import { IsPasswordConfirmed } from '../../decorators/confirm-password.decorator'; +import { IsPasswordConfirmed } from '../../../common/decorators/confirm-password.decorator'; export class ChangePasswordDto { @IsString() diff --git a/src/user/dto/create-user.dto.ts b/src/modules/user/dto/create-user.dto.ts similarity index 71% rename from src/user/dto/create-user.dto.ts rename to src/modules/user/dto/create-user.dto.ts index 17f376b..e5133ac 100644 --- a/src/user/dto/create-user.dto.ts +++ b/src/modules/user/dto/create-user.dto.ts @@ -5,8 +5,8 @@ import { IsOptional, IsString, } from 'class-validator'; -import { IsUserEmailUnique } from '../../decorators/user-email-unique.decorator'; -import { IsValidRoles } from '../../decorators/valid-roles.decorator'; +import { IsUserEmailUnique } from '../../../common/decorators/user-email-unique.decorator'; +import { IsValidRoles } from '../../../common/decorators/valid-roles.decorator'; export class CreateUserDto { @IsNotEmpty() diff --git a/src/user/dto/permission-assign.dto.ts b/src/modules/user/dto/permission-assign.dto.ts similarity index 59% rename from src/user/dto/permission-assign.dto.ts rename to src/modules/user/dto/permission-assign.dto.ts index ced0ffb..2f1fa78 100644 --- a/src/user/dto/permission-assign.dto.ts +++ b/src/modules/user/dto/permission-assign.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty } from 'class-validator'; -import { IsValidPermissions } from '../../decorators/valid-permission.decorator'; +import { IsValidPermissions } from '../../../common/decorators/valid-permission.decorator'; export class PermissionAssignDto { @IsNotEmpty() diff --git a/src/user/dto/profile-update.dto.ts b/src/modules/user/dto/profile-update.dto.ts similarity index 75% rename from src/user/dto/profile-update.dto.ts rename to src/modules/user/dto/profile-update.dto.ts index a9d8625..caf0b9b 100644 --- a/src/user/dto/profile-update.dto.ts +++ b/src/modules/user/dto/profile-update.dto.ts @@ -1,5 +1,5 @@ import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; -import { IsUserEmailUnique } from '../../decorators/user-email-unique.decorator'; +import { IsUserEmailUnique } from '../../../common/decorators/user-email-unique.decorator'; export class ProfileUpdateDto { @IsOptional() diff --git a/src/user/dto/reset-self-password.dto.ts b/src/modules/user/dto/reset-self-password.dto.ts similarity index 100% rename from src/user/dto/reset-self-password.dto.ts rename to src/modules/user/dto/reset-self-password.dto.ts diff --git a/src/user/dto/role-assign.dto.ts b/src/modules/user/dto/role-assign.dto.ts similarity index 59% rename from src/user/dto/role-assign.dto.ts rename to src/modules/user/dto/role-assign.dto.ts index d836344..d724a58 100644 --- a/src/user/dto/role-assign.dto.ts +++ b/src/modules/user/dto/role-assign.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty } from 'class-validator'; -import { IsValidRoles } from '../../decorators/valid-roles.decorator'; +import { IsValidRoles } from '../../../common/decorators/valid-roles.decorator'; export class RoleAssignDto { @IsNotEmpty() diff --git a/src/user/dto/update-user.dto.ts b/src/modules/user/dto/update-user.dto.ts similarity index 100% rename from src/user/dto/update-user.dto.ts rename to src/modules/user/dto/update-user.dto.ts diff --git a/src/entities/user.entity.ts b/src/modules/user/entities/user.entity.ts similarity index 86% rename from src/entities/user.entity.ts rename to src/modules/user/entities/user.entity.ts index 3996fcc..9094a04 100644 --- a/src/entities/user.entity.ts +++ b/src/modules/user/entities/user.entity.ts @@ -8,9 +8,9 @@ import { Unique, } from '@mikro-orm/core'; import { Exclude } from 'class-transformer'; +import { Permission } from '../../permission/entities/permission.entity'; +import { Role } from '../../role/entities/role.entity'; import { UserRepository } from '../repositories/user.repository'; -import { Permission } from './permission.entity'; -import { Role } from './role.entity'; @Entity({ repository: () => UserRepository }) export class User { diff --git a/src/repositories/user.repository.ts b/src/modules/user/repositories/user.repository.ts similarity index 100% rename from src/repositories/user.repository.ts rename to src/modules/user/repositories/user.repository.ts diff --git a/src/user/transformer/user.transformer.ts b/src/modules/user/transformer/user.transformer.ts similarity index 88% rename from src/user/transformer/user.transformer.ts rename to src/modules/user/transformer/user.transformer.ts index 2785106..7cdf121 100644 --- a/src/user/transformer/user.transformer.ts +++ b/src/modules/user/transformer/user.transformer.ts @@ -1,5 +1,5 @@ -import { Permission } from '../../entities/permission.entity'; -import { User } from '../../entities/user.entity'; +import { Permission } from '../../permission/entities/permission.entity'; +import { User } from '../entities/user.entity'; interface Options { loadRelations?: boolean; diff --git a/src/modules/user/types.d.ts b/src/modules/user/types.d.ts new file mode 100644 index 0000000..ea53a4e --- /dev/null +++ b/src/modules/user/types.d.ts @@ -0,0 +1,13 @@ +interface UserResponse { + id: number; + name: string; + email: string; + password?: string; + device?: string; + isActive: boolean; + createdAt: Date; + updatedAt?: Date; + lastLoginAt?: Date; + roles?: string[]; + permissions?: string[]; +} diff --git a/src/user/user.controller.spec.ts b/src/modules/user/user.controller.spec.ts similarity index 83% rename from src/user/user.controller.spec.ts rename to src/modules/user/user.controller.spec.ts index b774be5..3539305 100644 --- a/src/user/user.controller.spec.ts +++ b/src/modules/user/user.controller.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Response } from 'express'; -import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; -import { PermissionGuard } from '../common/guards/permission.guard'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { PermissionGuard } from '../../common/guards/permission.guard'; import { ChangePasswordDto } from './dto/change-password.dto'; import { CreateUserDto } from './dto/create-user.dto'; import { PermissionAssignDto } from './dto/permission-assign.dto'; @@ -11,6 +11,7 @@ import { RoleAssignDto } from './dto/role-assign.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { UserController } from './user.controller'; import { UserService } from './user.service'; +import { HttpStatus } from '@nestjs/common'; describe('UserController', () => { let controller: UserController; @@ -77,11 +78,11 @@ describe('UserController', () => { await controller.create(createUserDto, mockResponse); expect(userService.create).toHaveBeenCalledWith(createUserDto); - expect(mockResponse.status).toHaveBeenCalledWith(201); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.CREATED); expect(mockResponse.json).toHaveBeenCalledWith({ data: createdUser, message: 'User created successfully', - statusCode: 201, + statusCode: HttpStatus.CREATED, success: true, }); }); @@ -98,11 +99,11 @@ describe('UserController', () => { await controller.getProfile(loggedInUser, mockResponse); expect(userService.findOne).toHaveBeenCalledWith(1); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: userProfile, message: 'Profile fetched successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -115,11 +116,11 @@ describe('UserController', () => { await controller.getPermissions(loggedInUser, mockResponse); expect(userService.getUserPermissions).toHaveBeenCalledWith(1); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: permissions, message: 'Permissions fetched successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -144,11 +145,11 @@ describe('UserController', () => { ); expect(userService.updateProfile).toHaveBeenCalledWith(1, profileUpdateDto); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: updatedProfile, message: 'profile updated successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -173,11 +174,11 @@ describe('UserController', () => { 1, changePasswordDto, ); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: updatedUser, message: 'Password updated successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -203,12 +204,12 @@ describe('UserController', () => { await controller.findAll(1, 10, '', undefined, mockResponse); expect(userService.findAll).toHaveBeenCalled(); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: mockPaginatedResponse.data, meta: mockPaginatedResponse.meta, message: 'Users fetched successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -219,11 +220,11 @@ describe('UserController', () => { userService.findOne.mockResolvedValue(mockUser); await controller.findOne('1', mockResponse); expect(userService.findOne).toHaveBeenCalledWith(userId); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: mockUser, message: 'User fetched successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -240,11 +241,11 @@ describe('UserController', () => { await controller.findOne(userId, mockResponse); expect(userService.findOne).toHaveBeenCalledWith(999); - expect(mockResponse.status).toHaveBeenCalledWith(404); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.NOT_FOUND); expect(mockResponse.json).toHaveBeenCalledWith({ message: 'user not found', errors: ['user not found'], - statusCode: 404, + statusCode: HttpStatus.NOT_FOUND, success: false, }); }); @@ -264,11 +265,11 @@ describe('UserController', () => { userId, changePasswordDto, ); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: updatedUser, message: 'Password updated successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -282,11 +283,11 @@ describe('UserController', () => { await controller.update(userId, updateUserDto, mockResponse); expect(userService.update).toHaveBeenCalledWith(1, updateUserDto); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: updatedUser, message: 'User updated successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -298,7 +299,7 @@ describe('UserController', () => { await controller.remove(userId, mockResponse); expect(userService.remove).toHaveBeenCalledWith(10); - expect(mockResponse.status).toHaveBeenCalledWith(204); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.NO_CONTENT); }); it('should assign roles to a user', async () => { @@ -316,11 +317,11 @@ describe('UserController', () => { userId, roleAssignDto.roles, ); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: updatedUser, message: 'Roles assigned successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); @@ -346,11 +347,11 @@ describe('UserController', () => { userId, permissionAssignDto.permissions, ); - expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK); expect(mockResponse.json).toHaveBeenCalledWith({ data: updatedUser, message: 'Permissions assigned successfully', - statusCode: 200, + statusCode: HttpStatus.OK, success: true, }); }); diff --git a/src/user/user.controller.ts b/src/modules/user/user.controller.ts similarity index 85% rename from src/user/user.controller.ts rename to src/modules/user/user.controller.ts index bcac42c..c5d0dd8 100644 --- a/src/user/user.controller.ts +++ b/src/modules/user/user.controller.ts @@ -3,6 +3,7 @@ import { Controller, Delete, Get, + HttpStatus, Param, ParseIntPipe, Patch, @@ -13,13 +14,13 @@ import { UseGuards, } from '@nestjs/common'; import { Response } from 'express'; -import { BaseController } from '../common/controllers/base.controller'; -import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; -import { PermissionGuard } from '../common/guards/permission.guard'; -import { AuthUser } from '../decorators/auth-user.decorator'; -import { PermissionName } from '../enum/permission.enum'; -import { ValidationExceptionFilter } from '../filters/validation-exception.filter'; -import { Permissions } from './../decorators/permissions.decorator'; +import { BaseController } from '../../common/controllers/base.controller'; +import { AuthUser } from '../../common/decorators/auth-user.decorator'; +import { Permissions } from '../../common/decorators/permissions.decorator'; +import { ValidationExceptionFilter } from '../../common/filters/validation-exception.filter'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { PermissionGuard } from '../../common/guards/permission.guard'; +import { PermissionName } from '../../enums/permission.enum'; import { ChangePasswordDto } from './dto/change-password.dto'; import { CreateUserDto } from './dto/create-user.dto'; import { PermissionAssignDto } from './dto/permission-assign.dto'; @@ -44,7 +45,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'User created successfully', - 201, + HttpStatus.CREATED, res, ); } @@ -57,7 +58,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'Profile fetched successfully', - 200, + HttpStatus.OK, res, ); } @@ -76,7 +77,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'profile updated successfully', - 200, + HttpStatus.OK, res, ); } @@ -97,7 +98,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'Password updated successfully', - 200, + HttpStatus.OK, res, ); } @@ -110,7 +111,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( permissions, 'Permissions fetched successfully', - 200, + HttpStatus.OK, res, ); } @@ -142,7 +143,7 @@ export class UserController extends BaseController { users.data, users.meta, 'Users fetched successfully', - 200, + HttpStatus.OK, res, ); } @@ -158,7 +159,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'User fetched successfully', - 200, + HttpStatus.OK, res, ); } @@ -176,7 +177,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'Password updated successfully', - 200, + HttpStatus.OK, res, ); } @@ -194,7 +195,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'User updated successfully', - 200, + HttpStatus.OK, res, ); } @@ -210,7 +211,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( null, 'User deleted successfully', - 204, + HttpStatus.NO_CONTENT, res, ); } @@ -231,7 +232,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'Roles assigned successfully', - 200, + HttpStatus.OK, res, ); } @@ -252,7 +253,7 @@ export class UserController extends BaseController { return this.sendSuccessResponse( user, 'Permissions assigned successfully', - 200, + HttpStatus.OK, res, ); } diff --git a/src/user/user.module.ts b/src/modules/user/user.module.ts similarity index 57% rename from src/user/user.module.ts rename to src/modules/user/user.module.ts index 384797e..7610481 100644 --- a/src/user/user.module.ts +++ b/src/modules/user/user.module.ts @@ -1,12 +1,12 @@ import { MikroOrmModule } from '@mikro-orm/nestjs'; import { Module } from '@nestjs/common'; -import { UserEmailUniqueValidator } from '../decorators/user-email-unique.decorator'; -import { IsValidPermissionsValidator } from '../decorators/valid-permission.decorator'; -import { IsValidRolesValidator } from '../decorators/valid-roles.decorator'; -import { Permission } from '../entities/permission.entity'; -import { Role } from '../entities/role.entity'; -import { User } from '../entities/user.entity'; +import { UserEmailUniqueValidator } from '../../common/decorators/user-email-unique.decorator'; +import { IsValidPermissionsValidator } from '../../common/decorators/valid-permission.decorator'; +import { IsValidRolesValidator } from '../../common/decorators/valid-roles.decorator'; import { MiscModule } from '../misc/misc.module'; +import { Permission } from '../permission/entities/permission.entity'; +import { Role } from '../role/entities/role.entity'; +import { User } from './entities/user.entity'; import { UserTransformer } from './transformer/user.transformer'; import { UserController } from './user.controller'; import { UserService } from './user.service'; diff --git a/src/user/user.service.spec.ts b/src/modules/user/user.service.spec.ts similarity index 95% rename from src/user/user.service.spec.ts rename to src/modules/user/user.service.spec.ts index de90cdf..e784ce2 100644 --- a/src/user/user.service.spec.ts +++ b/src/modules/user/user.service.spec.ts @@ -2,11 +2,11 @@ import { Collection, EntityManager, EntityRepository } from '@mikro-orm/core'; import { getRepositoryToken } from '@mikro-orm/nestjs'; import { BadRequestException, ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { Permission } from '../entities/permission.entity'; -import { Role } from '../entities/role.entity'; -import { User } from '../entities/user.entity'; +import { User } from './entities/user.entity'; +import { createMockCollection } from '../../mocks/collection.mock'; import { MiscModule } from '../misc/misc.module'; -import { createMockCollection } from '../mocks/collection.mock'; +import { Permission } from '../permission/entities/permission.entity'; +import { Role } from '../role/entities/role.entity'; import { UserTransformer } from './transformer/user.transformer'; import { UserService } from './user.service'; diff --git a/src/user/user.service.ts b/src/modules/user/user.service.ts similarity index 95% rename from src/user/user.service.ts rename to src/modules/user/user.service.ts index 155e24c..0bb905a 100644 --- a/src/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -6,15 +6,15 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { Permission } from '../entities/permission.entity'; -import { Role } from '../entities/role.entity'; -import { User } from '../entities/user.entity'; import { PasswordService } from '../misc/password.service'; +import { Permission } from '../permission/entities/permission.entity'; +import { Role } from '../role/entities/role.entity'; import { ChangePasswordDto } from './dto/change-password.dto'; import { CreateUserDto } from './dto/create-user.dto'; import { ProfileUpdateDto } from './dto/profile-update.dto'; import { ChangeSelfPasswordDto } from './dto/reset-self-password.dto'; import { UpdateUserDto } from './dto/update-user.dto'; +import { User } from './entities/user.entity'; import { UserTransformer } from './transformer/user.transformer'; interface UserPaginatedList { diff --git a/types.d.ts b/types.d.ts index 8885f4d..c588dc4 100644 --- a/types.d.ts +++ b/types.d.ts @@ -26,31 +26,3 @@ interface PaginatedParams { searchFields?: string[]; selectFields?: Array<{ [key: string]: boolean | number | string }>; } - -interface JwtEncodeData { - email: string; - sub: number; -} - -interface UserResponse { - id: number; - name: string; - email: string; - password?: string; - device?: string; - isActive: boolean; - createdAt: Date; - updatedAt?: Date; - lastLoginAt?: Date; - roles?: string[]; - permissions?: string[]; -} - -interface LoginResponse extends UserResponse { - AccessToken: string; -} - -interface ValidateUserResponse { - user: Partial; - errors: string[]; -}