@kuldi/create-nestjs 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +27 -0
  2. package/package.json +15 -3
  3. package/src/cli.js +139 -0
  4. package/template/.editorconfig +12 -0
  5. package/template/.env.example +23 -0
  6. package/template/.eslintrc.js +25 -0
  7. package/template/.prettierrc +8 -0
  8. package/template/README.md +133 -0
  9. package/template/nest-cli.json +10 -0
  10. package/template/package-lock.json +11539 -0
  11. package/template/package.json +99 -0
  12. package/template/prisma/migrations/20260625045841_init/migration.sql +73 -0
  13. package/template/prisma/migrations/migration_lock.toml +3 -0
  14. package/template/prisma/schema.prisma +75 -0
  15. package/template/prisma/seeder/data/permission.seed.ts +67 -0
  16. package/template/prisma/seeder/data/position.seed.ts +21 -0
  17. package/template/prisma/seeder/data/user.seed.ts +39 -0
  18. package/template/prisma/seeder/index.ts +56 -0
  19. package/template/prisma.config.ts +8 -0
  20. package/template/src/app.module.ts +68 -0
  21. package/template/src/common/constants/permissions.constant.ts +27 -0
  22. package/template/src/common/decorators/api-response.decorator.ts +44 -0
  23. package/template/src/common/decorators/get-user.decorator.ts +11 -0
  24. package/template/src/common/decorators/permissions.decorator.ts +5 -0
  25. package/template/src/common/decorators/public.decorator.ts +4 -0
  26. package/template/src/common/dto/api-response.dto.ts +15 -0
  27. package/template/src/common/dto/pagination.dto.ts +33 -0
  28. package/template/src/common/filters/http-exception.filter.ts +54 -0
  29. package/template/src/common/guards/jwt-auth.guard.ts +32 -0
  30. package/template/src/common/guards/permissions.guard.ts +53 -0
  31. package/template/src/common/helpers/function/error-helper.ts +35 -0
  32. package/template/src/common/interceptors/logging.interceptor.ts +37 -0
  33. package/template/src/common/interceptors/transform.interceptor.ts +53 -0
  34. package/template/src/common/prisma/prisma.module.ts +9 -0
  35. package/template/src/common/prisma/prisma.service.ts +52 -0
  36. package/template/src/common/utils/password.util.ts +13 -0
  37. package/template/src/config/app.config.ts +10 -0
  38. package/template/src/config/database.config.ts +5 -0
  39. package/template/src/config/env.validation.ts +30 -0
  40. package/template/src/config/jwt.config.ts +8 -0
  41. package/template/src/config/swagger.config.ts +6 -0
  42. package/template/src/main.ts +84 -0
  43. package/template/src/modules/auth/auth.module.ts +28 -0
  44. package/template/src/modules/auth/auth.service.ts +173 -0
  45. package/template/src/modules/auth/controllers/v1/auth.controller.ts +71 -0
  46. package/template/src/modules/auth/core/dto/auth-response.dto.ts +19 -0
  47. package/template/src/modules/auth/core/dto/login-response.dto.ts +10 -0
  48. package/template/src/modules/auth/core/dto/login.dto.ts +15 -0
  49. package/template/src/modules/auth/core/dto/register.dto.ts +30 -0
  50. package/template/src/modules/auth/core/interfaces/jwt-payload.interface.ts +7 -0
  51. package/template/src/modules/auth/core/strategies/jwt.strategy.ts +59 -0
  52. package/template/src/modules/health/health.controller.ts +29 -0
  53. package/template/src/modules/health/health.module.ts +7 -0
  54. package/template/src/modules/users/controllers/v1/users.controller.ts +120 -0
  55. package/template/src/modules/users/core/dto/change-position.dto.ts +9 -0
  56. package/template/src/modules/users/core/dto/create-user.dto.ts +35 -0
  57. package/template/src/modules/users/core/dto/manage-permissions.dto.ts +13 -0
  58. package/template/src/modules/users/core/dto/update-user.dto.ts +30 -0
  59. package/template/src/modules/users/core/dto/user-query.dto.ts +22 -0
  60. package/template/src/modules/users/core/dto/user-response.dto.ts +32 -0
  61. package/template/src/modules/users/core/entities/user.entity.ts +45 -0
  62. package/template/src/modules/users/core/helpers/user-transform.helper.ts +31 -0
  63. package/template/src/modules/users/users.module.ts +10 -0
  64. package/template/src/modules/users/users.service.ts +344 -0
  65. package/template/test/app.e2e-spec.ts +40 -0
  66. package/template/test/jest-e2e.json +9 -0
  67. package/template/tsconfig.json +30 -0
  68. package/bin/cli.js +0 -71
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "nest-prisma-boilerplate",
3
+ "version": "1.0.0",
4
+ "description": "Kuli Digital NestJS Application",
5
+ "author": "Kuli Digital",
6
+ "private": true,
7
+ "license": "UNLICENSED",
8
+ "scripts": {
9
+ "build": "npm run clean && tsc",
10
+ "clean": "rm -rf dist",
11
+ "copy-templates": "cp -r templates dist/",
12
+ "dev": "ts-node src/cli.ts",
13
+ "prepublishOnly": "npm run build",
14
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
15
+ "start": "nest start",
16
+ "start:dev": "nest start --watch",
17
+ "start:debug": "nest start --debug --watch",
18
+ "start:prod": "node dist/main",
19
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
20
+ "test": "echo \"No tests yet\" && exit 0",
21
+ "test:watch": "jest --watch",
22
+ "test:cov": "jest --coverage",
23
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
24
+ "test:e2e": "jest --config ./test/jest-e2e.json",
25
+ "prisma:generate": "prisma generate",
26
+ "prisma:migrate": "prisma migrate dev",
27
+ "prisma:studio": "prisma studio",
28
+ "prisma:seed": "ts-node prisma/seeder/index.ts"
29
+ },
30
+ "dependencies": {
31
+ "@nestjs/common": "^11.1.27",
32
+ "@nestjs/config": "^4.0.4",
33
+ "@nestjs/core": "^11.1.27",
34
+ "@nestjs/jwt": "^11.0.2",
35
+ "@nestjs/passport": "^11.0.5",
36
+ "@nestjs/platform-express": "^11.1.27",
37
+ "@nestjs/swagger": "^11.4.4",
38
+ "@prisma/adapter-pg": "^7.8.0",
39
+ "@prisma/client": "^7.8.0",
40
+ "bcrypt": "^6.0.0",
41
+ "class-transformer": "^0.5.1",
42
+ "class-validator": "^0.15.1",
43
+ "cookie-parser": "^1.4.7",
44
+ "joi": "^18.2.3",
45
+ "passport": "^0.7.0",
46
+ "passport-jwt": "^4.0.1",
47
+ "pg": "^8.22.0",
48
+ "reflect-metadata": "^0.2.2",
49
+ "rxjs": "^7.8.2"
50
+ },
51
+ "devDependencies": {
52
+ "@nestjs/cli": "^11.0.23",
53
+ "@nestjs/schematics": "^11.1.0",
54
+ "@nestjs/testing": "^11.1.27",
55
+ "@types/bcrypt": "^6.0.0",
56
+ "@types/cookie-parser": "^1.4.10",
57
+ "@types/express": "^5.0.6",
58
+ "@types/jest": "^30.0.0",
59
+ "@types/node": "^26.0.1",
60
+ "@types/passport-jwt": "^4.0.1",
61
+ "@types/pg": "^8.20.0",
62
+ "@types/supertest": "^7.2.0",
63
+ "@typescript-eslint/eslint-plugin": "^8.62.0",
64
+ "@typescript-eslint/parser": "^8.62.0",
65
+ "eslint": "^10.5.0",
66
+ "eslint-config-prettier": "^10.1.8",
67
+ "eslint-plugin-prettier": "^5.5.6",
68
+ "jest": "^30.4.2",
69
+ "prettier": "^3.8.4",
70
+ "prisma": "^7.8.0",
71
+ "source-map-support": "^0.5.21",
72
+ "supertest": "^7.2.2",
73
+ "ts-jest": "^29.4.11",
74
+ "ts-loader": "^9.6.2",
75
+ "ts-node": "^10.9.2",
76
+ "tsconfig-paths": "^4.2.0",
77
+ "typescript": "^6.0.3"
78
+ },
79
+ "prisma": {
80
+ "seed": "ts-node prisma/seeder/index.ts"
81
+ },
82
+ "jest": {
83
+ "moduleFileExtensions": [
84
+ "js",
85
+ "json",
86
+ "ts"
87
+ ],
88
+ "rootDir": "src",
89
+ "testRegex": ".*\\.spec\\.ts$",
90
+ "transform": {
91
+ "^.+\\.(t|j)s$": "ts-jest"
92
+ },
93
+ "collectCoverageFrom": [
94
+ "**/*.(t|j)s"
95
+ ],
96
+ "coverageDirectory": "../coverage",
97
+ "testEnvironment": "node"
98
+ }
99
+ }
@@ -0,0 +1,73 @@
1
+ -- CreateTable
2
+ CREATE TABLE "users" (
3
+ "id" SERIAL NOT NULL,
4
+ "email" TEXT NOT NULL,
5
+ "password" TEXT NOT NULL,
6
+ "first_name" TEXT NOT NULL,
7
+ "last_name" TEXT NOT NULL,
8
+ "is_active" BOOLEAN NOT NULL DEFAULT true,
9
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
10
+ "updated_at" TIMESTAMP(3) NOT NULL,
11
+ "deleted_at" TIMESTAMP(3),
12
+ "position_id" INTEGER NOT NULL,
13
+
14
+ CONSTRAINT "users_pkey" PRIMARY KEY ("id")
15
+ );
16
+
17
+ -- CreateTable
18
+ CREATE TABLE "positions" (
19
+ "id" SERIAL NOT NULL,
20
+ "name" TEXT NOT NULL,
21
+ "description" TEXT,
22
+ "is_active" BOOLEAN NOT NULL DEFAULT true,
23
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
24
+ "updated_at" TIMESTAMP(3) NOT NULL,
25
+ "deleted_at" TIMESTAMP(3),
26
+
27
+ CONSTRAINT "positions_pkey" PRIMARY KEY ("id")
28
+ );
29
+
30
+ -- CreateTable
31
+ CREATE TABLE "permissions" (
32
+ "id" SERIAL NOT NULL,
33
+ "name" TEXT NOT NULL,
34
+ "description" TEXT,
35
+ "resource" TEXT NOT NULL,
36
+ "action" TEXT NOT NULL,
37
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
38
+ "updated_at" TIMESTAMP(3) NOT NULL,
39
+ "deleted_at" TIMESTAMP(3),
40
+
41
+ CONSTRAINT "permissions_pkey" PRIMARY KEY ("id")
42
+ );
43
+
44
+ -- CreateTable
45
+ CREATE TABLE "position_permissions" (
46
+ "id" SERIAL NOT NULL,
47
+ "position_id" INTEGER NOT NULL,
48
+ "permission_id" INTEGER NOT NULL,
49
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
50
+
51
+ CONSTRAINT "position_permissions_pkey" PRIMARY KEY ("id")
52
+ );
53
+
54
+ -- CreateIndex
55
+ CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
56
+
57
+ -- CreateIndex
58
+ CREATE UNIQUE INDEX "positions_name_key" ON "positions"("name");
59
+
60
+ -- CreateIndex
61
+ CREATE UNIQUE INDEX "permissions_name_key" ON "permissions"("name");
62
+
63
+ -- CreateIndex
64
+ CREATE UNIQUE INDEX "position_permissions_position_id_permission_id_key" ON "position_permissions"("position_id", "permission_id");
65
+
66
+ -- AddForeignKey
67
+ ALTER TABLE "users" ADD CONSTRAINT "users_position_id_fkey" FOREIGN KEY ("position_id") REFERENCES "positions"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
68
+
69
+ -- AddForeignKey
70
+ ALTER TABLE "position_permissions" ADD CONSTRAINT "position_permissions_position_id_fkey" FOREIGN KEY ("position_id") REFERENCES "positions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
71
+
72
+ -- AddForeignKey
73
+ ALTER TABLE "position_permissions" ADD CONSTRAINT "position_permissions_permission_id_fkey" FOREIGN KEY ("permission_id") REFERENCES "permissions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "postgresql"
@@ -0,0 +1,75 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ }
8
+
9
+ // ============================================
10
+ // RBAC Schema
11
+ // ============================================
12
+
13
+ model User {
14
+ id Int @id @default(autoincrement())
15
+ email String @unique
16
+ password String
17
+ first_name String
18
+ last_name String
19
+ is_active Boolean @default(true)
20
+ created_at DateTime @default(now())
21
+ updated_at DateTime @updatedAt
22
+ deleted_at DateTime?
23
+
24
+ // Relations
25
+ position_id Int
26
+ position Position @relation(fields: [position_id], references: [id])
27
+
28
+ @@map("users")
29
+ }
30
+
31
+ model Position {
32
+ id Int @id @default(autoincrement())
33
+ name String @unique
34
+ description String?
35
+ is_active Boolean @default(true)
36
+ created_at DateTime @default(now())
37
+ updated_at DateTime @updatedAt
38
+ deleted_at DateTime?
39
+
40
+ // Relations
41
+ users User[]
42
+ position_permissions PositionPermission[]
43
+
44
+ @@map("positions")
45
+ }
46
+
47
+ model Permission {
48
+ id Int @id @default(autoincrement())
49
+ name String @unique
50
+ description String?
51
+ resource String // e.g., 'USER', 'TRANSACTION', 'REPORT'
52
+ action String // e.g., 'VIEW', 'ADD', 'UPDATE', 'DELETE'
53
+ created_at DateTime @default(now())
54
+ updated_at DateTime @updatedAt
55
+ deleted_at DateTime?
56
+
57
+ // Relations
58
+ position_permissions PositionPermission[]
59
+
60
+ @@map("permissions")
61
+ }
62
+
63
+ model PositionPermission {
64
+ id Int @id @default(autoincrement())
65
+ position_id Int
66
+ permission_id Int
67
+ created_at DateTime @default(now())
68
+
69
+ // Relations
70
+ position Position @relation(fields: [position_id], references: [id], onDelete: Cascade)
71
+ permission Permission @relation(fields: [permission_id], references: [id], onDelete: Cascade)
72
+
73
+ @@unique([position_id, permission_id])
74
+ @@map("position_permissions")
75
+ }
@@ -0,0 +1,67 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ export const seedPermissions = async (
4
+ prisma: PrismaClient,
5
+ adminPositionId: number,
6
+ memberPositionId: number,
7
+ ) => {
8
+ console.log('🔐 Seeding permissions...');
9
+
10
+ const permissionsData = [
11
+ // User Management
12
+ { name: 'VIEW_USER', resource: 'USER', action: 'VIEW', description: 'View user information' },
13
+ { name: 'ADD_USER', resource: 'USER', action: 'ADD', description: 'Create new user' },
14
+ { name: 'UPDATE_USER', resource: 'USER', action: 'UPDATE', description: 'Update user information' },
15
+ { name: 'DELETE_USER', resource: 'USER', action: 'DELETE', description: 'Delete user' },
16
+ { name: 'MANAGE_USER_PERMISSION', resource: 'USER', action: 'MANAGE_PERMISSION', description: 'Assign or revoke user permissions' },
17
+ { name: 'CHANGE_USER_POSITION', resource: 'USER', action: 'CHANGE_POSITION', description: 'Change user position/role' },
18
+
19
+ // Position Management
20
+ { name: 'VIEW_POSITION', resource: 'POSITION', action: 'VIEW', description: 'View position information' },
21
+ { name: 'ADD_POSITION', resource: 'POSITION', action: 'ADD', description: 'Create new position' },
22
+ { name: 'UPDATE_POSITION', resource: 'POSITION', action: 'UPDATE', description: 'Update position information' },
23
+ { name: 'DELETE_POSITION', resource: 'POSITION', action: 'DELETE', description: 'Delete position' },
24
+
25
+ // Permission Management
26
+ { name: 'VIEW_PERMISSION', resource: 'PERMISSION', action: 'VIEW', description: 'View permission information' },
27
+ { name: 'ADD_PERMISSION', resource: 'PERMISSION', action: 'ADD', description: 'Create new permission' },
28
+ { name: 'UPDATE_PERMISSION', resource: 'PERMISSION', action: 'UPDATE', description: 'Update permission information' },
29
+ { name: 'DELETE_PERMISSION', resource: 'PERMISSION', action: 'DELETE', description: 'Delete permission' },
30
+ ];
31
+
32
+ const permissions = await Promise.all(
33
+ permissionsData.map((permission) =>
34
+ prisma.permission.create({ data: permission }),
35
+ ),
36
+ );
37
+
38
+ console.log(`✅ ${permissions.length} permissions seeded`);
39
+
40
+ // ============================================
41
+ // Assign Permissions to Positions
42
+ // ============================================
43
+ console.log('🔗 Assigning permissions to positions...');
44
+
45
+ // Admin gets all permissions
46
+ const adminPermissions = permissions.map((permission) => ({
47
+ position_id: adminPositionId,
48
+ permission_id: permission.id,
49
+ }));
50
+
51
+ await prisma.positionPermission.createMany({
52
+ data: adminPermissions,
53
+ });
54
+
55
+ // Member gets only VIEW_USER permission
56
+ const viewUserPermission = permissions.find((p) => p.name === 'VIEW_USER');
57
+ if (viewUserPermission) {
58
+ await prisma.positionPermission.create({
59
+ data: {
60
+ position_id: memberPositionId,
61
+ permission_id: viewUserPermission.id,
62
+ },
63
+ });
64
+ }
65
+
66
+ console.log('✅ Permissions assigned to positions');
67
+ };
@@ -0,0 +1,21 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ export const seedPositions = async (prisma: PrismaClient) => {
4
+ console.log('📋 Seeding positions...');
5
+ const adminPosition = await prisma.position.create({
6
+ data: {
7
+ name: 'Administrator',
8
+ description: 'Full system access with all permissions',
9
+ },
10
+ });
11
+
12
+ const memberPosition = await prisma.position.create({
13
+ data: {
14
+ name: 'Member',
15
+ description: 'Standard user with limited permissions',
16
+ },
17
+ });
18
+
19
+ console.log('✅ Positions seeded');
20
+ return { adminPosition, memberPosition };
21
+ };
@@ -0,0 +1,39 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ import * as bcrypt from 'bcrypt';
3
+
4
+ export const seedUsers = async (
5
+ prisma: PrismaClient,
6
+ adminPositionId: number,
7
+ memberPositionId: number,
8
+ ) => {
9
+ console.log('👥 Seeding users...');
10
+
11
+ const defaultPassword = 'password123';
12
+ const hashedPassword = await bcrypt.hash(defaultPassword, 10);
13
+
14
+ const adminUser = await prisma.user.create({
15
+ data: {
16
+ email: 'admin@kulidigital.com',
17
+ password: hashedPassword,
18
+ first_name: 'Admin',
19
+ last_name: 'Kuli Digital',
20
+ position_id: adminPositionId,
21
+ is_active: true,
22
+ },
23
+ });
24
+
25
+ const memberUser = await prisma.user.create({
26
+ data: {
27
+ email: 'member@kulidigital.com',
28
+ password: hashedPassword,
29
+ first_name: 'Member',
30
+ last_name: 'User',
31
+ position_id: memberPositionId,
32
+ is_active: true,
33
+ },
34
+ });
35
+
36
+ console.log('✅ Users seeded');
37
+
38
+ return { adminUser, memberUser, defaultPassword };
39
+ };
@@ -0,0 +1,56 @@
1
+ import * as dotenv from 'dotenv';
2
+ dotenv.config();
3
+
4
+ import { PrismaClient } from '@prisma/client';
5
+ import { PrismaPg } from '@prisma/adapter-pg';
6
+ import { Pool } from 'pg';
7
+ import { seedPositions } from './data/position.seed';
8
+ import { seedPermissions } from './data/permission.seed';
9
+ import { seedUsers } from './data/user.seed';
10
+
11
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
12
+ const adapter = new PrismaPg(pool as any);
13
+ const prisma = new PrismaClient({ adapter });
14
+
15
+ async function main() {
16
+ console.log('🌱 Starting database seeding...\n');
17
+
18
+ // Clear existing data
19
+ console.log('🗑️ Clearing existing data...');
20
+ await prisma.positionPermission.deleteMany();
21
+ await prisma.user.deleteMany();
22
+ await prisma.permission.deleteMany();
23
+ await prisma.position.deleteMany();
24
+
25
+ // Run seeders
26
+ const { adminPosition, memberPosition } = await seedPositions(prisma);
27
+ await seedPermissions(prisma, adminPosition.id, memberPosition.id);
28
+ const { adminUser, memberUser, defaultPassword } = await seedUsers(
29
+ prisma,
30
+ adminPosition.id,
31
+ memberPosition.id,
32
+ );
33
+
34
+ // Summary
35
+ console.log('\n✨ Database seeding completed!\n');
36
+ console.log('📊 Summary:');
37
+ console.log(` - Positions: ${await prisma.position.count()}`);
38
+ console.log(` - Permissions: ${await prisma.permission.count()}`);
39
+ console.log(` - Users: ${await prisma.user.count()}`);
40
+ console.log(
41
+ ` - Position-Permission Links: ${await prisma.positionPermission.count()}\n`,
42
+ );
43
+
44
+ console.log('🔑 Default Users:');
45
+ console.log(` Admin: ${adminUser.email} / ${defaultPassword}`);
46
+ console.log(` Member: ${memberUser.email} / ${defaultPassword}\n`);
47
+ }
48
+
49
+ main()
50
+ .catch((e) => {
51
+ console.error('❌ Error seeding database:', e);
52
+ process.exit(1);
53
+ })
54
+ .finally(async () => {
55
+ await prisma.$disconnect();
56
+ });
@@ -0,0 +1,8 @@
1
+ import 'dotenv/config';
2
+ import { defineConfig, env } from '@prisma/config';
3
+
4
+ export default defineConfig({
5
+ datasource: {
6
+ url: env('DATABASE_URL'),
7
+ },
8
+ });
@@ -0,0 +1,68 @@
1
+ import { APP_GUARD, APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
2
+ import { validationSchema } from './config/env.validation';
3
+ import databaseConfig from './config/database.config';
4
+ import swaggerConfig from './config/swagger.config';
5
+ import { ConfigModule } from '@nestjs/config';
6
+ import appConfig from './config/app.config';
7
+ import jwtConfig from './config/jwt.config';
8
+ import { Module } from '@nestjs/common';
9
+
10
+ // Common modules
11
+ import { TransformInterceptor } from './common/interceptors/transform.interceptor';
12
+ import { LoggingInterceptor } from './common/interceptors/logging.interceptor';
13
+ import { HttpExceptionFilter } from './common/filters/http-exception.filter';
14
+ import { PermissionsGuard } from './common/guards/permissions.guard';
15
+ import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
16
+ import { PrismaModule } from './common/prisma/prisma.module';
17
+
18
+ // Feature modules
19
+ import { HealthModule } from './modules/health/health.module';
20
+ import { UsersModule } from './modules/users/users.module';
21
+ import { AuthModule } from './modules/auth/auth.module';
22
+
23
+ @Module({
24
+ imports: [
25
+ // Configuration
26
+ ConfigModule.forRoot({
27
+ isGlobal: true,
28
+ validationSchema,
29
+ load: [appConfig, databaseConfig, jwtConfig, swaggerConfig],
30
+ }),
31
+
32
+ // Common modules
33
+ PrismaModule,
34
+
35
+ // Feature modules
36
+ AuthModule,
37
+ UsersModule,
38
+ HealthModule,
39
+ ],
40
+ providers: [
41
+ // Global guards
42
+ {
43
+ provide: APP_GUARD,
44
+ useClass: JwtAuthGuard,
45
+ },
46
+ {
47
+ provide: APP_GUARD,
48
+ useClass: PermissionsGuard,
49
+ },
50
+
51
+ // Global filters
52
+ {
53
+ provide: APP_FILTER,
54
+ useClass: HttpExceptionFilter,
55
+ },
56
+
57
+ // Global interceptors
58
+ {
59
+ provide: APP_INTERCEPTOR,
60
+ useClass: LoggingInterceptor,
61
+ },
62
+ {
63
+ provide: APP_INTERCEPTOR,
64
+ useClass: TransformInterceptor,
65
+ },
66
+ ],
67
+ })
68
+ export class AppModule {}
@@ -0,0 +1,27 @@
1
+ export const PERMISSIONS = {
2
+ // User Management
3
+ USER: {
4
+ VIEW: 'VIEW_USER',
5
+ ADD: 'ADD_USER',
6
+ UPDATE: 'UPDATE_USER',
7
+ DELETE: 'DELETE_USER',
8
+ MANAGE_PERMISSION: 'MANAGE_USER_PERMISSION',
9
+ CHANGE_POSITION: 'CHANGE_USER_POSITION',
10
+ },
11
+
12
+ // Position Management
13
+ POSITION: {
14
+ VIEW: 'VIEW_POSITION',
15
+ ADD: 'ADD_POSITION',
16
+ UPDATE: 'UPDATE_POSITION',
17
+ DELETE: 'DELETE_POSITION',
18
+ },
19
+
20
+ // Permission Management
21
+ PERMISSION: {
22
+ VIEW: 'VIEW_PERMISSION',
23
+ ADD: 'ADD_PERMISSION',
24
+ UPDATE: 'UPDATE_PERMISSION',
25
+ DELETE: 'DELETE_PERMISSION',
26
+ },
27
+ } as const;
@@ -0,0 +1,44 @@
1
+ import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
2
+ import { ApiResponseDto } from '@common/dto/api-response.dto';
3
+ import { applyDecorators, Type } from '@nestjs/common';
4
+
5
+ export const ApiSuccessResponse = <TModel extends Type<any>>(model: TModel) => {
6
+ return applyDecorators(
7
+ ApiExtraModels(ApiResponseDto, model),
8
+ ApiOkResponse({
9
+ schema: {
10
+ allOf: [
11
+ { $ref: getSchemaPath(ApiResponseDto) },
12
+ {
13
+ properties: {
14
+ data: {
15
+ $ref: getSchemaPath(model),
16
+ },
17
+ },
18
+ },
19
+ ],
20
+ },
21
+ }),
22
+ );
23
+ };
24
+
25
+ export const ApiSuccessArrayResponse = <TModel extends Type<any>>(model: TModel) => {
26
+ return applyDecorators(
27
+ ApiExtraModels(ApiResponseDto, model),
28
+ ApiOkResponse({
29
+ schema: {
30
+ allOf: [
31
+ { $ref: getSchemaPath(ApiResponseDto) },
32
+ {
33
+ properties: {
34
+ data: {
35
+ type: 'array',
36
+ items: { $ref: getSchemaPath(model) },
37
+ },
38
+ },
39
+ },
40
+ ],
41
+ },
42
+ }),
43
+ );
44
+ };
@@ -0,0 +1,11 @@
1
+ import { JwtPayload } from '@modules/auth/core/interfaces/jwt-payload.interface';
2
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
3
+
4
+ export const GetUser = createParamDecorator(
5
+ (data: keyof JwtPayload | undefined, ctx: ExecutionContext): JwtPayload | any => {
6
+ const request = ctx.switchToHttp().getRequest();
7
+ const user = request.user;
8
+
9
+ return data ? user?.[data] : user;
10
+ },
11
+ );
@@ -0,0 +1,5 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+
3
+ export const PERMISSIONS_KEY = 'permissions';
4
+ export const Permissions = (...permissions: string[]) =>
5
+ SetMetadata(PERMISSIONS_KEY, permissions);
@@ -0,0 +1,4 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+
3
+ export const IS_PUBLIC_KEY = 'isPublic';
4
+ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
@@ -0,0 +1,15 @@
1
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
+
3
+ export class ApiResponseDto<T = any> {
4
+ @ApiProperty({ example: 200 })
5
+ statusCode: number;
6
+
7
+ @ApiProperty({ example: 'Operation successful' })
8
+ message: string;
9
+
10
+ @ApiPropertyOptional()
11
+ data?: T;
12
+
13
+ @ApiPropertyOptional()
14
+ errors?: any;
15
+ }
@@ -0,0 +1,33 @@
1
+ import { IsOptional, IsInt, Min, Max } from 'class-validator';
2
+ import { ApiPropertyOptional } from '@nestjs/swagger';
3
+ import { Type } from 'class-transformer';
4
+
5
+ export class PaginationDto {
6
+ @ApiPropertyOptional({ minimum: 1, default: 1 })
7
+ @Type(() => Number)
8
+ @IsInt()
9
+ @Min(1)
10
+ @IsOptional()
11
+ page?: number = 1;
12
+
13
+ @ApiPropertyOptional({ minimum: 1, maximum: 100, default: 10 })
14
+ @Type(() => Number)
15
+ @IsInt()
16
+ @Min(1)
17
+ @Max(100)
18
+ @IsOptional()
19
+ limit?: number = 10;
20
+ }
21
+
22
+ export class PaginatedResponseDto<T> {
23
+ @ApiPropertyOptional()
24
+ data: T[];
25
+
26
+ @ApiPropertyOptional()
27
+ meta: {
28
+ total: number;
29
+ page: number;
30
+ limit: number;
31
+ totalPages: number;
32
+ };
33
+ }