@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.
- package/README.md +27 -0
- package/package.json +15 -3
- package/src/cli.js +139 -0
- package/template/.editorconfig +12 -0
- package/template/.env.example +23 -0
- package/template/.eslintrc.js +25 -0
- package/template/.prettierrc +8 -0
- package/template/README.md +133 -0
- package/template/nest-cli.json +10 -0
- package/template/package-lock.json +11539 -0
- package/template/package.json +99 -0
- package/template/prisma/migrations/20260625045841_init/migration.sql +73 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +75 -0
- package/template/prisma/seeder/data/permission.seed.ts +67 -0
- package/template/prisma/seeder/data/position.seed.ts +21 -0
- package/template/prisma/seeder/data/user.seed.ts +39 -0
- package/template/prisma/seeder/index.ts +56 -0
- package/template/prisma.config.ts +8 -0
- package/template/src/app.module.ts +68 -0
- package/template/src/common/constants/permissions.constant.ts +27 -0
- package/template/src/common/decorators/api-response.decorator.ts +44 -0
- package/template/src/common/decorators/get-user.decorator.ts +11 -0
- package/template/src/common/decorators/permissions.decorator.ts +5 -0
- package/template/src/common/decorators/public.decorator.ts +4 -0
- package/template/src/common/dto/api-response.dto.ts +15 -0
- package/template/src/common/dto/pagination.dto.ts +33 -0
- package/template/src/common/filters/http-exception.filter.ts +54 -0
- package/template/src/common/guards/jwt-auth.guard.ts +32 -0
- package/template/src/common/guards/permissions.guard.ts +53 -0
- package/template/src/common/helpers/function/error-helper.ts +35 -0
- package/template/src/common/interceptors/logging.interceptor.ts +37 -0
- package/template/src/common/interceptors/transform.interceptor.ts +53 -0
- package/template/src/common/prisma/prisma.module.ts +9 -0
- package/template/src/common/prisma/prisma.service.ts +52 -0
- package/template/src/common/utils/password.util.ts +13 -0
- package/template/src/config/app.config.ts +10 -0
- package/template/src/config/database.config.ts +5 -0
- package/template/src/config/env.validation.ts +30 -0
- package/template/src/config/jwt.config.ts +8 -0
- package/template/src/config/swagger.config.ts +6 -0
- package/template/src/main.ts +84 -0
- package/template/src/modules/auth/auth.module.ts +28 -0
- package/template/src/modules/auth/auth.service.ts +173 -0
- package/template/src/modules/auth/controllers/v1/auth.controller.ts +71 -0
- package/template/src/modules/auth/core/dto/auth-response.dto.ts +19 -0
- package/template/src/modules/auth/core/dto/login-response.dto.ts +10 -0
- package/template/src/modules/auth/core/dto/login.dto.ts +15 -0
- package/template/src/modules/auth/core/dto/register.dto.ts +30 -0
- package/template/src/modules/auth/core/interfaces/jwt-payload.interface.ts +7 -0
- package/template/src/modules/auth/core/strategies/jwt.strategy.ts +59 -0
- package/template/src/modules/health/health.controller.ts +29 -0
- package/template/src/modules/health/health.module.ts +7 -0
- package/template/src/modules/users/controllers/v1/users.controller.ts +120 -0
- package/template/src/modules/users/core/dto/change-position.dto.ts +9 -0
- package/template/src/modules/users/core/dto/create-user.dto.ts +35 -0
- package/template/src/modules/users/core/dto/manage-permissions.dto.ts +13 -0
- package/template/src/modules/users/core/dto/update-user.dto.ts +30 -0
- package/template/src/modules/users/core/dto/user-query.dto.ts +22 -0
- package/template/src/modules/users/core/dto/user-response.dto.ts +32 -0
- package/template/src/modules/users/core/entities/user.entity.ts +45 -0
- package/template/src/modules/users/core/helpers/user-transform.helper.ts +31 -0
- package/template/src/modules/users/users.module.ts +10 -0
- package/template/src/modules/users/users.service.ts +344 -0
- package/template/test/app.e2e-spec.ts +40 -0
- package/template/test/jest-e2e.json +9 -0
- package/template/tsconfig.json +30 -0
- 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,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,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,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
|
+
}
|