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