nestjs-prisma-cli 1.0.8 → 1.0.10
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 +17 -3
- package/bin/index.js +45 -32
- package/package.json +2 -2
- package/template/prisma/schema.prisma +34 -17
- package/template/src/app.module.ts +7 -2
- package/template/src/common/config/index.ts +4 -0
- package/template/src/common/config/multer-config.ts +11 -0
- package/template/src/common/config/swagger.config.ts +36 -0
- package/template/src/common/decorator/index.ts +4 -0
- package/template/src/common/decorator/response-message.decorator.ts +5 -0
- package/template/src/common/dto/paginated-response.dto.ts +14 -2
- package/template/src/common/dto/pagination.dto.ts +18 -0
- package/template/src/common/enums/http-error-type-enum.ts +31 -0
- package/template/src/common/enums/index.ts +2 -1
- package/template/src/common/enums/message-code.enum.ts +2 -0
- package/template/src/common/http-interceptor/http-exception.filter.ts +99 -57
- package/template/src/common/http-interceptor/logging.interceptor.ts +55 -10
- package/template/src/common/http-interceptor/response.interceptor.ts +68 -28
- package/template/src/common/s3/s3.service.ts +9 -24
- package/template/src/common/utils/pagination.util.ts +46 -16
- package/template/src/main.ts +11 -38
- package/template/src/modules/auth/auth.controller.ts +21 -0
- package/template/src/modules/auth/auth.service.ts +9 -9
- package/template/src/modules/auth/jwt/jwt.guard.ts +1 -1
- package/template/src/modules/user/dto/create-user.dto.ts +8 -1
- package/template/src/modules/user/user.controller.ts +53 -16
- package/template/src/modules/user/user.module.ts +3 -0
- package/template/src/modules/user/user.service.ts +132 -98
- package/template/src/common/classes/base64.ts +0 -22
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ A CLI tool to quickly scaffold a **NestJS + Prisma** project with built-in suppo
|
|
|
14
14
|
- 📖 Swagger (OpenAPI) setup
|
|
15
15
|
- 🔑 Authentication boilerplate
|
|
16
16
|
- ☁️ AWS S3 integration
|
|
17
|
+
- 📜 Logging via Winston (rotating logs and DB persistence)
|
|
17
18
|
|
|
18
19
|
Compatible with **Node.js >=18** and **NestJS v10+**.
|
|
19
20
|
|
|
@@ -31,19 +32,20 @@ Once the CLI is installed, you can use the following commands:
|
|
|
31
32
|
npm install -g nestjs-prisma-cli
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
|
|
35
35
|
### 2️⃣ Check CLI version
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
nestgen -v
|
|
39
39
|
```
|
|
40
|
+
|
|
40
41
|
# or
|
|
42
|
+
|
|
41
43
|
```bash
|
|
42
44
|
nestgen --version
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
|
|
46
47
|
### 3️⃣ Generate a new project
|
|
48
|
+
|
|
47
49
|
```bash
|
|
48
50
|
nestgen
|
|
49
51
|
```
|
|
@@ -61,31 +63,43 @@ Step 3: 🎉 Project ready! Next steps:
|
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
### Navigate into your project
|
|
66
|
+
|
|
64
67
|
```bash
|
|
65
68
|
cd my-app
|
|
66
69
|
```
|
|
70
|
+
|
|
67
71
|
### Update .env
|
|
72
|
+
|
|
68
73
|
```bash
|
|
69
74
|
DATABASE_URL="your_database_connection_string"
|
|
70
75
|
```
|
|
76
|
+
|
|
71
77
|
```bash
|
|
72
78
|
npx prisma generate
|
|
73
79
|
```
|
|
80
|
+
|
|
74
81
|
### SQL Databases
|
|
82
|
+
|
|
75
83
|
### (PostgreSQL, MySQL, SQLite, CockroachDB, SQLServer)
|
|
84
|
+
|
|
76
85
|
```bash
|
|
77
86
|
npx prisma migrate dev --name init
|
|
78
87
|
```
|
|
88
|
+
|
|
79
89
|
### NoSQL Database
|
|
90
|
+
|
|
80
91
|
### (MongoDB)
|
|
92
|
+
|
|
81
93
|
```bash
|
|
82
94
|
npx prisma db push
|
|
83
95
|
```
|
|
96
|
+
|
|
84
97
|
### Then, follow this
|
|
98
|
+
|
|
85
99
|
```bash
|
|
86
100
|
npx ts-node prisma/seed.ts
|
|
87
101
|
```
|
|
102
|
+
|
|
88
103
|
```bash
|
|
89
104
|
npm run start:dev
|
|
90
105
|
```
|
|
91
|
-
|
package/bin/index.js
CHANGED
|
@@ -56,42 +56,42 @@ model User {
|
|
|
56
56
|
name String?
|
|
57
57
|
email String
|
|
58
58
|
password String
|
|
59
|
+
profileUrl String?
|
|
59
60
|
isActive Boolean @default(true)
|
|
60
61
|
createdAt DateTime @default(now())
|
|
61
62
|
updatedAt DateTime @updatedAt
|
|
62
63
|
@@map("tbl_user")
|
|
63
64
|
}
|
|
65
|
+
|
|
66
|
+
model Log {
|
|
67
|
+
id String @id @default(auto()) @map("_id") @db.ObjectId
|
|
68
|
+
method String
|
|
69
|
+
path String
|
|
70
|
+
statusCode Int
|
|
71
|
+
messageCode String?
|
|
72
|
+
message String?
|
|
73
|
+
headers Json
|
|
74
|
+
body Json?
|
|
75
|
+
query Json?
|
|
76
|
+
duration Int
|
|
77
|
+
createdAt DateTime @default(now())
|
|
78
|
+
|
|
79
|
+
@@map("tbl_log")
|
|
80
|
+
}
|
|
64
81
|
`;
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
try {
|
|
68
85
|
let prismaContent = await fs.readFile(templatePrismaPath, "utf-8");
|
|
69
|
-
return prismaContent.replace(
|
|
70
|
-
/datasource\s+db\s*{[^}]*provider\s*=\s*".*"/,
|
|
71
|
-
`datasource db {\n provider = "${selectedProvider}"`
|
|
72
|
-
);
|
|
73
|
-
} catch {
|
|
74
|
-
return `generator client {
|
|
75
|
-
provider = "prisma-client-js"
|
|
76
|
-
}
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
prismaContent = prismaContent.replace(
|
|
88
|
+
/(datasource\s+db\s*{[^}]*provider\s*=\s*")\w+(")/,
|
|
89
|
+
`$1${selectedProvider}$2`
|
|
90
|
+
);
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
name String?
|
|
87
|
-
email String
|
|
88
|
-
password String
|
|
89
|
-
isActive Boolean @default(true)
|
|
90
|
-
createdAt DateTime @default(now())
|
|
91
|
-
updatedAt DateTime @updatedAt
|
|
92
|
-
@@map("tbl_user")
|
|
93
|
-
}
|
|
94
|
-
`;
|
|
92
|
+
return prismaContent;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw new Error(`Failed to read Prisma template: ${err.message}`);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -141,12 +141,27 @@ async function main() {
|
|
|
141
141
|
"@aws-sdk/s3-request-presigner",
|
|
142
142
|
"moment",
|
|
143
143
|
];
|
|
144
|
-
await execa(pkgManager, ["install", ...coreDeps], { cwd: projectPath, stdio: "inherit" });
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
const devDeps = [
|
|
146
|
+
"prisma",
|
|
147
|
+
"@types/multer",
|
|
148
|
+
];
|
|
149
|
+
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
await execa(pkgManager, ["install", ...coreDeps], {
|
|
152
|
+
cwd: projectPath,
|
|
153
|
+
stdio: "inherit",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await execa(pkgManager, ["install", "@prisma/client"], {
|
|
157
|
+
cwd: projectPath,
|
|
158
|
+
stdio: "inherit",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await execa(pkgManager, ["install", "-D", ...devDeps], {
|
|
162
|
+
cwd: projectPath,
|
|
163
|
+
stdio: "inherit",
|
|
164
|
+
});
|
|
150
165
|
|
|
151
166
|
const templatePath = path.resolve(__dirname, "../template");
|
|
152
167
|
const projectPrismaPath = path.join(projectPath, "prisma/schema.prisma");
|
|
@@ -154,7 +169,6 @@ async function main() {
|
|
|
154
169
|
|
|
155
170
|
if (await fs.pathExists(templatePath)) {
|
|
156
171
|
await fs.copy(templatePath, projectPath, { overwrite: true });
|
|
157
|
-
console.log(chalk.green("✅ Template files copied!"));
|
|
158
172
|
}
|
|
159
173
|
|
|
160
174
|
const providerMap = {
|
|
@@ -199,10 +213,9 @@ PORT=3000
|
|
|
199
213
|
await fs.outputFile(path.join(projectPath, ".env"), envContent);
|
|
200
214
|
|
|
201
215
|
console.log(chalk.yellow("🎉 Project ready!"));
|
|
202
|
-
console.log(chalk.green("✅
|
|
203
|
-
console.log(chalk.cyan(
|
|
216
|
+
console.log(chalk.green("✅ Congratulations! Your project has been created successfully."));
|
|
217
|
+
console.log(chalk.cyan(`👉 Next steps: cd ${projectName}`));
|
|
204
218
|
|
|
205
|
-
console.log(chalk.yellow("🔧 Next steps (run manually):"));
|
|
206
219
|
console.log(chalk.cyan(`1. Generate Prisma Client:`));
|
|
207
220
|
console.log(chalk.cyan(` npx prisma generate`));
|
|
208
221
|
if (selectedProvider === "mongodb") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-prisma-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "A CLI to generate NestJS + Prisma project boilerplate with Swagger, Auth, and AWS S3 setup",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"author": "Kyaw Soe",
|
|
30
30
|
"license": "MIT",
|
|
31
|
-
"homepage": "https://github.com/kyawsoe-dev/nestjs-generator-cli
|
|
31
|
+
"homepage": "https://github.com/kyawsoe-dev/nestjs-generator-cli#readme",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"chalk": "^4.1.2",
|
|
34
34
|
"execa": "^9.6.0",
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
generator client {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "mysql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
model User {
|
|
11
|
+
id Int @id @default(autoincrement())
|
|
12
|
+
userId String @unique
|
|
13
|
+
name String?
|
|
14
|
+
email String
|
|
15
|
+
password String
|
|
16
|
+
profileUrl String?
|
|
17
|
+
createdAt DateTime @default(now())
|
|
18
|
+
updatedAt DateTime @updatedAt
|
|
19
|
+
|
|
20
|
+
@@map("tbl_user")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
model Log {
|
|
24
|
+
id Int @id @default(autoincrement())
|
|
25
|
+
method String
|
|
26
|
+
path String
|
|
27
|
+
statusCode Int
|
|
28
|
+
messageCode String?
|
|
29
|
+
message String?
|
|
30
|
+
headers Json
|
|
31
|
+
body Json?
|
|
32
|
+
query Json?
|
|
33
|
+
duration Int
|
|
34
|
+
createdAt DateTime @default(now())
|
|
35
|
+
|
|
36
|
+
@@map("tbl_log")
|
|
37
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
|
-
import { APP_INTERCEPTOR } from '@nestjs/core';
|
|
2
|
+
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
|
|
3
3
|
import { APP_GUARD } from '@nestjs/core';
|
|
4
4
|
import { AppController } from './app.controller';
|
|
5
5
|
import { AppService } from './app.service';
|
|
@@ -7,6 +7,7 @@ import { PrismaModule } from './prisma/prisma.module';
|
|
|
7
7
|
import {
|
|
8
8
|
HttpResponseInterceptor,
|
|
9
9
|
LoggingInterceptor,
|
|
10
|
+
HttpExceptionFilter,
|
|
10
11
|
} from './common/http-interceptor/index';
|
|
11
12
|
import { JwtAuthGuard } from './modules/auth/jwt/jwt.guard';
|
|
12
13
|
import { ConfigModule } from '@nestjs/config';
|
|
@@ -20,7 +21,7 @@ import { AuthModule } from './modules/auth/auth.module';
|
|
|
20
21
|
}),
|
|
21
22
|
PrismaModule,
|
|
22
23
|
UserModule,
|
|
23
|
-
AuthModule
|
|
24
|
+
AuthModule,
|
|
24
25
|
],
|
|
25
26
|
controllers: [AppController],
|
|
26
27
|
providers: [
|
|
@@ -28,6 +29,10 @@ import { AuthModule } from './modules/auth/auth.module';
|
|
|
28
29
|
provide: APP_INTERCEPTOR,
|
|
29
30
|
useClass: LoggingInterceptor,
|
|
30
31
|
},
|
|
32
|
+
{
|
|
33
|
+
provide: APP_FILTER,
|
|
34
|
+
useClass: HttpExceptionFilter,
|
|
35
|
+
},
|
|
31
36
|
{
|
|
32
37
|
provide: APP_INTERCEPTOR,
|
|
33
38
|
useClass: HttpResponseInterceptor,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
|
|
2
|
+
|
|
3
|
+
export const MulterConfig: MulterOptions = {
|
|
4
|
+
limits: { fileSize: 10 * 1024 * 1024 },
|
|
5
|
+
fileFilter: (req, file, cb) => {
|
|
6
|
+
if (!file.mimetype.match(/^image\/(jpg|jpeg|png|gif)$/)) {
|
|
7
|
+
return cb(new Error('Only image files are allowed!'), false);
|
|
8
|
+
}
|
|
9
|
+
cb(null, true);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
4
|
+
import { INestApplication } from '@nestjs/common';
|
|
5
|
+
|
|
6
|
+
const title = 'API';
|
|
7
|
+
const description = 'API Swagger documentation';
|
|
8
|
+
|
|
9
|
+
export const SwaggerConfig = (app: INestApplication) => {
|
|
10
|
+
const options = new DocumentBuilder()
|
|
11
|
+
.setTitle(title)
|
|
12
|
+
.setDescription(description)
|
|
13
|
+
.setVersion('1.0')
|
|
14
|
+
.addBearerAuth(
|
|
15
|
+
{
|
|
16
|
+
type: 'http',
|
|
17
|
+
scheme: 'bearer',
|
|
18
|
+
bearerFormat: 'JWT',
|
|
19
|
+
},
|
|
20
|
+
'bearerAuth',
|
|
21
|
+
)
|
|
22
|
+
.addSecurityRequirements('bearerAuth')
|
|
23
|
+
.build();
|
|
24
|
+
|
|
25
|
+
const document = SwaggerModule.createDocument(app, options);
|
|
26
|
+
|
|
27
|
+
const outputPath = join(process.cwd(), 'swagger-openapi-v1.json');
|
|
28
|
+
writeFileSync(outputPath, JSON.stringify(document, null, 2));
|
|
29
|
+
console.log(`Swagger JSON saved to ${outputPath}`);
|
|
30
|
+
|
|
31
|
+
SwaggerModule.setup('api', app, document, {
|
|
32
|
+
swaggerOptions: {
|
|
33
|
+
persistAuthorization: true,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
};
|
|
@@ -5,10 +5,22 @@ export class PaginatedResponseDto<T> {
|
|
|
5
5
|
total: number;
|
|
6
6
|
|
|
7
7
|
@ApiProperty()
|
|
8
|
-
|
|
8
|
+
limit: number;
|
|
9
9
|
|
|
10
10
|
@ApiProperty()
|
|
11
|
-
|
|
11
|
+
currentPage: number;
|
|
12
|
+
|
|
13
|
+
@ApiProperty()
|
|
14
|
+
firstPage: number;
|
|
15
|
+
|
|
16
|
+
@ApiProperty()
|
|
17
|
+
lastPage: number;
|
|
18
|
+
|
|
19
|
+
@ApiProperty()
|
|
20
|
+
nextPage: number | null;
|
|
21
|
+
|
|
22
|
+
@ApiProperty()
|
|
23
|
+
previousPage: number | null;
|
|
12
24
|
|
|
13
25
|
@ApiProperty({ isArray: true })
|
|
14
26
|
data: T[];
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { Transform } from 'class-transformer';
|
|
2
3
|
import { Type } from 'class-transformer';
|
|
3
4
|
import {
|
|
4
5
|
IsInt,
|
|
5
6
|
IsOptional,
|
|
6
7
|
IsString,
|
|
7
8
|
Min,
|
|
9
|
+
IsIn,
|
|
8
10
|
IsBooleanString,
|
|
9
11
|
} from 'class-validator';
|
|
10
12
|
|
|
@@ -23,6 +25,22 @@ export class PaginationDto {
|
|
|
23
25
|
@Min(1)
|
|
24
26
|
limit?: number = 20;
|
|
25
27
|
|
|
28
|
+
@ApiPropertyOptional({ example: 'id', description: 'Field to sort by' })
|
|
29
|
+
@IsOptional()
|
|
30
|
+
@IsString()
|
|
31
|
+
sortBy?: string;
|
|
32
|
+
|
|
33
|
+
@ApiPropertyOptional({
|
|
34
|
+
example: 'desc',
|
|
35
|
+
description: 'Sort order: asc or desc',
|
|
36
|
+
})
|
|
37
|
+
@IsOptional()
|
|
38
|
+
@IsIn(['asc', 'desc'])
|
|
39
|
+
@Transform(({ value }) =>
|
|
40
|
+
typeof value === 'string' ? value.toLowerCase() : value,
|
|
41
|
+
)
|
|
42
|
+
sortOrder?: 'asc' | 'desc';
|
|
43
|
+
|
|
26
44
|
@ApiPropertyOptional({ example: '', description: 'Search term' })
|
|
27
45
|
@IsOptional()
|
|
28
46
|
@IsString()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export enum HttpErrorType {
|
|
2
|
+
BAD_REQUEST = 400,
|
|
3
|
+
UNAUTHORIZED = 401,
|
|
4
|
+
PAYMENT_REQUIRED = 402,
|
|
5
|
+
FORBIDDEN = 403,
|
|
6
|
+
NOT_FOUND = 404,
|
|
7
|
+
METHOD_NOT_ALLOWED = 405,
|
|
8
|
+
NOT_ACCEPTABLE = 406,
|
|
9
|
+
PROXY_AUTHENTICATION_REQUIRED = 407,
|
|
10
|
+
REQUEST_TIMEOUT = 408,
|
|
11
|
+
CONFLICT = 409,
|
|
12
|
+
GONE = 410,
|
|
13
|
+
LENGTH_REQUIRED = 411,
|
|
14
|
+
PRECONDITION_FAILED = 412,
|
|
15
|
+
PAYLOAD_TOO_LARGE = 413,
|
|
16
|
+
URI_TOO_LONG = 414,
|
|
17
|
+
UNSUPPORTED_MEDIA_TYPE = 415,
|
|
18
|
+
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
|
19
|
+
EXPECTATION_FAILED = 417,
|
|
20
|
+
I_AM_A_TEAPOT = 418,
|
|
21
|
+
MISDIRECTED = 421,
|
|
22
|
+
UNPROCESSABLE_ENTITY = 422,
|
|
23
|
+
FAILED_DEPENDENCY = 424,
|
|
24
|
+
TOO_MANY_REQUESTS = 429,
|
|
25
|
+
INTERNAL_SERVER_ERROR = 500,
|
|
26
|
+
NOT_IMPLEMENTED = 501,
|
|
27
|
+
BAD_GATEWAY = 502,
|
|
28
|
+
SERVICE_UNAVAILABLE = 503,
|
|
29
|
+
GATEWAY_TIMEOUT = 504,
|
|
30
|
+
HTTP_VERSION_NOT_SUPPORTED = 505,
|
|
31
|
+
}
|
|
@@ -5,6 +5,7 @@ export enum MESSAGE_CODE {
|
|
|
5
5
|
PASSWORD_SUCCESS = 204,
|
|
6
6
|
DATA_NOT_FOUND = 205,
|
|
7
7
|
USER_VERIFY = 206,
|
|
8
|
+
|
|
8
9
|
WRONG_USER_PW = 401,
|
|
9
10
|
INVALID_USER_ID = 402,
|
|
10
11
|
MISSED_TOKEN = 403,
|
|
@@ -16,5 +17,6 @@ export enum MESSAGE_CODE {
|
|
|
16
17
|
ROUTE_NOT_FOUND = 410,
|
|
17
18
|
REQUEST_FIELD_REQUIRED = 411,
|
|
18
19
|
DELETE_USER = 412,
|
|
20
|
+
|
|
19
21
|
SERVER_ERROR = 501,
|
|
20
22
|
}
|