response-standardizer 1.2.1 → 1.2.3
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/dist/index.d.ts +1 -1
- package/dist/index.js +52 -37
- package/dist/types.js +2 -1
- package/dist/utils.js +9 -4
- package/package.json +2 -1
- package/src/index.ts +2 -2
- package/src/utils.ts +0 -1
- package/tsconfig.json +6 -4
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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.ServiceException = exports.handleValidationException = exports.handleRestException = exports.log = exports.info = exports.warn = exports.error = exports.RestMiddleware = exports.RestResponse = exports.protect = exports.initKeycloak = void 0;
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
5
11
|
let KEYCLOAK_PUBLIC_KEY = null;
|
|
6
|
-
|
|
12
|
+
const initKeycloak = async (config) => {
|
|
7
13
|
const KEYCLOAK_SERVICE = config.service ?? "localhost";
|
|
8
14
|
const KEYCLOAK_REALM = config.realm ?? "master";
|
|
9
15
|
const realmUrl = `http://${KEYCLOAK_SERVICE}/realms/${KEYCLOAK_REALM}`;
|
|
10
|
-
info(`Keycloak PublicKey Url: ${realmUrl}`);
|
|
11
|
-
const resp = await
|
|
16
|
+
(0, exports.info)(`Keycloak PublicKey Url: ${realmUrl}`);
|
|
17
|
+
const resp = await axios_1.default.get(realmUrl);
|
|
12
18
|
const key = resp.data.public_key;
|
|
13
19
|
KEYCLOAK_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----\n${key.match(/.{1,64}/g)?.join("\n")}\n-----END PUBLIC KEY-----`;
|
|
14
20
|
};
|
|
15
|
-
|
|
21
|
+
exports.initKeycloak = initKeycloak;
|
|
22
|
+
const protect = (allowedRoles) => {
|
|
16
23
|
return (req, res, next) => {
|
|
17
24
|
const authHeader = req.headers["authorization"];
|
|
18
25
|
if (!authHeader)
|
|
19
|
-
return RestResponse.unauthorized(req, res);
|
|
26
|
+
return exports.RestResponse.unauthorized(req, res);
|
|
20
27
|
const token = authHeader.split(" ")[1];
|
|
21
28
|
if (!token)
|
|
22
|
-
return RestResponse.unauthorized(req, res, "Token malformed");
|
|
29
|
+
return exports.RestResponse.unauthorized(req, res, "Token malformed");
|
|
23
30
|
try {
|
|
24
|
-
const decoded =
|
|
31
|
+
const decoded = jsonwebtoken_1.default.verify(token, KEYCLOAK_PUBLIC_KEY, { algorithms: ["RS256"] });
|
|
25
32
|
req.user = decoded;
|
|
26
33
|
req.token = token;
|
|
27
34
|
if (allowedRoles)
|
|
@@ -29,15 +36,16 @@ export const protect = (allowedRoles) => {
|
|
|
29
36
|
next();
|
|
30
37
|
}
|
|
31
38
|
catch (err) {
|
|
32
|
-
error(err?.message);
|
|
33
|
-
return RestResponse.unauthorized(req, res, "Token is not valid");
|
|
39
|
+
(0, exports.error)(err?.message);
|
|
40
|
+
return exports.RestResponse.unauthorized(req, res, "Token is not valid");
|
|
34
41
|
}
|
|
35
42
|
};
|
|
36
43
|
};
|
|
44
|
+
exports.protect = protect;
|
|
37
45
|
const role = (req, res, next, allowedRoles) => {
|
|
38
46
|
const user = req.user;
|
|
39
47
|
if (!user) {
|
|
40
|
-
return RestResponse.unauthorized(req, res);
|
|
48
|
+
return exports.RestResponse.unauthorized(req, res);
|
|
41
49
|
}
|
|
42
50
|
const realmRoles = (user?.realm_access?.roles ?? []).map((r) => r.toUpperCase());
|
|
43
51
|
const clientRoles = Object.values(user?.resource_access ?? {})
|
|
@@ -47,7 +55,7 @@ const role = (req, res, next, allowedRoles) => {
|
|
|
47
55
|
const allRoles = [...realmRoles, ...clientRoles];
|
|
48
56
|
const hasAccess = allowedUpper.some((role) => allRoles.includes(role));
|
|
49
57
|
if (!hasAccess) {
|
|
50
|
-
return RestResponse.accessDenied(req, res);
|
|
58
|
+
return exports.RestResponse.accessDenied(req, res);
|
|
51
59
|
}
|
|
52
60
|
next();
|
|
53
61
|
};
|
|
@@ -81,7 +89,7 @@ const accessDenied = (req, res, message = "Access denied") => {
|
|
|
81
89
|
const notFound = (req, res, message = "Not found") => {
|
|
82
90
|
res.status(404).json({ message });
|
|
83
91
|
};
|
|
84
|
-
|
|
92
|
+
exports.RestResponse = {
|
|
85
93
|
success,
|
|
86
94
|
paginate,
|
|
87
95
|
created,
|
|
@@ -102,8 +110,8 @@ const responseHandlerMiddleware = (req, res, next) => {
|
|
|
102
110
|
message: data?.message || res.locals.message || (res.statusCode < 300 ? "OK" : "Error"),
|
|
103
111
|
errors: data?.errors ?? null,
|
|
104
112
|
meta: {
|
|
105
|
-
requestId: res.locals.requestId || generateRequestId(),
|
|
106
|
-
timestamp: getTimestamp()
|
|
113
|
+
requestId: res.locals.requestId || (0, utils_1.generateRequestId)(),
|
|
114
|
+
timestamp: (0, utils_1.getTimestamp)()
|
|
107
115
|
}
|
|
108
116
|
};
|
|
109
117
|
return oldJson(response);
|
|
@@ -116,10 +124,10 @@ const responseHandlerMiddleware = (req, res, next) => {
|
|
|
116
124
|
}
|
|
117
125
|
return oldSend(data);
|
|
118
126
|
};
|
|
119
|
-
res.locals.requestId = generateRequestId();
|
|
127
|
+
res.locals.requestId = (0, utils_1.generateRequestId)();
|
|
120
128
|
next();
|
|
121
129
|
};
|
|
122
|
-
|
|
130
|
+
exports.RestMiddleware = {
|
|
123
131
|
responseHandlerMiddleware
|
|
124
132
|
};
|
|
125
133
|
const colors = {
|
|
@@ -129,16 +137,19 @@ const colors = {
|
|
|
129
137
|
YELLOW: "\x1b[33m"
|
|
130
138
|
};
|
|
131
139
|
const isProduction = process.env.NODE_ENV === "production";
|
|
132
|
-
|
|
133
|
-
log("ERROR", message, meta);
|
|
140
|
+
const error = (message, meta) => {
|
|
141
|
+
(0, exports.log)("ERROR", message, meta);
|
|
134
142
|
};
|
|
135
|
-
|
|
136
|
-
|
|
143
|
+
exports.error = error;
|
|
144
|
+
const warn = (message, meta) => {
|
|
145
|
+
(0, exports.log)("WARN", message, meta);
|
|
137
146
|
};
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
exports.warn = warn;
|
|
148
|
+
const info = (message, meta) => {
|
|
149
|
+
(0, exports.log)("INFO", message, meta);
|
|
140
150
|
};
|
|
141
|
-
|
|
151
|
+
exports.info = info;
|
|
152
|
+
const log = (level, message, meta) => {
|
|
142
153
|
const timestamp = new Date().toISOString();
|
|
143
154
|
// انتخاب رنگ فقط در dev
|
|
144
155
|
let color = colors.RESET;
|
|
@@ -156,7 +167,7 @@ export const log = (level, message, meta) => {
|
|
|
156
167
|
if (stack) {
|
|
157
168
|
const match = stack.match(/\((.*):(\d+):(\d+)\)/);
|
|
158
169
|
if (match) {
|
|
159
|
-
const fileName =
|
|
170
|
+
const fileName = path_1.default.basename(match[1]);
|
|
160
171
|
const line = match[2];
|
|
161
172
|
const column = match[3];
|
|
162
173
|
location = `${fileName}:${line}:${column}`;
|
|
@@ -166,29 +177,31 @@ export const log = (level, message, meta) => {
|
|
|
166
177
|
// چاپ لاگ
|
|
167
178
|
console.log(`${color}[${timestamp}][${location}][${level}] ${message}${metaStr}${colors.RESET}`);
|
|
168
179
|
};
|
|
169
|
-
|
|
180
|
+
exports.log = log;
|
|
181
|
+
const handleRestException = (req, res, err) => {
|
|
170
182
|
if (err instanceof ServiceException) {
|
|
171
183
|
if (err?.status === 401) {
|
|
172
|
-
RestResponse.unauthorized(req, res, err.message);
|
|
184
|
+
exports.RestResponse.unauthorized(req, res, err.message);
|
|
173
185
|
}
|
|
174
186
|
else if (err?.status === 403) {
|
|
175
|
-
RestResponse.accessDenied(req, res, err.message);
|
|
187
|
+
exports.RestResponse.accessDenied(req, res, err.message);
|
|
176
188
|
}
|
|
177
189
|
else if (err?.status === 400) {
|
|
178
|
-
RestResponse.validationError(req, res, err.errors);
|
|
190
|
+
exports.RestResponse.validationError(req, res, err.errors);
|
|
179
191
|
}
|
|
180
192
|
else if (err?.status === 500) {
|
|
181
|
-
RestResponse.exceptionError(req, res, err.message);
|
|
193
|
+
exports.RestResponse.exceptionError(req, res, err.message);
|
|
182
194
|
}
|
|
183
195
|
else if (err?.status === 404) {
|
|
184
|
-
RestResponse.notFound(req, res, err.message);
|
|
196
|
+
exports.RestResponse.notFound(req, res, err.message);
|
|
185
197
|
}
|
|
186
198
|
}
|
|
187
199
|
else if (err instanceof Error) {
|
|
188
|
-
RestResponse.exceptionError(req, res, err.message);
|
|
200
|
+
exports.RestResponse.exceptionError(req, res, err.message);
|
|
189
201
|
}
|
|
190
202
|
};
|
|
191
|
-
|
|
203
|
+
exports.handleRestException = handleRestException;
|
|
204
|
+
const handleValidationException = (error) => {
|
|
192
205
|
const zodError = error;
|
|
193
206
|
if (zodError.issues) {
|
|
194
207
|
const validationErrors = {};
|
|
@@ -202,7 +215,8 @@ export const handleValidationException = (error) => {
|
|
|
202
215
|
}
|
|
203
216
|
throw new ServiceException("Internal server error", 500, error);
|
|
204
217
|
};
|
|
205
|
-
|
|
218
|
+
exports.handleValidationException = handleValidationException;
|
|
219
|
+
class ServiceException extends Error {
|
|
206
220
|
constructor(message, status = 400, errors = null) {
|
|
207
221
|
super(message);
|
|
208
222
|
this.name = "ServiceException";
|
|
@@ -210,3 +224,4 @@ export class ServiceException extends Error {
|
|
|
210
224
|
this.errors = errors;
|
|
211
225
|
}
|
|
212
226
|
}
|
|
227
|
+
exports.ServiceException = ServiceException;
|
package/dist/types.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/utils.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTimestamp = exports.generateRequestId = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const generateRequestId = () => {
|
|
6
|
+
return (0, uuid_1.v4)();
|
|
4
7
|
};
|
|
5
|
-
|
|
8
|
+
exports.generateRequestId = generateRequestId;
|
|
9
|
+
const getTimestamp = () => {
|
|
6
10
|
return new Date().toISOString();
|
|
7
11
|
};
|
|
12
|
+
exports.getTimestamp = getTimestamp;
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "response-standardizer",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Express middleware to standardize API responses",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "commonjs",
|
|
7
8
|
"scripts": {
|
|
8
9
|
"build": "tsc",
|
|
9
10
|
"prepare": "npm run build"
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { ErrorFields, StandardResponse, RestResponseFunctions, RestMiddlewareFunctions, AuthRequest, Pagination } from "./types
|
|
3
|
-
import { generateRequestId, getTimestamp } from "./utils
|
|
2
|
+
import { ErrorFields, StandardResponse, RestResponseFunctions, RestMiddlewareFunctions, AuthRequest, Pagination } from "./types";
|
|
3
|
+
import { generateRequestId, getTimestamp } from "./utils";
|
|
4
4
|
import axios from "axios";
|
|
5
5
|
import jwt, { TokenExpiredError } from "jsonwebtoken";
|
|
6
6
|
import path from "path";
|
package/src/utils.ts
CHANGED
package/tsconfig.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2020",
|
|
4
|
-
"module": "
|
|
4
|
+
"module": "CommonJS",
|
|
5
5
|
"moduleResolution": "Node",
|
|
6
|
-
"
|
|
6
|
+
"rootDir": "src",
|
|
7
7
|
"outDir": "dist",
|
|
8
8
|
"strict": true,
|
|
9
|
+
"declaration": true,
|
|
9
10
|
"esModuleInterop": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
12
|
"skipLibCheck": true
|
|
11
13
|
},
|
|
12
|
-
"include": ["src
|
|
13
|
-
}
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|