express-mongodb-backend 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 DevRiaz
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # express-mongodb-backend
2
+
3
+ One command to create a full backend app with your `src/app/*` structure.
4
+ By default it generates only 2 modules: **auth** and **user**.
5
+
6
+ ## After publishing
7
+ ```bash
8
+ npm create express-mongodb-backend tourManagement
9
+ # or
10
+ npx express-mongodb-backend tourManagement
11
+ ```
12
+
13
+ ## Options
14
+ ```bash
15
+ express-mongodb-backend tourManagement --modules auth user booking
16
+ express-mongodb-backend tourManagement --force
17
+ ```
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const generate_1 = require("./generate");
6
+ const program = new commander_1.Command();
7
+ program
8
+ .name("fullbackend")
9
+ .description("Create a full backend project with src/app structure and starter modules.")
10
+ .argument("<projectName>", "project folder name, e.g. tourManagement")
11
+ .option("--modules <names...>", "module folders inside src/app/modules (default: auth user)")
12
+ .option("-f, --force", "overwrite if project folder exists", false)
13
+ .option("--dry-run", "print what would be created (no writing)", false)
14
+ .action(async (projectName, options) => {
15
+ try {
16
+ const all = await (0, generate_1.createFullBackend)(projectName, { modules: options.modules, force: options.force, dryRun: options.dryRun });
17
+ if (options.dryRun) {
18
+ // eslint-disable-next-line no-console
19
+ console.log(all.join("\n"));
20
+ return;
21
+ }
22
+ // eslint-disable-next-line no-console
23
+ console.log(`Created backend project: ${projectName}`);
24
+ // eslint-disable-next-line no-console
25
+ console.log("Next:");
26
+ // eslint-disable-next-line no-console
27
+ console.log(` cd ${projectName.replace(/[^a-zA-Z0-9]+/g, "-").toLowerCase()}`);
28
+ // eslint-disable-next-line no-console
29
+ console.log(" npm i");
30
+ // eslint-disable-next-line no-console
31
+ console.log(" npm run dev");
32
+ }
33
+ catch (err) {
34
+ // eslint-disable-next-line no-console
35
+ console.error(err?.message ?? String(err));
36
+ process.exit(1);
37
+ }
38
+ });
39
+ program.parse(process.argv);
@@ -0,0 +1,6 @@
1
+ export type CreateOptions = {
2
+ modules?: string[];
3
+ force?: boolean;
4
+ dryRun?: boolean;
5
+ };
6
+ export declare function createFullBackend(projectNameRaw: string, opts?: CreateOptions): Promise<string[]>;
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createFullBackend = createFullBackend;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const injected_files_1 = require("./injected-files");
10
+ const module_templates_1 = require("./module-templates");
11
+ const project_templates_1 = require("./project-templates");
12
+ const text_1 = require("./text");
13
+ /**
14
+ * Special modules can have different file sets than the generic CRUD module.
15
+ * - user: includes constant + interface + model + service + controller + validation + route
16
+ * - auth: includes service + controller + route
17
+ */
18
+ const MODULE_FILE_PLAN = {
19
+ user: [
20
+ ["constant", "user.constant.ts"],
21
+ ["interface", "user.interface.ts"],
22
+ ["model", "user.model.ts"],
23
+ ["service", "user.service.ts"],
24
+ ["controller", "user.controller.ts"],
25
+ ["validation", "user.validation.ts"],
26
+ ["route", "user.route.ts"],
27
+ ],
28
+ auth: [
29
+ ["service", "auth.service.ts"],
30
+ ["controller", "auth.controller.ts"],
31
+ ["route", "auth.route.ts"],
32
+ ],
33
+ };
34
+ const genericFilePlan = (m) => [
35
+ ["controller", `${m}.controller.ts`],
36
+ ["service", `${m}.service.ts`],
37
+ ["model", `${m}.model.ts`],
38
+ ["interface", `${m}.interface.ts`],
39
+ ["validation", `${m}.validation.ts`],
40
+ ["route", `${m}.route.ts`],
41
+ ];
42
+ /**
43
+ * Picks a template string for a module file.
44
+ * Priority:
45
+ * 1) MODULE_TEMPLATES[m][key] (special module templates)
46
+ * 2) MODULE_TEMPLATES.__generic__[key] OR MODULE_TEMPLATES[key] (legacy generic templates)
47
+ */
48
+ function pickTemplate(moduleName, key) {
49
+ const mt = module_templates_1.MODULE_TEMPLATES;
50
+ const special = mt?.[moduleName]?.[key];
51
+ if (typeof special === "string")
52
+ return special;
53
+ // New recommended generic container
54
+ const generic = mt?.__generic__?.[key];
55
+ if (typeof generic === "string")
56
+ return generic;
57
+ // Backward compatibility: old shape MODULE_TEMPLATES[key]
58
+ const legacy = mt?.[key];
59
+ if (typeof legacy === "string")
60
+ return legacy;
61
+ throw new Error(`No template found for module="${moduleName}" key="${key}". ` +
62
+ `Add it to MODULE_TEMPLATES["${moduleName}"]["${key}"] or MODULE_TEMPLATES.__generic__["${key}"].`);
63
+ }
64
+ function buildRoutesIndex(modules) {
65
+ // normalize + keep order, remove duplicates
66
+ const uniqueModules = Array.from(new Set(modules)).map(text_1.toModuleName);
67
+ const importLines = uniqueModules
68
+ .map((m) => {
69
+ const filName = m.substring(0, 1).toUpperCase() + m.substring(1).toLowerCase();
70
+ return `import { ${filName}Routes } from "../modules/${m.toLowerCase()}/${m.toLowerCase()}.route";`;
71
+ })
72
+ .join("\n");
73
+ const useLines = uniqueModules
74
+ .map((m) => {
75
+ const filName = m.substring(0, 1).toUpperCase() + m.substring(1).toLowerCase();
76
+ return `router.use("/${m.toLowerCase()}", ${filName}Routes);`;
77
+ })
78
+ .join("\n");
79
+ return `import express from "express";
80
+ ${importLines}
81
+
82
+ export const router = express.Router();
83
+
84
+ ${useLines}
85
+ `;
86
+ }
87
+ /**
88
+ * Renders template.
89
+ * - For generic modules we apply(__NAME__/__name__ replacements)
90
+ * - For user/auth (special modules), apply() is still safe; it won't harm if there are no markers.
91
+ */
92
+ function renderTemplate(raw, moduleName) {
93
+ return (0, text_1.apply)(raw, moduleName);
94
+ }
95
+ async function createFullBackend(projectNameRaw, opts = {}) {
96
+ const projectName = (0, text_1.toKebabOrLower)(projectNameRaw);
97
+ const root = path_1.default.resolve(projectName);
98
+ // default modules
99
+ const modules = (opts.modules && opts.modules.length ? opts.modules : ["auth", "user"]).map(text_1.toModuleName);
100
+ const planned = [];
101
+ // handle existing folder
102
+ if (await fs_extra_1.default.pathExists(root)) {
103
+ if (!opts.force)
104
+ throw new Error(`Folder already exists: ${root} (use --force)`);
105
+ if (!opts.dryRun)
106
+ await fs_extra_1.default.remove(root);
107
+ }
108
+ // base skeleton files
109
+ for (const [rel, content] of Object.entries(project_templates_1.PROJECT_FILES)) {
110
+ planned.push(path_1.default.join(root, rel));
111
+ if (!opts.dryRun) {
112
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(path_1.default.join(root, rel)));
113
+ await fs_extra_1.default.writeFile(path_1.default.join(root, rel), content, "utf8");
114
+ }
115
+ }
116
+ // inject app.ts/server.ts/config/utils etc
117
+ for (const [rel, content] of Object.entries(injected_files_1.INJECTED_FILES)) {
118
+ planned.push(path_1.default.join(root, rel));
119
+ if (!opts.dryRun) {
120
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(path_1.default.join(root, rel)));
121
+ await fs_extra_1.default.writeFile(path_1.default.join(root, rel), content, "utf8");
122
+ }
123
+ }
124
+ // ensure modules base dir exists
125
+ const modulesBase = path_1.default.join(root, "src", "app", "modules");
126
+ planned.push(modulesBase);
127
+ if (!opts.dryRun)
128
+ await fs_extra_1.default.ensureDir(modulesBase);
129
+ // generate modules
130
+ for (const m of modules) {
131
+ const moduleDir = path_1.default.join(modulesBase, m);
132
+ // ✅ different files for user/auth, generic for others
133
+ const files = MODULE_FILE_PLAN[m] ?? genericFilePlan(m);
134
+ for (const [key, filename] of files) {
135
+ const rel = path_1.default.join("src", "app", "modules", m, filename);
136
+ planned.push(path_1.default.join(root, rel));
137
+ if (!opts.dryRun) {
138
+ await fs_extra_1.default.ensureDir(moduleDir);
139
+ const raw = pickTemplate(m, key);
140
+ const rendered = renderTemplate(raw, m);
141
+ await fs_extra_1.default.writeFile(path_1.default.join(root, rel), rendered, "utf8");
142
+ }
143
+ }
144
+ // ✅ Write dynamic routes index.ts based on requested modules
145
+ const routesIndexRel = path_1.default.join("src", "app", "routes", "index.ts");
146
+ const routesIndexAbs = path_1.default.join(root, routesIndexRel);
147
+ planned.push(routesIndexAbs);
148
+ if (!opts.dryRun) {
149
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(routesIndexAbs));
150
+ await fs_extra_1.default.writeFile(routesIndexAbs, buildRoutesIndex(modules), "utf8");
151
+ }
152
+ }
153
+ // project package.json
154
+ const appPkg = {
155
+ name: projectName,
156
+ version: "1.0.0",
157
+ main: "index.js",
158
+ scripts: {
159
+ start: "node ./dist/server.js",
160
+ dev: "ts-node-dev --respawn --transpile-only ./src/server.ts",
161
+ build: "tsc",
162
+ lint: "npx eslint ./src",
163
+ test: 'echo "Error: no test specified" && exit 1',
164
+ },
165
+ keywords: [],
166
+ author: "",
167
+ license: "ISC",
168
+ description: "",
169
+ dependencies: {
170
+ axios: "^1.10.0",
171
+ bcryptjs: "^3.0.2",
172
+ cloudinary: "^1.41.3",
173
+ "cookie-parser": "^1.4.7",
174
+ cors: "^2.8.5",
175
+ dotenv: "^17.0.1",
176
+ ejs: "^3.1.10",
177
+ express: "^5.1.0",
178
+ "express-session": "^1.18.1",
179
+ "http-status-codes": "^2.3.0",
180
+ jsonwebtoken: "^9.0.2",
181
+ mongoose: "^8.16.1",
182
+ multer: "^2.0.2",
183
+ "multer-storage-cloudinary": "^4.0.0",
184
+ nodemailer: "^7.0.5",
185
+ passport: "^0.7.0",
186
+ "passport-google-oauth20": "^2.0.0",
187
+ "passport-local": "^1.0.0",
188
+ pdfkit: "^0.17.1",
189
+ redis: "^5.6.1",
190
+ zod: "^3.25.74",
191
+ },
192
+ devDependencies: {
193
+ "@eslint/js": "^9.30.1",
194
+ "@types/bcryptjs": "^2.4.6",
195
+ "@types/cookie-parser": "^1.4.9",
196
+ "@types/cors": "^2.8.19",
197
+ "@types/dotenv": "^6.1.1",
198
+ "@types/ejs": "^3.1.5",
199
+ "@types/express": "^5.0.3",
200
+ "@types/express-session": "^1.18.2",
201
+ "@types/jsonwebtoken": "^9.0.10",
202
+ "@types/multer": "^2.0.0",
203
+ "@types/nodemailer": "^6.4.17",
204
+ "@types/passport": "^1.0.17",
205
+ "@types/passport-google-oauth20": "^2.0.16",
206
+ "@types/passport-local": "^1.0.38",
207
+ "@types/pdfkit": "^0.17.2",
208
+ eslint: "^9.30.1",
209
+ "ts-node-dev": "^2.0.0",
210
+ typescript: "^5.8.3",
211
+ "typescript-eslint": "^8.35.1",
212
+ },
213
+ };
214
+ planned.push(path_1.default.join(root, "package.json"));
215
+ if (!opts.dryRun)
216
+ await fs_extra_1.default.writeJson(path_1.default.join(root, "package.json"), appPkg, { spaces: 2 });
217
+ return planned;
218
+ }
@@ -0,0 +1,2 @@
1
+ export { createFullBackend } from './generate';
2
+ export type { CreateOptions } from './generate';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createFullBackend = void 0;
4
+ var generate_1 = require("./generate");
5
+ Object.defineProperty(exports, "createFullBackend", { enumerable: true, get: function () { return generate_1.createFullBackend; } });
@@ -0,0 +1 @@
1
+ export declare const INJECTED_FILES: Record<string, string>;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INJECTED_FILES = void 0;
4
+ exports.INJECTED_FILES = {
5
+ "src/app.ts": "import cookieParser from \"cookie-parser\";\nimport cors from \"cors\";\nimport express, { Request, Response } from \"express\";\nimport expressSession from \"express-session\";\nimport passport from \"passport\";\nimport { envVars } from \"./app/config/env\";\nimport \"./app/config/passport\";\nimport { globalErrorHandler } from \"./app/middlewares/globalErrorHandler\";\nimport notFound from \"./app/middlewares/notFound\";\nimport { router } from \"./app/routes\";\n\nconst app = express()\n\n\napp.use(expressSession({\n secret: envVars.EXPRESS_SESSION_SECRET,\n resave: false,\n saveUninitialized: false\n}))\napp.use(passport.initialize())\napp.use(passport.session())\napp.use(cookieParser())\napp.use(express.json())\napp.set(\"trust proxy\", 1);\napp.use(express.urlencoded({ extended: true }))\napp.use(cors({\n origin: envVars.FRONTEND_URL,\n credentials: true\n}))\n\napp.use(\"/api/v1\", router)\n\napp.get(\"/\", (req: Request, res: Response) => {\n res.status(200).json({\n message: \"Welcome to Tour Management System Backend\"\n })\n})\n\n\napp.use(globalErrorHandler)\n\napp.use(notFound)\n\nexport default app",
6
+ "src/server.ts": "/* eslint-disable no-console */\nimport { Server } from \"http\";\nimport mongoose from \"mongoose\";\nimport app from \"./app\";\nimport { envVars } from \"./app/config/env\";\nimport { connectRedis } from \"./app/config/redis.config\";\nimport { seedSuperAdmin } from \"./app/utils/seedSuperAdmin\";\n\nlet server: Server;\n\n\nconst startServer = async () => {\n try {\n await mongoose.connect(envVars.DB_URL)\n\n console.log(\"Connected to DB!!\");\n\n server = app.listen(envVars.PORT, () => {\n console.log(`Server is listening to port ${envVars.PORT}`);\n });\n } catch (error) {\n console.log(error);\n }\n}\n\n(async () => {\n await connectRedis()\n await startServer()\n await seedSuperAdmin()\n})()\n\nprocess.on(\"SIGTERM\", () => {\n console.log(\"SIGTERM signal recieved... Server shutting down..\");\n\n if (server) {\n server.close(() => {\n process.exit(1)\n });\n }\n\n process.exit(1)\n})\n\nprocess.on(\"SIGINT\", () => {\n console.log(\"SIGINT signal recieved... Server shutting down..\");\n\n if (server) {\n server.close(() => {\n process.exit(1)\n });\n }\n\n process.exit(1)\n})\n\n\nprocess.on(\"unhandledRejection\", (err) => {\n console.log(\"Unhandled Rejecttion detected... Server shutting down..\", err);\n\n if (server) {\n server.close(() => {\n process.exit(1)\n });\n }\n\n process.exit(1)\n})\n\nprocess.on(\"uncaughtException\", (err) => {\n console.log(\"Uncaught Exception detected... Server shutting down..\", err);\n\n if (server) {\n server.close(() => {\n process.exit(1)\n });\n }\n\n process.exit(1)\n})\n\n// Unhandler rejection error\n// Promise.reject(new Error(\"I forgot to catch this promise\"))\n\n// Uncaught Exception Error\n// throw new Error(\"I forgot to handle this local erro\")\n\n\n/**\n * unhandled rejection error\n * uncaught rejection error\n * signal termination sigterm\n */\n\n",
7
+ "src/app/config/cloudinary.config.ts": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\n// Frontedn -> Form Data with Image File -> Multer -> Form data -> Req (Body + File)\n\nimport { v2 as cloudinary, UploadApiResponse } from \"cloudinary\";\nimport stream from \"stream\";\nimport AppError from \"../errorHelpers/AppError\";\nimport { envVars } from \"./env\";\n\n// Amader folder -> image -> form data -> File -> Multer -> Amader project / pc te Nijer ekta folder(temporary) -> Req.file\n\n//req.file -> cloudinary(req.file) -> url -> mongoose -> mongodb\n\n\ncloudinary.config({\n cloud_name: envVars.CLOUDINARY.CLOUDINARY_CLOUD_NAME,\n api_key: envVars.CLOUDINARY.CLOUDINARY_API_KEY,\n api_secret: envVars.CLOUDINARY.CLOUDINARY_API_SECRET\n})\n\nexport const uploadBufferToCloudinary = async (buffer: Buffer, fileName: string): Promise<UploadApiResponse | undefined> => {\n try {\n return new Promise((resolve, reject) => {\n\n const public_id = `pdf/${fileName}-${Date.now()}`\n\n const bufferStream = new stream.PassThrough();\n bufferStream.end(buffer)\n\n cloudinary.uploader.upload_stream(\n {\n resource_type: \"auto\",\n public_id: public_id,\n folder: \"pdf\"\n },\n (error, result) => {\n if (error) {\n return reject(error);\n }\n resolve(result)\n }\n ).end(buffer)\n\n\n })\n\n } catch (error: any) {\n console.log(error);\n throw new AppError(401, `Error uploading file ${error.message}`)\n }\n}\n\nexport const deleteImageFromCLoudinary = async (url: string) => {\n try {\n //https://res.cloudinary.com/djzppynpk/image/upload/v1753126572/ay9roxiv8ue-1753126570086-download-2-jpg.jpg.jpg\n\n const regex = /\\/v\\d+\\/(.*?)\\.(jpg|jpeg|png|gif|webp)$/i;\n\n const match = url.match(regex);\n\n console.log({ match });\n\n if (match && match[1]) {\n const public_id = match[1];\n await cloudinary.uploader.destroy(public_id)\n console.log(`File ${public_id} is deleted from cloudinary`);\n\n }\n } catch (error: any) {\n throw new AppError(401, \"Cloudinary image deletion failed\", error.message)\n }\n}\n\nexport const cloudinaryUpload = cloudinary\n\n\n\n// const uploadToCloudinary = cloudinary.uploader.upload()\n\n//\n\n//Multer storage cloudinary\n//Amader folder -> image -> form data -> File -> Multer -> storage in cloudinary -> url -> req.file -> url -> mongoose -> mongodb",
8
+ "src/app/config/env.ts": "import dotenv from \"dotenv\";\n\ndotenv.config()\n\ninterface EnvConfig {\n PORT: string,\n DB_URL: string,\n NODE_ENV: \"development\" | \"production\"\n BCRYPT_SALT_ROUND: string\n JWT_ACCESS_SECRET: string\n JWT_ACCESS_EXPIRES: string\n JWT_REFRESH_SECRET: string\n JWT_REFRESH_EXPIRES: string\n SUPER_ADMIN_EMAIL: string\n SUPER_ADMIN_PASSWORD: string\n GOOGLE_CLIENT_SECRET: string\n GOOGLE_CLIENT_ID: string\n GOOGLE_CALLBACK_URL: string\n EXPRESS_SESSION_SECRET: string\n FRONTEND_URL: string\n SSL: {\n STORE_ID: string,\n STORE_PASS: string,\n SSL_PAYMENT_API: string,\n SSL_VALIDATION_API: string,\n SSL_SUCCESS_FRONTEND_URL: string,\n SSL_FAIL_FRONTEND_URL: string,\n SSL_CANCEL_FRONTEND_URL: string,\n SSL_SUCCESS_BACKEND_URL: string,\n SSL_FAIL_BACKEND_URL: string,\n SSL_CANCEL_BACKEND_URL: string,\n SSL_IPN_URL: string\n };\n CLOUDINARY: {\n CLOUDINARY_CLOUD_NAME: string;\n CLOUDINARY_API_KEY: string;\n CLOUDINARY_API_SECRET: string;\n };\n EMAIL_SENDER: {\n SMTP_USER: string;\n SMTP_PASS: string;\n SMTP_PORT: string;\n SMTP_HOST: string;\n SMTP_FROM: string;\n };\n REDIS_HOST: string;\n REDIS_PORT: string;\n REDIS_USERNAME: string;\n REDIS_PASSWORD: string;\n\n\n}\n\nconst loadEnvVariables = (): EnvConfig => {\n const requiredEnvVariables: string[] = [\"PORT\", \"DB_URL\", \"NODE_ENV\", \"BCRYPT_SALT_ROUND\", \"JWT_ACCESS_EXPIRES\", \"JWT_ACCESS_SECRET\", \"SUPER_ADMIN_EMAIL\", \"SUPER_ADMIN_PASSWORD\", \"JWT_REFRESH_SECRET\", \"JWT_REFRESH_EXPIRES\", \"GOOGLE_CLIENT_SECRET\", \"GOOGLE_CLIENT_ID\", \"GOOGLE_CALLBACK_URL\", \"EXPRESS_SESSION_SECRET\", \"FRONTEND_URL\", \"SSL_STORE_ID\",\n \"SSL_STORE_PASS\",\n \"SSL_PAYMENT_API\", \"SSL_VALIDATION_API\", \"SSL_SUCCESS_FRONTEND_URL\",\n \"SSL_FAIL_FRONTEND_URL\",\n \"SSL_CANCEL_FRONTEND_URL\",\n \"SSL_SUCCESS_BACKEND_URL\",\n \"SSL_FAIL_BACKEND_URL\",\n \"SSL_CANCEL_BACKEND_URL\", \"CLOUDINARY_CLOUD_NAME\",\n \"CLOUDINARY_API_KEY\",\n \"CLOUDINARY_API_SECRET\", \"SMTP_PASS\",\n \"SMTP_PORT\",\n \"SMTP_HOST\",\n \"SMTP_USER\",\n \"SMTP_FROM\", \"REDIS_HOST\",\n \"REDIS_PORT\",\n \"REDIS_USERNAME\",\n \"REDIS_PASSWORD\", \"SSL_IPN_URL\"];\n\n requiredEnvVariables.forEach(key => {\n if (!process.env[key]) {\n throw new Error(`Missing require environment variabl ${key}`)\n }\n })\n\n return {\n PORT: process.env.PORT as string,\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n DB_URL: process.env.DB_URL!,\n NODE_ENV: process.env.NODE_ENV as \"development\" | \"production\",\n BCRYPT_SALT_ROUND: process.env.BCRYPT_SALT_ROUND as string,\n JWT_ACCESS_SECRET: process.env.JWT_ACCESS_SECRET as string,\n JWT_ACCESS_EXPIRES: process.env.JWT_ACCESS_EXPIRES as string,\n JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET as string,\n JWT_REFRESH_EXPIRES: process.env.JWT_REFRESH_EXPIRES as string,\n SUPER_ADMIN_EMAIL: process.env.SUPER_ADMIN_EMAIL as string,\n SUPER_ADMIN_PASSWORD: process.env.SUPER_ADMIN_PASSWORD as string,\n GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET as string,\n GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID as string,\n GOOGLE_CALLBACK_URL: process.env.GOOGLE_CALLBACK_URL as string,\n EXPRESS_SESSION_SECRET: process.env.EXPRESS_SESSION_SECRET as string,\n FRONTEND_URL: process.env.FRONTEND_URL as string,\n // ssl\n SSL: {\n STORE_ID: process.env.SSL_STORE_ID as string,\n STORE_PASS: process.env.SSL_STORE_PASS as string,\n SSL_PAYMENT_API: process.env.SSL_PAYMENT_API as string,\n SSL_VALIDATION_API: process.env.SSL_VALIDATION_API as string,\n SSL_SUCCESS_FRONTEND_URL: process.env.SSL_SUCCESS_FRONTEND_URL as string,\n SSL_FAIL_FRONTEND_URL: process.env.SSL_FAIL_FRONTEND_URL as string,\n SSL_CANCEL_FRONTEND_URL: process.env.SSL_CANCEL_FRONTEND_URL as string,\n SSL_SUCCESS_BACKEND_URL: process.env.SSL_SUCCESS_BACKEND_URL as string,\n SSL_FAIL_BACKEND_URL: process.env.SSL_FAIL_BACKEND_URL as string,\n SSL_CANCEL_BACKEND_URL: process.env.SSL_CANCEL_BACKEND_URL as string,\n SSL_IPN_URL: process.env.SSL_IPN_URL as string\n },\n CLOUDINARY: {\n CLOUDINARY_CLOUD_NAME: process.env.CLOUDINARY_CLOUD_NAME as string,\n CLOUDINARY_API_KEY: process.env.CLOUDINARY_API_KEY as string,\n CLOUDINARY_API_SECRET: process.env.CLOUDINARY_API_SECRET as string,\n },\n EMAIL_SENDER: {\n SMTP_USER: process.env.SMTP_USER as string,\n SMTP_PASS: process.env.SMTP_PASS as string,\n SMTP_PORT: process.env.SMTP_PORT as string,\n SMTP_HOST: process.env.SMTP_HOST as string,\n SMTP_FROM: process.env.SMTP_FROM as string,\n },\n REDIS_HOST: process.env.REDIS_HOST as string,\n REDIS_PORT: process.env.REDIS_PORT as string,\n REDIS_USERNAME: process.env.REDIS_USERNAME as string,\n REDIS_PASSWORD: process.env.REDIS_PASSWORD as string,\n\n }\n}\n\nexport const envVars = loadEnvVariables()",
9
+ "src/app/config/multer.config.ts": "import multer from \"multer\";\nimport { CloudinaryStorage } from \"multer-storage-cloudinary\";\nimport { cloudinaryUpload } from \"./cloudinary.config\";\n\n\nconst storage = new CloudinaryStorage({\n cloudinary: cloudinaryUpload,\n params: {\n public_id: (req, file) => {\n // My Special.Image#!@.png => 4545adsfsadf-45324263452-my-image.png\n // My Special.Image#!@.png => [My Special, Image#!@, png]\n\n const fileName = file.originalname\n .toLowerCase()\n .replace(/\\s+/g, \"-\") // empty space remove replace with dash\n .replace(/\\./g, \"-\")\n // eslint-disable-next-line no-useless-escape\n .replace(/[^a-z0-9\\-\\.]/g, \"\") // non alpha numeric - !@#$\n\n const extension = file.originalname.split(\".\").pop()\n\n // binary -> 0,1 hexa decimal -> 0-9 A-F base 36 -> 0-9 a-z\n // 0.2312345121 -> \"0.hedfa674338sasfamx\" -> \n //452384772534\n const uniqueFileName = Math.random().toString(36).substring(2) + \"-\" + Date.now() + \"-\" + fileName + \".\" + extension\n\n return uniqueFileName\n }\n }\n})\n\nexport const multerUpload = multer({ storage: storage })",
10
+ "src/app/config/passport.ts": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport bcryptjs from \"bcryptjs\";\nimport passport from \"passport\";\nimport { Strategy as GoogleStrategy, Profile, VerifyCallback } from \"passport-google-oauth20\";\nimport { Strategy as LocalStrategy } from \"passport-local\";\nimport { IsActive, Role } from \"../modules/user/user.interface\";\nimport { User } from \"../modules/user/user.model\";\nimport { envVars } from \"./env\";\n\n\npassport.use(\n new LocalStrategy({\n usernameField: \"email\",\n passwordField: \"password\"\n }, async (email: string, password: string, done) => {\n try {\n const isUserExist = await User.findOne({ email })\n\n if (!isUserExist) {\n return done(\"User does not exist\")\n }\n\n if (!isUserExist.isVerified) {\n return done(\"User is not verified\")\n }\n\n if (isUserExist.isActive === IsActive.BLOCKED || isUserExist.isActive === IsActive.INACTIVE) {\n return done(`User is ${isUserExist.isActive}`)\n }\n if (isUserExist.isDeleted) {\n return done(\"User is deleted\")\n }\n\n\n const isGoogleAuthenticated = isUserExist.auths.some(providerObjects => providerObjects.provider == \"google\")\n\n if (isGoogleAuthenticated && !isUserExist.password) {\n return done(null, false, { message: \"You have authenticated through Google. So if you want to login with credentials, then at first login with google and set a password for your Gmail and then you can login with email and password.\" })\n }\n\n const isPasswordMatched = await bcryptjs.compare(password as string, isUserExist.password as string)\n\n if (!isPasswordMatched) {\n return done(null, false, { message: \"Password does not match\" })\n }\n\n return done(null, isUserExist)\n\n } catch (error) {\n console.log(error);\n done(error)\n }\n })\n)\n\npassport.use(\n new GoogleStrategy(\n {\n clientID: envVars.GOOGLE_CLIENT_ID,\n clientSecret: envVars.GOOGLE_CLIENT_SECRET,\n callbackURL: envVars.GOOGLE_CALLBACK_URL\n }, async (accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback) => {\n\n try {\n const email = profile.emails?.[0].value;\n\n if (!email) {\n return done(null, false, { mesaage: \"No email found\" })\n }\n\n let isUserExist = await User.findOne({ email })\n if (isUserExist && !isUserExist.isVerified) {\n return done(null, false, { message: \"User is not verified\" })\n }\n\n if (isUserExist && (isUserExist.isActive === IsActive.BLOCKED || isUserExist.isActive === IsActive.INACTIVE)) {\n done(`User is ${isUserExist.isActive}`)\n }\n\n if (isUserExist && isUserExist.isDeleted) {\n return done(null, false, { message: \"User is deleted\" })\n }\n\n if (!isUserExist) {\n isUserExist = await User.create({\n email,\n name: profile.displayName,\n picture: profile.photos?.[0].value,\n role: Role.USER,\n isVerified: true,\n auths: [\n {\n provider: \"google\",\n providerId: profile.id\n }\n ]\n })\n }\n\n return done(null, isUserExist)\n\n\n } catch (error) {\n console.log(\"Google Strategy Error\", error);\n return done(error)\n }\n }\n )\n)\n\npassport.serializeUser((user: any, done: (err: any, id?: unknown) => void) => {\n done(null, user._id)\n})\n\npassport.deserializeUser(async (id: string, done: any) => {\n try {\n const user = await User.findById(id);\n done(null, user)\n } catch (error) {\n console.log(error);\n done(error)\n }\n})",
11
+ "src/app/config/redis.config.ts": "import { createClient } from 'redis';\nimport { envVars } from './env';\n\nexport const redisClient = createClient({\n username: envVars.REDIS_USERNAME,\n password: envVars.REDIS_PASSWORD,\n socket: {\n host: envVars.REDIS_HOST,\n port: Number(envVars.REDIS_PORT)\n }\n});\n\nredisClient.on('error', err => console.log('Redis Client Error', err));\n\nexport const connectRedis = async () => {\n if (!redisClient.isOpen) {\n await redisClient.connect();\n console.log(\"Redis Connected\");\n }\n}\n",
12
+ "src/app/utils/catchAsync.ts": "import { NextFunction, Request, Response } from \"express\";\n\ntype AsyncHandler = (req: Request, res: Response, next: NextFunction) => Promise<void>\n\nexport const catchAsync = (fn: AsyncHandler) => (req: Request, res: Response, next: NextFunction) => {\n Promise.resolve(fn(req, res, next)).catch((err: any) => {\n next(err)\n })\n}",
13
+ "src/app/utils/sendResponse.ts": "import { Response } from \"express\";\n\ninterface TMeta {\n page: number;\n limit: number;\n totalPage: number;\n total: number\n}\n\ninterface TResponse<T> {\n statusCode: number;\n success: boolean;\n message: string;\n data: T;\n meta?: TMeta\n}\n\nexport const sendResponse = <T>(res: Response, data: TResponse<T>) => {\n res.status(data.statusCode).json({\n statusCode: data.statusCode,\n success: data.success,\n message: data.message,\n meta: data.meta,\n data: data.data\n })\n}",
14
+ "src/app/utils/getTransactionId.ts": "export const getTransactionId = () => {\n return `tran_${Date.now()}_${Math.floor(Math.random() * 1000)}`\n}",
15
+ "src/app/utils/invoice.ts": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport PDFDocument from \"pdfkit\";\nimport AppError from \"../errorHelpers/AppError\";\n\nexport interface IInvoiceData {\n transactionId: string;\n bookingDate: Date;\n userName: string;\n tourTitle: string;\n guestCount: number;\n totalAmount: number;\n}\n\nexport const generatePdf = async (invoiceData: IInvoiceData): Promise<Buffer<ArrayBufferLike>> => {\n try {\n return new Promise((resolve, reject) => {\n const doc = new PDFDocument({ size: \"A4\", margin: 50 })\n const buffer: Uint8Array[] = [];\n\n doc.on(\"data\", (chunk) => buffer.push(chunk))\n doc.on(\"end\", () => resolve(Buffer.concat(buffer)))\n doc.on(\"error\", (err) => reject(err))\n\n doc.fontSize(20).text(\"Invoice\", { align: \"center\" });\n doc.moveDown()\n doc.fontSize(14).text(`Transaction ID : ${invoiceData.transactionId}`)\n doc.text(`Booking Date : ${invoiceData.bookingDate}`)\n doc.text(`Customer : ${invoiceData.userName}`)\n\n doc.moveDown();\n\n doc.text(`Tour: ${invoiceData.tourTitle}`);\n doc.text(`Guests: ${invoiceData.guestCount}`);\n doc.text(`Total Amount: $${invoiceData.totalAmount.toFixed(2)}`);\n doc.moveDown();\n\n doc.text(\"Thank you for booking with us!\", { align: \"center\" });\n\n doc.end()\n\n })\n\n } catch (error: any) {\n console.log(error);\n throw new AppError(401, `Pdf creation error ${error.message}`)\n }\n}",
16
+ "src/app/utils/jwt.ts": "import jwt, { JwtPayload, SignOptions } from \"jsonwebtoken\"\n\nexport const generateToken = (payload: JwtPayload, secret: string, expiresIn: string) => {\n const token = jwt.sign(payload, secret, {\n expiresIn\n } as SignOptions)\n\n return token\n}\n\nexport const verifyToken = (token: string, secret: string) => {\n\n const verifiedToken = jwt.verify(token, secret);\n\n return verifiedToken\n}",
17
+ "src/app/utils/QueryBuilder.ts": "import { Query } from \"mongoose\";\nimport { excludeField } from \"../constants\";\n\nexport class QueryBuilder<T> {\n public modelQuery: Query<T[], T>;\n public readonly query: Record<string, string>\n\n constructor(modelQuery: Query<T[], T>, query: Record<string, string>) {\n this.modelQuery = modelQuery;\n this.query = query;\n }\n\n\n filter(): this {\n const filter = { ...this.query }\n\n for (const field of excludeField) {\n delete filter[field]\n }\n\n this.modelQuery = this.modelQuery.find(filter)\n\n return this;\n }\n\n search(searchableField: string[]): this {\n const searchTerm = this.query.searchTerm || \"\"\n const searchQuery = {\n $or: searchableField.map(field => ({ [field]: { $regex: searchTerm, $options: \"i\" } }))\n }\n this.modelQuery = this.modelQuery.find(searchQuery)\n return this\n }\n\n sort(): this {\n\n const sort = this.query.sort || \"-createdAt\";\n\n this.modelQuery = this.modelQuery.sort(sort)\n\n return this;\n }\n fields(): this {\n\n const fields = this.query.fields?.split(\",\").join(\" \") || \"\"\n\n this.modelQuery = this.modelQuery.select(fields)\n\n return this;\n }\n paginate(): this {\n\n const page = Number(this.query.page) || 1\n const limit = Number(this.query.limit) || 10\n const skip = (page - 1) * limit\n\n this.modelQuery = this.modelQuery.skip(skip).limit(limit)\n\n return this;\n }\n\n build() {\n return this.modelQuery\n }\n\n async getMeta() {\n const totalDocuments = await this.modelQuery.model.countDocuments()\n\n const page = Number(this.query.page) || 1\n const limit = Number(this.query.limit) || 10\n\n const totalPage = Math.ceil(totalDocuments / limit)\n\n return { page, limit, total: totalDocuments, totalPage }\n }\n}\n",
18
+ "src/app/utils/seedSuperAdmin.ts": "import bcryptjs from \"bcryptjs\";\nimport { envVars } from \"../config/env\";\nimport { IAuthProvider, IUser, Role } from \"../modules/user/user.interface\";\nimport { User } from \"../modules/user/user.model\";\n\nexport const seedSuperAdmin = async () => {\n try {\n const isSuperAdminExist = await User.findOne({ email: envVars.SUPER_ADMIN_EMAIL })\n\n if (isSuperAdminExist) {\n console.log(\"Super Admin Already Exists!\");\n return;\n }\n\n console.log(\"Trying to create Super Admin...\");\n\n const hashedPassword = await bcryptjs.hash(envVars.SUPER_ADMIN_PASSWORD, Number(envVars.BCRYPT_SALT_ROUND))\n\n const authProvider: IAuthProvider = {\n provider: \"credentials\",\n providerId: envVars.SUPER_ADMIN_EMAIL\n }\n\n const payload: IUser = {\n name: \"Super admin\",\n role: Role.SUPER_ADMIN,\n email: envVars.SUPER_ADMIN_EMAIL,\n password: hashedPassword,\n isVerified: true,\n auths: [authProvider]\n\n }\n\n const superadmin = await User.create(payload)\n console.log(\"Super Admin Created Successfuly! \\n\");\n console.log(superadmin);\n } catch (error) {\n console.log(error);\n }\n}",
19
+ "src/app/utils/sendEmail.ts": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport ejs from \"ejs\";\nimport nodemailer from \"nodemailer\";\nimport path from \"path\";\nimport { envVars } from \"../config/env\";\nimport AppError from \"../errorHelpers/AppError\";\n\nconst transporter = nodemailer.createTransport({\n secure: true,\n auth: {\n user: envVars.EMAIL_SENDER.SMTP_USER,\n pass: envVars.EMAIL_SENDER.SMTP_PASS\n },\n port: Number(envVars.EMAIL_SENDER.SMTP_PORT),\n host: envVars.EMAIL_SENDER.SMTP_HOST\n})\n\ninterface SendEmailOptions {\n to: string,\n subject: string;\n templateName: string;\n templateData?: Record<string, any>\n attachments?: {\n filename: string,\n content: Buffer | string,\n contentType: string\n }[]\n}\n\nexport const sendEmail = async ({\n to,\n subject,\n templateName,\n templateData,\n attachments\n}: SendEmailOptions) => {\n try {\n const templatePath = path.join(__dirname, `templates/${templateName}.ejs`)\n const html = await ejs.renderFile(templatePath, templateData)\n const info = await transporter.sendMail({\n from: envVars.EMAIL_SENDER.SMTP_FROM,\n to: to,\n subject: subject,\n html: html,\n attachments: attachments?.map(attachment => ({\n filename: attachment.filename,\n content: attachment.content,\n contentType: attachment.contentType\n }))\n })\n console.log(`\\u2709\\uFE0F Email sent to ${to}: ${info.messageId}`);\n } catch (error: any) {\n console.log(\"email sending error\", error.message);\n throw new AppError(401, \"Email error\")\n }\n\n}",
20
+ "src/app/utils/setCookie.ts": "import { Response } from \"express\";\nimport { envVars } from \"../config/env\";\n\nexport interface AuthTokens {\n accessToken?: string;\n refreshToken?: string;\n}\n\nexport const setAuthCookie = (res: Response, tokenInfo: AuthTokens) => {\n if (tokenInfo.accessToken) {\n res.cookie(\"accessToken\", tokenInfo.accessToken, {\n httpOnly: true,\n secure: envVars.NODE_ENV === \"production\",\n sameSite: \"none\"\n })\n }\n\n if (tokenInfo.refreshToken) {\n res.cookie(\"refreshToken\", tokenInfo.refreshToken, {\n httpOnly: true,\n secure: envVars.NODE_ENV === \"production\",\n sameSite: \"none\"\n })\n }\n}",
21
+ "src/app/utils/userTokens.ts": "import httpStatus from \"http-status-codes\";\nimport { JwtPayload } from \"jsonwebtoken\";\nimport { envVars } from \"../config/env\";\nimport AppError from \"../errorHelpers/AppError\";\nimport { IsActive, IUser } from \"../modules/user/user.interface\";\nimport { User } from \"../modules/user/user.model\";\nimport { generateToken, verifyToken } from \"./jwt\";\n\nexport const createUserTokens = (user: Partial<IUser>) => {\n const jwtPayload = {\n userId: user._id,\n email: user.email,\n role: user.role\n }\n const accessToken = generateToken(jwtPayload, envVars.JWT_ACCESS_SECRET, envVars.JWT_ACCESS_EXPIRES)\n\n const refreshToken = generateToken(jwtPayload, envVars.JWT_REFRESH_SECRET, envVars.JWT_REFRESH_EXPIRES)\n\n\n return {\n accessToken,\n refreshToken\n }\n}\n\nexport const createNewAccessTokenWithRefreshToken = async (refreshToken: string) => {\n\n const verifiedRefreshToken = verifyToken(refreshToken, envVars.JWT_REFRESH_SECRET) as JwtPayload\n\n\n const isUserExist = await User.findOne({ email: verifiedRefreshToken.email })\n\n if (!isUserExist) {\n throw new AppError(httpStatus.BAD_REQUEST, \"User does not exist\")\n }\n if (isUserExist.isActive === IsActive.BLOCKED || isUserExist.isActive === IsActive.INACTIVE) {\n throw new AppError(httpStatus.BAD_REQUEST, `User is ${isUserExist.isActive}`)\n }\n if (isUserExist.isDeleted) {\n throw new AppError(httpStatus.BAD_REQUEST, \"User is deleted\")\n }\n\n const jwtPayload = {\n userId: isUserExist._id,\n email: isUserExist.email,\n role: isUserExist.role\n }\n const accessToken = generateToken(jwtPayload, envVars.JWT_ACCESS_SECRET, envVars.JWT_ACCESS_EXPIRES)\n\n return accessToken\n}",
22
+ "src/app/utils/templates/forgetPassword.ejs": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n <title>Forgot Password</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n background: #f7f7f7;\n color: #333;\n }\n .container {\n max-width: 500px;\n margin: 40px auto;\n background: #fff;\n padding: 30px;\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n }\n .btn {\n display: inline-block;\n padding: 12px 24px;\n background: #007bff;\n color: #fff;\n text-decoration: none;\n border-radius: 4px;\n margin-top: 20px;\n }\n .footer {\n margin-top: 30px;\n font-size: 12px;\n color: #888;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h2>Password Reset Request</h2>\n <p>Hello <%= name %>,</p>\n <p>\n We received a request to reset your password for your account. Click the\n button below to set a new password:\n </p>\n <a class=\"btn\" href=\"<%= resetUILink %>\">Reset Password</a>\n <p>\n If you did not request a password reset, please ignore this email. This\n link will expire in 10 minutes.\n </p>\n <div class=\"footer\">&copy; PH Tour Management. All rights reserved.</div>\n </div>\n </body>\n</html>\n",
23
+ "src/app/utils/templates/invoice.ejs": "<h1>Invoice for Booking ></h1>\n<p>Payment ID:</p>\n<p>Amount Paid:</p>\n<p>Thank you for your booking!</p>\n<p><a>Download Invoice PDF</a></p>\n",
24
+ "src/app/utils/templates/otp.ejs": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n <title>OTP Verification</title>\n <style>\n body {\n font-family: Arial, sans-serif;\n background-color: #f5f7fa;\n margin: 0;\n padding: 0;\n }\n .container {\n max-width: 480px;\n margin: 40px auto;\n background: #ffffff;\n padding: 30px;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);\n text-align: center;\n }\n h2 {\n color: #333;\n }\n .otp {\n font-size: 28px;\n font-weight: bold;\n letter-spacing: 6px;\n color: #007bff;\n margin: 20px 0;\n }\n p {\n color: #555;\n font-size: 14px;\n }\n .footer {\n margin-top: 30px;\n font-size: 12px;\n color: #999;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <h2>Hello <%= name %>,</h2>\n <p>Your One-Time Password (OTP) is:</p>\n <div class=\"otp\"><%= otp %></div>\n <p>This OTP is valid for <strong>2 minutes</strong>.</p>\n <p>If you did not request this code, please ignore this email.</p>\n <div class=\"footer\">&copy; Tour Management System</div>\n </div>\n </body>\n</html>\n"
25
+ };
@@ -0,0 +1,22 @@
1
+ export declare const MODULE_TEMPLATES: {
2
+ readonly controller: "import { Request, Response } from \"express\";\nimport httpStatus from \"http-status-codes\";\nimport { catchAsync } from \"../../utils/catchAsync\";\nimport { sendResponse } from \"../../utils/sendResponse\";\nimport { __NAME__Service } from \"./__name__.service\";\n\nconst create = catchAsync(async (req: Request, res: Response) => {\n const doc = await __NAME__Service.create(req.body);\n sendResponse(res, { statusCode: httpStatus.CREATED, success: true, message: \"__name__ created\", data: doc });\n});\n\nconst getAll = catchAsync(async (_req: Request, res: Response) => {\n const docs = await __NAME__Service.getAll();\n sendResponse(res, { statusCode: httpStatus.OK, success: true, message: \"__name__ list\", data: docs });\n});\n\nconst getById = catchAsync(async (req: Request, res: Response) => {\n const doc = await __NAME__Service.getById(req.params.id);\n sendResponse(res, { statusCode: httpStatus.OK, success: true, message: \"__name__ found\", data: doc });\n});\n\nconst update = catchAsync(async (req: Request, res: Response) => {\n const doc = await __NAME__Service.update(req.params.id, req.body);\n sendResponse(res, { statusCode: httpStatus.OK, success: true, message: \"__name__ updated\", data: doc });\n});\n\nconst remove = catchAsync(async (req: Request, res: Response) => {\n const doc = await __NAME__Service.delete(req.params.id);\n sendResponse(res, { statusCode: httpStatus.OK, success: true, message: \"__name__ deleted\", data: doc });\n});\n\nexport const __NAME__Controller = { create, getAll, getById, update, remove };\n";
3
+ readonly interface: "export type __NAME__Status = \"Active\" | \"Inactive\";\n\nexport default interface T__NAME__ {\n name: string;\n status: __NAME__Status;\n}\n";
4
+ readonly model: "import { model, Schema } from \"mongoose\";\nimport type T__NAME__ from \"./__name__.interface\";\n\nconst __name__Schema = new Schema<T__NAME__>(\n {\n name: { type: String, required: true, trim: true },\n status: { type: String, enum: [\"Active\",\"Inactive\"], default: \"Active\" }\n },\n { timestamps: true }\n);\n\nexport const __NAME__ = model<T__NAME__>(\"__NAME__\", __name__Schema);\nexport default __NAME__;\n";
5
+ readonly service: "import httpStatus from \"http-status-codes\";\nimport AppError from \"../../errorHelpers/AppError\";\nimport { __NAME__ } from \"./__name__.model\";\nimport type T__NAME__ from \"./__name__.interface\";\n\nexport const __NAME__Service = {\n create: async (payload: T__NAME__) => {\n const doc = await __NAME__.create(payload);\n return doc;\n },\n\n getAll: async () => {\n return __NAME__.find().sort({ createdAt: -1 });\n },\n\n getById: async (id: string) => {\n const doc = await __NAME__.findById(id);\n if (!doc) throw new AppError(httpStatus.NOT_FOUND, \"__name__ not found\");\n return doc;\n },\n\n update: async (id: string, payload: Partial<T__NAME__>) => {\n const doc = await __NAME__.findByIdAndUpdate(id, payload, { new: true });\n if (!doc) throw new AppError(httpStatus.NOT_FOUND, \"__name__ not found\");\n return doc;\n },\n\n delete: async (id: string) => {\n const doc = await __NAME__.findByIdAndDelete(id);\n if (!doc) throw new AppError(httpStatus.NOT_FOUND, \"__name__ not found\");\n return doc;\n }\n};\n";
6
+ readonly validation: "import { z } from \"zod\";\n\nexport const create__NAME__ZodSchema = z.object({\n name: z.string().min(1),\n status: z.enum([\"Active\",\"Inactive\"]).optional()\n});\n\nexport const update__NAME__ZodSchema = create__NAME__ZodSchema.partial();\n";
7
+ readonly route: "import express from \"express\";\nimport { checkAuth } from \"../../middlewares/checkAuth\";\nimport { validateRequest } from \"../../middlewares/validateRequest\";\nimport { Role } from \"../user/user.interface\";\nimport { __NAME__Controller } from \"./__name__.controller\";\nimport { create__NAME__ZodSchema, update__NAME__ZodSchema } from \"./__name__.validation\";\n\nconst router = express.Router();\n\nrouter.post(\"/\", checkAuth(Role.ADMIN, Role.SUPER_ADMIN), validateRequest(create__NAME__ZodSchema), __NAME__Controller.create);\nrouter.get(\"/\", checkAuth(Role.ADMIN, Role.SUPER_ADMIN), __NAME__Controller.getAll);\nrouter.get(\"/:id\", checkAuth(Role.ADMIN, Role.SUPER_ADMIN), __NAME__Controller.getById);\nrouter.put(\"/:id\", checkAuth(Role.ADMIN, Role.SUPER_ADMIN), validateRequest(update__NAME__ZodSchema), __NAME__Controller.update);\nrouter.delete(\"/:id\", checkAuth(Role.ADMIN, Role.SUPER_ADMIN), __NAME__Controller.remove);\n\nexport const __NAME__Routes = router;\n";
8
+ readonly user: {
9
+ readonly constant: "export const userSearchableFields = [\"name\", \"email\", \"address\"];\n";
10
+ readonly interface: "import { Types } from \"mongoose\";\n\nexport enum Role {\n SUPER_ADMIN = \"SUPER_ADMIN\",\n ADMIN = \"ADMIN\",\n USER = \"USER\",\n GUIDE = \"GUIDE\",\n}\n\nexport interface IAuthProvider {\n provider: \"google\" | \"credentials\";\n providerId: string;\n}\n\nexport enum IsActive {\n ACTIVE = \"ACTIVE\",\n INACTIVE = \"INACTIVE\",\n BLOCKED = \"BLOCKED\",\n}\n\nexport interface IUser {\n _id?: Types.ObjectId;\n name: string;\n email: string;\n password?: string;\n phone?: string;\n picture?: string;\n address?: string;\n isDeleted?: string;\n isActive?: IsActive;\n isVerified?: boolean;\n role: Role;\n auths: IAuthProvider[];\n bookings?: Types.ObjectId[];\n guides?: Types.ObjectId[];\n createdAt?: Date;\n}\n";
11
+ readonly model: "import { model, Schema } from \"mongoose\";\nimport { IAuthProvider, IsActive, IUser, Role } from \"./user.interface\";\n\nconst authProviderSchema = new Schema<IAuthProvider>(\n {\n provider: { type: String, required: true },\n providerId: { type: String, required: true },\n },\n { versionKey: false, _id: false }\n);\n\nconst userSchema = new Schema<IUser>(\n {\n name: { type: String, required: true },\n email: { type: String, required: true, unique: true },\n password: { type: String },\n role: { type: String, enum: Object.values(Role), default: Role.USER },\n phone: { type: String },\n picture: { type: String },\n address: { type: String },\n isDeleted: { type: Boolean, default: false },\n isActive: { type: String, enum: Object.values(IsActive), default: IsActive.ACTIVE },\n isVerified: { type: Boolean, default: false },\n auths: [authProviderSchema],\n },\n { timestamps: true, versionKey: false }\n);\n\nexport const User = model<IUser>(\"User\", userSchema);\n";
12
+ readonly service: "import bcryptjs from \"bcryptjs\";\nimport httpStatus from \"http-status-codes\";\nimport { JwtPayload } from \"jsonwebtoken\";\nimport { envVars } from \"../../config/env\";\nimport AppError from \"../../errorHelpers/AppError\";\nimport { QueryBuilder } from \"../../utils/QueryBuilder\";\nimport { userSearchableFields } from \"./user.constant\";\nimport { IAuthProvider, IUser, Role } from \"./user.interface\";\nimport { User } from \"./user.model\";\n\nconst createUser = async (payload: Partial<IUser>) => {\n const { email, password, ...rest } = payload;\n\n const isUserExist = await User.findOne({ email });\n if (isUserExist) {\n throw new AppError(httpStatus.BAD_REQUEST, \"User Already Exist\");\n }\n\n const hashedPassword = await bcryptjs.hash(\n password as string,\n Number(envVars.BCRYPT_SALT_ROUND)\n );\n\n const authProvider: IAuthProvider = { provider: \"credentials\", providerId: email as string };\n\n const user = await User.create({\n email,\n password: hashedPassword,\n auths: [authProvider],\n ...rest,\n });\n\n return user;\n};\n\nconst updateUser = async (userId: string, payload: Partial<IUser>, decodedToken: JwtPayload) => {\n if (decodedToken.role === Role.USER || decodedToken.role === Role.GUIDE) {\n if (userId !== decodedToken.userId) {\n throw new AppError(401, \"You are not authorized\");\n }\n }\n\n const ifUserExist = await User.findById(userId);\n if (!ifUserExist) {\n throw new AppError(httpStatus.NOT_FOUND, \"User Not Found\");\n }\n\n if (decodedToken.role === Role.ADMIN && ifUserExist.role === Role.SUPER_ADMIN) {\n throw new AppError(401, \"You are not authorized\");\n }\n\n if (payload.role) {\n if (decodedToken.role === Role.USER || decodedToken.role === Role.GUIDE) {\n throw new AppError(httpStatus.FORBIDDEN, \"You are not authorized\");\n }\n }\n\n if (payload.isActive || payload.isDeleted || payload.isVerified) {\n if (decodedToken.role === Role.USER || decodedToken.role === Role.GUIDE) {\n throw new AppError(httpStatus.FORBIDDEN, \"You are not authorized\");\n }\n }\n\n const newUpdatedUser = await User.findByIdAndUpdate(userId, payload, {\n new: true,\n runValidators: true,\n });\n\n return newUpdatedUser;\n};\n\nconst getAllUsers = async (query: Record<string, string>) => {\n const queryBuilder = new QueryBuilder(User.find(), query);\n\n const usersData = queryBuilder\n .filter()\n .search(userSearchableFields)\n .sort()\n .fields()\n .paginate();\n\n const [data, meta] = await Promise.all([usersData.build(), queryBuilder.getMeta()]);\n\n return { data, meta };\n};\n\nconst getSingleUser = async (id: string) => {\n const user = await User.findById(id).select(\"-password\");\n return { data: user };\n};\n\nconst getMe = async (userId: string) => {\n const user = await User.findById(userId).select(\"-password\");\n return { data: user };\n};\n\nexport const UserServices = {\n createUser,\n getAllUsers,\n getSingleUser,\n updateUser,\n getMe,\n};\n";
13
+ readonly controller: "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { NextFunction, Request, Response } from \"express\";\nimport httpStatus from \"http-status-codes\";\nimport { JwtPayload } from \"jsonwebtoken\";\nimport { catchAsync } from \"../../utils/catchAsync\";\nimport { sendResponse } from \"../../utils/sendResponse\";\nimport { UserServices } from \"./user.service\";\n\nconst createUser = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const user = await UserServices.createUser(req.body);\n\n sendResponse(res, {\n success: true,\n statusCode: httpStatus.CREATED,\n message: \"User Created Successfully\",\n data: user,\n });\n});\n\nconst updateUser = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const userId = req.params.id;\n const verifiedToken = req.user;\n const payload = req.body;\n\n const user = await UserServices.updateUser(userId, payload, verifiedToken as JwtPayload);\n\n sendResponse(res, {\n success: true,\n statusCode: httpStatus.CREATED,\n message: \"User Updated Successfully\",\n data: user,\n });\n});\n\nconst getAllUsers = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const query = req.query;\n const result = await UserServices.getAllUsers(query as Record<string, string>);\n\n sendResponse(res, {\n success: true,\n statusCode: httpStatus.CREATED,\n message: \"All Users Retrieved Successfully\",\n data: result.data,\n meta: result.meta,\n });\n});\n\nconst getMe = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const decodedToken = req.user as JwtPayload;\n const result = await UserServices.getMe(decodedToken.userId);\n\n sendResponse(res, {\n success: true,\n statusCode: httpStatus.CREATED,\n message: \"Your profile Retrieved Successfully\",\n data: result.data,\n });\n});\n\nconst getSingleUser = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const id = req.params.id;\n const result = await UserServices.getSingleUser(id);\n\n sendResponse(res, {\n success: true,\n statusCode: httpStatus.CREATED,\n message: \"User Retrieved Successfully\",\n data: result.data,\n });\n});\n\nexport const UserControllers = {\n createUser,\n getAllUsers,\n getSingleUser,\n updateUser,\n getMe,\n};\n";
14
+ readonly validation: "import z from \"zod\";\nimport { IsActive, Role } from \"./user.interface\";\n\nexport const createUserZodSchema = z.object({\n name: z.string({ invalid_type_error: \"Name must be string\" }).min(2).max(50),\n email: z.string({ invalid_type_error: \"Email must be string\" }).email().min(5).max(100),\n password: z\n .string({ invalid_type_error: \"Password must be string\" })\n .min(8)\n .regex(/^(?=.*[A-Z])/, { message: \"Password must contain at least 1 uppercase letter.\" })\n .regex(/^(?=.*[!@#$%^&*])/, { message: \"Password must contain at least 1 special character.\" })\n .regex(/^(?=.*\\d)/, { message: \"Password must contain at least 1 number.\" }),\n phone: z\n .string({ invalid_type_error: \"Phone Number must be string\" })\n .regex(/^(?:\\+8801\\d{9}|01\\d{9})$/, {\n message: \"Phone number must be valid for Bangladesh. Format: +8801XXXXXXXXX or 01XXXXXXXXX\",\n })\n .optional(),\n address: z.string({ invalid_type_error: \"Address must be string\" }).max(200).optional(),\n});\n\nexport const updateUserZodSchema = z.object({\n name: z.string({ invalid_type_error: \"Name must be string\" }).min(2).max(50).optional(),\n phone: z\n .string({ invalid_type_error: \"Phone Number must be string\" })\n .regex(/^(?:\\+8801\\d{9}|01\\d{9})$/, {\n message: \"Phone number must be valid for Bangladesh. Format: +8801XXXXXXXXX or 01XXXXXXXXX\",\n })\n .optional(),\n role: z.enum(Object.values(Role) as [string]).optional(),\n isActive: z.enum(Object.values(IsActive) as [string]).optional(),\n isDeleted: z.boolean({ invalid_type_error: \"isDeleted must be true or false\" }).optional(),\n isVerified: z.boolean({ invalid_type_error: \"isVerified must be true or false\" }).optional(),\n address: z.string({ invalid_type_error: \"Address must be string\" }).max(200).optional(),\n});\n";
15
+ readonly route: "import { Router } from \"express\";\nimport { checkAuth } from \"../../middlewares/checkAuth\";\nimport { validateRequest } from \"../../middlewares/validateRequest\";\nimport { UserControllers } from \"./user.controller\";\nimport { Role } from \"./user.interface\";\nimport { updateUserZodSchema } from \"./user.validation\";\n\nconst router = Router();\n\nrouter.post(\"/register\", UserControllers.createUser);\n\nrouter.get(\"/all-users\", checkAuth(Role.ADMIN, Role.SUPER_ADMIN), UserControllers.getAllUsers);\nrouter.get(\"/me\", checkAuth(...Object.values(Role)), UserControllers.getMe);\nrouter.get(\"/:id\", checkAuth(Role.ADMIN, Role.SUPER_ADMIN), UserControllers.getSingleUser);\n\nrouter.patch(\n \"/:id\",\n validateRequest(updateUserZodSchema),\n checkAuth(...Object.values(Role)),\n UserControllers.updateUser\n);\n\nexport const UserRoutes = router;\n";
16
+ };
17
+ readonly auth: {
18
+ readonly service: "/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-non-null-assertion */\nimport bcryptjs from \"bcryptjs\";\nimport httpStatus from \"http-status-codes\";\nimport jwt, { JwtPayload } from \"jsonwebtoken\";\nimport { envVars } from \"../../config/env\";\nimport AppError from \"../../errorHelpers/AppError\";\nimport { sendEmail } from \"../../utils/sendEmail\";\nimport { createNewAccessTokenWithRefreshToken } from \"../../utils/userTokens\";\nimport { IAuthProvider, IsActive } from \"../user/user.interface\";\nimport { User } from \"../user/user.model\";\n\nconst getNewAccessToken = async (refreshToken: string) => {\n const newAccessToken = await createNewAccessTokenWithRefreshToken(refreshToken);\n return { accessToken: newAccessToken };\n};\n\nconst resetPassword = async (payload: Record<string, any>, decodedToken: JwtPayload) => {\n if (payload.id != decodedToken.userId) {\n throw new AppError(401, \"You can not reset your password\");\n }\n\n const isUserExist = await User.findById(decodedToken.userId);\n if (!isUserExist) {\n throw new AppError(401, \"User does not exist\");\n }\n\n const hashedPassword = await bcryptjs.hash(payload.newPassword, Number(envVars.BCRYPT_SALT_ROUND));\n isUserExist.password = hashedPassword;\n\n await isUserExist.save();\n};\n\nconst forgotPassword = async (email: string) => {\n const isUserExist = await User.findOne({ email });\n\n if (!isUserExist) throw new AppError(httpStatus.BAD_REQUEST, \"User does not exist\");\n if (!isUserExist.isVerified) throw new AppError(httpStatus.BAD_REQUEST, \"User is not verified\");\n if (isUserExist.isActive === IsActive.BLOCKED || isUserExist.isActive === IsActive.INACTIVE) {\n throw new AppError(httpStatus.BAD_REQUEST, `User is ${isUserExist.isActive}`);\n }\n if (isUserExist.isDeleted) throw new AppError(httpStatus.BAD_REQUEST, \"User is deleted\");\n\n const jwtPayload = { userId: isUserExist._id, email: isUserExist.email, role: isUserExist.role };\n\n const resetToken = jwt.sign(jwtPayload, envVars.JWT_ACCESS_SECRET, { expiresIn: \"10m\" });\n\n const resetUILink = `${envVars.FRONTEND_URL}/reset-password?id=${isUserExist._id}&token=${resetToken}`;\n\n sendEmail({\n to: isUserExist.email,\n subject: \"Password Reset\",\n templateName: \"forgetPassword\",\n templateData: { name: isUserExist.name, resetUILink },\n });\n};\n\nconst setPassword = async (userId: string, plainPassword: string) => {\n const user = await User.findById(userId);\n if (!user) throw new AppError(404, \"User not found\");\n\n if (user.password && user.auths.some((p) => p.provider === \"google\")) {\n throw new AppError(\n httpStatus.BAD_REQUEST,\n \"You have already set you password. Now you can change the password from your profile password update\"\n );\n }\n\n const hashedPassword = await bcryptjs.hash(plainPassword, Number(envVars.BCRYPT_SALT_ROUND));\n\n const credentialProvider: IAuthProvider = { provider: \"credentials\", providerId: user.email };\n const auths: IAuthProvider[] = [...user.auths, credentialProvider];\n\n user.password = hashedPassword;\n user.auths = auths;\n\n await user.save();\n};\n\nconst changePassword = async (oldPassword: string, newPassword: string, decodedToken: JwtPayload) => {\n const user = await User.findById(decodedToken.userId);\n\n const isOldPasswordMatch = await bcryptjs.compare(oldPassword, user!.password as string);\n if (!isOldPasswordMatch) throw new AppError(httpStatus.UNAUTHORIZED, \"Old Password does not match\");\n\n user!.password = await bcryptjs.hash(newPassword, Number(envVars.BCRYPT_SALT_ROUND));\n user!.save();\n};\n\nexport const AuthServices = {\n getNewAccessToken,\n changePassword,\n setPassword,\n forgotPassword,\n resetPassword,\n};\n";
19
+ readonly controller: "/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { NextFunction, Request, Response } from \"express\";\nimport httpStatus from \"http-status-codes\";\nimport { JwtPayload } from \"jsonwebtoken\";\nimport passport from \"passport\";\nimport { envVars } from \"../../config/env\";\nimport AppError from \"../../errorHelpers/AppError\";\nimport { catchAsync } from \"../../utils/catchAsync\";\nimport { sendResponse } from \"../../utils/sendResponse\";\nimport { setAuthCookie } from \"../../utils/setCookie\";\nimport { createUserTokens } from \"../../utils/userTokens\";\nimport { AuthServices } from \"./auth.service\";\n\nconst credentialsLogin = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n passport.authenticate(\"local\", async (err: any, user: any, info: any) => {\n if (err) return next(new AppError(401, err));\n if (!user) return next(new AppError(401, info.message));\n\n const userTokens = await createUserTokens(user);\n const { password: pass, ...rest } = user.toObject();\n\n setAuthCookie(res, userTokens);\n\n sendResponse(res, {\n success: true,\n statusCode: httpStatus.OK,\n message: \"User Logged In Successfully\",\n data: { accessToken: userTokens.accessToken, refreshToken: userTokens.refreshToken, user: rest },\n });\n })(req, res, next);\n});\n\nconst getNewAccessToken = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const refreshToken = req.cookies.refreshToken;\n if (!refreshToken) throw new AppError(httpStatus.BAD_REQUEST, \"No refresh token recieved from cookies\");\n\n const tokenInfo = await AuthServices.getNewAccessToken(refreshToken as string);\n setAuthCookie(res, tokenInfo);\n\n sendResponse(res, {\n success: true,\n statusCode: httpStatus.OK,\n message: \"New Access Token Retrived Successfully\",\n data: tokenInfo,\n });\n});\n\nconst logout = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n res.clearCookie(\"accessToken\", { httpOnly: true, secure: false, sameSite: \"lax\" });\n res.clearCookie(\"refreshToken\", { httpOnly: true, secure: false, sameSite: \"lax\" });\n\n sendResponse(res, { success: true, statusCode: httpStatus.OK, message: \"User Logged Out Successfully\", data: null });\n});\n\nconst changePassword = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const newPassword = req.body.newPassword;\n const oldPassword = req.body.oldPassword;\n const decodedToken = req.user;\n\n await AuthServices.changePassword(oldPassword, newPassword, decodedToken as JwtPayload);\n\n sendResponse(res, { success: true, statusCode: httpStatus.OK, message: \"Password Changed Successfully\", data: null });\n});\n\nconst resetPassword = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const decodedToken = req.user;\n\n await AuthServices.resetPassword(req.body, decodedToken as JwtPayload);\n\n sendResponse(res, { success: true, statusCode: httpStatus.OK, message: \"Password Changed Successfully\", data: null });\n});\n\nconst setPassword = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const decodedToken = req.user as JwtPayload;\n const { password } = req.body;\n\n await AuthServices.setPassword(decodedToken.userId, password);\n\n sendResponse(res, { success: true, statusCode: httpStatus.OK, message: \"Password Changed Successfully\", data: null });\n});\n\nconst forgotPassword = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n const { email } = req.body;\n\n await AuthServices.forgotPassword(email);\n\n sendResponse(res, { success: true, statusCode: httpStatus.OK, message: \"Email Sent Successfully\", data: null });\n});\n\nconst googleCallbackController = catchAsync(async (req: Request, res: Response, next: NextFunction) => {\n let redirectTo = req.query.state ? (req.query.state as string) : \"\";\n\n if (redirectTo.startsWith(\"/\")) redirectTo = redirectTo.slice(1);\n\n const user = req.user;\n if (!user) throw new AppError(httpStatus.NOT_FOUND, \"User Not Found\");\n\n const tokenInfo = createUserTokens(user);\n\n setAuthCookie(res, tokenInfo);\n\n res.redirect(`${envVars.FRONTEND_URL}/${redirectTo}`);\n});\n\nexport const AuthControllers = {\n credentialsLogin,\n getNewAccessToken,\n logout,\n resetPassword,\n setPassword,\n forgotPassword,\n changePassword,\n googleCallbackController,\n};\n";
20
+ readonly route: "import { NextFunction, Request, Response, Router } from \"express\";\nimport passport from \"passport\";\nimport { envVars } from \"../../config/env\";\nimport { checkAuth } from \"../../middlewares/checkAuth\";\nimport { Role } from \"../user/user.interface\";\nimport { AuthControllers } from \"./auth.controller\";\n\nconst router = Router();\n\nrouter.post(\"/login\", AuthControllers.credentialsLogin);\nrouter.post(\"/refresh-token\", AuthControllers.getNewAccessToken);\nrouter.post(\"/logout\", AuthControllers.logout);\n\nrouter.post(\"/change-password\", checkAuth(...Object.values(Role)), AuthControllers.changePassword);\nrouter.post(\"/set-password\", checkAuth(...Object.values(Role)), AuthControllers.setPassword);\n\nrouter.post(\"/forgot-password\", AuthControllers.forgotPassword);\nrouter.post(\"/reset-password\", checkAuth(...Object.values(Role)), AuthControllers.resetPassword);\n\nrouter.get(\"/google\", async (req: Request, res: Response, next: NextFunction) => {\n const redirect = req.query.redirect || \"/\";\n passport.authenticate(\"google\", { scope: [\"profile\", \"email\"], state: redirect as string })(req, res, next);\n});\n\nrouter.get(\n \"/google/callback\",\n passport.authenticate(\"google\", {\n failureRedirect: `${envVars.FRONTEND_URL}/login?error=There is some issues with your account. Please contact with out support team!`,\n }),\n AuthControllers.googleCallbackController\n);\n\nexport const AuthRoutes = router;\n";
21
+ };
22
+ };