crypt-express-app 1.3.20
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 +329 -0
- package/dist/generate-module.js +658 -0
- package/dist/index.js +121 -0
- package/dist/scripts/generate-app.js +198 -0
- package/dist/scripts/generate-locales.js +25 -0
- package/dist/scripts/generate-middleware.js +167 -0
- package/dist/scripts/generate-module.js +739 -0
- package/dist/scripts/generate-root-files.js +759 -0
- package/dist/scripts/generate-utils.js +1002 -0
- package/dist/scripts/middleware.js +165 -0
- package/dist/scripts/setup-prisma.js +25 -0
- package/package.json +28 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import * as readline from "readline";
|
|
6
|
+
import { generateApp } from "./scripts/generate-app.js";
|
|
7
|
+
import { generateLocales } from "./scripts/generate-locales.js";
|
|
8
|
+
import { generateMiddlewares } from "./scripts/generate-middleware.js";
|
|
9
|
+
import { generateRootFiles } from "./scripts/generate-root-files.js";
|
|
10
|
+
import { generateModule } from "./scripts/generate-module.js";
|
|
11
|
+
import { generateUtils } from "./scripts/generate-utils.js";
|
|
12
|
+
import { generatePrismaClient } from "./scripts/setup-prisma.js";
|
|
13
|
+
/* ===================== CLI SETUP ===================== */
|
|
14
|
+
const rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
});
|
|
18
|
+
function ask(question) {
|
|
19
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
20
|
+
}
|
|
21
|
+
/* ===================== MAIN ===================== */
|
|
22
|
+
(async function main() {
|
|
23
|
+
let inputName = process.argv[2];
|
|
24
|
+
if (process.argv.length > 3) {
|
|
25
|
+
console.error("❌ Only one project name allowed");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (!inputName) {
|
|
29
|
+
inputName = await ask("Enter project name: ");
|
|
30
|
+
}
|
|
31
|
+
inputName = inputName.trim();
|
|
32
|
+
if (!inputName) {
|
|
33
|
+
console.error("❌ Project name required");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const useCurrentDir = inputName === ".";
|
|
37
|
+
// normalize only if not "."
|
|
38
|
+
const normalizedName = useCurrentDir
|
|
39
|
+
? path.basename(process.cwd())
|
|
40
|
+
: inputName
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.trim()
|
|
43
|
+
.replace(/\s+/g, "-") // spaces → dash
|
|
44
|
+
.replace(/[^a-z0-9-]/g, ""); // remove invalid chars
|
|
45
|
+
if (!normalizedName) {
|
|
46
|
+
console.error("❌ Invalid project name");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const projectDir = useCurrentDir
|
|
50
|
+
? process.cwd()
|
|
51
|
+
: path.join(process.cwd(), normalizedName);
|
|
52
|
+
if (!useCurrentDir) {
|
|
53
|
+
if (fs.existsSync(projectDir)) {
|
|
54
|
+
console.error("❌ Directory already exists");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const files = fs.readdirSync(projectDir).filter((f) => !f.startsWith("."));
|
|
61
|
+
if (files.length > 0) {
|
|
62
|
+
console.error("❌ Current directory must be empty");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
rl.close();
|
|
67
|
+
process.env.PROJECT_NAME = normalizedName;
|
|
68
|
+
console.log(`📦 Creating project: ${normalizedName}`);
|
|
69
|
+
generateRootFiles(projectDir, normalizedName);
|
|
70
|
+
generateApp(projectDir);
|
|
71
|
+
generateMiddlewares(projectDir);
|
|
72
|
+
generateLocales(projectDir);
|
|
73
|
+
generateModule(projectDir, "common");
|
|
74
|
+
generateUtils(projectDir);
|
|
75
|
+
generatePrismaClient(projectDir);
|
|
76
|
+
console.log("📦 Installing dependencies...");
|
|
77
|
+
try {
|
|
78
|
+
execSync("pnpm install", {
|
|
79
|
+
cwd: projectDir,
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
});
|
|
82
|
+
console.log("✅ Dependencies installed");
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error("❌ pnpm install failed");
|
|
86
|
+
console.error(err);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
execSync("npx prisma generate", {
|
|
90
|
+
cwd: projectDir,
|
|
91
|
+
stdio: "inherit",
|
|
92
|
+
});
|
|
93
|
+
console.log("✅ Prisma client generated");
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.error("❌ Prisma client generation failed");
|
|
97
|
+
console.error(err);
|
|
98
|
+
}
|
|
99
|
+
// try {
|
|
100
|
+
// execSync("docker compose up -d redis postgres", {
|
|
101
|
+
// cwd: projectDir,
|
|
102
|
+
// stdio: "inherit",
|
|
103
|
+
// });
|
|
104
|
+
// console.log("✅ Redis and Postgres started");
|
|
105
|
+
// } catch (err) {
|
|
106
|
+
// console.error("❌ Failed to start Redis and Postgres");
|
|
107
|
+
// console.error(err);
|
|
108
|
+
// }
|
|
109
|
+
// try {
|
|
110
|
+
// execSync("npx prisma db push", {
|
|
111
|
+
// cwd: projectDir,
|
|
112
|
+
// stdio: "inherit",
|
|
113
|
+
// });
|
|
114
|
+
// console.log("✅ Prisma client generated");
|
|
115
|
+
// } catch (err) {
|
|
116
|
+
// console.error("❌ Prisma client generation failed");
|
|
117
|
+
// console.error(err);
|
|
118
|
+
// }
|
|
119
|
+
console.log("✅ Project generated");
|
|
120
|
+
})();
|
|
121
|
+
/* ===================== CONFIG GENERATION ===================== */
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export function generateApp(projectDir) {
|
|
4
|
+
const basePath = projectDir;
|
|
5
|
+
const srcPath = path.join(basePath, "src");
|
|
6
|
+
// ensure src directory exists
|
|
7
|
+
if (!fs.existsSync(srcPath)) {
|
|
8
|
+
fs.mkdirSync(srcPath, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
/* ================= app.ts ================= */
|
|
11
|
+
const routeContent = `
|
|
12
|
+
import express, { Router } from 'express';
|
|
13
|
+
import swaggerUi from "swagger-ui-express";
|
|
14
|
+
import { swaggerSpec } from "./utils/swagger";
|
|
15
|
+
import redis from './utils/redis';
|
|
16
|
+
import prisma from './prisma/client';
|
|
17
|
+
import commonRoutes from "./modules/common/common.routes";
|
|
18
|
+
|
|
19
|
+
const routers: Router = express.Router();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @openapi
|
|
23
|
+
* tags:
|
|
24
|
+
* - name: Root
|
|
25
|
+
* description: Root route and labeling operations
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
routers.get("/", (req, res) => {
|
|
29
|
+
res.send(req.t("WELCOME"));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @openapi
|
|
34
|
+
* /health/check:
|
|
35
|
+
* get:
|
|
36
|
+
* tags: [Root]
|
|
37
|
+
* summary: Health check endpoint
|
|
38
|
+
* description: Returns the status of the app, Redis, and Supabase function.
|
|
39
|
+
* responses:
|
|
40
|
+
* 200:
|
|
41
|
+
* description: App health status
|
|
42
|
+
* content:
|
|
43
|
+
* application/json:
|
|
44
|
+
* schema:
|
|
45
|
+
* type: object
|
|
46
|
+
* properties:
|
|
47
|
+
* success:
|
|
48
|
+
* type: boolean
|
|
49
|
+
* example: true
|
|
50
|
+
* status:
|
|
51
|
+
* type: number
|
|
52
|
+
* example: 200
|
|
53
|
+
* message:
|
|
54
|
+
* type: string
|
|
55
|
+
* example: App health check
|
|
56
|
+
* services:
|
|
57
|
+
* type: object
|
|
58
|
+
* properties:
|
|
59
|
+
* app:
|
|
60
|
+
* type: string
|
|
61
|
+
* example: ok
|
|
62
|
+
* redis:
|
|
63
|
+
* type: string
|
|
64
|
+
* example: ok
|
|
65
|
+
* dbStatus:
|
|
66
|
+
* type: string
|
|
67
|
+
* example: ok
|
|
68
|
+
* time:
|
|
69
|
+
* type: string
|
|
70
|
+
* format: date-time
|
|
71
|
+
* example: 2026-03-11T10:21:00.000Z
|
|
72
|
+
*/
|
|
73
|
+
routers.get("/health/check", async (_req, res) => {
|
|
74
|
+
let redisStatus = "ok";
|
|
75
|
+
let dbStatus = "ok";
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await redis.ping();
|
|
79
|
+
} catch {
|
|
80
|
+
redisStatus = "failed";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await prisma.$queryRaw\`SELECT 1\`;
|
|
85
|
+
} catch {
|
|
86
|
+
dbStatus = "failed";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
res.json({
|
|
90
|
+
success: true,
|
|
91
|
+
status: 200,
|
|
92
|
+
message: "App health check",
|
|
93
|
+
services: {
|
|
94
|
+
app: "ok",
|
|
95
|
+
redis: redisStatus,
|
|
96
|
+
db: dbStatus,
|
|
97
|
+
},
|
|
98
|
+
time: new Date().toISOString(),
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
routers.use(
|
|
103
|
+
"/api/docs",
|
|
104
|
+
swaggerUi.serve,
|
|
105
|
+
swaggerUi.setup(swaggerSpec, {
|
|
106
|
+
swaggerOptions: {
|
|
107
|
+
persistAuthorization: true,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
routers.use("/api/common", commonRoutes);
|
|
112
|
+
|
|
113
|
+
export default routers;
|
|
114
|
+
`;
|
|
115
|
+
fs.writeFileSync(path.join(srcPath, "routes.ts"), routeContent.trim());
|
|
116
|
+
/* ================= app.ts ================= */
|
|
117
|
+
const appContent = `
|
|
118
|
+
import express, { type Express } from "express";
|
|
119
|
+
import cors from "cors";
|
|
120
|
+
import morgan from "morgan";
|
|
121
|
+
import { json, urlencoded } from "express";
|
|
122
|
+
import i18next from "i18next";
|
|
123
|
+
import Backend from "i18next-fs-backend";
|
|
124
|
+
import middleware from "i18next-http-middleware";
|
|
125
|
+
import path from "path";
|
|
126
|
+
import { errorMiddleware } from "./middlewares/error.middleware";
|
|
127
|
+
import { loggerMiddleware } from "./middlewares/logger.middleware";
|
|
128
|
+
import swaggerUi from "swagger-ui-express";
|
|
129
|
+
import { swaggerSpec } from "./utils/swagger";
|
|
130
|
+
import routes from "./routes";
|
|
131
|
+
|
|
132
|
+
export const app: Express = express();
|
|
133
|
+
|
|
134
|
+
i18next
|
|
135
|
+
.use(Backend)
|
|
136
|
+
.use(middleware.LanguageDetector)
|
|
137
|
+
.init({
|
|
138
|
+
fallbackLng: "en",
|
|
139
|
+
preload: ["en", "bn"],
|
|
140
|
+
backend: {
|
|
141
|
+
loadPath: path.join(__dirname, "locales/{{lng}}/translation.json"),
|
|
142
|
+
},
|
|
143
|
+
detection: {
|
|
144
|
+
order: ["querystring", "cookie", "header"],
|
|
145
|
+
caches: ["cookie"],
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
app.use(cors());
|
|
150
|
+
app.use(json());
|
|
151
|
+
app.use(urlencoded({ extended: true }));
|
|
152
|
+
app.use(morgan("dev"));
|
|
153
|
+
app.use(middleware.handle(i18next));
|
|
154
|
+
app.use(loggerMiddleware);
|
|
155
|
+
app.use(routes);
|
|
156
|
+
app.use(errorMiddleware);
|
|
157
|
+
|
|
158
|
+
export default app;
|
|
159
|
+
`;
|
|
160
|
+
fs.writeFileSync(path.join(srcPath, "app.ts"), appContent.trim());
|
|
161
|
+
/* ================= server.ts ================= */
|
|
162
|
+
const serverContent = `
|
|
163
|
+
import 'dotenv/config'
|
|
164
|
+
import app from "./src/app";
|
|
165
|
+
import bootstrapSystem from "./src/utils/bootstrap";
|
|
166
|
+
import { ConnectIamRedis } from "./src/utils/iam.redis";
|
|
167
|
+
|
|
168
|
+
const port = process.env.PORT ?? 3000
|
|
169
|
+
|
|
170
|
+
async function startServer() {
|
|
171
|
+
try {
|
|
172
|
+
await bootstrapSystem();
|
|
173
|
+
await ConnectIamRedis();
|
|
174
|
+
|
|
175
|
+
app.listen(port, () => {
|
|
176
|
+
console.log(\`🚀 Auth running at http://localhost:5000\`);
|
|
177
|
+
console.log(\`📘 Auth swagger docs at http://localhost:5000/api/docs\`);
|
|
178
|
+
console.log(\`🚀 Server running at http://localhost:\${port}\`);
|
|
179
|
+
console.log(\`📘 Swagger docs at http://localhost:\${port}/api/docs\`);
|
|
180
|
+
});
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error("Error.........", error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
process.on("uncaughtException", (err) => {
|
|
187
|
+
console.error("🔥 Uncaught Exception:", err);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
process.on("unhandledRejection", (reason) => {
|
|
191
|
+
console.error("🔥 Unhandled Rejection:", reason);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
startServer();
|
|
195
|
+
`;
|
|
196
|
+
fs.writeFileSync(path.join(basePath, "server.ts"), serverContent.trim());
|
|
197
|
+
console.log("✅ App files generated successfully inside src");
|
|
198
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export function generateLocales(projectDir) {
|
|
4
|
+
const localesPath = path.join(projectDir, "src", "locales");
|
|
5
|
+
const bnPath = path.join(localesPath, "bn");
|
|
6
|
+
const enPath = path.join(localesPath, "en");
|
|
7
|
+
// create directories if not exist
|
|
8
|
+
[localesPath, bnPath, enPath].forEach((dir) => {
|
|
9
|
+
if (!fs.existsSync(dir)) {
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
// translations
|
|
14
|
+
const bnContent = {
|
|
15
|
+
WELCOME: "আমাদের এপিতে স্বাগতম",
|
|
16
|
+
USER_NOT_FOUND: "ব্যবহারকারী পাওয়া যায়নি",
|
|
17
|
+
};
|
|
18
|
+
const enContent = {
|
|
19
|
+
WELCOME: "Welcome to our API",
|
|
20
|
+
USER_NOT_FOUND: "User not found",
|
|
21
|
+
};
|
|
22
|
+
fs.writeFileSync(path.join(bnPath, "translation.json"), JSON.stringify(bnContent, null, 2));
|
|
23
|
+
fs.writeFileSync(path.join(enPath, "translation.json"), JSON.stringify(enContent, null, 2));
|
|
24
|
+
console.log("Locales generated successfully inside src/locales");
|
|
25
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
export function generateMiddlewares(projectDir) {
|
|
4
|
+
const basePath = path.join(projectDir, "src", "middlewares");
|
|
5
|
+
if (!fs.existsSync(basePath)) {
|
|
6
|
+
fs.mkdirSync(basePath, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
/* ================= AUTHORIZATION ================= */
|
|
9
|
+
const authorizationContent = `
|
|
10
|
+
import { Request, Response, NextFunction } from "express";
|
|
11
|
+
import { TypeResource, TypeAction, Resource } from "../utils/consts";
|
|
12
|
+
import iamCacheService from "../utils/iam.redis";
|
|
13
|
+
|
|
14
|
+
export function authorizationMiddleware(authorization: {
|
|
15
|
+
resource: Resource;
|
|
16
|
+
resourceType: TypeResource;
|
|
17
|
+
actions: TypeAction[];
|
|
18
|
+
}) {
|
|
19
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
20
|
+
try {
|
|
21
|
+
const userId = req?.user?.userId;
|
|
22
|
+
const isMasterRealmUser = req?.user?.isMasterRealmUser;
|
|
23
|
+
if (!userId) {
|
|
24
|
+
return res.status(401).json({ message: "Unauthorized" });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cached = await iamCacheService.get(\`user:\${userId}:permissions\`);
|
|
28
|
+
if (!cached) {
|
|
29
|
+
return res.status(403).json({ message: "Permissions not loaded" });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const profile = JSON.parse(cached);
|
|
33
|
+
const permissions: string[] = profile.permissions || [];
|
|
34
|
+
|
|
35
|
+
// check permission
|
|
36
|
+
const allowed = permissions.some((perm) => {
|
|
37
|
+
const [clientId, resName, action, type] = perm.split(":");
|
|
38
|
+
return (
|
|
39
|
+
resName === authorization.resource &&
|
|
40
|
+
type === authorization.resourceType &&
|
|
41
|
+
authorization.actions.includes(action as TypeAction)
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!allowed) {
|
|
46
|
+
return res.status(403).json({ message: "Access denied" });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
next();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(error);
|
|
52
|
+
return res.status(500).json({ message: "Authorization error" });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
fs.writeFileSync(path.join(basePath, "authorization.middleware.ts"), authorizationContent.trim());
|
|
58
|
+
/* ================= AUTH ================= */
|
|
59
|
+
const authContent = `
|
|
60
|
+
import { Request, Response, NextFunction } from "express";
|
|
61
|
+
import jwt from "jsonwebtoken";
|
|
62
|
+
import { iamCacheService } from "../utils/iam.redis";
|
|
63
|
+
|
|
64
|
+
interface JwtPayload {
|
|
65
|
+
realmId: string;
|
|
66
|
+
sessionId: string;
|
|
67
|
+
userId: string;
|
|
68
|
+
email: string;
|
|
69
|
+
clientIdInternal: string;
|
|
70
|
+
isMasterRealmUser: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare global {
|
|
74
|
+
namespace Express {
|
|
75
|
+
interface Request {
|
|
76
|
+
user?: JwtPayload;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @description Middleware to protect routes and verify JWT token
|
|
83
|
+
*/
|
|
84
|
+
export const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
|
|
85
|
+
const authHeader = req.headers["authorization"];
|
|
86
|
+
if (!authHeader) return res.status(401).json({ message: "Authorization header missing" });
|
|
87
|
+
|
|
88
|
+
const token = authHeader.split(" ")[1]; // Bearer <token>
|
|
89
|
+
if (!token) return res.status(401).json({ message: "Token missing" });
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const decodedPayload = jwt.decode(token) as JwtPayload | null;
|
|
93
|
+
if (!decodedPayload || !decodedPayload.realmId) {
|
|
94
|
+
return res.status(401).json({ message: "Invalid token: realmId missing" });
|
|
95
|
+
}
|
|
96
|
+
const realmId = decodedPayload.realmId as string;
|
|
97
|
+
|
|
98
|
+
// 2. Redis থেকে secret load করা
|
|
99
|
+
const secretKey = \`realm:\${realmId}:secret\`;
|
|
100
|
+
const secret = await iamCacheService.get(secretKey) as string;
|
|
101
|
+
if (!secret) return res.status(401).json({ message: "JWT secret not configured in Redis" });
|
|
102
|
+
|
|
103
|
+
// const secret = JWT_SECRET;
|
|
104
|
+
const decoded = jwt.verify(token, secret, { algorithms: ["HS256"] }) as JwtPayload;
|
|
105
|
+
|
|
106
|
+
const isBlacklisted = await iamCacheService.get(\`blacklist:\${token}\`);
|
|
107
|
+
if (isBlacklisted) {
|
|
108
|
+
return res.status(401).json({ message: "Token revoked" });
|
|
109
|
+
}
|
|
110
|
+
req.user = decoded; // attach payload to request
|
|
111
|
+
next();
|
|
112
|
+
} catch (err) {
|
|
113
|
+
return res.status(401).json({ message: "Invalid or expired token" });
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
`;
|
|
118
|
+
fs.writeFileSync(path.join(basePath, "auth.middleware.ts"), authContent.trim());
|
|
119
|
+
/* ================= ERROR ================= */
|
|
120
|
+
const errorContent = `
|
|
121
|
+
import { Request, Response, NextFunction } from "express";
|
|
122
|
+
|
|
123
|
+
export const errorMiddleware = (
|
|
124
|
+
err: any,
|
|
125
|
+
req: Request,
|
|
126
|
+
res: Response,
|
|
127
|
+
next: NextFunction
|
|
128
|
+
) => {
|
|
129
|
+
console.error(err);
|
|
130
|
+
|
|
131
|
+
const status = err.status || 500;
|
|
132
|
+
const message = err.message || "Internal Server Error";
|
|
133
|
+
|
|
134
|
+
res.status(status).json({
|
|
135
|
+
success: false,
|
|
136
|
+
status,
|
|
137
|
+
message,
|
|
138
|
+
...(process.env.NODE_ENV === "development" && { stack: err.stack }),
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
`;
|
|
142
|
+
fs.writeFileSync(path.join(basePath, "error.middleware.ts"), errorContent.trim());
|
|
143
|
+
/* ================= LOGGER ================= */
|
|
144
|
+
const loggerContent = `
|
|
145
|
+
import { Request, Response, NextFunction } from "express";
|
|
146
|
+
import logger from "../utils/logger";
|
|
147
|
+
|
|
148
|
+
export const loggerMiddleware = (
|
|
149
|
+
req: Request,
|
|
150
|
+
res: Response,
|
|
151
|
+
next: NextFunction
|
|
152
|
+
) => {
|
|
153
|
+
const start = Date.now();
|
|
154
|
+
|
|
155
|
+
res.on("finish", () => {
|
|
156
|
+
const duration = Date.now() - start;
|
|
157
|
+
logger.info(
|
|
158
|
+
\`[\${new Date().toISOString()}] \${req.method} \${req.originalUrl} \${res.statusCode} - \${duration}ms\`
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
next();
|
|
163
|
+
};
|
|
164
|
+
`;
|
|
165
|
+
fs.writeFileSync(path.join(basePath, "logger.middleware.ts"), loggerContent.trim());
|
|
166
|
+
console.log(`✅ Middleware files generated successfully at ${basePath}`);
|
|
167
|
+
}
|