create-zhx-monorepo 0.1.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/README.md +34 -0
- package/bin/index.js +65 -0
- package/package.json +18 -0
- package/templates/monorepo-starter/.vscode/settings.json +3 -0
- package/templates/monorepo-starter/README.md +42 -0
- package/templates/monorepo-starter/apps/web/components.json +20 -0
- package/templates/monorepo-starter/apps/web/eslint.config.mjs +4 -0
- package/templates/monorepo-starter/apps/web/next-env.d.ts +6 -0
- package/templates/monorepo-starter/apps/web/next.config.mjs +6 -0
- package/templates/monorepo-starter/apps/web/package.json +31 -0
- package/templates/monorepo-starter/apps/web/postcss.config.mjs +1 -0
- package/templates/monorepo-starter/apps/web/public/.gitkeep +0 -0
- package/templates/monorepo-starter/apps/web/sitemap.config.cjs +6 -0
- package/templates/monorepo-starter/apps/web/src/app/(auth)/layout.tsx +7 -0
- package/templates/monorepo-starter/apps/web/src/app/(root)/layout.tsx +15 -0
- package/templates/monorepo-starter/apps/web/src/app/(root)/page.tsx +14 -0
- package/templates/monorepo-starter/apps/web/src/app/globals.css +1 -0
- package/templates/monorepo-starter/apps/web/src/app/layout.tsx +24 -0
- package/templates/monorepo-starter/apps/web/src/components/Footer.tsx +9 -0
- package/templates/monorepo-starter/apps/web/src/components/Header.tsx +11 -0
- package/templates/monorepo-starter/apps/web/src/components/ThemeSwitch.tsx +34 -0
- package/templates/monorepo-starter/apps/web/src/hooks/.gitkeep +0 -0
- package/templates/monorepo-starter/apps/web/src/lib/.gitkeep +0 -0
- package/templates/monorepo-starter/apps/web/src/providers/index.tsx +10 -0
- package/templates/monorepo-starter/apps/web/src/providers/theme.tsx +19 -0
- package/templates/monorepo-starter/apps/web/src/types/index.d.ts +16 -0
- package/templates/monorepo-starter/apps/web/tsconfig.json +20 -0
- package/templates/monorepo-starter/eslint.config.mjs +12 -0
- package/templates/monorepo-starter/package.json +23 -0
- package/templates/monorepo-starter/packages/config/eslint/base.js +31 -0
- package/templates/monorepo-starter/packages/config/eslint/nest.js +26 -0
- package/templates/monorepo-starter/packages/config/eslint/next.js +39 -0
- package/templates/monorepo-starter/packages/config/eslint/react.js +30 -0
- package/templates/monorepo-starter/packages/config/package.json +32 -0
- package/templates/monorepo-starter/packages/config/typescript/base.json +31 -0
- package/templates/monorepo-starter/packages/config/typescript/nest.json +14 -0
- package/templates/monorepo-starter/packages/config/typescript/next.json +10 -0
- package/templates/monorepo-starter/packages/config/typescript/react.json +9 -0
- package/templates/monorepo-starter/packages/ui/components.json +20 -0
- package/templates/monorepo-starter/packages/ui/eslint.config.mjs +4 -0
- package/templates/monorepo-starter/packages/ui/package.json +38 -0
- package/templates/monorepo-starter/packages/ui/postcss.config.mjs +6 -0
- package/templates/monorepo-starter/packages/ui/src/components/button.tsx +71 -0
- package/templates/monorepo-starter/packages/ui/src/hooks/.gitkeep +0 -0
- package/templates/monorepo-starter/packages/ui/src/lib/utils.ts +6 -0
- package/templates/monorepo-starter/packages/ui/src/styles/globals.css +182 -0
- package/templates/monorepo-starter/packages/ui/tsconfig.json +13 -0
- package/templates/monorepo-starter/packages/ui/tsconfig.lint.json +8 -0
- package/templates/monorepo-starter/pnpm-lock.yaml +12441 -0
- package/templates/monorepo-starter/pnpm-workspace.yaml +17 -0
- package/templates/monorepo-starter/server/.env.example +64 -0
- package/templates/monorepo-starter/server/README.md +63 -0
- package/templates/monorepo-starter/server/eslint.config.mjs +4 -0
- package/templates/monorepo-starter/server/nest-cli.json +12 -0
- package/templates/monorepo-starter/server/package.json +97 -0
- package/templates/monorepo-starter/server/prisma/generated/browser.ts +54 -0
- package/templates/monorepo-starter/server/prisma/generated/client.ts +76 -0
- package/templates/monorepo-starter/server/prisma/generated/commonInputTypes.ts +577 -0
- package/templates/monorepo-starter/server/prisma/generated/enums.ts +68 -0
- package/templates/monorepo-starter/server/prisma/generated/internal/class.ts +250 -0
- package/templates/monorepo-starter/server/prisma/generated/internal/prismaNamespace.ts +1436 -0
- package/templates/monorepo-starter/server/prisma/generated/internal/prismaNamespaceBrowser.ts +227 -0
- package/templates/monorepo-starter/server/prisma/generated/models/BackupCode.ts +1375 -0
- package/templates/monorepo-starter/server/prisma/generated/models/Notification.ts +1587 -0
- package/templates/monorepo-starter/server/prisma/generated/models/Otp.ts +1488 -0
- package/templates/monorepo-starter/server/prisma/generated/models/RefreshToken.ts +1515 -0
- package/templates/monorepo-starter/server/prisma/generated/models/RoleAssignment.ts +1385 -0
- package/templates/monorepo-starter/server/prisma/generated/models/SecuritySetting.ts +1422 -0
- package/templates/monorepo-starter/server/prisma/generated/models/User.ts +2498 -0
- package/templates/monorepo-starter/server/prisma/generated/models.ts +18 -0
- package/templates/monorepo-starter/server/prisma/migrations/20251218164821_init/migration.sql +210 -0
- package/templates/monorepo-starter/server/prisma/migrations/migration_lock.toml +3 -0
- package/templates/monorepo-starter/server/prisma/schema.prisma +193 -0
- package/templates/monorepo-starter/server/prisma.config.ts +13 -0
- package/templates/monorepo-starter/server/scripts/generate.sh +14 -0
- package/templates/monorepo-starter/server/src/app.module.ts +49 -0
- package/templates/monorepo-starter/server/src/lib/decorators/logger.decorator.ts +20 -0
- package/templates/monorepo-starter/server/src/lib/decorators/public.decorator.ts +4 -0
- package/templates/monorepo-starter/server/src/lib/decorators/roles.decorator.ts +5 -0
- package/templates/monorepo-starter/server/src/lib/dto/auth.dto.ts +64 -0
- package/templates/monorepo-starter/server/src/lib/dto/security-setting.dto.ts +21 -0
- package/templates/monorepo-starter/server/src/lib/filters/exceptions.filter.ts +62 -0
- package/templates/monorepo-starter/server/src/lib/guards/auth.guard.ts +104 -0
- package/templates/monorepo-starter/server/src/lib/interceptors/response.interceptor.ts +33 -0
- package/templates/monorepo-starter/server/src/lib/pipes/validation.pipe.ts +7 -0
- package/templates/monorepo-starter/server/src/lib/schemas/env.schema.ts +99 -0
- package/templates/monorepo-starter/server/src/lib/utils/cookie.util.ts +23 -0
- package/templates/monorepo-starter/server/src/lib/utils/general.util.ts +24 -0
- package/templates/monorepo-starter/server/src/main.ts +41 -0
- package/templates/monorepo-starter/server/src/modules/auth/auth.controller.ts +74 -0
- package/templates/monorepo-starter/server/src/modules/auth/auth.module.ts +16 -0
- package/templates/monorepo-starter/server/src/modules/auth/auth.service.ts +525 -0
- package/templates/monorepo-starter/server/src/modules/auth/oauth.controller.ts +58 -0
- package/templates/monorepo-starter/server/src/modules/auth/oauth.service.ts +165 -0
- package/templates/monorepo-starter/server/src/modules/auth/otp.service.ts +133 -0
- package/templates/monorepo-starter/server/src/modules/auth/role.service.ts +99 -0
- package/templates/monorepo-starter/server/src/modules/auth/security-setting.service.ts +102 -0
- package/templates/monorepo-starter/server/src/modules/env/env.module.ts +9 -0
- package/templates/monorepo-starter/server/src/modules/env/env.service.ts +22 -0
- package/templates/monorepo-starter/server/src/modules/logger/logger.module.ts +12 -0
- package/templates/monorepo-starter/server/src/modules/logger/logger.service.ts +37 -0
- package/templates/monorepo-starter/server/src/modules/logger/winston.config.ts +32 -0
- package/templates/monorepo-starter/server/src/modules/notification/notification.module.ts +11 -0
- package/templates/monorepo-starter/server/src/modules/notification/notification.service.ts +118 -0
- package/templates/monorepo-starter/server/src/modules/prisma/prisma.extension.ts +72 -0
- package/templates/monorepo-starter/server/src/modules/prisma/prisma.module.ts +9 -0
- package/templates/monorepo-starter/server/src/modules/prisma/prisma.service.ts +49 -0
- package/templates/monorepo-starter/server/src/modules/public/public.controller.ts +21 -0
- package/templates/monorepo-starter/server/src/modules/public/public.module.ts +9 -0
- package/templates/monorepo-starter/server/src/modules/public/public.service.ts +30 -0
- package/templates/monorepo-starter/server/src/modules/scheduler/cleanup.service.ts +33 -0
- package/templates/monorepo-starter/server/src/modules/scheduler/scheduler.module.ts +7 -0
- package/templates/monorepo-starter/server/src/modules/template/template.module.ts +8 -0
- package/templates/monorepo-starter/server/src/modules/template/template.service.ts +33 -0
- package/templates/monorepo-starter/server/src/modules/token/token.module.ts +11 -0
- package/templates/monorepo-starter/server/src/modules/token/token.service.ts +131 -0
- package/templates/monorepo-starter/server/src/types/express.d.ts +10 -0
- package/templates/monorepo-starter/server/src/types/index.d.ts +13 -0
- package/templates/monorepo-starter/server/templates/notification.templates.ts +243 -0
- package/templates/monorepo-starter/server/test/app.e2e-spec.ts +25 -0
- package/templates/monorepo-starter/server/test/jest-e2e.json +9 -0
- package/templates/monorepo-starter/server/tsconfig.json +23 -0
- package/templates/monorepo-starter/server/tsup.config.ts +14 -0
- package/templates/monorepo-starter/tsconfig.json +3 -0
- package/templates/monorepo-starter/turbo.json +21 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
// biome-ignore-all lint: generated file
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
/*
|
|
7
|
+
* This is a barrel export file for all models and their related types.
|
|
8
|
+
*
|
|
9
|
+
* 🟢 You can import this file directly.
|
|
10
|
+
*/
|
|
11
|
+
export type * from './models/User'
|
|
12
|
+
export type * from './models/RoleAssignment'
|
|
13
|
+
export type * from './models/SecuritySetting'
|
|
14
|
+
export type * from './models/BackupCode'
|
|
15
|
+
export type * from './models/RefreshToken'
|
|
16
|
+
export type * from './models/Otp'
|
|
17
|
+
export type * from './models/Notification'
|
|
18
|
+
export type * from './commonInputTypes'
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "UserRole" AS ENUM ('admin', 'customer');
|
|
3
|
+
|
|
4
|
+
-- CreateEnum
|
|
5
|
+
CREATE TYPE "OtpPurpose" AS ENUM ('setPassword', 'resetPassword', 'verifyIdentifier', 'changeIdentifier', 'enableMfa', 'disableMfa', 'verifyMfa');
|
|
6
|
+
|
|
7
|
+
-- CreateEnum
|
|
8
|
+
CREATE TYPE "OtpType" AS ENUM ('otp', 'token');
|
|
9
|
+
|
|
10
|
+
-- CreateEnum
|
|
11
|
+
CREATE TYPE "MfaMethod" AS ENUM ('email', 'sms', 'whatsapp', 'authApp');
|
|
12
|
+
|
|
13
|
+
-- CreateEnum
|
|
14
|
+
CREATE TYPE "NotificationType" AS ENUM ('email', 'sms', 'whatsapp', 'inApp');
|
|
15
|
+
|
|
16
|
+
-- CreateEnum
|
|
17
|
+
CREATE TYPE "NotificationStatus" AS ENUM ('pending', 'sent', 'failed', 'read');
|
|
18
|
+
|
|
19
|
+
-- CreateTable
|
|
20
|
+
CREATE TABLE "User" (
|
|
21
|
+
"id" TEXT NOT NULL,
|
|
22
|
+
"username" TEXT,
|
|
23
|
+
"password" TEXT,
|
|
24
|
+
"firstName" TEXT NOT NULL,
|
|
25
|
+
"lastName" TEXT,
|
|
26
|
+
"displayName" TEXT NOT NULL,
|
|
27
|
+
"imageUrl" TEXT,
|
|
28
|
+
"email" TEXT,
|
|
29
|
+
"phone" TEXT,
|
|
30
|
+
"isEmailVerified" BOOLEAN NOT NULL DEFAULT false,
|
|
31
|
+
"isPhoneVerified" BOOLEAN NOT NULL DEFAULT false,
|
|
32
|
+
"lastLoginAt" TIMESTAMP(3),
|
|
33
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
34
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
35
|
+
"deletedAt" TIMESTAMP(3),
|
|
36
|
+
|
|
37
|
+
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- CreateTable
|
|
41
|
+
CREATE TABLE "RoleAssignment" (
|
|
42
|
+
"id" TEXT NOT NULL,
|
|
43
|
+
"userId" TEXT NOT NULL,
|
|
44
|
+
"role" "UserRole" NOT NULL,
|
|
45
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
46
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
47
|
+
"revokedAt" TIMESTAMP(3),
|
|
48
|
+
|
|
49
|
+
CONSTRAINT "RoleAssignment_pkey" PRIMARY KEY ("id")
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- CreateTable
|
|
53
|
+
CREATE TABLE "SecuritySetting" (
|
|
54
|
+
"id" TEXT NOT NULL,
|
|
55
|
+
"userId" TEXT NOT NULL,
|
|
56
|
+
"preferredMfa" "MfaMethod",
|
|
57
|
+
"recoveryEmail" TEXT,
|
|
58
|
+
"recoveryPhone" TEXT,
|
|
59
|
+
"isMfaEnabled" BOOLEAN NOT NULL DEFAULT false,
|
|
60
|
+
"loginAlerts" BOOLEAN NOT NULL DEFAULT true,
|
|
61
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
62
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
63
|
+
|
|
64
|
+
CONSTRAINT "SecuritySetting_pkey" PRIMARY KEY ("id")
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
-- CreateTable
|
|
68
|
+
CREATE TABLE "BackupCode" (
|
|
69
|
+
"id" TEXT NOT NULL,
|
|
70
|
+
"userId" TEXT NOT NULL,
|
|
71
|
+
"code" TEXT NOT NULL,
|
|
72
|
+
"usedAt" TIMESTAMP(3),
|
|
73
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
74
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
75
|
+
|
|
76
|
+
CONSTRAINT "BackupCode_pkey" PRIMARY KEY ("id")
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
-- CreateTable
|
|
80
|
+
CREATE TABLE "RefreshToken" (
|
|
81
|
+
"id" TEXT NOT NULL,
|
|
82
|
+
"token" TEXT NOT NULL,
|
|
83
|
+
"userId" TEXT NOT NULL,
|
|
84
|
+
"ip" TEXT NOT NULL,
|
|
85
|
+
"userAgent" TEXT NOT NULL,
|
|
86
|
+
"lastUsed" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
87
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
89
|
+
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
|
90
|
+
"blacklisted" BOOLEAN NOT NULL DEFAULT false,
|
|
91
|
+
|
|
92
|
+
CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id")
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
-- CreateTable
|
|
96
|
+
CREATE TABLE "Otp" (
|
|
97
|
+
"id" TEXT NOT NULL,
|
|
98
|
+
"userId" TEXT NOT NULL,
|
|
99
|
+
"purpose" "OtpPurpose" NOT NULL,
|
|
100
|
+
"type" "OtpType" NOT NULL DEFAULT 'otp',
|
|
101
|
+
"secret" TEXT NOT NULL,
|
|
102
|
+
"expiresAt" TIMESTAMP(3) NOT NULL,
|
|
103
|
+
"usedAt" TIMESTAMP(3),
|
|
104
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
105
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
106
|
+
|
|
107
|
+
CONSTRAINT "Otp_pkey" PRIMARY KEY ("id")
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
-- CreateTable
|
|
111
|
+
CREATE TABLE "Notification" (
|
|
112
|
+
"id" TEXT NOT NULL,
|
|
113
|
+
"userId" TEXT NOT NULL,
|
|
114
|
+
"type" "NotificationType" NOT NULL,
|
|
115
|
+
"title" TEXT,
|
|
116
|
+
"message" TEXT,
|
|
117
|
+
"purpose" TEXT NOT NULL,
|
|
118
|
+
"metadata" JSONB,
|
|
119
|
+
"status" "NotificationStatus" NOT NULL DEFAULT 'pending',
|
|
120
|
+
"sentAt" TIMESTAMP(3),
|
|
121
|
+
"readAt" TIMESTAMP(3),
|
|
122
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
123
|
+
"updatedAt" TIMESTAMP(3) NOT NULL,
|
|
124
|
+
|
|
125
|
+
CONSTRAINT "Notification_pkey" PRIMARY KEY ("id")
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
-- CreateIndex
|
|
129
|
+
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
|
130
|
+
|
|
131
|
+
-- CreateIndex
|
|
132
|
+
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
133
|
+
|
|
134
|
+
-- CreateIndex
|
|
135
|
+
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
|
|
136
|
+
|
|
137
|
+
-- CreateIndex
|
|
138
|
+
CREATE INDEX "User_email_idx" ON "User"("email");
|
|
139
|
+
|
|
140
|
+
-- CreateIndex
|
|
141
|
+
CREATE INDEX "User_phone_idx" ON "User"("phone");
|
|
142
|
+
|
|
143
|
+
-- CreateIndex
|
|
144
|
+
CREATE INDEX "User_username_idx" ON "User"("username");
|
|
145
|
+
|
|
146
|
+
-- CreateIndex
|
|
147
|
+
CREATE INDEX "User_email_isEmailVerified_idx" ON "User"("email", "isEmailVerified");
|
|
148
|
+
|
|
149
|
+
-- CreateIndex
|
|
150
|
+
CREATE INDEX "User_phone_isPhoneVerified_idx" ON "User"("phone", "isPhoneVerified");
|
|
151
|
+
|
|
152
|
+
-- CreateIndex
|
|
153
|
+
CREATE UNIQUE INDEX "RoleAssignment_userId_role_key" ON "RoleAssignment"("userId", "role");
|
|
154
|
+
|
|
155
|
+
-- CreateIndex
|
|
156
|
+
CREATE UNIQUE INDEX "SecuritySetting_userId_key" ON "SecuritySetting"("userId");
|
|
157
|
+
|
|
158
|
+
-- CreateIndex
|
|
159
|
+
CREATE INDEX "SecuritySetting_userId_idx" ON "SecuritySetting"("userId");
|
|
160
|
+
|
|
161
|
+
-- CreateIndex
|
|
162
|
+
CREATE INDEX "SecuritySetting_isMfaEnabled_idx" ON "SecuritySetting"("isMfaEnabled");
|
|
163
|
+
|
|
164
|
+
-- CreateIndex
|
|
165
|
+
CREATE UNIQUE INDEX "BackupCode_code_key" ON "BackupCode"("code");
|
|
166
|
+
|
|
167
|
+
-- CreateIndex
|
|
168
|
+
CREATE INDEX "BackupCode_userId_usedAt_idx" ON "BackupCode"("userId", "usedAt");
|
|
169
|
+
|
|
170
|
+
-- CreateIndex
|
|
171
|
+
CREATE UNIQUE INDEX "RefreshToken_token_key" ON "RefreshToken"("token");
|
|
172
|
+
|
|
173
|
+
-- CreateIndex
|
|
174
|
+
CREATE INDEX "RefreshToken_userId_idx" ON "RefreshToken"("userId");
|
|
175
|
+
|
|
176
|
+
-- CreateIndex
|
|
177
|
+
CREATE INDEX "RefreshToken_expiresAt_idx" ON "RefreshToken"("expiresAt");
|
|
178
|
+
|
|
179
|
+
-- CreateIndex
|
|
180
|
+
CREATE INDEX "RefreshToken_userId_expiresAt_idx" ON "RefreshToken"("userId", "expiresAt");
|
|
181
|
+
|
|
182
|
+
-- CreateIndex
|
|
183
|
+
CREATE INDEX "Otp_userId_purpose_idx" ON "Otp"("userId", "purpose");
|
|
184
|
+
|
|
185
|
+
-- CreateIndex
|
|
186
|
+
CREATE INDEX "Otp_expiresAt_idx" ON "Otp"("expiresAt");
|
|
187
|
+
|
|
188
|
+
-- CreateIndex
|
|
189
|
+
CREATE INDEX "Notification_userId_idx" ON "Notification"("userId");
|
|
190
|
+
|
|
191
|
+
-- CreateIndex
|
|
192
|
+
CREATE INDEX "Notification_userId_readAt_idx" ON "Notification"("userId", "readAt");
|
|
193
|
+
|
|
194
|
+
-- AddForeignKey
|
|
195
|
+
ALTER TABLE "RoleAssignment" ADD CONSTRAINT "RoleAssignment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
196
|
+
|
|
197
|
+
-- AddForeignKey
|
|
198
|
+
ALTER TABLE "SecuritySetting" ADD CONSTRAINT "SecuritySetting_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
199
|
+
|
|
200
|
+
-- AddForeignKey
|
|
201
|
+
ALTER TABLE "BackupCode" ADD CONSTRAINT "BackupCode_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
202
|
+
|
|
203
|
+
-- AddForeignKey
|
|
204
|
+
ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
205
|
+
|
|
206
|
+
-- AddForeignKey
|
|
207
|
+
ALTER TABLE "Otp" ADD CONSTRAINT "Otp_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
208
|
+
|
|
209
|
+
-- AddForeignKey
|
|
210
|
+
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// prisma/schema.prisma
|
|
2
|
+
|
|
3
|
+
generator client {
|
|
4
|
+
provider = "prisma-client"
|
|
5
|
+
output = "./generated"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "postgresql"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//
|
|
13
|
+
// ENUMS
|
|
14
|
+
//
|
|
15
|
+
|
|
16
|
+
enum UserRole {
|
|
17
|
+
admin
|
|
18
|
+
customer
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
enum OtpPurpose {
|
|
22
|
+
setPassword
|
|
23
|
+
resetPassword
|
|
24
|
+
verifyIdentifier
|
|
25
|
+
changeIdentifier
|
|
26
|
+
enableMfa
|
|
27
|
+
disableMfa
|
|
28
|
+
verifyMfa
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
enum OtpType {
|
|
32
|
+
otp
|
|
33
|
+
token
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
enum MfaMethod {
|
|
37
|
+
email
|
|
38
|
+
sms
|
|
39
|
+
whatsapp
|
|
40
|
+
authApp
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
enum NotificationType {
|
|
44
|
+
email
|
|
45
|
+
sms
|
|
46
|
+
whatsapp
|
|
47
|
+
inApp
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
enum NotificationStatus {
|
|
51
|
+
pending
|
|
52
|
+
sent
|
|
53
|
+
failed
|
|
54
|
+
read
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//
|
|
58
|
+
// AUTH MODELS
|
|
59
|
+
//
|
|
60
|
+
|
|
61
|
+
model User {
|
|
62
|
+
id String @id @default(ulid())
|
|
63
|
+
username String? @unique
|
|
64
|
+
password String?
|
|
65
|
+
firstName String
|
|
66
|
+
lastName String?
|
|
67
|
+
displayName String
|
|
68
|
+
imageUrl String?
|
|
69
|
+
|
|
70
|
+
// Contact
|
|
71
|
+
email String? @unique
|
|
72
|
+
phone String? @unique
|
|
73
|
+
isEmailVerified Boolean @default(false)
|
|
74
|
+
isPhoneVerified Boolean @default(false)
|
|
75
|
+
|
|
76
|
+
// Status
|
|
77
|
+
lastLoginAt DateTime?
|
|
78
|
+
|
|
79
|
+
createdAt DateTime @default(now())
|
|
80
|
+
updatedAt DateTime @updatedAt
|
|
81
|
+
deletedAt DateTime?
|
|
82
|
+
|
|
83
|
+
// Relations
|
|
84
|
+
refreshTokens RefreshToken[]
|
|
85
|
+
otps Otp[]
|
|
86
|
+
roles RoleAssignment[]
|
|
87
|
+
securitySetting SecuritySetting?
|
|
88
|
+
|
|
89
|
+
notifications Notification[]
|
|
90
|
+
backupCodes BackupCode[]
|
|
91
|
+
|
|
92
|
+
@@index([email])
|
|
93
|
+
@@index([phone])
|
|
94
|
+
@@index([username])
|
|
95
|
+
@@index([email, isEmailVerified])
|
|
96
|
+
@@index([phone, isPhoneVerified])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
model RoleAssignment {
|
|
100
|
+
id String @id @default(ulid())
|
|
101
|
+
userId String
|
|
102
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
103
|
+
role UserRole
|
|
104
|
+
|
|
105
|
+
createdAt DateTime @default(now())
|
|
106
|
+
updatedAt DateTime @updatedAt
|
|
107
|
+
revokedAt DateTime?
|
|
108
|
+
|
|
109
|
+
@@unique([userId, role])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
model SecuritySetting {
|
|
113
|
+
id String @id @default(ulid())
|
|
114
|
+
userId String @unique
|
|
115
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
116
|
+
|
|
117
|
+
preferredMfa MfaMethod?
|
|
118
|
+
recoveryEmail String?
|
|
119
|
+
recoveryPhone String?
|
|
120
|
+
isMfaEnabled Boolean @default(false)
|
|
121
|
+
loginAlerts Boolean @default(true)
|
|
122
|
+
|
|
123
|
+
createdAt DateTime @default(now())
|
|
124
|
+
updatedAt DateTime @updatedAt
|
|
125
|
+
|
|
126
|
+
@@index([userId])
|
|
127
|
+
@@index([isMfaEnabled])
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
model BackupCode {
|
|
131
|
+
id String @id @default(ulid())
|
|
132
|
+
userId String
|
|
133
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
134
|
+
code String @unique
|
|
135
|
+
usedAt DateTime?
|
|
136
|
+
createdAt DateTime @default(now())
|
|
137
|
+
expiresAt DateTime
|
|
138
|
+
|
|
139
|
+
@@index([userId, usedAt])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
model RefreshToken {
|
|
143
|
+
id String @id @default(ulid())
|
|
144
|
+
token String @unique
|
|
145
|
+
userId String
|
|
146
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
147
|
+
ip String
|
|
148
|
+
userAgent String
|
|
149
|
+
lastUsed DateTime @default(now())
|
|
150
|
+
createdAt DateTime @default(now())
|
|
151
|
+
expiresAt DateTime
|
|
152
|
+
isActive Boolean @default(true)
|
|
153
|
+
blacklisted Boolean @default(false)
|
|
154
|
+
|
|
155
|
+
@@index([userId])
|
|
156
|
+
@@index([expiresAt])
|
|
157
|
+
@@index([userId, expiresAt])
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
model Otp {
|
|
161
|
+
id String @id @default(ulid())
|
|
162
|
+
userId String
|
|
163
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
164
|
+
purpose OtpPurpose
|
|
165
|
+
type OtpType @default(otp)
|
|
166
|
+
secret String
|
|
167
|
+
expiresAt DateTime
|
|
168
|
+
usedAt DateTime?
|
|
169
|
+
createdAt DateTime @default(now())
|
|
170
|
+
updatedAt DateTime @updatedAt
|
|
171
|
+
|
|
172
|
+
@@index([userId, purpose])
|
|
173
|
+
@@index([expiresAt])
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
model Notification {
|
|
177
|
+
id String @id @default(cuid())
|
|
178
|
+
userId String
|
|
179
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
180
|
+
type NotificationType
|
|
181
|
+
title String?
|
|
182
|
+
message String?
|
|
183
|
+
purpose String
|
|
184
|
+
metadata Json?
|
|
185
|
+
status NotificationStatus @default(pending)
|
|
186
|
+
sentAt DateTime?
|
|
187
|
+
readAt DateTime?
|
|
188
|
+
createdAt DateTime @default(now())
|
|
189
|
+
updatedAt DateTime @updatedAt
|
|
190
|
+
|
|
191
|
+
@@index([userId])
|
|
192
|
+
@@index([userId, readAt])
|
|
193
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
import { defineConfig, env } from "prisma/config";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
schema: 'prisma/schema.prisma',
|
|
6
|
+
migrations: {
|
|
7
|
+
path: 'prisma/migrations',
|
|
8
|
+
seed: 'tsx prisma/seed.ts',
|
|
9
|
+
},
|
|
10
|
+
datasource: {
|
|
11
|
+
url: env("DB_URI")
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Get the name (e.g., "auth") from the argument
|
|
4
|
+
NAME=$1
|
|
5
|
+
|
|
6
|
+
if [ -z "$NAME" ]; then
|
|
7
|
+
echo "❌ Please provide a name (e.g., ./generate.sh auth)"
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
# Run NestJS CLI commands
|
|
12
|
+
nest g module $NAME
|
|
13
|
+
nest g service $NAME
|
|
14
|
+
nest g controller $NAME
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { ConfigModule } from "@nestjs/config";
|
|
3
|
+
import { ScheduleModule } from "@nestjs/schedule";
|
|
4
|
+
import { APP_GUARD, Reflector } from "@nestjs/core";
|
|
5
|
+
import { EnvModule } from "@modules/env/env.module";
|
|
6
|
+
import { validateEnv } from "@schemas/env.schema";
|
|
7
|
+
import { AuthGuard } from "@guards/auth.guard";
|
|
8
|
+
import { AuthModule } from "@modules/auth/auth.module";
|
|
9
|
+
import { TokenModule } from "@modules/token/token.module";
|
|
10
|
+
import { PublicModule } from "@modules/public/public.module";
|
|
11
|
+
import { TokenService } from "@modules/token/token.service";
|
|
12
|
+
import { PrismaModule } from "@modules/prisma/prisma.module";
|
|
13
|
+
import { LoggerModule } from "@modules/logger/logger.module";
|
|
14
|
+
import { SchedulerModule } from "@modules/scheduler/scheduler.module";
|
|
15
|
+
import { NotificationModule } from "@modules/notification/notification.module";
|
|
16
|
+
import { TemplateModule } from "@modules/template/template.module";
|
|
17
|
+
import { AllExceptionsFilter } from "@filters/exceptions.filter";
|
|
18
|
+
import { ResponseInterceptor } from "@/lib/interceptors/response.interceptor";
|
|
19
|
+
|
|
20
|
+
@Module({
|
|
21
|
+
imports: [
|
|
22
|
+
PublicModule,
|
|
23
|
+
PrismaModule,
|
|
24
|
+
LoggerModule,
|
|
25
|
+
EnvModule,
|
|
26
|
+
AuthModule,
|
|
27
|
+
TokenModule,
|
|
28
|
+
TemplateModule,
|
|
29
|
+
NotificationModule,
|
|
30
|
+
SchedulerModule,
|
|
31
|
+
ScheduleModule.forRoot(),
|
|
32
|
+
ConfigModule.forRoot({
|
|
33
|
+
isGlobal: true,
|
|
34
|
+
validate: validateEnv,
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
|
|
38
|
+
providers: [
|
|
39
|
+
{
|
|
40
|
+
provide: APP_GUARD,
|
|
41
|
+
useFactory: (tokenService, reflector) =>
|
|
42
|
+
new AuthGuard(tokenService, reflector),
|
|
43
|
+
inject: [TokenService, Reflector],
|
|
44
|
+
},
|
|
45
|
+
AllExceptionsFilter,
|
|
46
|
+
ResponseInterceptor,
|
|
47
|
+
],
|
|
48
|
+
})
|
|
49
|
+
export class AppModule {}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Inject } from "@nestjs/common";
|
|
2
|
+
import { LoggerService } from "@modules/logger/logger.service";
|
|
3
|
+
|
|
4
|
+
export function InjectLogger(context?: string): PropertyDecorator {
|
|
5
|
+
return (target: object, propertyKey: string | symbol) => {
|
|
6
|
+
Inject(LoggerService)(target, propertyKey);
|
|
7
|
+
|
|
8
|
+
const originalInit = target.constructor.prototype?.onModuleInit;
|
|
9
|
+
|
|
10
|
+
target.constructor.prototype.onModuleInit = function (...args: any[]) {
|
|
11
|
+
const logger: LoggerService = (this as any)[propertyKey];
|
|
12
|
+
if (logger && typeof logger.setContext === "function") {
|
|
13
|
+
const ctx = context || target.constructor.name;
|
|
14
|
+
logger.setContext(ctx);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (originalInit) return originalInit.apply(this, args);
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { OtpPurpose, OtpType } from "@generated/prisma";
|
|
2
|
+
import {
|
|
3
|
+
IsEnum,
|
|
4
|
+
IsNotEmpty,
|
|
5
|
+
IsOptional,
|
|
6
|
+
IsString,
|
|
7
|
+
MinLength,
|
|
8
|
+
} from "class-validator";
|
|
9
|
+
|
|
10
|
+
export class SignInDto {
|
|
11
|
+
@IsString()
|
|
12
|
+
@IsNotEmpty()
|
|
13
|
+
identifier!: string;
|
|
14
|
+
|
|
15
|
+
@IsString()
|
|
16
|
+
@IsNotEmpty()
|
|
17
|
+
@MinLength(8)
|
|
18
|
+
password!: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class SignUpDto extends SignInDto {
|
|
22
|
+
@IsOptional()
|
|
23
|
+
@IsString()
|
|
24
|
+
username?: string;
|
|
25
|
+
|
|
26
|
+
@IsString()
|
|
27
|
+
@IsNotEmpty()
|
|
28
|
+
firstName!: string;
|
|
29
|
+
|
|
30
|
+
@IsOptional()
|
|
31
|
+
@IsString()
|
|
32
|
+
lastName?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class RequestOtpDto {
|
|
36
|
+
@IsString()
|
|
37
|
+
@IsNotEmpty()
|
|
38
|
+
identifier!: string;
|
|
39
|
+
|
|
40
|
+
@IsEnum(OtpPurpose)
|
|
41
|
+
purpose!: OtpPurpose;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class ValidateOtpDto extends RequestOtpDto {
|
|
45
|
+
@IsString()
|
|
46
|
+
@IsNotEmpty()
|
|
47
|
+
secret!: string;
|
|
48
|
+
|
|
49
|
+
@IsOptional()
|
|
50
|
+
@IsEnum(OtpType)
|
|
51
|
+
type?: OtpType;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class ResetPasswordDto extends ValidateOtpDto {
|
|
55
|
+
@IsString()
|
|
56
|
+
@IsNotEmpty()
|
|
57
|
+
newPassword!: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class ChangeIdentifierDto extends ValidateOtpDto {
|
|
61
|
+
@IsString()
|
|
62
|
+
@IsNotEmpty()
|
|
63
|
+
newIdentifier!: string;
|
|
64
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IsBoolean, IsNotEmpty, IsOptional, IsString } from "class-validator";
|
|
2
|
+
|
|
3
|
+
export class VerifyBackupCodeDto {
|
|
4
|
+
@IsNotEmpty()
|
|
5
|
+
@IsString()
|
|
6
|
+
code!: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class UpdateSecuritySettingDto {
|
|
10
|
+
@IsOptional()
|
|
11
|
+
@IsString()
|
|
12
|
+
recoveryEmail?: string;
|
|
13
|
+
|
|
14
|
+
@IsOptional()
|
|
15
|
+
@IsString()
|
|
16
|
+
recoveryPhone?: string;
|
|
17
|
+
|
|
18
|
+
@IsOptional()
|
|
19
|
+
@IsBoolean()
|
|
20
|
+
loginAlerts?: boolean;
|
|
21
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { LoggerService } from "@modules/logger/logger.service";
|
|
2
|
+
import type { Request, Response } from "express";
|
|
3
|
+
import {
|
|
4
|
+
Catch,
|
|
5
|
+
HttpException,
|
|
6
|
+
HttpStatus,
|
|
7
|
+
type ExceptionFilter,
|
|
8
|
+
type ArgumentsHost,
|
|
9
|
+
} from "@nestjs/common";
|
|
10
|
+
import { InjectLogger } from "@decorators/logger.decorator";
|
|
11
|
+
|
|
12
|
+
@Catch()
|
|
13
|
+
export class AllExceptionsFilter implements ExceptionFilter {
|
|
14
|
+
@InjectLogger()
|
|
15
|
+
private readonly logger!: LoggerService;
|
|
16
|
+
|
|
17
|
+
catch(exception: any, host: ArgumentsHost) {
|
|
18
|
+
const ctx = host.switchToHttp();
|
|
19
|
+
const req = ctx.getRequest<Request>();
|
|
20
|
+
const res = ctx.getResponse<Response>();
|
|
21
|
+
|
|
22
|
+
const status =
|
|
23
|
+
exception instanceof HttpException
|
|
24
|
+
? exception.getStatus()
|
|
25
|
+
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
26
|
+
|
|
27
|
+
let message: any = "Internal server error";
|
|
28
|
+
|
|
29
|
+
if (exception instanceof HttpException) {
|
|
30
|
+
const res = exception.getResponse();
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
res &&
|
|
34
|
+
typeof res === "object" &&
|
|
35
|
+
"message" in res &&
|
|
36
|
+
Array.isArray((res as any).message)
|
|
37
|
+
) {
|
|
38
|
+
message = (res as any).message;
|
|
39
|
+
} else if (typeof res === "string") {
|
|
40
|
+
message = res;
|
|
41
|
+
} else if (typeof exception.message === "string") {
|
|
42
|
+
message = exception.message;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// if (response.headersSent) return;
|
|
46
|
+
|
|
47
|
+
this.logger.error(`❌ Exception caught`, {
|
|
48
|
+
message: exception.message,
|
|
49
|
+
stack: exception.stack,
|
|
50
|
+
status,
|
|
51
|
+
path: req.url,
|
|
52
|
+
method: req.method,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
res.status(status).json({
|
|
56
|
+
status,
|
|
57
|
+
data: null,
|
|
58
|
+
message,
|
|
59
|
+
success: false,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|