create-node-prodkit 1.0.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 +1540 -0
- package/bin/create.js +108 -0
- package/package.json +48 -0
- package/templates/.env.example +28 -0
- package/templates/gitignore +6 -0
- package/templates/package.json +33 -0
- package/templates/src/app.js +21 -0
- package/templates/src/config/app.config.js +25 -0
- package/templates/src/controllers/sample.controller.js +11 -0
- package/templates/src/db/mongo.db.js +14 -0
- package/templates/src/db/mysql.db.js +17 -0
- package/templates/src/db/postgres.db.js +20 -0
- package/templates/src/middlewares/encrypt.middleware.js +4 -0
- package/templates/src/middlewares/error.middleware.js +23 -0
- package/templates/src/middlewares/ip.middleware.js +20 -0
- package/templates/src/middlewares/rateLimit.middleware.js +18 -0
- package/templates/src/models/sample.model.js +0 -0
- package/templates/src/repositories/sample.repository.js +10 -0
- package/templates/src/routes/index.route.js +8 -0
- package/templates/src/routes/sample1.route.js +8 -0
- package/templates/src/server.js +6 -0
- package/templates/src/services/sample.service.js +17 -0
- package/templates/src/utils/asyncHandler.js +5 -0
- package/templates/src/utils/axios.util.js +19 -0
- package/templates/src/utils/log.util.js +51 -0
- package/templates/src/utils/logSanitizer.util.js +50 -0
- package/templates/src/utils/response.util.js +43 -0
- package/templates/src/utils/sensitiveKeys.util.js +3 -0
- package/templates/src/validations/sample.validation.js +0 -0
package/bin/create.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
|
|
8
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
const green = (t) => `\x1b[32m${t}\x1b[0m`;
|
|
13
|
+
const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
|
|
14
|
+
const yellow = (t) => `\x1b[33m${t}\x1b[0m`;
|
|
15
|
+
const bold = (t) => `\x1b[1m${t}\x1b[0m`;
|
|
16
|
+
const dim = (t) => `\x1b[2m${t}\x1b[0m`;
|
|
17
|
+
|
|
18
|
+
// ─── Argument parsing ─────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const projectName = process.argv[2];
|
|
21
|
+
|
|
22
|
+
if (!projectName) {
|
|
23
|
+
console.error(`
|
|
24
|
+
${bold("Usage:")}
|
|
25
|
+
npx create-node-prodkit ${cyan("<project-name>")}
|
|
26
|
+
|
|
27
|
+
${bold("Example:")}
|
|
28
|
+
npx create-node-prodkit my-api
|
|
29
|
+
`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!/^[a-z0-9-_]+$/i.test(projectName)) {
|
|
34
|
+
console.error(`\n Project name "${projectName}" contains invalid characters.\n Use only letters, numbers, hyphens, and underscores.\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const targetDir = join(process.cwd(), projectName);
|
|
39
|
+
|
|
40
|
+
if (existsSync(targetDir)) {
|
|
41
|
+
console.error(`\n Directory "${projectName}" already exists. Choose a different name.\n`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── File copy ────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const templateDir = join(__dirname, "../templates");
|
|
48
|
+
|
|
49
|
+
// Files where {{PROJECT_NAME}} should be replaced
|
|
50
|
+
const PLACEHOLDER_FILES = new Set(["package.json"]);
|
|
51
|
+
|
|
52
|
+
function copyDir(src, dest) {
|
|
53
|
+
mkdirSync(dest, { recursive: true });
|
|
54
|
+
|
|
55
|
+
for (const entry of readdirSync(src)) {
|
|
56
|
+
const srcPath = join(src, entry);
|
|
57
|
+
const destPath = join(dest, entry);
|
|
58
|
+
|
|
59
|
+
if (statSync(srcPath).isDirectory()) {
|
|
60
|
+
copyDir(srcPath, destPath);
|
|
61
|
+
} else {
|
|
62
|
+
let content = readFileSync(srcPath, "utf8");
|
|
63
|
+
|
|
64
|
+
if (PLACEHOLDER_FILES.has(entry)) {
|
|
65
|
+
content = content.replaceAll("{{PROJECT_NAME}}", projectName);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Rename gitignore → .gitignore (npm strips dotfiles from tarballs)
|
|
69
|
+
const finalDest = entry === "gitignore"
|
|
70
|
+
? join(dest, ".gitignore")
|
|
71
|
+
: destPath;
|
|
72
|
+
|
|
73
|
+
writeFileSync(finalDest, content, "utf8");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ─── Bootstrap ────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
console.log(`
|
|
81
|
+
${bold("create-node-prodkit")} ${dim("v" + JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf8")).version)}
|
|
82
|
+
|
|
83
|
+
Scaffolding project ${cyan(bold(projectName))} ...
|
|
84
|
+
`);
|
|
85
|
+
|
|
86
|
+
copyDir(templateDir, targetDir);
|
|
87
|
+
console.log(` ${green("✓")} Files copied`);
|
|
88
|
+
|
|
89
|
+
console.log(` ${yellow("⟳")} Installing dependencies ...`);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
execSync("npm install", { cwd: targetDir, stdio: "inherit" });
|
|
93
|
+
} catch {
|
|
94
|
+
console.error("\n npm install failed. Run it manually inside the project.\n");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(`
|
|
99
|
+
${green(bold("✓ Done!"))} Your project is ready.
|
|
100
|
+
|
|
101
|
+
${dim("Next steps:")}
|
|
102
|
+
|
|
103
|
+
${cyan(`cd ${projectName}`)}
|
|
104
|
+
${cyan("cp .env.example .env")}
|
|
105
|
+
${cyan("npm run dev")}
|
|
106
|
+
|
|
107
|
+
${dim("Docs:")} README.md
|
|
108
|
+
`);
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-node-prodkit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-grade Node.js + Express skeleton CLI — scaffold a new project in seconds",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "vinay",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"create-node-prodkit": "./bin/create.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"templates",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "nodemon src/server.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"nodejs",
|
|
21
|
+
"express",
|
|
22
|
+
"scaffold",
|
|
23
|
+
"boilerplate",
|
|
24
|
+
"starter",
|
|
25
|
+
"cli",
|
|
26
|
+
"production"
|
|
27
|
+
],
|
|
28
|
+
"nodemonConfig": {
|
|
29
|
+
"watch": ["src"],
|
|
30
|
+
"ext": "js,json",
|
|
31
|
+
"ignore": ["logs/*"],
|
|
32
|
+
"exec": "node src/server.js"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"axios": "^1.13.5",
|
|
36
|
+
"dotenv": "^17.3.1",
|
|
37
|
+
"express": "^5.2.1",
|
|
38
|
+
"express-rate-limit": "^8.2.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"eslint": "^9.39.2",
|
|
42
|
+
"nodemon": "^3.1.13",
|
|
43
|
+
"prettier": "^3.8.1"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Server
|
|
2
|
+
PORT=3000
|
|
3
|
+
|
|
4
|
+
# API
|
|
5
|
+
API_TIMEOUT=5000
|
|
6
|
+
|
|
7
|
+
# Logging
|
|
8
|
+
# internal → writes to logs/ directory
|
|
9
|
+
# external → POSTs to LOG_SERVICE_URL
|
|
10
|
+
LOG_MODE=internal
|
|
11
|
+
LOG_SERVICE_URL=
|
|
12
|
+
|
|
13
|
+
# LOG_TYPE controls sensitive field handling in logs
|
|
14
|
+
# 1 = show values as-is (development)
|
|
15
|
+
# 2 = mask values (staging)
|
|
16
|
+
# 3 = remove keys entirely (production)
|
|
17
|
+
LOG_TYPE=1
|
|
18
|
+
|
|
19
|
+
# Database (uncomment and fill in what you need)
|
|
20
|
+
# MONGO_URI=mongodb://localhost:27017/mydb
|
|
21
|
+
# MYSQL_HOST=localhost
|
|
22
|
+
# MYSQL_USER=root
|
|
23
|
+
# MYSQL_PASSWORD=
|
|
24
|
+
# MYSQL_DATABASE=mydb
|
|
25
|
+
# PG_HOST=localhost
|
|
26
|
+
# PG_USER=postgres
|
|
27
|
+
# PG_PASSWORD=
|
|
28
|
+
# PG_DATABASE=mydb
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "src/server.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "nodemon src/server.js",
|
|
11
|
+
"start": "node src/server.js"
|
|
12
|
+
},
|
|
13
|
+
"nodemonConfig": {
|
|
14
|
+
"watch": ["src"],
|
|
15
|
+
"ext": "js,json",
|
|
16
|
+
"ignore": ["logs/*"],
|
|
17
|
+
"exec": "node src/server.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"axios": "^1.13.5",
|
|
21
|
+
"dotenv": "^17.3.1",
|
|
22
|
+
"express": "^5.2.1",
|
|
23
|
+
"express-rate-limit": "^8.2.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"eslint": "^9.39.2",
|
|
27
|
+
"nodemon": "^3.1.13",
|
|
28
|
+
"prettier": "^3.8.1"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import routes from "./routes/index.route.js";
|
|
3
|
+
import { errorMiddleware } from "./middlewares/error.middleware.js";
|
|
4
|
+
import { ipMiddleware } from "./middlewares/ip.middleware.js";
|
|
5
|
+
import { rateLimitMiddleware } from "./middlewares/rateLimit.middleware.js";
|
|
6
|
+
|
|
7
|
+
const app = express();
|
|
8
|
+
|
|
9
|
+
app.use(express.json());
|
|
10
|
+
app.use(rateLimitMiddleware);
|
|
11
|
+
app.use(ipMiddleware);
|
|
12
|
+
|
|
13
|
+
app.get("/", (req, res) => {
|
|
14
|
+
res.send("Welcome to the API!");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
app.use("/api/v1", routes);
|
|
18
|
+
|
|
19
|
+
app.use(errorMiddleware);
|
|
20
|
+
|
|
21
|
+
export default app;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
dotenv.config();
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
port: process.env.PORT || 3000,
|
|
6
|
+
|
|
7
|
+
api: {
|
|
8
|
+
timeout: Number(process.env.API_TIMEOUT) || 5000,
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
rateLimit: {
|
|
12
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
13
|
+
max: 100, // max requests per window per IP
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
logging: {
|
|
17
|
+
mode: process.env.LOG_MODE || "internal", // internal | external
|
|
18
|
+
externalUrl: process.env.LOG_SERVICE_URL || "",
|
|
19
|
+
directory: "logs",
|
|
20
|
+
fileName: "app",
|
|
21
|
+
maxSize: 5 * 1024 * 1024,
|
|
22
|
+
dailyRotate: true,
|
|
23
|
+
logType: Number(process.env.LOG_TYPE) || 1
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { asyncHandler } from "../utils/asyncHandler.js";
|
|
2
|
+
import { ResponseUtil } from "../utils/response.util.js";
|
|
3
|
+
import { sampleService } from "../services/sample.service.js";
|
|
4
|
+
|
|
5
|
+
export const getSample = asyncHandler(async (req, res) => {
|
|
6
|
+
|
|
7
|
+
const data = await sampleService();
|
|
8
|
+
|
|
9
|
+
return ResponseUtil.success(res, "Posts fetched successfully", data);
|
|
10
|
+
|
|
11
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// MongoDB connection setup
|
|
2
|
+
// Install: npm install mongoose
|
|
3
|
+
//
|
|
4
|
+
// import mongoose from "mongoose";
|
|
5
|
+
// import config from "../config/app.config.js";
|
|
6
|
+
//
|
|
7
|
+
// export const connectMongo = async () => {
|
|
8
|
+
// await mongoose.connect(config.db.uri);
|
|
9
|
+
// console.log("MongoDB connected");
|
|
10
|
+
// };
|
|
11
|
+
//
|
|
12
|
+
// Then in server.js:
|
|
13
|
+
// import { connectMongo } from "./db/mongo.db.js";
|
|
14
|
+
// connectMongo().then(() => app.listen(config.port, ...));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// MySQL connection setup
|
|
2
|
+
// Install: npm install mysql2
|
|
3
|
+
//
|
|
4
|
+
// import mysql from "mysql2/promise";
|
|
5
|
+
// import config from "../config/app.config.js";
|
|
6
|
+
//
|
|
7
|
+
// export const db = mysql.createPool({
|
|
8
|
+
// host: config.db.host,
|
|
9
|
+
// user: config.db.user,
|
|
10
|
+
// password: config.db.password,
|
|
11
|
+
// database: config.db.database,
|
|
12
|
+
// });
|
|
13
|
+
//
|
|
14
|
+
// export const connectMySQL = async () => {
|
|
15
|
+
// await db.getConnection();
|
|
16
|
+
// console.log("MySQL connected");
|
|
17
|
+
// };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// PostgreSQL connection setup
|
|
2
|
+
// Install: npm install pg
|
|
3
|
+
//
|
|
4
|
+
// import pg from "pg";
|
|
5
|
+
// import config from "../config/app.config.js";
|
|
6
|
+
//
|
|
7
|
+
// const { Pool } = pg;
|
|
8
|
+
//
|
|
9
|
+
// export const db = new Pool({
|
|
10
|
+
// host: config.db.host,
|
|
11
|
+
// user: config.db.user,
|
|
12
|
+
// password: config.db.password,
|
|
13
|
+
// database: config.db.database,
|
|
14
|
+
// port: config.db.port || 5432,
|
|
15
|
+
// });
|
|
16
|
+
//
|
|
17
|
+
// export const connectPostgres = async () => {
|
|
18
|
+
// await db.connect();
|
|
19
|
+
// console.log("PostgreSQL connected");
|
|
20
|
+
// };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ResponseUtil } from "../utils/response.util.js";
|
|
2
|
+
import { logService } from "../utils/log.util.js";
|
|
3
|
+
|
|
4
|
+
export const errorMiddleware = async (err, req, res, next) => {
|
|
5
|
+
await logService("error", err.message, {
|
|
6
|
+
stack: err.stack
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
switch (err.status) {
|
|
10
|
+
case 400:
|
|
11
|
+
return ResponseUtil.badRequest(res, err.message);
|
|
12
|
+
case 401:
|
|
13
|
+
return ResponseUtil.unauthorized(res, err.message);
|
|
14
|
+
case 403:
|
|
15
|
+
return ResponseUtil.forbidden(res, err.message);
|
|
16
|
+
case 404:
|
|
17
|
+
return ResponseUtil.notFound(res, err.message);
|
|
18
|
+
case 422:
|
|
19
|
+
return ResponseUtil.validation(res, err.message, err.errors);
|
|
20
|
+
default:
|
|
21
|
+
return ResponseUtil.exception(res, err.message);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ResponseUtil } from "../utils/response.util.js";
|
|
2
|
+
|
|
3
|
+
export const ipMiddleware = (req, res, next) => {
|
|
4
|
+
const allowedIps = [
|
|
5
|
+
"127.0.0.1",
|
|
6
|
+
"::1",
|
|
7
|
+
"192.168.1.10"
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const clientIp =
|
|
11
|
+
req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
|
|
12
|
+
req.socket.remoteAddress;
|
|
13
|
+
|
|
14
|
+
if (!allowedIps.includes(clientIp)) {
|
|
15
|
+
return ResponseUtil.forbidden(res, "Access denied: Unauthorized IP address");
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
next();
|
|
20
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import rateLimit from "express-rate-limit";
|
|
2
|
+
import config from "../config/app.config.js";
|
|
3
|
+
import { ResponseUtil } from "../utils/response.util.js";
|
|
4
|
+
|
|
5
|
+
export const rateLimitMiddleware = rateLimit({
|
|
6
|
+
windowMs: config.rateLimit.windowMs,
|
|
7
|
+
max: config.rateLimit.max,
|
|
8
|
+
standardHeaders: true,
|
|
9
|
+
legacyHeaders: false,
|
|
10
|
+
|
|
11
|
+
handler: (req, res) => {
|
|
12
|
+
return ResponseUtil.send(
|
|
13
|
+
res,
|
|
14
|
+
429,
|
|
15
|
+
"Too many requests. Please try again later."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sampleRepository } from "../repositories/sample.repository.js";
|
|
2
|
+
import { logService } from "../utils/log.util.js";
|
|
3
|
+
|
|
4
|
+
export const sampleService = async () => {
|
|
5
|
+
|
|
6
|
+
const data = await sampleRepository.getPosts();
|
|
7
|
+
await logService("info", "All posts fetched from repository", { count: data.length, posts: data});
|
|
8
|
+
|
|
9
|
+
if (!data) {
|
|
10
|
+
const error = new Error("Data not found");
|
|
11
|
+
error.status = 404;
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
return data;
|
|
17
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import config from "../config/app.config.js";
|
|
3
|
+
|
|
4
|
+
export const apiCall = async (options) => {
|
|
5
|
+
try {
|
|
6
|
+
const response = await axios({
|
|
7
|
+
...options,
|
|
8
|
+
timeout: config.api.timeout,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return response.data;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
attempts++;
|
|
14
|
+
|
|
15
|
+
if (attempts >= config.api.retries) {
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import config from "../config/app.config.js";
|
|
5
|
+
import { sanitizeLogData } from "./logSanitizer.util.js";
|
|
6
|
+
|
|
7
|
+
const logDir = config.logging.directory;
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(logDir)) {
|
|
10
|
+
fs.mkdirSync(logDir);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const getFileName = () => {
|
|
14
|
+
if (config.logging.dailyRotate) {
|
|
15
|
+
const date = new Date().toISOString().split("T")[0];
|
|
16
|
+
return `${config.logging.fileName}-${date}.log`;
|
|
17
|
+
}
|
|
18
|
+
return `${config.logging.fileName}.log`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const logService = async (level, message, meta = {}) => {
|
|
22
|
+
const sanitizedMeta = sanitizeLogData(meta);
|
|
23
|
+
|
|
24
|
+
const logData = {
|
|
25
|
+
level,
|
|
26
|
+
message,
|
|
27
|
+
timestamp: new Date().toLocaleString("en-IN", { timeZone: "Asia/Kolkata" }),
|
|
28
|
+
meta: sanitizedMeta,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// External log mode
|
|
32
|
+
if (config.logging.mode === "external" && config.logging.externalUrl) {
|
|
33
|
+
try {
|
|
34
|
+
await axios.post(config.logging.externalUrl, logData);
|
|
35
|
+
return;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error("External logging failed");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Internal file logging
|
|
42
|
+
const filePath = path.join(logDir, getFileName());
|
|
43
|
+
const logLine = JSON.stringify(logData) + "\n";
|
|
44
|
+
|
|
45
|
+
fs.appendFileSync(filePath, logLine);
|
|
46
|
+
|
|
47
|
+
const stats = fs.statSync(filePath);
|
|
48
|
+
if (stats.size > config.logging.maxSize) {
|
|
49
|
+
fs.renameSync(filePath, `${filePath}.backup`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { sensitiveKeys } from "./sensitiveKeys.util.js";
|
|
2
|
+
import config from "../config/app.config.js";
|
|
3
|
+
|
|
4
|
+
const maskValue = (value) => {
|
|
5
|
+
if (typeof value !== "string") return "******";
|
|
6
|
+
if (value.length <= 4) return "****";
|
|
7
|
+
return value.slice(0, 2) + "****" + value.slice(-2);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const sanitizeLogData = (data) => {
|
|
11
|
+
if (!data || typeof data !== "object") return data;
|
|
12
|
+
|
|
13
|
+
const processObject = (obj) => {
|
|
14
|
+
if (Array.isArray(obj)) {
|
|
15
|
+
return obj.map(processObject);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const newObj = {};
|
|
19
|
+
|
|
20
|
+
for (const key in obj) {
|
|
21
|
+
const value = obj[key];
|
|
22
|
+
|
|
23
|
+
if (sensitiveKeys.includes(key)) {
|
|
24
|
+
// LOG_TYPE 1 → show everything
|
|
25
|
+
if (config.logging.logType === 1) {
|
|
26
|
+
newObj[key] = value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// LOG_TYPE 2 → mask
|
|
30
|
+
else if (config.logging.logType === 2) {
|
|
31
|
+
newObj[key] = maskValue(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// LOG_TYPE 3 → remove
|
|
35
|
+
else if (config.logging.logType === 3) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
newObj[key] =
|
|
40
|
+
typeof value === "object" && value !== null
|
|
41
|
+
? processObject(value)
|
|
42
|
+
: value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return newObj;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return processObject(data);
|
|
50
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const ResponseUtil = {
|
|
2
|
+
send(res, statusCode, message, data = null, errors = null) {
|
|
3
|
+
return res.status(statusCode).json({
|
|
4
|
+
success: statusCode < 400,
|
|
5
|
+
statusCode,
|
|
6
|
+
message,
|
|
7
|
+
data,
|
|
8
|
+
errors
|
|
9
|
+
});
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
success(res, message = "Success", data = null) {
|
|
13
|
+
return this.send(res, 200, message, data);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
created(res, message = "Created successfully", data = null) {
|
|
17
|
+
return this.send(res, 201, message, data);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
badRequest(res, message = "Bad request", errors = null) {
|
|
21
|
+
return this.send(res, 400, message, null, errors);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
unauthorized(res, message = "Unauthorized") {
|
|
25
|
+
return this.send(res, 401, message);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
forbidden(res, message = "Forbidden") {
|
|
29
|
+
return this.send(res, 403, message);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
notFound(res, message = "Resource not found") {
|
|
33
|
+
return this.send(res, 404, message);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
validation(res, message = "Validation failed", errors = []) {
|
|
37
|
+
return this.send(res, 422, message, null, errors);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
exception(res, message = "Internal server error") {
|
|
41
|
+
return this.send(res, 500, message);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
File without changes
|