@urbansolv/create-nestjs-app 1.2.4 ā 1.2.7
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/dist/templates/nestjs-app/src/common/dto/api-response.dto.ts +2 -2
- package/dist/templates/nestjs-app/src/common/filters/http-exception.filter.ts +1 -1
- package/dist/templates/nestjs-app/src/common/interceptors/transform.interceptor.ts +2 -2
- package/dist/templates/nestjs-app/src/common/prisma/prisma.service.ts +0 -1
- package/dist/templates/nestjs-app/src/modules/auth/auth.service.ts +0 -7
- package/dist/templates/nestjs-app/src/modules/auth/controllers/v1/auth.controller.ts +4 -0
- package/dist/templates/nestjs-app/src/modules/users/controllers/v1/users.controller.ts +2 -2
- package/dist/templates/nestjs-app/src/modules/users/users.service.ts +4 -0
- package/package.json +2 -1
- package/templates/nestjs-app/.editorconfig +12 -0
- package/templates/nestjs-app/.env.example +24 -0
- package/templates/nestjs-app/.eslintrc.js +25 -0
- package/templates/nestjs-app/.prettierrc +8 -0
- package/templates/nestjs-app/README.md +133 -0
- package/templates/nestjs-app/nest-cli.json +10 -0
- package/templates/nestjs-app/package.json +88 -0
- package/templates/nestjs-app/prisma/schema.prisma +79 -0
- package/templates/nestjs-app/prisma/seed.ts +153 -0
- package/templates/nestjs-app/src/app.module.ts +68 -0
- package/templates/nestjs-app/src/common/constants/permissions.constant.ts +27 -0
- package/templates/nestjs-app/src/common/decorators/api-response.decorator.ts +44 -0
- package/templates/nestjs-app/src/common/decorators/get-user.decorator.ts +11 -0
- package/templates/nestjs-app/src/common/decorators/permissions.decorator.ts +5 -0
- package/templates/nestjs-app/src/common/decorators/public.decorator.ts +4 -0
- package/templates/nestjs-app/src/common/dto/api-response.dto.ts +21 -0
- package/templates/nestjs-app/src/common/dto/pagination.dto.ts +33 -0
- package/templates/nestjs-app/src/common/filters/http-exception.filter.ts +56 -0
- package/templates/nestjs-app/src/common/guards/jwt-auth.guard.ts +32 -0
- package/templates/nestjs-app/src/common/guards/permissions.guard.ts +53 -0
- package/templates/nestjs-app/src/common/interceptors/logging.interceptor.ts +37 -0
- package/templates/nestjs-app/src/common/interceptors/transform.interceptor.ts +55 -0
- package/templates/nestjs-app/src/common/prisma/prisma.module.ts +9 -0
- package/templates/nestjs-app/src/common/prisma/prisma.service.ts +45 -0
- package/templates/nestjs-app/src/common/utils/password.util.ts +13 -0
- package/templates/nestjs-app/src/config/app.config.ts +10 -0
- package/templates/nestjs-app/src/config/database.config.ts +5 -0
- package/templates/nestjs-app/src/config/env.validation.ts +30 -0
- package/templates/nestjs-app/src/config/jwt.config.ts +8 -0
- package/templates/nestjs-app/src/config/swagger.config.ts +6 -0
- package/templates/nestjs-app/src/main.ts +93 -0
- package/templates/nestjs-app/src/modules/auth/auth.module.ts +28 -0
- package/templates/nestjs-app/src/modules/auth/auth.service.ts +173 -0
- package/templates/nestjs-app/src/modules/auth/controllers/v1/auth.controller.ts +37 -0
- package/templates/nestjs-app/src/modules/auth/core/dto/auth-response.dto.ts +19 -0
- package/templates/nestjs-app/src/modules/auth/core/dto/login-response.dto.ts +10 -0
- package/templates/nestjs-app/src/modules/auth/core/dto/login.dto.ts +15 -0
- package/templates/nestjs-app/src/modules/auth/core/dto/register.dto.ts +30 -0
- package/templates/nestjs-app/src/modules/auth/core/interfaces/jwt-payload.interface.ts +7 -0
- package/templates/nestjs-app/src/modules/auth/core/strategies/jwt.strategy.ts +54 -0
- package/templates/nestjs-app/src/modules/health/health.controller.ts +29 -0
- package/templates/nestjs-app/src/modules/health/health.module.ts +7 -0
- package/templates/nestjs-app/src/modules/users/controllers/v1/users.controller.ts +118 -0
- package/templates/nestjs-app/src/modules/users/core/dto/change-position.dto.ts +9 -0
- package/templates/nestjs-app/src/modules/users/core/dto/create-user.dto.ts +35 -0
- package/templates/nestjs-app/src/modules/users/core/dto/manage-permissions.dto.ts +13 -0
- package/templates/nestjs-app/src/modules/users/core/dto/update-user.dto.ts +30 -0
- package/templates/nestjs-app/src/modules/users/core/dto/user-query.dto.ts +22 -0
- package/templates/nestjs-app/src/modules/users/core/dto/user-response.dto.ts +32 -0
- package/templates/nestjs-app/src/modules/users/core/entities/user.entity.ts +45 -0
- package/templates/nestjs-app/src/modules/users/core/helpers/user-transform.helper.ts +31 -0
- package/templates/nestjs-app/src/modules/users/users.module.ts +10 -0
- package/templates/nestjs-app/src/modules/users/users.service.ts +344 -0
- package/templates/nestjs-app/test/app.e2e-spec.ts +40 -0
- package/templates/nestjs-app/test/jest-e2e.json +9 -0
- package/templates/nestjs-app/tsconfig.json +26 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
2
|
|
|
3
3
|
export class ApiResponseDto<T = any> {
|
|
4
|
-
@ApiProperty({ example:
|
|
5
|
-
|
|
4
|
+
@ApiProperty({ example: 200 })
|
|
5
|
+
statusCode: number;
|
|
6
6
|
|
|
7
7
|
@ApiProperty({ example: 'Operation successful' })
|
|
8
8
|
message: string;
|
|
@@ -18,7 +18,7 @@ export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponseDt
|
|
|
18
18
|
return next.handle().pipe(
|
|
19
19
|
map((data) => {
|
|
20
20
|
// If data is already in ApiResponseDto format, return as is
|
|
21
|
-
if (data && typeof data === 'object' && '
|
|
21
|
+
if (data && typeof data === 'object' && 'statusCode' in data && 'message' in data) {
|
|
22
22
|
return data;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -43,7 +43,7 @@ export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponseDt
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
return {
|
|
46
|
-
|
|
46
|
+
statusCode: response.statusCode || HttpStatus.OK,
|
|
47
47
|
message,
|
|
48
48
|
data,
|
|
49
49
|
timestamp: new Date().toISOString(),
|
|
@@ -48,13 +48,6 @@ export class AuthService {
|
|
|
48
48
|
throw new UnauthorizedException('Account is inactive');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
console.log({
|
|
52
|
-
passwordInput: loginDto.password,
|
|
53
|
-
passwordHash: user.password,
|
|
54
|
-
isActive: user.is_active,
|
|
55
|
-
deletedAt: user.deleted_at,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
51
|
// Verify password
|
|
59
52
|
const isPasswordValid = await PasswordUtil.compare(password, user.password);
|
|
60
53
|
if (!isPasswordValid) {
|
|
@@ -6,9 +6,13 @@ import { RegisterDto } from '../../core/dto/register.dto';
|
|
|
6
6
|
import { AuthResponseDto } from '../../core/dto/auth-response.dto';
|
|
7
7
|
import { Public } from '@common/decorators/public.decorator';
|
|
8
8
|
import { ApiSuccessResponse } from '@common/decorators/api-response.decorator';
|
|
9
|
+
import { JwtAuthGuard } from '@common/guards/jwt-auth.guard';
|
|
10
|
+
import { PermissionsGuard } from '@common/guards/permissions.guard';
|
|
9
11
|
|
|
10
12
|
@ApiTags('Authentication')
|
|
13
|
+
@ApiBearerAuth('JWT-auth')
|
|
11
14
|
@Controller({ path: 'auth', version: '1' })
|
|
15
|
+
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
|
12
16
|
export class AuthController {
|
|
13
17
|
constructor(private readonly authService: AuthService) {}
|
|
14
18
|
|
|
@@ -24,13 +24,13 @@ import { Permissions } from '@common/decorators/permissions.decorator';
|
|
|
24
24
|
import { PERMISSIONS } from '@common/constants/permissions.constant';
|
|
25
25
|
import { ApiSuccessResponse, ApiSuccessArrayResponse } from '@common/decorators/api-response.decorator';
|
|
26
26
|
import { PaginatedResponseDto } from '@common/dto/pagination.dto';
|
|
27
|
-
import { AuthGuard } from '@nestjs/passport';
|
|
28
27
|
import { PermissionsGuard } from '@common/guards/permissions.guard';
|
|
28
|
+
import { JwtAuthGuard } from '@common/guards/jwt-auth.guard';
|
|
29
29
|
|
|
30
30
|
@ApiTags('Users')
|
|
31
31
|
@ApiBearerAuth('JWT-auth')
|
|
32
32
|
@Controller({ path: 'users', version: '1' })
|
|
33
|
-
@UseGuards(
|
|
33
|
+
@UseGuards(JwtAuthGuard, PermissionsGuard)
|
|
34
34
|
export class UsersController {
|
|
35
35
|
constructor(private readonly usersService: UsersService) {}
|
|
36
36
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbansolv/create-nestjs-app",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "CLI generator for Urbansolv NestJS boilerplate with RBAC and Prisma",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"homepage": "https://github.com/godwimp/create-nestjs-app#readme",
|
|
40
40
|
"files": [
|
|
41
41
|
"dist",
|
|
42
|
+
"templates",
|
|
42
43
|
"README.md",
|
|
43
44
|
"LICENSE"
|
|
44
45
|
],
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Application
|
|
2
|
+
NODE_ENV=development
|
|
3
|
+
PORT=3000
|
|
4
|
+
APP_NAME=__PROJECT_NAME__
|
|
5
|
+
|
|
6
|
+
# Database
|
|
7
|
+
DATABASE_URL="postgresql://username:password@localhost:5432/__DATABASE_NAME__?schema=public"
|
|
8
|
+
|
|
9
|
+
# JWT
|
|
10
|
+
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
|
11
|
+
JWT_EXPIRATION=7d
|
|
12
|
+
JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production
|
|
13
|
+
JWT_REFRESH_EXPIRATION=30d
|
|
14
|
+
|
|
15
|
+
# CORS
|
|
16
|
+
CORS_ORIGIN=http://localhost:3001
|
|
17
|
+
|
|
18
|
+
# API
|
|
19
|
+
API_PREFIX=api
|
|
20
|
+
API_VERSION=v1
|
|
21
|
+
|
|
22
|
+
# Swagger
|
|
23
|
+
SWAGGER_ENABLED=true
|
|
24
|
+
SWAGGER_PATH=docs
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
parserOptions: {
|
|
4
|
+
project: 'tsconfig.json',
|
|
5
|
+
tsconfigRootDir: __dirname,
|
|
6
|
+
sourceType: 'module',
|
|
7
|
+
},
|
|
8
|
+
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
9
|
+
extends: [
|
|
10
|
+
'plugin:@typescript-eslint/recommended',
|
|
11
|
+
'plugin:prettier/recommended',
|
|
12
|
+
],
|
|
13
|
+
root: true,
|
|
14
|
+
env: {
|
|
15
|
+
node: true,
|
|
16
|
+
jest: true,
|
|
17
|
+
},
|
|
18
|
+
ignorePatterns: ['.eslintrc.js'],
|
|
19
|
+
rules: {
|
|
20
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
|
21
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
22
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
23
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Urbansolv NestJS Backend
|
|
2
|
+
|
|
3
|
+
Backend application built with NestJS following Urbansolv standardization manual.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- š JWT Authentication with RBAC
|
|
8
|
+
- š Complete User Management
|
|
9
|
+
- šÆ Permission-based Access Control
|
|
10
|
+
- š Auto-generated Swagger Documentation
|
|
11
|
+
- ā
Input Validation
|
|
12
|
+
- š API Versioning
|
|
13
|
+
- š Structured Logging
|
|
14
|
+
- šļø Prisma ORM with PostgreSQL
|
|
15
|
+
- š§Ŗ Testing Setup (Unit & E2E)
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- Node.js >= 18.x
|
|
20
|
+
- PostgreSQL >= 14.x
|
|
21
|
+
- npm/yarn/pnpm
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
```bash
|
|
25
|
+
# Install dependencies
|
|
26
|
+
npm install
|
|
27
|
+
|
|
28
|
+
# Setup environment variables
|
|
29
|
+
cp .env.example .env
|
|
30
|
+
# Edit .env with your database credentials
|
|
31
|
+
|
|
32
|
+
# Generate Prisma Client
|
|
33
|
+
npm run prisma:generate
|
|
34
|
+
|
|
35
|
+
# Run migrations
|
|
36
|
+
npm run prisma:migrate
|
|
37
|
+
|
|
38
|
+
# Seed database
|
|
39
|
+
npm run prisma:seed
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Running the Application
|
|
43
|
+
```bash
|
|
44
|
+
# Development
|
|
45
|
+
npm run start:dev
|
|
46
|
+
|
|
47
|
+
# Production build
|
|
48
|
+
npm run build
|
|
49
|
+
npm run start:prod
|
|
50
|
+
|
|
51
|
+
# Debug mode
|
|
52
|
+
npm run start:debug
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Database Commands
|
|
56
|
+
```bash
|
|
57
|
+
# Generate Prisma Client
|
|
58
|
+
npm run prisma:generate
|
|
59
|
+
|
|
60
|
+
# Create migration
|
|
61
|
+
npm run prisma:migrate
|
|
62
|
+
|
|
63
|
+
# Seed database
|
|
64
|
+
npm run prisma:seed
|
|
65
|
+
|
|
66
|
+
# Open Prisma Studio
|
|
67
|
+
npm run prisma:studio
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Testing
|
|
71
|
+
```bash
|
|
72
|
+
# Unit tests
|
|
73
|
+
npm run test
|
|
74
|
+
|
|
75
|
+
# E2E tests
|
|
76
|
+
npm run test:e2e
|
|
77
|
+
|
|
78
|
+
# Test coverage
|
|
79
|
+
npm run test:cov
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## API Documentation
|
|
83
|
+
|
|
84
|
+
After starting the application, visit:
|
|
85
|
+
- Swagger UI: `http://localhost:3000/api-docs`
|
|
86
|
+
|
|
87
|
+
## Default Users
|
|
88
|
+
|
|
89
|
+
After seeding the database:
|
|
90
|
+
|
|
91
|
+
**Admin User:**
|
|
92
|
+
- Email: `admin@urbansolv.com`
|
|
93
|
+
- Password: `password123`
|
|
94
|
+
- All permissions granted
|
|
95
|
+
|
|
96
|
+
**Member User:**
|
|
97
|
+
- Email: `member@urbansolv.com`
|
|
98
|
+
- Password: `password123`
|
|
99
|
+
- Limited permissions (VIEW_USER only)
|
|
100
|
+
|
|
101
|
+
## Project Structure
|
|
102
|
+
```
|
|
103
|
+
src/
|
|
104
|
+
āāā common/ # Shared resources
|
|
105
|
+
ā āāā decorators/ # Custom decorators
|
|
106
|
+
ā āāā filters/ # Exception filters
|
|
107
|
+
ā āāā guards/ # Auth & permission guards
|
|
108
|
+
ā āāā interceptors/ # Logging & transform
|
|
109
|
+
ā āāā prisma/ # Prisma service
|
|
110
|
+
ā āāā utils/ # Helper functions
|
|
111
|
+
āāā config/ # Configuration files
|
|
112
|
+
āāā modules/ # Feature modules
|
|
113
|
+
ā āāā auth/ # Authentication
|
|
114
|
+
ā āāā users/ # User management
|
|
115
|
+
ā āāā health/ # Health checks
|
|
116
|
+
āāā app.module.ts # Root module
|
|
117
|
+
āāā main.ts # Application entry point
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Environment Variables
|
|
121
|
+
|
|
122
|
+
See `.env.example` for all available configuration options.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
Proprietary - Urbansolv
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 20.2 .gitkeep for migrations
|
|
130
|
+
|
|
131
|
+
**templates/nestjs-app/prisma/migrations/.gitkeep**
|
|
132
|
+
```
|
|
133
|
+
# This file keeps the migrations directory in git
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Urbansolv NestJS Application",
|
|
5
|
+
"author": "Urbansolv",
|
|
6
|
+
"private": true,
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "npm run clean && tsc && npm run copy-templates",
|
|
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/seed.ts"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@nestjs/common": "^11.0.1",
|
|
32
|
+
"@nestjs/config": "^4.0.2",
|
|
33
|
+
"@nestjs/core": "^11.0.1",
|
|
34
|
+
"@nestjs/jwt": "^11.0.2",
|
|
35
|
+
"@nestjs/passport": "^11.0.5",
|
|
36
|
+
"@nestjs/platform-express": "^11.0.1",
|
|
37
|
+
"@nestjs/swagger": "^11.2.3",
|
|
38
|
+
"@prisma/client": "^6.19.0",
|
|
39
|
+
"bcryptjs": "^3.0.3",
|
|
40
|
+
"class-transformer": "^0.5.1",
|
|
41
|
+
"class-validator": "^0.14.0",
|
|
42
|
+
"joi": "^18.0.2",
|
|
43
|
+
"passport": "^0.7.0",
|
|
44
|
+
"passport-jwt": "^4.0.1",
|
|
45
|
+
"reflect-metadata": "^0.2.2",
|
|
46
|
+
"rxjs": "^7.8.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@nestjs/cli": "^11.0.0",
|
|
50
|
+
"@nestjs/schematics": "^11.0.0",
|
|
51
|
+
"@nestjs/testing": "^11.0.1",
|
|
52
|
+
"@types/bcrypt": "^6.0.0",
|
|
53
|
+
"@types/express": "^5.0.5",
|
|
54
|
+
"@types/jest": "^30.0.0",
|
|
55
|
+
"@types/node": "^22.10.7",
|
|
56
|
+
"@types/passport-jwt": "^4.0.1",
|
|
57
|
+
"@types/supertest": "^6.0.2",
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
59
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
60
|
+
"eslint": "^9.18.0",
|
|
61
|
+
"eslint-config-prettier": "^10.0.1",
|
|
62
|
+
"eslint-plugin-prettier": "^5.2.2",
|
|
63
|
+
"jest": "^30.0.0",
|
|
64
|
+
"prettier": "^3.4.2",
|
|
65
|
+
"prisma": "^6.19.0",
|
|
66
|
+
"source-map-support": "^0.5.21",
|
|
67
|
+
"supertest": "^7.0.0",
|
|
68
|
+
"ts-jest": "^29.2.5",
|
|
69
|
+
"ts-loader": "^9.5.2",
|
|
70
|
+
"ts-node": "^10.9.2",
|
|
71
|
+
"tsconfig-paths": "^4.2.0",
|
|
72
|
+
"typescript": "^5.7.3"
|
|
73
|
+
},
|
|
74
|
+
"prisma": {
|
|
75
|
+
"seed": "ts-node prisma/seed.ts"
|
|
76
|
+
},
|
|
77
|
+
"jest": {
|
|
78
|
+
"moduleFileExtensions": ["js", "json", "ts"],
|
|
79
|
+
"rootDir": "src",
|
|
80
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
81
|
+
"transform": {
|
|
82
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
83
|
+
},
|
|
84
|
+
"collectCoverageFrom": ["**/*.(t|j)s"],
|
|
85
|
+
"coverageDirectory": "../coverage",
|
|
86
|
+
"testEnvironment": "node"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "postgresql"
|
|
10
|
+
url = env("DATABASE_URL")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// RBAC Schema
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
model User {
|
|
18
|
+
id Int @id @default(autoincrement())
|
|
19
|
+
email String @unique
|
|
20
|
+
password String
|
|
21
|
+
first_name String
|
|
22
|
+
last_name String
|
|
23
|
+
is_active Boolean @default(true)
|
|
24
|
+
created_at DateTime @default(now())
|
|
25
|
+
updated_at DateTime @updatedAt
|
|
26
|
+
deleted_at DateTime?
|
|
27
|
+
|
|
28
|
+
// Relations
|
|
29
|
+
position_id Int
|
|
30
|
+
position Position @relation(fields: [position_id], references: [id])
|
|
31
|
+
|
|
32
|
+
@@map("users")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
model Position {
|
|
36
|
+
id Int @id @default(autoincrement())
|
|
37
|
+
name String @unique
|
|
38
|
+
description String?
|
|
39
|
+
is_active Boolean @default(true)
|
|
40
|
+
created_at DateTime @default(now())
|
|
41
|
+
updated_at DateTime @updatedAt
|
|
42
|
+
deleted_at DateTime?
|
|
43
|
+
|
|
44
|
+
// Relations
|
|
45
|
+
users User[]
|
|
46
|
+
position_permissions PositionPermission[]
|
|
47
|
+
|
|
48
|
+
@@map("positions")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
model Permission {
|
|
52
|
+
id Int @id @default(autoincrement())
|
|
53
|
+
name String @unique
|
|
54
|
+
description String?
|
|
55
|
+
resource String // e.g., 'USER', 'TRANSACTION', 'REPORT'
|
|
56
|
+
action String // e.g., 'VIEW', 'ADD', 'UPDATE', 'DELETE'
|
|
57
|
+
created_at DateTime @default(now())
|
|
58
|
+
updated_at DateTime @updatedAt
|
|
59
|
+
deleted_at DateTime?
|
|
60
|
+
|
|
61
|
+
// Relations
|
|
62
|
+
position_permissions PositionPermission[]
|
|
63
|
+
|
|
64
|
+
@@map("permissions")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
model PositionPermission {
|
|
68
|
+
id Int @id @default(autoincrement())
|
|
69
|
+
position_id Int
|
|
70
|
+
permission_id Int
|
|
71
|
+
created_at DateTime @default(now())
|
|
72
|
+
|
|
73
|
+
// Relations
|
|
74
|
+
position Position @relation(fields: [position_id], references: [id], onDelete: Cascade)
|
|
75
|
+
permission Permission @relation(fields: [permission_id], references: [id], onDelete: Cascade)
|
|
76
|
+
|
|
77
|
+
@@unique([position_id, permission_id])
|
|
78
|
+
@@map("position_permissions")
|
|
79
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
2
|
+
import * as bcrypt from 'bcryptjs';
|
|
3
|
+
|
|
4
|
+
const prisma = new PrismaClient();
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
console.log('š± Starting database seeding...\n');
|
|
8
|
+
|
|
9
|
+
// Clear existing data
|
|
10
|
+
console.log('šļø Clearing existing data...');
|
|
11
|
+
await prisma.positionPermission.deleteMany();
|
|
12
|
+
await prisma.user.deleteMany();
|
|
13
|
+
await prisma.permission.deleteMany();
|
|
14
|
+
await prisma.position.deleteMany();
|
|
15
|
+
|
|
16
|
+
// ============================================
|
|
17
|
+
// Seed Positions
|
|
18
|
+
// ============================================
|
|
19
|
+
console.log('š Seeding positions...');
|
|
20
|
+
const adminPosition = await prisma.position.create({
|
|
21
|
+
data: {
|
|
22
|
+
name: 'Administrator',
|
|
23
|
+
description: 'Full system access with all permissions',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const memberPosition = await prisma.position.create({
|
|
28
|
+
data: {
|
|
29
|
+
name: 'Member',
|
|
30
|
+
description: 'Standard user with limited permissions',
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log('ā
Positions seeded');
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// Seed Permissions
|
|
38
|
+
// ============================================
|
|
39
|
+
console.log('š Seeding permissions...');
|
|
40
|
+
|
|
41
|
+
const permissionsData = [
|
|
42
|
+
// User Management
|
|
43
|
+
{ name: 'VIEW_USER', resource: 'USER', action: 'VIEW', description: 'View user information' },
|
|
44
|
+
{ name: 'ADD_USER', resource: 'USER', action: 'ADD', description: 'Create new user' },
|
|
45
|
+
{ name: 'UPDATE_USER', resource: 'USER', action: 'UPDATE', description: 'Update user information' },
|
|
46
|
+
{ name: 'DELETE_USER', resource: 'USER', action: 'DELETE', description: 'Delete user' },
|
|
47
|
+
{ name: 'MANAGE_USER_PERMISSION', resource: 'USER', action: 'MANAGE_PERMISSION', description: 'Assign or revoke user permissions' },
|
|
48
|
+
{ name: 'CHANGE_USER_POSITION', resource: 'USER', action: 'CHANGE_POSITION', description: 'Change user position/role' },
|
|
49
|
+
|
|
50
|
+
// Position Management
|
|
51
|
+
{ name: 'VIEW_POSITION', resource: 'POSITION', action: 'VIEW', description: 'View position information' },
|
|
52
|
+
{ name: 'ADD_POSITION', resource: 'POSITION', action: 'ADD', description: 'Create new position' },
|
|
53
|
+
{ name: 'UPDATE_POSITION', resource: 'POSITION', action: 'UPDATE', description: 'Update position information' },
|
|
54
|
+
{ name: 'DELETE_POSITION', resource: 'POSITION', action: 'DELETE', description: 'Delete position' },
|
|
55
|
+
|
|
56
|
+
// Permission Management
|
|
57
|
+
{ name: 'VIEW_PERMISSION', resource: 'PERMISSION', action: 'VIEW', description: 'View permission information' },
|
|
58
|
+
{ name: 'ADD_PERMISSION', resource: 'PERMISSION', action: 'ADD', description: 'Create new permission' },
|
|
59
|
+
{ name: 'UPDATE_PERMISSION', resource: 'PERMISSION', action: 'UPDATE', description: 'Update permission information' },
|
|
60
|
+
{ name: 'DELETE_PERMISSION', resource: 'PERMISSION', action: 'DELETE', description: 'Delete permission' },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const permissions = await Promise.all(
|
|
64
|
+
permissionsData.map((permission) =>
|
|
65
|
+
prisma.permission.create({ data: permission })
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
console.log(`ā
${permissions.length} permissions seeded`);
|
|
70
|
+
|
|
71
|
+
// ============================================
|
|
72
|
+
// Assign Permissions to Positions
|
|
73
|
+
// ============================================
|
|
74
|
+
console.log('š Assigning permissions to positions...');
|
|
75
|
+
|
|
76
|
+
// Admin gets all permissions
|
|
77
|
+
const adminPermissions = permissions.map((permission) => ({
|
|
78
|
+
position_id: adminPosition.id,
|
|
79
|
+
permission_id: permission.id,
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
await prisma.positionPermission.createMany({
|
|
83
|
+
data: adminPermissions,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Member gets only VIEW_USER permission
|
|
87
|
+
const viewUserPermission = permissions.find((p) => p.name === 'VIEW_USER');
|
|
88
|
+
if (viewUserPermission) {
|
|
89
|
+
await prisma.positionPermission.create({
|
|
90
|
+
data: {
|
|
91
|
+
position_id: memberPosition.id,
|
|
92
|
+
permission_id: viewUserPermission.id,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('ā
Permissions assigned to positions');
|
|
98
|
+
|
|
99
|
+
// ============================================
|
|
100
|
+
// Seed Users
|
|
101
|
+
// ============================================
|
|
102
|
+
console.log('š„ Seeding users...');
|
|
103
|
+
|
|
104
|
+
const defaultPassword = 'password123';
|
|
105
|
+
const hashedPassword = await bcrypt.hash(defaultPassword, 10);
|
|
106
|
+
|
|
107
|
+
const adminUser = await prisma.user.create({
|
|
108
|
+
data: {
|
|
109
|
+
email: 'admin@urbansolv.co.id',
|
|
110
|
+
password: hashedPassword,
|
|
111
|
+
first_name: 'Admin',
|
|
112
|
+
last_name: 'Urbansolv',
|
|
113
|
+
position_id: adminPosition.id,
|
|
114
|
+
is_active: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const memberUser = await prisma.user.create({
|
|
119
|
+
data: {
|
|
120
|
+
email: 'member@urbansolv.co.id',
|
|
121
|
+
password: hashedPassword,
|
|
122
|
+
first_name: 'Member',
|
|
123
|
+
last_name: 'User',
|
|
124
|
+
position_id: memberPosition.id,
|
|
125
|
+
is_active: true,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
console.log('ā
Users seeded');
|
|
130
|
+
|
|
131
|
+
// ============================================
|
|
132
|
+
// Summary
|
|
133
|
+
// ============================================
|
|
134
|
+
console.log('\n⨠Database seeding completed!\n');
|
|
135
|
+
console.log('š Summary:');
|
|
136
|
+
console.log(` - Positions: ${await prisma.position.count()}`);
|
|
137
|
+
console.log(` - Permissions: ${await prisma.permission.count()}`);
|
|
138
|
+
console.log(` - Users: ${await prisma.user.count()}`);
|
|
139
|
+
console.log(` - Position-Permission Links: ${await prisma.positionPermission.count()}\n`);
|
|
140
|
+
|
|
141
|
+
console.log('š Default Users:');
|
|
142
|
+
console.log(` Admin: ${adminUser.email} / ${defaultPassword}`);
|
|
143
|
+
console.log(` Member: ${memberUser.email} / ${defaultPassword}\n`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main()
|
|
147
|
+
.catch((e) => {
|
|
148
|
+
console.error('ā Error seeding database:', e);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
})
|
|
151
|
+
.finally(async () => {
|
|
152
|
+
await prisma.$disconnect();
|
|
153
|
+
});
|