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.
Files changed (21) hide show
  1. package/package.json +1 -1
  2. package/templates/monorepo-starter/gitignore +1 -1
  3. package/templates/monorepo-starter/pnpm-lock.yaml +13375 -12441
  4. package/templates/monorepo-starter/server/.env.example +5 -2
  5. package/templates/monorepo-starter/server/package.json +100 -97
  6. package/templates/monorepo-starter/server/prisma/schema.prisma +47 -3
  7. package/templates/monorepo-starter/server/scripts/gen-soft-delete.ts +52 -0
  8. package/templates/monorepo-starter/server/src/app.module.ts +2 -5
  9. package/templates/monorepo-starter/server/src/lib/constants/app.ts +1 -0
  10. package/templates/monorepo-starter/server/src/lib/guards/auth.guard.ts +4 -2
  11. package/templates/monorepo-starter/server/src/lib/schemas/env.schema.ts +4 -1
  12. package/templates/monorepo-starter/server/src/lib/templates/notification.templates.ts +114 -81
  13. package/templates/monorepo-starter/server/src/modules/auth/auth.service.ts +10 -9
  14. package/templates/monorepo-starter/server/src/modules/logger/winston.config.ts +15 -1
  15. package/templates/monorepo-starter/server/src/modules/notification/nodemailer.service.ts +34 -0
  16. package/templates/monorepo-starter/server/src/modules/notification/notification.service.ts +17 -11
  17. package/templates/monorepo-starter/server/src/modules/prisma/prisma.extension.ts +5 -15
  18. package/templates/monorepo-starter/server/src/modules/prisma/soft-delete.models.ts +6 -0
  19. package/templates/monorepo-starter/server/src/modules/token/token.service.ts +3 -4
  20. package/templates/monorepo-starter/server/tsconfig.json +1 -0
  21. 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 (Resend)
50
+ # Email (Nodemailer)
51
51
  # ==============================
52
- RESEND_API_KEY="your-resend-api-key"
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:module": "bash ./scripts/nest-gen.sh",
20
- "test": "jest",
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
- },
26
- "dependencies": {
27
- "@nestjs/common": "^11.1.9",
28
- "@nestjs/config": "^4.0.2",
29
- "@nestjs/core": "^11.1.9",
30
- "@nestjs/jwt": "^11.0.2",
31
- "@nestjs/passport": "^11.0.5",
32
- "@nestjs/platform-express": "^11.1.9",
33
- "@nestjs/schedule": "^6.1.0",
34
- "@prisma/adapter-pg": "^7.2.0",
35
- "@prisma/client": "^7.2.0",
36
- "argon2": "^0.44.0",
37
- "class-transformer": "^0.5.1",
38
- "class-validator": "^0.14.3",
39
- "cookie-parser": "^1.4.7",
40
- "ms": "^2.1.3",
41
- "nest-winston": "^1.10.2",
42
- "passport": "^0.7.0",
43
- "passport-facebook": "^3.0.0",
44
- "passport-google-oauth20": "^2.0.0",
45
- "pg": "^8.16.3",
46
- "reflect-metadata": "^0.2.2",
47
- "resend": "^6.6.0",
48
- "rxjs": "^7.8.2",
49
- "winston": "^3.19.0",
50
- "winston-daily-rotate-file": "^5.0.0",
51
- "zod": "^4.2.1"
52
- },
53
- "devDependencies": {
54
- "@nestjs/cli": "^11.0.14",
55
- "@nestjs/schematics": "^11.0.9",
56
- "@nestjs/testing": "^11.1.9",
57
- "@swc/cli": "^0.7.9",
58
- "@swc/core": "^1.15.7",
59
- "@types/cookie-parser": "^1.4.10",
60
- "@types/express": "^5.0.6",
61
- "@types/jest": "^30.0.0",
62
- "@types/ms": "^2.1.0",
63
- "@types/node": "^25.0.3",
64
- "@types/passport": "^1.0.17",
65
- "@types/passport-facebook": "^3.0.4",
66
- "@types/passport-google-oauth20": "^2.0.17",
67
- "@types/pg": "^8.16.0",
68
- "@types/supertest": "^6.0.3",
69
- "@workspace/config": "workspace:^",
70
- "jest": "^30.2.0",
71
- "prisma": "^7.2.0",
72
- "source-map-support": "^0.5.21",
73
- "supertest": "^7.1.4",
74
- "ts-jest": "^29.4.6",
75
- "ts-loader": "^9.5.4",
76
- "ts-node": "^10.9.2",
77
- "tsconfig-paths": "^4.2.0",
78
- "tsup": "^8.5.1"
79
- },
80
- "jest": {
81
- "moduleFileExtensions": [
82
- "js",
83
- "json",
84
- "ts"
85
- ],
86
- "rootDir": "src",
87
- "testRegex": ".*\\.spec\\.ts$",
88
- "transform": {
89
- "^.+\\.(t|j)s$": "ts-jest"
90
- },
91
- "collectCoverageFrom": [
92
- "**/*.(t|j)s"
93
- ],
94
- "coverageDirectory": "../coverage",
95
- "testEnvironment": "node"
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, Reflector } from "@nestjs/core";
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
- useFactory: (tokenService, reflector) =>
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
- RESEND_API_KEY: z.string(),
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