@ecashlib/shared-message 1.0.2 → 1.0.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/error-handler.d.ts +50 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +249 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -15
- package/dist/message-cms.adapter.d.ts +17 -0
- package/dist/message-cms.adapter.d.ts.map +1 -0
- package/dist/message-cms.adapter.js +51 -0
- package/dist/message.d.ts +30 -0
- package/dist/message.d.ts.map +1 -0
- package/dist/message.js +2 -0
- package/dist/message.util.d.ts +35 -0
- package/dist/message.util.d.ts.map +1 -0
- package/dist/message.util.js +75 -0
- package/dist/redis.repository.d.ts +213 -0
- package/dist/redis.repository.d.ts.map +1 -0
- package/dist/redis.repository.js +619 -0
- package/package.json +40 -37
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { FastifyInstance } from "fastify";
|
|
2
|
+
import { MessageUtil } from "./message.util";
|
|
3
|
+
/**
|
|
4
|
+
* Response interfaces
|
|
5
|
+
*/
|
|
6
|
+
export interface PopupNotification {
|
|
7
|
+
title: string;
|
|
8
|
+
left: string | null;
|
|
9
|
+
middle: string | null;
|
|
10
|
+
right: string | null;
|
|
11
|
+
count: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ErrorResponse {
|
|
14
|
+
success: false;
|
|
15
|
+
code: string;
|
|
16
|
+
message: string;
|
|
17
|
+
data: null;
|
|
18
|
+
metadata: null;
|
|
19
|
+
popup: PopupNotification | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Custom Error Classes
|
|
23
|
+
*/
|
|
24
|
+
export declare class BusinessLogicError extends Error {
|
|
25
|
+
code: string;
|
|
26
|
+
language: string;
|
|
27
|
+
constructor(code: string, language?: string);
|
|
28
|
+
}
|
|
29
|
+
export declare class ValidationError extends Error {
|
|
30
|
+
details?: any | undefined;
|
|
31
|
+
constructor(message: string, details?: any | undefined);
|
|
32
|
+
}
|
|
33
|
+
export declare class NotFoundError extends Error {
|
|
34
|
+
constructor(message?: string);
|
|
35
|
+
}
|
|
36
|
+
export declare class ConflictError extends Error {
|
|
37
|
+
constructor(message?: string);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Factory function to create error handler plugin
|
|
41
|
+
*/
|
|
42
|
+
export declare function createErrorHandlerPlugin(messageUtil: MessageUtil): (fastify: FastifyInstance) => Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Helper functions to throw standardized errors
|
|
45
|
+
*/
|
|
46
|
+
export declare const throwBusinessLogicError: (code: string, language?: string) => never;
|
|
47
|
+
export declare const throwValidationError: (message: string, details?: any) => never;
|
|
48
|
+
export declare const throwNotFoundError: (message?: string) => never;
|
|
49
|
+
export declare const throwConflictError: (message?: string) => never;
|
|
50
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAEH,eAAe,EAGlB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,IAAI,CAAC;IACf,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACnC;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAE9B,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,MAAM;gBADhB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAAa;CAKrC;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAG3B,OAAO,CAAC,EAAE,GAAG;gBADpB,OAAO,EAAE,MAAM,EACR,OAAO,CAAC,EAAE,GAAG,YAAA;CAK3B;AAED,qBAAa,aAAc,SAAQ,KAAK;gBACxB,OAAO,GAAE,MAA6B;CAIrD;AAED,qBAAa,aAAc,SAAQ,KAAK;gBACxB,OAAO,GAAE,MAA4B;CAIpD;AAyCD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,WAAW,aAEpB,eAAe,mBAmK3D;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAChC,MAAM,MAAM,EACZ,WAAU,MAAa,KACxB,KAEF,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,EAAE,UAAU,GAAG,KAAG,KAErE,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,KAErD,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,KAErD,CAAC"}
|
|
@@ -0,0 +1,249 @@
|
|
|
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.throwConflictError = exports.throwNotFoundError = exports.throwValidationError = exports.throwBusinessLogicError = exports.ConflictError = exports.NotFoundError = exports.ValidationError = exports.BusinessLogicError = void 0;
|
|
7
|
+
exports.createErrorHandlerPlugin = createErrorHandlerPlugin;
|
|
8
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
9
|
+
/**
|
|
10
|
+
* Custom Error Classes
|
|
11
|
+
*/
|
|
12
|
+
class BusinessLogicError extends Error {
|
|
13
|
+
constructor(code, language = "vi") {
|
|
14
|
+
super(code); // Use code as temporary message
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.language = language;
|
|
17
|
+
this.name = "BusinessLogicError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.BusinessLogicError = BusinessLogicError;
|
|
21
|
+
class ValidationError extends Error {
|
|
22
|
+
constructor(message, details) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.details = details;
|
|
25
|
+
this.name = "ValidationError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ValidationError = ValidationError;
|
|
29
|
+
class NotFoundError extends Error {
|
|
30
|
+
constructor(message = "Resource not found") {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "NotFoundError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.NotFoundError = NotFoundError;
|
|
36
|
+
class ConflictError extends Error {
|
|
37
|
+
constructor(message = "Resource conflict") {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "ConflictError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ConflictError = ConflictError;
|
|
43
|
+
/**
|
|
44
|
+
* Default popup configurations
|
|
45
|
+
*/
|
|
46
|
+
const DEFAULT_POPUPS = {
|
|
47
|
+
ERROR: {
|
|
48
|
+
title: "Error",
|
|
49
|
+
left: null,
|
|
50
|
+
middle: null,
|
|
51
|
+
right: "OK",
|
|
52
|
+
count: 1
|
|
53
|
+
},
|
|
54
|
+
WARNING: {
|
|
55
|
+
title: "Warning",
|
|
56
|
+
left: "Cancel",
|
|
57
|
+
middle: null,
|
|
58
|
+
right: "OK",
|
|
59
|
+
count: 2
|
|
60
|
+
},
|
|
61
|
+
INFO: {
|
|
62
|
+
title: "Information",
|
|
63
|
+
left: null,
|
|
64
|
+
middle: null,
|
|
65
|
+
right: "OK",
|
|
66
|
+
count: 1
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Default response codes
|
|
71
|
+
*/
|
|
72
|
+
const RESPONSE_CODES = {
|
|
73
|
+
SUCCESS: "COMM0200",
|
|
74
|
+
BAD_REQUEST: "COMM0002",
|
|
75
|
+
NOT_FOUND: "COMM0404",
|
|
76
|
+
CONFLICT: "COMM0409",
|
|
77
|
+
VALIDATION_ERROR: "COMM0002",
|
|
78
|
+
SYSTEM_ERROR: "COMM0001"
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Factory function to create error handler plugin
|
|
82
|
+
*/
|
|
83
|
+
function createErrorHandlerPlugin(messageUtil) {
|
|
84
|
+
return (0, fastify_plugin_1.default)(async function errorHandler(fastify) {
|
|
85
|
+
// Set error handler
|
|
86
|
+
fastify.setErrorHandler(async (error, request, reply) => {
|
|
87
|
+
// Log error for debugging
|
|
88
|
+
fastify.log.error({
|
|
89
|
+
error: error.message,
|
|
90
|
+
stack: error.stack,
|
|
91
|
+
url: request.url,
|
|
92
|
+
method: request.method,
|
|
93
|
+
params: request.params,
|
|
94
|
+
query: request.query,
|
|
95
|
+
body: request.body
|
|
96
|
+
}, "Request error occurred");
|
|
97
|
+
let response;
|
|
98
|
+
const statusCode = 200; // Always return HTTP 200
|
|
99
|
+
// Handle different types of errors
|
|
100
|
+
if (error instanceof BusinessLogicError) {
|
|
101
|
+
// Fetch message and popup from CMS via MessageUtil
|
|
102
|
+
const messageContent = await messageUtil.getMessageContentByCode(error.code, error.language);
|
|
103
|
+
if (messageContent) {
|
|
104
|
+
response = {
|
|
105
|
+
success: false,
|
|
106
|
+
code: error.code,
|
|
107
|
+
message: messageContent.message,
|
|
108
|
+
data: null,
|
|
109
|
+
metadata: null,
|
|
110
|
+
popup: messageContent.popup || DEFAULT_POPUPS.ERROR
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Fallback if message not found in CMS
|
|
115
|
+
fastify.log.warn(`Message not found for code: ${error.code}, language: ${error.language}`);
|
|
116
|
+
response = {
|
|
117
|
+
success: false,
|
|
118
|
+
code: error.code,
|
|
119
|
+
message: `Error: ${error.code}`,
|
|
120
|
+
data: null,
|
|
121
|
+
metadata: null,
|
|
122
|
+
popup: DEFAULT_POPUPS.ERROR
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (error instanceof ValidationError) {
|
|
127
|
+
response = {
|
|
128
|
+
success: false,
|
|
129
|
+
code: RESPONSE_CODES.VALIDATION_ERROR,
|
|
130
|
+
message: error.message,
|
|
131
|
+
data: null,
|
|
132
|
+
metadata: null,
|
|
133
|
+
popup: DEFAULT_POPUPS.WARNING
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
else if (error instanceof NotFoundError) {
|
|
137
|
+
response = {
|
|
138
|
+
success: false,
|
|
139
|
+
code: RESPONSE_CODES.NOT_FOUND,
|
|
140
|
+
message: error.message,
|
|
141
|
+
data: null,
|
|
142
|
+
metadata: null,
|
|
143
|
+
popup: null
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
else if (error instanceof ConflictError) {
|
|
147
|
+
response = {
|
|
148
|
+
success: false,
|
|
149
|
+
code: RESPONSE_CODES.CONFLICT,
|
|
150
|
+
message: error.message,
|
|
151
|
+
data: null,
|
|
152
|
+
metadata: null,
|
|
153
|
+
popup: DEFAULT_POPUPS.WARNING
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
else if (error.validation) {
|
|
157
|
+
// Fastify validation errors
|
|
158
|
+
const validationMessage = error.validation
|
|
159
|
+
.map((err) => `${err.instancePath || err.schemaPath}: ${err.message}`)
|
|
160
|
+
.join(", ");
|
|
161
|
+
response = {
|
|
162
|
+
success: false,
|
|
163
|
+
code: RESPONSE_CODES.VALIDATION_ERROR,
|
|
164
|
+
message: `Validation failed: ${validationMessage}`,
|
|
165
|
+
data: null,
|
|
166
|
+
metadata: null,
|
|
167
|
+
popup: null
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
else if (error.statusCode === 404) {
|
|
171
|
+
response = {
|
|
172
|
+
success: false,
|
|
173
|
+
code: RESPONSE_CODES.NOT_FOUND,
|
|
174
|
+
message: "Endpoint not found",
|
|
175
|
+
data: null,
|
|
176
|
+
metadata: null,
|
|
177
|
+
popup: null
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
else if (error.statusCode === 429) {
|
|
181
|
+
response = {
|
|
182
|
+
success: false,
|
|
183
|
+
code: RESPONSE_CODES.BAD_REQUEST,
|
|
184
|
+
message: "Too many requests, please try again later.",
|
|
185
|
+
data: null,
|
|
186
|
+
metadata: null,
|
|
187
|
+
popup: DEFAULT_POPUPS.WARNING
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
else if (error.statusCode && error.statusCode < 500) {
|
|
191
|
+
// Client errors (4xx)
|
|
192
|
+
response = {
|
|
193
|
+
success: false,
|
|
194
|
+
code: RESPONSE_CODES.BAD_REQUEST,
|
|
195
|
+
message: error.message || "Bad request",
|
|
196
|
+
data: null,
|
|
197
|
+
metadata: null,
|
|
198
|
+
popup: null
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Server errors (5xx) or unknown errors
|
|
203
|
+
response = {
|
|
204
|
+
success: false,
|
|
205
|
+
code: RESPONSE_CODES.SYSTEM_ERROR,
|
|
206
|
+
message: "An unexpected error occurred",
|
|
207
|
+
data: null,
|
|
208
|
+
metadata: null,
|
|
209
|
+
popup: DEFAULT_POPUPS.ERROR
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Send response - always HTTP 200
|
|
213
|
+
reply.status(statusCode).send(response);
|
|
214
|
+
});
|
|
215
|
+
// Handle 404 for routes that don't exist
|
|
216
|
+
fastify.setNotFoundHandler(async (request, reply) => {
|
|
217
|
+
const response = {
|
|
218
|
+
success: false,
|
|
219
|
+
code: RESPONSE_CODES.NOT_FOUND,
|
|
220
|
+
message: `Route ${request.method} ${request.url} not found`,
|
|
221
|
+
data: null,
|
|
222
|
+
metadata: null,
|
|
223
|
+
popup: null
|
|
224
|
+
};
|
|
225
|
+
reply.status(200).send(response);
|
|
226
|
+
});
|
|
227
|
+
}, {
|
|
228
|
+
name: "shared-error-handler"
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Helper functions to throw standardized errors
|
|
233
|
+
*/
|
|
234
|
+
const throwBusinessLogicError = (code, language = "vi") => {
|
|
235
|
+
throw new BusinessLogicError(code, language);
|
|
236
|
+
};
|
|
237
|
+
exports.throwBusinessLogicError = throwBusinessLogicError;
|
|
238
|
+
const throwValidationError = (message, details) => {
|
|
239
|
+
throw new ValidationError(message, details);
|
|
240
|
+
};
|
|
241
|
+
exports.throwValidationError = throwValidationError;
|
|
242
|
+
const throwNotFoundError = (message) => {
|
|
243
|
+
throw new NotFoundError(message);
|
|
244
|
+
};
|
|
245
|
+
exports.throwNotFoundError = throwNotFoundError;
|
|
246
|
+
const throwConflictError = (message) => {
|
|
247
|
+
throw new ConflictError(message);
|
|
248
|
+
};
|
|
249
|
+
exports.throwConflictError = throwConflictError;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { MessageUtil } from "./message.util";
|
|
2
|
+
export { MessageCMSAdapter } from "./message-cms.adapter";
|
|
3
|
+
export { RedisRepository } from "./redis.repository";
|
|
4
|
+
export type { MessageContent, MessageServicePort } from "./message";
|
|
5
|
+
export { default as messageUtilPlugin } from "./message.util";
|
|
6
|
+
export { default as redisRepositoryPlugin } from "./redis.repository";
|
|
7
|
+
export { BusinessLogicError, ValidationError, NotFoundError, ConflictError, createErrorHandlerPlugin, throwBusinessLogicError, throwValidationError, throwNotFoundError, throwConflictError } from "./error-handler";
|
|
8
|
+
export type { PopupNotification, ErrorResponse } from "./error-handler";
|
|
3
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAGpE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAGtE,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,aAAa,EACb,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EACrB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
4
|
};
|
|
16
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
exports.throwConflictError = exports.throwNotFoundError = exports.throwValidationError = exports.throwBusinessLogicError = exports.createErrorHandlerPlugin = exports.ConflictError = exports.NotFoundError = exports.ValidationError = exports.BusinessLogicError = exports.redisRepositoryPlugin = exports.messageUtilPlugin = exports.RedisRepository = exports.MessageCMSAdapter = exports.MessageUtil = void 0;
|
|
7
|
+
var message_util_1 = require("./message.util");
|
|
8
|
+
Object.defineProperty(exports, "MessageUtil", { enumerable: true, get: function () { return message_util_1.MessageUtil; } });
|
|
9
|
+
var message_cms_adapter_1 = require("./message-cms.adapter");
|
|
10
|
+
Object.defineProperty(exports, "MessageCMSAdapter", { enumerable: true, get: function () { return message_cms_adapter_1.MessageCMSAdapter; } });
|
|
11
|
+
var redis_repository_1 = require("./redis.repository");
|
|
12
|
+
Object.defineProperty(exports, "RedisRepository", { enumerable: true, get: function () { return redis_repository_1.RedisRepository; } });
|
|
13
|
+
// Fastify plugins (default exports)
|
|
14
|
+
var message_util_2 = require("./message.util");
|
|
15
|
+
Object.defineProperty(exports, "messageUtilPlugin", { enumerable: true, get: function () { return __importDefault(message_util_2).default; } });
|
|
16
|
+
var redis_repository_2 = require("./redis.repository");
|
|
17
|
+
Object.defineProperty(exports, "redisRepositoryPlugin", { enumerable: true, get: function () { return __importDefault(redis_repository_2).default; } });
|
|
18
|
+
// Error handling
|
|
19
|
+
var error_handler_1 = require("./error-handler");
|
|
20
|
+
Object.defineProperty(exports, "BusinessLogicError", { enumerable: true, get: function () { return error_handler_1.BusinessLogicError; } });
|
|
21
|
+
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return error_handler_1.ValidationError; } });
|
|
22
|
+
Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return error_handler_1.NotFoundError; } });
|
|
23
|
+
Object.defineProperty(exports, "ConflictError", { enumerable: true, get: function () { return error_handler_1.ConflictError; } });
|
|
24
|
+
Object.defineProperty(exports, "createErrorHandlerPlugin", { enumerable: true, get: function () { return error_handler_1.createErrorHandlerPlugin; } });
|
|
25
|
+
Object.defineProperty(exports, "throwBusinessLogicError", { enumerable: true, get: function () { return error_handler_1.throwBusinessLogicError; } });
|
|
26
|
+
Object.defineProperty(exports, "throwValidationError", { enumerable: true, get: function () { return error_handler_1.throwValidationError; } });
|
|
27
|
+
Object.defineProperty(exports, "throwNotFoundError", { enumerable: true, get: function () { return error_handler_1.throwNotFoundError; } });
|
|
28
|
+
Object.defineProperty(exports, "throwConflictError", { enumerable: true, get: function () { return error_handler_1.throwConflictError; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MessageContent } from "./message";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter for fetching messages from CMS API
|
|
4
|
+
*/
|
|
5
|
+
export declare class MessageCMSAdapter {
|
|
6
|
+
private readonly cmsApiUrl;
|
|
7
|
+
private readonly cmsContextPath;
|
|
8
|
+
constructor();
|
|
9
|
+
/**
|
|
10
|
+
* Fetch message content from CMS API
|
|
11
|
+
* @param code Message code
|
|
12
|
+
* @param language Language code
|
|
13
|
+
* @returns Message content or null if not found
|
|
14
|
+
*/
|
|
15
|
+
getMessageContentByCode(code: string, language: string): Promise<MessageContent | null>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=message-cms.adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-cms.adapter.d.ts","sourceRoot":"","sources":["../src/message-cms.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAuB3C;;GAEG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;;IAOxC;;;;;OAKG;IACG,uBAAuB,CACzB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;CAyCpC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageCMSAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Adapter for fetching messages from CMS API
|
|
6
|
+
*/
|
|
7
|
+
class MessageCMSAdapter {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.cmsApiUrl = process.env.CMS_API_URL || "";
|
|
10
|
+
this.cmsContextPath = process.env.CMS_CONTEXT_PATH || "";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fetch message content from CMS API
|
|
14
|
+
* @param code Message code
|
|
15
|
+
* @param language Language code
|
|
16
|
+
* @returns Message content or null if not found
|
|
17
|
+
*/
|
|
18
|
+
async getMessageContentByCode(code, language) {
|
|
19
|
+
const fullUrl = `${this.cmsApiUrl}${this.cmsContextPath}/api/v1/messages/${encodeURIComponent(code)}/content/${encodeURIComponent(language)}`;
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(fullUrl, {
|
|
22
|
+
method: "GET",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json"
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
// Handle 404 - message not found
|
|
28
|
+
if (response.status === 404) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
const errorData = await response.json();
|
|
33
|
+
throw new Error(`CMS API error: ${errorData.message || response.statusText}`);
|
|
34
|
+
}
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
if (!data.success || !data.data) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
code: data.data.code,
|
|
41
|
+
classificationCode: data.data.classificationCode,
|
|
42
|
+
message: data.data.message,
|
|
43
|
+
popup: data.data.popup
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new Error(`Không thể lấy thông tin message từ CMS API: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.MessageCMSAdapter = MessageCMSAdapter;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message content structure returned from CMS API
|
|
3
|
+
*/
|
|
4
|
+
export interface MessagePopup {
|
|
5
|
+
title: string;
|
|
6
|
+
left: string | null;
|
|
7
|
+
middle: string | null;
|
|
8
|
+
right: string | null;
|
|
9
|
+
count: number;
|
|
10
|
+
}
|
|
11
|
+
export interface MessageContent {
|
|
12
|
+
code: string;
|
|
13
|
+
classificationCode: string;
|
|
14
|
+
message: string;
|
|
15
|
+
popup?: MessagePopup;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Port interface for message service implementations
|
|
19
|
+
* This allows different implementations (direct API, cached, mock, etc.)
|
|
20
|
+
*/
|
|
21
|
+
export interface MessageServicePort {
|
|
22
|
+
/**
|
|
23
|
+
* Get message content by code and language
|
|
24
|
+
* @param code Message code
|
|
25
|
+
* @param language Language code (e.g., 'vi', 'en')
|
|
26
|
+
* @returns Message content or null if not found
|
|
27
|
+
*/
|
|
28
|
+
getMessageContentByCode(code: string, language: string): Promise<MessageContent | null>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../src/message.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,YAAY,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;;OAKG;IACH,uBAAuB,CACnB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACrC"}
|
package/dist/message.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FastifyInstance } from "fastify";
|
|
2
|
+
import { MessageContent, MessageServicePort } from "./message";
|
|
3
|
+
declare module "fastify" {
|
|
4
|
+
interface FastifyInstance {
|
|
5
|
+
messageUtil: MessageUtil;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Message utility with cache-first strategy
|
|
10
|
+
* Checks Redis cache first, then calls CMS API if not cached
|
|
11
|
+
*/
|
|
12
|
+
export declare class MessageUtil implements MessageServicePort {
|
|
13
|
+
private readonly redisRepository;
|
|
14
|
+
private readonly cmsAdapter;
|
|
15
|
+
private readonly logger;
|
|
16
|
+
private readonly defaultTTL;
|
|
17
|
+
constructor(fastify: FastifyInstance);
|
|
18
|
+
/**
|
|
19
|
+
* Get message content by code and language with cache-first strategy
|
|
20
|
+
* @param code Message code
|
|
21
|
+
* @param language Language code (e.g., 'vi', 'en')
|
|
22
|
+
* @returns Message content or null if not found
|
|
23
|
+
*/
|
|
24
|
+
getMessageContentByCode(code: string, language: string): Promise<MessageContent | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Invalidate cached message
|
|
27
|
+
* @param code Message code
|
|
28
|
+
* @param language Language code
|
|
29
|
+
* @returns true if cache was deleted
|
|
30
|
+
*/
|
|
31
|
+
invalidateCache(code: string, language: string): Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
declare const _default: (fastify: FastifyInstance) => Promise<void>;
|
|
34
|
+
export default _default;
|
|
35
|
+
//# sourceMappingURL=message.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.util.d.ts","sourceRoot":"","sources":["../src/message.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAI/D,OAAO,QAAQ,SAAS,CAAC;IACrB,UAAU,eAAe;QACrB,WAAW,EAAE,WAAW,CAAC;KAC5B;CACJ;AAED;;;GAGG;AACH,qBAAa,WAAY,YAAW,kBAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;gBAE/B,OAAO,EAAE,eAAe;IAMpC;;;;;OAKG;IACG,uBAAuB,CACzB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IA4CjC;;;;;OAKG;IACG,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAgB1E;kCAGmB,eAAe;AADnC,wBAME"}
|
|
@@ -0,0 +1,75 @@
|
|
|
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.MessageUtil = void 0;
|
|
7
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
8
|
+
const message_cms_adapter_1 = require("./message-cms.adapter");
|
|
9
|
+
/**
|
|
10
|
+
* Message utility with cache-first strategy
|
|
11
|
+
* Checks Redis cache first, then calls CMS API if not cached
|
|
12
|
+
*/
|
|
13
|
+
class MessageUtil {
|
|
14
|
+
constructor(fastify) {
|
|
15
|
+
this.defaultTTL = 3600; // 1 hour
|
|
16
|
+
this.redisRepository = fastify.redisRepository;
|
|
17
|
+
this.cmsAdapter = new message_cms_adapter_1.MessageCMSAdapter();
|
|
18
|
+
this.logger = fastify.log;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get message content by code and language with cache-first strategy
|
|
22
|
+
* @param code Message code
|
|
23
|
+
* @param language Language code (e.g., 'vi', 'en')
|
|
24
|
+
* @returns Message content or null if not found
|
|
25
|
+
*/
|
|
26
|
+
async getMessageContentByCode(code, language) {
|
|
27
|
+
const cacheKey = `message:${code}:${language}`;
|
|
28
|
+
try {
|
|
29
|
+
// Step 1: Check Redis cache
|
|
30
|
+
const cachedMessage = await this.redisRepository.getObject(cacheKey);
|
|
31
|
+
if (cachedMessage) {
|
|
32
|
+
this.logger.debug(`Message found in cache - code: ${code}, language: ${language}`);
|
|
33
|
+
return cachedMessage;
|
|
34
|
+
}
|
|
35
|
+
this.logger.debug(`Message not in cache, fetching from CMS - code: ${code}, language: ${language}`);
|
|
36
|
+
// Step 2: Fetch from CMS API
|
|
37
|
+
const messageContent = await this.cmsAdapter.getMessageContentByCode(code, language);
|
|
38
|
+
// Step 3: Cache the result if found
|
|
39
|
+
if (messageContent) {
|
|
40
|
+
await this.redisRepository.setObject(cacheKey, messageContent, this.defaultTTL);
|
|
41
|
+
this.logger.debug(`Message cached - code: ${code}, language: ${language}, ttl: ${this.defaultTTL}s`);
|
|
42
|
+
}
|
|
43
|
+
return messageContent;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.log(error);
|
|
47
|
+
this.logger.error(`Error getting message - code: ${code}, language: ${language}`, error);
|
|
48
|
+
// Return null on error to prevent breaking the application
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Invalidate cached message
|
|
54
|
+
* @param code Message code
|
|
55
|
+
* @param language Language code
|
|
56
|
+
* @returns true if cache was deleted
|
|
57
|
+
*/
|
|
58
|
+
async invalidateCache(code, language) {
|
|
59
|
+
const cacheKey = `message:${code}:${language}`;
|
|
60
|
+
try {
|
|
61
|
+
const deleted = await this.redisRepository.delete(cacheKey);
|
|
62
|
+
this.logger.debug(`Cache invalidated - code: ${code}, language: ${language}, deleted: ${deleted}`);
|
|
63
|
+
return deleted;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
this.logger.error(`Error invalidating cache - code: ${code}, language: ${language}`, error);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.MessageUtil = MessageUtil;
|
|
72
|
+
exports.default = (0, fastify_plugin_1.default)(async (fastify) => {
|
|
73
|
+
const util = new MessageUtil(fastify);
|
|
74
|
+
fastify.decorate("messageUtil", util);
|
|
75
|
+
}, { name: "message-util", dependencies: ["redis-repository"] });
|