create-zhx-monorepo 0.2.1 → 0.3.0
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/package.json +1 -1
- package/templates/monorepo-starter/gitignore +1 -1
- package/templates/monorepo-starter/pnpm-lock.yaml +13375 -12441
- package/templates/monorepo-starter/server/.env.example +5 -2
- package/templates/monorepo-starter/server/package.json +100 -97
- package/templates/monorepo-starter/server/prisma/schema.prisma +47 -3
- package/templates/monorepo-starter/server/scripts/gen-soft-delete.ts +52 -0
- package/templates/monorepo-starter/server/src/app.module.ts +2 -5
- package/templates/monorepo-starter/server/src/lib/constants/app.ts +1 -0
- package/templates/monorepo-starter/server/src/lib/guards/auth.guard.ts +4 -2
- package/templates/monorepo-starter/server/src/lib/schemas/env.schema.ts +4 -1
- package/templates/monorepo-starter/server/src/lib/templates/notification.templates.ts +114 -81
- package/templates/monorepo-starter/server/src/modules/auth/auth.service.ts +10 -9
- package/templates/monorepo-starter/server/src/modules/logger/winston.config.ts +15 -1
- package/templates/monorepo-starter/server/src/modules/notification/nodemailer.service.ts +34 -0
- package/templates/monorepo-starter/server/src/modules/notification/notification.service.ts +17 -11
- package/templates/monorepo-starter/server/src/modules/prisma/prisma.extension.ts +5 -15
- package/templates/monorepo-starter/server/src/modules/prisma/soft-delete.models.ts +6 -0
- package/templates/monorepo-starter/server/src/modules/token/token.service.ts +3 -4
- package/templates/monorepo-starter/server/tsconfig.json +1 -0
- package/templates/monorepo-starter/server/tsup.config.ts +1 -1
|
@@ -47,9 +47,12 @@ TWILIO_AUTH_TOKEN="your-twilio-auth-token"
|
|
|
47
47
|
TWILIO_PHONE_NUMBER="+1234567890"
|
|
48
48
|
|
|
49
49
|
# ==============================
|
|
50
|
-
# Email (
|
|
50
|
+
# Email (Nodemailer)
|
|
51
51
|
# ==============================
|
|
52
|
-
|
|
52
|
+
SMTP_HOST='your-smtp-host'
|
|
53
|
+
SMTP_PORT='your-smtp-port'
|
|
54
|
+
SMTP_USER='your-smtp-user'
|
|
55
|
+
SMTP_PASS='your-smtp-pass'
|
|
53
56
|
|
|
54
57
|
# ==============================
|
|
55
58
|
# API Keys
|
|
@@ -1,97 +1,100 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "server",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"private": true,
|
|
6
|
-
"license": "UNLICENSED",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"dev": "tsup src/main.ts --watch",
|
|
9
|
-
"build": "tsup",
|
|
10
|
-
"start": "node dist/main.js",
|
|
11
|
-
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
12
|
-
"format": "prettier --write \"{src,apps,libs,test}/**/*.ts\"",
|
|
13
|
-
"prisma:generate": "prisma generate",
|
|
14
|
-
"prisma:migrate:dev": "prisma migrate dev --name init",
|
|
15
|
-
"prisma:migrate:prod": "prisma migrate deploy --name init",
|
|
16
|
-
"prisma:studio": "prisma studio",
|
|
17
|
-
"prisma:validate": "prisma validate",
|
|
18
|
-
"prisma:reset": "prisma migrate reset",
|
|
19
|
-
"generate:
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"test
|
|
23
|
-
"test:
|
|
24
|
-
"test:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"@nestjs/
|
|
30
|
-
"@nestjs/
|
|
31
|
-
"@nestjs/
|
|
32
|
-
"@nestjs/
|
|
33
|
-
"@nestjs/
|
|
34
|
-
"@
|
|
35
|
-
"@
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"@nestjs/
|
|
57
|
-
"@
|
|
58
|
-
"@
|
|
59
|
-
"@
|
|
60
|
-
"@
|
|
61
|
-
"@types/
|
|
62
|
-
"@types/
|
|
63
|
-
"@types/
|
|
64
|
-
"@types/
|
|
65
|
-
"@types/
|
|
66
|
-
"@types/
|
|
67
|
-
"@types/
|
|
68
|
-
"@types/
|
|
69
|
-
"@
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "tsup src/main.ts --watch",
|
|
9
|
+
"build": "tsup",
|
|
10
|
+
"start": "node dist/main.js",
|
|
11
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
12
|
+
"format": "prettier --write \"{src,apps,libs,test}/**/*.ts\"",
|
|
13
|
+
"prisma:generate": "prisma generate && pnpm prisma:generate:sdm",
|
|
14
|
+
"prisma:migrate:dev": "prisma migrate dev --name init",
|
|
15
|
+
"prisma:migrate:prod": "prisma migrate deploy --name init",
|
|
16
|
+
"prisma:studio": "prisma studio",
|
|
17
|
+
"prisma:validate": "prisma validate",
|
|
18
|
+
"prisma:reset": "prisma migrate reset",
|
|
19
|
+
"prisma:generate:sdm": "ts-node scripts/gen-soft-delete",
|
|
20
|
+
"prisma:setup": "pnpm prisma:migrate:dev && pnpm prisma:generate",
|
|
21
|
+
"generate:module": "bash ./scripts/nest-gen.sh",
|
|
22
|
+
"test": "jest",
|
|
23
|
+
"test:watch": "jest --watch",
|
|
24
|
+
"test:cov": "jest --coverage",
|
|
25
|
+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
26
|
+
"test:e2e": "jest --config ./test/jest-e2e.json"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@nestjs/common": "^11.1.9",
|
|
30
|
+
"@nestjs/config": "^4.0.2",
|
|
31
|
+
"@nestjs/core": "^11.1.9",
|
|
32
|
+
"@nestjs/jwt": "^11.0.2",
|
|
33
|
+
"@nestjs/passport": "^11.0.5",
|
|
34
|
+
"@nestjs/platform-express": "^11.1.9",
|
|
35
|
+
"@nestjs/schedule": "^6.1.0",
|
|
36
|
+
"@prisma/adapter-pg": "^7.2.0",
|
|
37
|
+
"@prisma/client": "^7.2.0",
|
|
38
|
+
"argon2": "^0.44.0",
|
|
39
|
+
"class-transformer": "^0.5.1",
|
|
40
|
+
"class-validator": "^0.14.3",
|
|
41
|
+
"cookie-parser": "^1.4.7",
|
|
42
|
+
"ms": "^2.1.3",
|
|
43
|
+
"nest-winston": "^1.10.2",
|
|
44
|
+
"nodemailer": "^7.0.11",
|
|
45
|
+
"passport": "^0.7.0",
|
|
46
|
+
"passport-facebook": "^3.0.0",
|
|
47
|
+
"passport-google-oauth20": "^2.0.0",
|
|
48
|
+
"pg": "^8.16.3",
|
|
49
|
+
"reflect-metadata": "^0.2.2",
|
|
50
|
+
"rxjs": "^7.8.2",
|
|
51
|
+
"winston": "^3.19.0",
|
|
52
|
+
"winston-daily-rotate-file": "^5.0.0",
|
|
53
|
+
"zod": "^4.2.1"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@nestjs/cli": "^11.0.14",
|
|
57
|
+
"@nestjs/schematics": "^11.0.9",
|
|
58
|
+
"@nestjs/testing": "^11.1.9",
|
|
59
|
+
"@swc/cli": "^0.7.9",
|
|
60
|
+
"@swc/core": "^1.15.7",
|
|
61
|
+
"@types/cookie-parser": "^1.4.10",
|
|
62
|
+
"@types/express": "^5.0.6",
|
|
63
|
+
"@types/jest": "^30.0.0",
|
|
64
|
+
"@types/ms": "^2.1.0",
|
|
65
|
+
"@types/node": "^25.0.3",
|
|
66
|
+
"@types/nodemailer": "^7.0.4",
|
|
67
|
+
"@types/passport": "^1.0.17",
|
|
68
|
+
"@types/passport-facebook": "^3.0.4",
|
|
69
|
+
"@types/passport-google-oauth20": "^2.0.17",
|
|
70
|
+
"@types/pg": "^8.16.0",
|
|
71
|
+
"@types/supertest": "^6.0.3",
|
|
72
|
+
"@workspace/config": "workspace:^",
|
|
73
|
+
"jest": "^30.2.0",
|
|
74
|
+
"prisma": "^7.2.0",
|
|
75
|
+
"source-map-support": "^0.5.21",
|
|
76
|
+
"supertest": "^7.1.4",
|
|
77
|
+
"ts-jest": "^29.4.6",
|
|
78
|
+
"ts-loader": "^9.5.4",
|
|
79
|
+
"ts-node": "^10.9.2",
|
|
80
|
+
"tsconfig-paths": "^4.2.0",
|
|
81
|
+
"tsup": "^8.5.1"
|
|
82
|
+
},
|
|
83
|
+
"jest": {
|
|
84
|
+
"moduleFileExtensions": [
|
|
85
|
+
"js",
|
|
86
|
+
"json",
|
|
87
|
+
"ts"
|
|
88
|
+
],
|
|
89
|
+
"rootDir": "src",
|
|
90
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
91
|
+
"transform": {
|
|
92
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
93
|
+
},
|
|
94
|
+
"collectCoverageFrom": [
|
|
95
|
+
"**/*.(t|j)s"
|
|
96
|
+
],
|
|
97
|
+
"coverageDirectory": "../coverage",
|
|
98
|
+
"testEnvironment": "node"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// prisma/schema.prisma
|
|
2
|
-
|
|
3
1
|
generator client {
|
|
4
2
|
provider = "prisma-client"
|
|
5
3
|
output = "./generated"
|
|
@@ -15,6 +13,7 @@ datasource db {
|
|
|
15
13
|
|
|
16
14
|
enum UserRole {
|
|
17
15
|
admin
|
|
16
|
+
agent
|
|
18
17
|
customer
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -54,11 +53,54 @@ enum NotificationStatus {
|
|
|
54
53
|
read
|
|
55
54
|
}
|
|
56
55
|
|
|
56
|
+
enum PassengerType {
|
|
57
|
+
ADT
|
|
58
|
+
CHD
|
|
59
|
+
INF
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
enum FlightTicketType {
|
|
63
|
+
oneway
|
|
64
|
+
return
|
|
65
|
+
multicity
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
enum VisaEntryType {
|
|
69
|
+
single
|
|
70
|
+
multiple
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
enum VisaServiceType {
|
|
74
|
+
umrah
|
|
75
|
+
visit
|
|
76
|
+
tourist
|
|
77
|
+
transit
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
enum TourCategory {
|
|
81
|
+
domestic
|
|
82
|
+
international
|
|
83
|
+
custom
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
enum BookingStatus {
|
|
87
|
+
pending
|
|
88
|
+
confirmed
|
|
89
|
+
canceled
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
enum TourPriceCategory {
|
|
93
|
+
standard
|
|
94
|
+
deluxe
|
|
95
|
+
premium
|
|
96
|
+
}
|
|
97
|
+
|
|
57
98
|
//
|
|
58
99
|
// AUTH MODELS
|
|
59
100
|
//
|
|
60
101
|
|
|
61
102
|
model User {
|
|
103
|
+
// Core fields
|
|
62
104
|
id String @id @default(ulid())
|
|
63
105
|
username String? @unique
|
|
64
106
|
password String?
|
|
@@ -67,7 +109,7 @@ model User {
|
|
|
67
109
|
displayName String
|
|
68
110
|
imageUrl String?
|
|
69
111
|
|
|
70
|
-
// Contact
|
|
112
|
+
// Contact info
|
|
71
113
|
email String? @unique
|
|
72
114
|
phone String? @unique
|
|
73
115
|
isEmailVerified Boolean @default(false)
|
|
@@ -76,6 +118,7 @@ model User {
|
|
|
76
118
|
// Status
|
|
77
119
|
lastLoginAt DateTime?
|
|
78
120
|
|
|
121
|
+
// Meta
|
|
79
122
|
createdAt DateTime @default(now())
|
|
80
123
|
updatedAt DateTime @updatedAt
|
|
81
124
|
deletedAt DateTime?
|
|
@@ -89,6 +132,7 @@ model User {
|
|
|
89
132
|
notifications Notification[]
|
|
90
133
|
backupCodes BackupCode[]
|
|
91
134
|
|
|
135
|
+
// Indexes
|
|
92
136
|
@@index([email])
|
|
93
137
|
@@index([phone])
|
|
94
138
|
@@index([username])
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const SCHEMA_PATH = path.resolve(__dirname, "../prisma/schema.prisma");
|
|
9
|
+
const OUTPUT_PATH = path.resolve(
|
|
10
|
+
__dirname,
|
|
11
|
+
"../src/modules/prisma/soft-delete.models.ts"
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const schema = fs.readFileSync(SCHEMA_PATH, "utf8");
|
|
15
|
+
|
|
16
|
+
// Normalize line endings for consistency
|
|
17
|
+
const normalizedSchema = schema.replace(/\r\n/g, "\n");
|
|
18
|
+
|
|
19
|
+
// Match full model blocks (multiline-safe)
|
|
20
|
+
const modelRegex = /model\s+(\w+)\s*\{([\s\S]*?)^\s*\}/gm;
|
|
21
|
+
|
|
22
|
+
const softDeleteModels: string[] = [];
|
|
23
|
+
let totalModels = 0;
|
|
24
|
+
|
|
25
|
+
let match: RegExpExecArray | null;
|
|
26
|
+
while ((match = modelRegex.exec(normalizedSchema))) {
|
|
27
|
+
const modelName = match[1] as string;
|
|
28
|
+
const body = match[2] as string;
|
|
29
|
+
totalModels++;
|
|
30
|
+
|
|
31
|
+
const hasDeletedAt = /deletedAt\s+DateTime\?/i.test(body);
|
|
32
|
+
|
|
33
|
+
if (hasDeletedAt) {
|
|
34
|
+
softDeleteModels.push(modelName);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Generate the output file
|
|
39
|
+
const output = `// AUTO-GENERATED FILE — DO NOT EDIT
|
|
40
|
+
// Generated from schema.prisma
|
|
41
|
+
|
|
42
|
+
export const SoftDeleteModels = new Set<string>([
|
|
43
|
+
${softDeleteModels.map((m) => ` "${m}"`).join(",\n")}
|
|
44
|
+
]);
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(OUTPUT_PATH, output, "utf8");
|
|
48
|
+
|
|
49
|
+
console.log(
|
|
50
|
+
`✅ Generated soft delete models (${softDeleteModels.length}/${totalModels} models):`,
|
|
51
|
+
softDeleteModels.join(", ")
|
|
52
|
+
);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common";
|
|
2
2
|
import { ConfigModule } from "@nestjs/config";
|
|
3
3
|
import { ScheduleModule } from "@nestjs/schedule";
|
|
4
|
-
import { APP_GUARD
|
|
4
|
+
import { APP_GUARD } from "@nestjs/core";
|
|
5
5
|
import { EnvModule } from "@modules/env/env.module";
|
|
6
6
|
import { validateEnv } from "@schemas/env.schema";
|
|
7
7
|
import { AuthGuard } from "@guards/auth.guard";
|
|
8
8
|
import { AuthModule } from "@modules/auth/auth.module";
|
|
9
9
|
import { TokenModule } from "@modules/token/token.module";
|
|
10
10
|
import { PublicModule } from "@modules/public/public.module";
|
|
11
|
-
import { TokenService } from "@modules/token/token.service";
|
|
12
11
|
import { PrismaModule } from "@modules/prisma/prisma.module";
|
|
13
12
|
import { LoggerModule } from "@modules/logger/logger.module";
|
|
14
13
|
import { SchedulerModule } from "@modules/scheduler/scheduler.module";
|
|
@@ -38,9 +37,7 @@ import { ResponseInterceptor } from "@/lib/interceptors/response.interceptor";
|
|
|
38
37
|
providers: [
|
|
39
38
|
{
|
|
40
39
|
provide: APP_GUARD,
|
|
41
|
-
|
|
42
|
-
new AuthGuard(tokenService, reflector),
|
|
43
|
-
inject: [TokenService, Reflector],
|
|
40
|
+
useClass: AuthGuard,
|
|
44
41
|
},
|
|
45
42
|
AllExceptionsFilter,
|
|
46
43
|
ResponseInterceptor,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const appName = "Your App";
|
|
@@ -58,6 +58,8 @@ export class AuthGuard implements CanActivate {
|
|
|
58
58
|
});
|
|
59
59
|
return true;
|
|
60
60
|
} catch (err) {
|
|
61
|
+
if (err instanceof UnauthorizedException) throw err;
|
|
62
|
+
|
|
61
63
|
if (!refreshToken)
|
|
62
64
|
throw new UnauthorizedException("Refresh token is missing");
|
|
63
65
|
|
|
@@ -81,9 +83,9 @@ export class AuthGuard implements CanActivate {
|
|
|
81
83
|
});
|
|
82
84
|
|
|
83
85
|
return true;
|
|
84
|
-
} catch {
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (err instanceof UnauthorizedException) throw err;
|
|
85
88
|
this.logger.warn(`Access denied: Missing or invalid token`);
|
|
86
|
-
|
|
87
89
|
throw new UnauthorizedException(
|
|
88
90
|
"Unauthorized: Invalid or expired token."
|
|
89
91
|
);
|
|
@@ -67,7 +67,10 @@ export const envSchema = z.object({
|
|
|
67
67
|
// ==============================
|
|
68
68
|
// Email (Resend)
|
|
69
69
|
// ==============================
|
|
70
|
-
|
|
70
|
+
SMTP_HOST: z.string(),
|
|
71
|
+
SMTP_PORT: z.coerce.number(),
|
|
72
|
+
SMTP_USER: z.string(),
|
|
73
|
+
SMTP_PASS: z.string(),
|
|
71
74
|
|
|
72
75
|
// ==============================
|
|
73
76
|
// API Keys
|