@vereign/core 1.1.0 → 1.2.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/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +210 -33
- package/dist/index.mjs +211 -32
- package/package.json +6 -5
package/dist/index.d.mts
CHANGED
|
@@ -8,6 +8,9 @@ declare class App {
|
|
|
8
8
|
constructor(logger: Logger, appVersion: string);
|
|
9
9
|
private initializeMiddlewares;
|
|
10
10
|
private initializeRoutes;
|
|
11
|
+
/**
|
|
12
|
+
* General error handler for all errors, so we can throw from anywhere in the project
|
|
13
|
+
*/
|
|
11
14
|
private initializeErrorHandling;
|
|
12
15
|
listen(port: number): void;
|
|
13
16
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ declare class App {
|
|
|
8
8
|
constructor(logger: Logger, appVersion: string);
|
|
9
9
|
private initializeMiddlewares;
|
|
10
10
|
private initializeRoutes;
|
|
11
|
+
/**
|
|
12
|
+
* General error handler for all errors, so we can throw from anywhere in the project
|
|
13
|
+
*/
|
|
11
14
|
private initializeErrorHandling;
|
|
12
15
|
listen(port: number): void;
|
|
13
16
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,22 @@ var __create = Object.create;
|
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
8
22
|
var __export = (target, all) => {
|
|
9
23
|
for (var name in all)
|
|
10
24
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -86,50 +100,172 @@ var import_express2 = __toESM(require("express"));
|
|
|
86
100
|
// src/chmed16/controller.ts
|
|
87
101
|
var import_chmed_parser = require("@vereign/chmed-parser");
|
|
88
102
|
var import_pdf_generator = require("@vereign/pdf-generator");
|
|
103
|
+
|
|
104
|
+
// src/app-error.ts
|
|
105
|
+
var z = __toESM(require("zod/v4"));
|
|
106
|
+
var AppError = class extends Error {
|
|
107
|
+
constructor(status, message, code, details) {
|
|
108
|
+
super(message);
|
|
109
|
+
this.status = status;
|
|
110
|
+
this.code = code;
|
|
111
|
+
this.details = details;
|
|
112
|
+
Error.captureStackTrace(this, this.constructor);
|
|
113
|
+
}
|
|
114
|
+
toJSON() {
|
|
115
|
+
return __spreadValues({
|
|
116
|
+
status: this.status,
|
|
117
|
+
code: this.code,
|
|
118
|
+
message: this.message
|
|
119
|
+
}, this.details ? { errors: this.details } : {});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var errorDetailsSchema = z.object({
|
|
123
|
+
path: z.string(),
|
|
124
|
+
message: z.string(),
|
|
125
|
+
code: z.string().optional()
|
|
126
|
+
});
|
|
127
|
+
var appErrorSchema = z.object({
|
|
128
|
+
status: z.number().int(),
|
|
129
|
+
code: z.string(),
|
|
130
|
+
message: z.string(),
|
|
131
|
+
errors: z.array(errorDetailsSchema).optional()
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// src/chmed16/controller.ts
|
|
89
135
|
var CHMED16AController = class {
|
|
90
136
|
constructor(logger) {
|
|
91
|
-
this.process = (req, res) => __async(this, null, function* () {
|
|
137
|
+
this.process = (req, res, next) => __async(this, null, function* () {
|
|
92
138
|
const { chmed, lang } = req.body;
|
|
93
139
|
const start = Date.now();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
pdf.generate(json, res);
|
|
103
|
-
this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
|
|
104
|
-
this.logger.info(`Total request time: ${Date.now() - start} ms`);
|
|
140
|
+
try {
|
|
141
|
+
this.logger.info("Processing started");
|
|
142
|
+
const json = yield this.parseChmedData(chmed, lang);
|
|
143
|
+
yield this.generatePdfResponse(json, res);
|
|
144
|
+
this.logger.info(`Total request time: ${Date.now() - start} ms`);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
next(error);
|
|
147
|
+
}
|
|
105
148
|
});
|
|
106
149
|
this.logger = logger;
|
|
107
150
|
this.sds = new import_chmed_parser.SDS("https://sds-test.hin.ch", logger);
|
|
108
151
|
this.parser = new import_chmed_parser.CHMEDParser(logger);
|
|
109
152
|
}
|
|
153
|
+
parseChmedData(chmed, lang) {
|
|
154
|
+
return __async(this, null, function* () {
|
|
155
|
+
try {
|
|
156
|
+
const parseStart = Date.now();
|
|
157
|
+
const json = yield this.parser.parse(chmed, this.sds, lang);
|
|
158
|
+
this.logger.info(`Parsing took ${Date.now() - parseStart} ms`);
|
|
159
|
+
return json;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
this.logger.error("Parsing failed:", error);
|
|
162
|
+
throw new AppError(
|
|
163
|
+
400,
|
|
164
|
+
"Failed to parse CHMED data. Please check the input format.",
|
|
165
|
+
"PARSE_ERROR",
|
|
166
|
+
[
|
|
167
|
+
{
|
|
168
|
+
path: "chmed",
|
|
169
|
+
message: "Invalid CHMED format or structure",
|
|
170
|
+
code: "invalid_format"
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
generatePdfResponse(json, res) {
|
|
178
|
+
return __async(this, null, function* () {
|
|
179
|
+
const pdfStart = Date.now();
|
|
180
|
+
try {
|
|
181
|
+
const pdf = new import_pdf_generator.PDFLib(this.logger);
|
|
182
|
+
const pdfBuffer = yield pdf.generateBuffer(json);
|
|
183
|
+
res.setHeader("Content-Type", "application/pdf");
|
|
184
|
+
res.setHeader("Content-Disposition", 'inline; filename="chmed16a.pdf"');
|
|
185
|
+
res.send(pdfBuffer);
|
|
186
|
+
this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
this.logger.error("PDF generation failed:", error);
|
|
189
|
+
throw new AppError(
|
|
190
|
+
500,
|
|
191
|
+
"Failed to generate PDF. The parsed data may be invalid.",
|
|
192
|
+
"PDF_GENERATION_ERROR",
|
|
193
|
+
[
|
|
194
|
+
{
|
|
195
|
+
path: "pdf_generation",
|
|
196
|
+
message: "PDF creation failed due to invalid or corrupted data",
|
|
197
|
+
code: "generation_failed"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
110
204
|
};
|
|
111
205
|
|
|
112
206
|
// src/chmed16/middleware.ts
|
|
113
|
-
var
|
|
207
|
+
var import_zod = require("zod");
|
|
114
208
|
|
|
115
209
|
// src/chmed16/request.ts
|
|
116
|
-
var
|
|
117
|
-
var
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
210
|
+
var z2 = __toESM(require("zod/v4"));
|
|
211
|
+
var chmedPattern = /^CHMED[0-9A-Za-z]+/;
|
|
212
|
+
var bodySchema = z2.object({
|
|
213
|
+
chmed: z2.string().min(5, "string is required").refine(
|
|
214
|
+
(val) => {
|
|
215
|
+
if (chmedPattern.test(val)) return true;
|
|
216
|
+
try {
|
|
217
|
+
const url = new URL(val);
|
|
218
|
+
const hash = url.hash.slice(1);
|
|
219
|
+
return chmedPattern.test(hash);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{ message: "chmed must be a valid CHMED string" }
|
|
225
|
+
).meta({
|
|
226
|
+
description: "A valid CHMED string",
|
|
227
|
+
example: "CHMED16A1H4sIAAAAAAAACr2WX2/iOBDAv4rl1wtZ/yEh8LTlaHuVll1Eu620qz4YGIiV4CDHqbbb49v0M9zTvfHFbhwaWvpvV6eqgIMzGTv+zYzHc0NHymkwjvZu6NFntQTao4PCKhrQT3e3x1ZNwKKgP0A1yrvtqMV5S0gUHYOZ4bOeCOipswBeoQ/WlM6qsgTidb7pFUolYwxv/tTu+k7Hv8Is8GYG2B2lhfFvY50uSZIEG/5QfjhUOvdKuKhwUS/l4wR0udKQh9MUNU5mJe19v6Fn1yuoV3Ku/AAeyjAOedjGJriMO6FgHC9hFCby7+HB8fBwwBin68uADmHmDTD4VLohGBzsF2RhSXssoONpPf0JqrTXwbYToRilMY8uG5HYiqJOeyfid6JuZyeSTSdu1PnuWWe7FPUQhzc4XW/Kr0ajhYUf8Jg2jnbP+fpyva6h9BRdaNx2Qv8KKqSUIo5rs22nkIhabDUG7sgWCE0FE6zFui3hJx24s2JrkhMzhrK2yQD1WcgCjs3/Y7v0q98uAGMhQ/WzbDEuvTVPi9nEgjFo2oAerFYnPj5QfqYmOTgHBK5cHhJtSFakOZhyc1tZmFvQsCQXPpAsUdU83/xTggkJTjIuFI4fffFdWNXWPK0mfm2NMSlnIolrqzSk7f9L+oDyF6QDWFkoS108Ih0WdoFcpDJqkm5uzUIvCG4cMlRp/hO0Q6x9qic0URRLlryF336fpp9XLi2m6cxW0+zDX2B/7lN9fZmGPIsT0M+TkZpmD/YBJoZIdCL5lmT8l2QnZg6Ze+KmV4CeCTrZcPEdjOi2u51Evq+bRspm2pSFIR8IzuByjMAcFvto54VFpCU5naa5moMJfj8Wa1j+3A6LuiwRb5VLmsZfhR1WZQb5FdhypYyp8ADZ32jqh16qnEjyg7jN7SLX05SAxtyTLl/YZPw+KsWOLZEJa0dv5cemve7Hc4RyxWr+BKoPmvRhpuw8JDwiLSIZGWpToavI1Z1fDzFL4rZr+Dm5wI0LRKHH3ebfafaiER7lz3tj3DtaRDxm3fh9t6hPNza9dumyKh/5WLuHyHKLakiuzILwFmthC8hMGYW+v4BpWkLuz4/6GX6fDW/xNL7buJfbbfa+2C+dlZzsTkuOLq2w7tr3/b2D6et4+OrREVZPddnkbzAlwNLrozYWDTvU2ghYttWf1jOX5oPjDvCkQO1OzDgWVTIWss6C30q/duzUpSOaivvSkYkzHvek6MnkD8Z79QTjJYYc7WsP6LPRXV2KNYHKcyC+mMnqYgaMB1UelCwqR8Bm+eYWTebdisVQ39eXdP0fL5lXGtoKAAA"
|
|
228
|
+
}),
|
|
229
|
+
lang: z2.enum(["en", "de", "fr", "it"]).optional().meta({
|
|
230
|
+
description: "Language code",
|
|
231
|
+
example: "en"
|
|
232
|
+
})
|
|
233
|
+
}).meta({ description: "Request body for CHMED endpoint" });
|
|
121
234
|
|
|
122
235
|
// src/chmed16/middleware.ts
|
|
123
|
-
var validateBody = (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
236
|
+
var validateBody = (logger) => {
|
|
237
|
+
return (req, res, next) => {
|
|
238
|
+
try {
|
|
239
|
+
req.body = bodySchema.parse(req.body);
|
|
240
|
+
next();
|
|
241
|
+
} catch (e) {
|
|
242
|
+
logger.debug("Body validation failed", { error: e });
|
|
243
|
+
if (e instanceof import_zod.ZodError) {
|
|
244
|
+
const details = e.issues.map((err) => ({
|
|
245
|
+
path: err.path.join("."),
|
|
246
|
+
message: err.message,
|
|
247
|
+
code: err.code
|
|
248
|
+
}));
|
|
249
|
+
logger.warn("Validation failed", { details });
|
|
250
|
+
return res.status(400).json(
|
|
251
|
+
new AppError(
|
|
252
|
+
400,
|
|
253
|
+
"Invalid request body",
|
|
254
|
+
"BAD_REQUEST",
|
|
255
|
+
details
|
|
256
|
+
).toJSON()
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
logger.error("Unexpected error during body validation", { error: e });
|
|
260
|
+
return res.status(500).json(
|
|
261
|
+
new AppError(
|
|
262
|
+
500,
|
|
263
|
+
"Internal Server Error",
|
|
264
|
+
"INTERNAL_SERVER_ERROR"
|
|
265
|
+
).toJSON()
|
|
266
|
+
);
|
|
130
267
|
}
|
|
131
|
-
|
|
132
|
-
}
|
|
268
|
+
};
|
|
133
269
|
};
|
|
134
270
|
|
|
135
271
|
// src/chmed16/router.ts
|
|
@@ -137,12 +273,17 @@ var CHMED16ARouter = class {
|
|
|
137
273
|
static getRouter(logger) {
|
|
138
274
|
const router = import_express2.default.Router();
|
|
139
275
|
const controller = new CHMED16AController(logger);
|
|
140
|
-
router.post(
|
|
276
|
+
router.post(
|
|
277
|
+
"/chmed",
|
|
278
|
+
validateBody(logger),
|
|
279
|
+
controller.process.bind(controller)
|
|
280
|
+
);
|
|
141
281
|
return router;
|
|
142
282
|
}
|
|
143
283
|
};
|
|
144
284
|
|
|
145
285
|
// src/index.ts
|
|
286
|
+
var import_zod2 = require("zod");
|
|
146
287
|
var App = class {
|
|
147
288
|
constructor(logger, appVersion) {
|
|
148
289
|
this.logger = logger;
|
|
@@ -160,15 +301,51 @@ var App = class {
|
|
|
160
301
|
});
|
|
161
302
|
}
|
|
162
303
|
initializeRoutes() {
|
|
163
|
-
this.app.use("/
|
|
304
|
+
this.app.use("/", HealthRouter.getRouter(this.logger, this.appVersion));
|
|
164
305
|
this.app.use("/v1", CHMED16ARouter.getRouter(this.logger));
|
|
165
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* General error handler for all errors, so we can throw from anywhere in the project
|
|
309
|
+
*/
|
|
166
310
|
initializeErrorHandling() {
|
|
167
311
|
this.app.use(
|
|
168
|
-
//eslint-disable-next-line
|
|
169
|
-
(
|
|
170
|
-
this.logger.
|
|
171
|
-
|
|
312
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
313
|
+
(e, req, res, next) => {
|
|
314
|
+
this.logger.debug("General Error Handler");
|
|
315
|
+
this.logger.debug(e);
|
|
316
|
+
if (e instanceof import_zod2.ZodError) {
|
|
317
|
+
const details = e.issues.map((err) => ({
|
|
318
|
+
path: err.path.join("."),
|
|
319
|
+
message: err.message,
|
|
320
|
+
code: err.code
|
|
321
|
+
}));
|
|
322
|
+
return res.status(400).json(
|
|
323
|
+
new AppError(
|
|
324
|
+
400,
|
|
325
|
+
"Invalid request body",
|
|
326
|
+
"BAD_REQUEST",
|
|
327
|
+
details
|
|
328
|
+
).toJSON()
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (e instanceof AppError) {
|
|
332
|
+
if (e.code === "PARSE_ERROR") {
|
|
333
|
+
this.logger.warn(`Parse error: ${e.message}`);
|
|
334
|
+
} else if (e.code === "PDF_GENERATION_ERROR") {
|
|
335
|
+
this.logger.error(`PDF generation error: ${e.message}`);
|
|
336
|
+
} else {
|
|
337
|
+
this.logger.error(`App error: ${e.message}`);
|
|
338
|
+
}
|
|
339
|
+
return res.status(e.status).json(e.toJSON());
|
|
340
|
+
}
|
|
341
|
+
this.logger.error("Unexpected error:", e);
|
|
342
|
+
return res.status(500).json(
|
|
343
|
+
new AppError(
|
|
344
|
+
500,
|
|
345
|
+
"Internal Server Error",
|
|
346
|
+
"INTERNAL_SERVER_ERROR"
|
|
347
|
+
).toJSON()
|
|
348
|
+
);
|
|
172
349
|
}
|
|
173
350
|
);
|
|
174
351
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
1
17
|
var __async = (__this, __arguments, generator) => {
|
|
2
18
|
return new Promise((resolve, reject) => {
|
|
3
19
|
var fulfilled = (value) => {
|
|
@@ -53,50 +69,172 @@ import express2 from "express";
|
|
|
53
69
|
// src/chmed16/controller.ts
|
|
54
70
|
import { CHMEDParser, SDS } from "@vereign/chmed-parser";
|
|
55
71
|
import { PDFLib } from "@vereign/pdf-generator";
|
|
72
|
+
|
|
73
|
+
// src/app-error.ts
|
|
74
|
+
import * as z from "zod/v4";
|
|
75
|
+
var AppError = class extends Error {
|
|
76
|
+
constructor(status, message, code, details) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.status = status;
|
|
79
|
+
this.code = code;
|
|
80
|
+
this.details = details;
|
|
81
|
+
Error.captureStackTrace(this, this.constructor);
|
|
82
|
+
}
|
|
83
|
+
toJSON() {
|
|
84
|
+
return __spreadValues({
|
|
85
|
+
status: this.status,
|
|
86
|
+
code: this.code,
|
|
87
|
+
message: this.message
|
|
88
|
+
}, this.details ? { errors: this.details } : {});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var errorDetailsSchema = z.object({
|
|
92
|
+
path: z.string(),
|
|
93
|
+
message: z.string(),
|
|
94
|
+
code: z.string().optional()
|
|
95
|
+
});
|
|
96
|
+
var appErrorSchema = z.object({
|
|
97
|
+
status: z.number().int(),
|
|
98
|
+
code: z.string(),
|
|
99
|
+
message: z.string(),
|
|
100
|
+
errors: z.array(errorDetailsSchema).optional()
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// src/chmed16/controller.ts
|
|
56
104
|
var CHMED16AController = class {
|
|
57
105
|
constructor(logger) {
|
|
58
|
-
this.process = (req, res) => __async(this, null, function* () {
|
|
106
|
+
this.process = (req, res, next) => __async(this, null, function* () {
|
|
59
107
|
const { chmed, lang } = req.body;
|
|
60
108
|
const start = Date.now();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
pdf.generate(json, res);
|
|
70
|
-
this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
|
|
71
|
-
this.logger.info(`Total request time: ${Date.now() - start} ms`);
|
|
109
|
+
try {
|
|
110
|
+
this.logger.info("Processing started");
|
|
111
|
+
const json = yield this.parseChmedData(chmed, lang);
|
|
112
|
+
yield this.generatePdfResponse(json, res);
|
|
113
|
+
this.logger.info(`Total request time: ${Date.now() - start} ms`);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
next(error);
|
|
116
|
+
}
|
|
72
117
|
});
|
|
73
118
|
this.logger = logger;
|
|
74
119
|
this.sds = new SDS("https://sds-test.hin.ch", logger);
|
|
75
120
|
this.parser = new CHMEDParser(logger);
|
|
76
121
|
}
|
|
122
|
+
parseChmedData(chmed, lang) {
|
|
123
|
+
return __async(this, null, function* () {
|
|
124
|
+
try {
|
|
125
|
+
const parseStart = Date.now();
|
|
126
|
+
const json = yield this.parser.parse(chmed, this.sds, lang);
|
|
127
|
+
this.logger.info(`Parsing took ${Date.now() - parseStart} ms`);
|
|
128
|
+
return json;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
this.logger.error("Parsing failed:", error);
|
|
131
|
+
throw new AppError(
|
|
132
|
+
400,
|
|
133
|
+
"Failed to parse CHMED data. Please check the input format.",
|
|
134
|
+
"PARSE_ERROR",
|
|
135
|
+
[
|
|
136
|
+
{
|
|
137
|
+
path: "chmed",
|
|
138
|
+
message: "Invalid CHMED format or structure",
|
|
139
|
+
code: "invalid_format"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
generatePdfResponse(json, res) {
|
|
147
|
+
return __async(this, null, function* () {
|
|
148
|
+
const pdfStart = Date.now();
|
|
149
|
+
try {
|
|
150
|
+
const pdf = new PDFLib(this.logger);
|
|
151
|
+
const pdfBuffer = yield pdf.generateBuffer(json);
|
|
152
|
+
res.setHeader("Content-Type", "application/pdf");
|
|
153
|
+
res.setHeader("Content-Disposition", 'inline; filename="chmed16a.pdf"');
|
|
154
|
+
res.send(pdfBuffer);
|
|
155
|
+
this.logger.info(`PDF generation took ${Date.now() - pdfStart} ms`);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
this.logger.error("PDF generation failed:", error);
|
|
158
|
+
throw new AppError(
|
|
159
|
+
500,
|
|
160
|
+
"Failed to generate PDF. The parsed data may be invalid.",
|
|
161
|
+
"PDF_GENERATION_ERROR",
|
|
162
|
+
[
|
|
163
|
+
{
|
|
164
|
+
path: "pdf_generation",
|
|
165
|
+
message: "PDF creation failed due to invalid or corrupted data",
|
|
166
|
+
code: "generation_failed"
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
77
173
|
};
|
|
78
174
|
|
|
79
175
|
// src/chmed16/middleware.ts
|
|
80
176
|
import { ZodError } from "zod";
|
|
81
177
|
|
|
82
178
|
// src/chmed16/request.ts
|
|
83
|
-
import
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
179
|
+
import * as z2 from "zod/v4";
|
|
180
|
+
var chmedPattern = /^CHMED[0-9A-Za-z]+/;
|
|
181
|
+
var bodySchema = z2.object({
|
|
182
|
+
chmed: z2.string().min(5, "string is required").refine(
|
|
183
|
+
(val) => {
|
|
184
|
+
if (chmedPattern.test(val)) return true;
|
|
185
|
+
try {
|
|
186
|
+
const url = new URL(val);
|
|
187
|
+
const hash = url.hash.slice(1);
|
|
188
|
+
return chmedPattern.test(hash);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{ message: "chmed must be a valid CHMED string" }
|
|
194
|
+
).meta({
|
|
195
|
+
description: "A valid CHMED string",
|
|
196
|
+
example: "CHMED16A1H4sIAAAAAAAACr2WX2/iOBDAv4rl1wtZ/yEh8LTlaHuVll1Eu620qz4YGIiV4CDHqbbb49v0M9zTvfHFbhwaWvpvV6eqgIMzGTv+zYzHc0NHymkwjvZu6NFntQTao4PCKhrQT3e3x1ZNwKKgP0A1yrvtqMV5S0gUHYOZ4bOeCOipswBeoQ/WlM6qsgTidb7pFUolYwxv/tTu+k7Hv8Is8GYG2B2lhfFvY50uSZIEG/5QfjhUOvdKuKhwUS/l4wR0udKQh9MUNU5mJe19v6Fn1yuoV3Ku/AAeyjAOedjGJriMO6FgHC9hFCby7+HB8fBwwBin68uADmHmDTD4VLohGBzsF2RhSXssoONpPf0JqrTXwbYToRilMY8uG5HYiqJOeyfid6JuZyeSTSdu1PnuWWe7FPUQhzc4XW/Kr0ajhYUf8Jg2jnbP+fpyva6h9BRdaNx2Qv8KKqSUIo5rs22nkIhabDUG7sgWCE0FE6zFui3hJx24s2JrkhMzhrK2yQD1WcgCjs3/Y7v0q98uAGMhQ/WzbDEuvTVPi9nEgjFo2oAerFYnPj5QfqYmOTgHBK5cHhJtSFakOZhyc1tZmFvQsCQXPpAsUdU83/xTggkJTjIuFI4fffFdWNXWPK0mfm2NMSlnIolrqzSk7f9L+oDyF6QDWFkoS108Ih0WdoFcpDJqkm5uzUIvCG4cMlRp/hO0Q6x9qic0URRLlryF336fpp9XLi2m6cxW0+zDX2B/7lN9fZmGPIsT0M+TkZpmD/YBJoZIdCL5lmT8l2QnZg6Ze+KmV4CeCTrZcPEdjOi2u51Evq+bRspm2pSFIR8IzuByjMAcFvto54VFpCU5naa5moMJfj8Wa1j+3A6LuiwRb5VLmsZfhR1WZQb5FdhypYyp8ADZ32jqh16qnEjyg7jN7SLX05SAxtyTLl/YZPw+KsWOLZEJa0dv5cemve7Hc4RyxWr+BKoPmvRhpuw8JDwiLSIZGWpToavI1Z1fDzFL4rZr+Dm5wI0LRKHH3ebfafaiER7lz3tj3DtaRDxm3fh9t6hPNza9dumyKh/5WLuHyHKLakiuzILwFmthC8hMGYW+v4BpWkLuz4/6GX6fDW/xNL7buJfbbfa+2C+dlZzsTkuOLq2w7tr3/b2D6et4+OrREVZPddnkbzAlwNLrozYWDTvU2ghYttWf1jOX5oPjDvCkQO1OzDgWVTIWss6C30q/duzUpSOaivvSkYkzHvek6MnkD8Z79QTjJYYc7WsP6LPRXV2KNYHKcyC+mMnqYgaMB1UelCwqR8Bm+eYWTebdisVQ39eXdP0fL5lXGtoKAAA"
|
|
197
|
+
}),
|
|
198
|
+
lang: z2.enum(["en", "de", "fr", "it"]).optional().meta({
|
|
199
|
+
description: "Language code",
|
|
200
|
+
example: "en"
|
|
201
|
+
})
|
|
202
|
+
}).meta({ description: "Request body for CHMED endpoint" });
|
|
88
203
|
|
|
89
204
|
// src/chmed16/middleware.ts
|
|
90
|
-
var validateBody = (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
205
|
+
var validateBody = (logger) => {
|
|
206
|
+
return (req, res, next) => {
|
|
207
|
+
try {
|
|
208
|
+
req.body = bodySchema.parse(req.body);
|
|
209
|
+
next();
|
|
210
|
+
} catch (e) {
|
|
211
|
+
logger.debug("Body validation failed", { error: e });
|
|
212
|
+
if (e instanceof ZodError) {
|
|
213
|
+
const details = e.issues.map((err) => ({
|
|
214
|
+
path: err.path.join("."),
|
|
215
|
+
message: err.message,
|
|
216
|
+
code: err.code
|
|
217
|
+
}));
|
|
218
|
+
logger.warn("Validation failed", { details });
|
|
219
|
+
return res.status(400).json(
|
|
220
|
+
new AppError(
|
|
221
|
+
400,
|
|
222
|
+
"Invalid request body",
|
|
223
|
+
"BAD_REQUEST",
|
|
224
|
+
details
|
|
225
|
+
).toJSON()
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
logger.error("Unexpected error during body validation", { error: e });
|
|
229
|
+
return res.status(500).json(
|
|
230
|
+
new AppError(
|
|
231
|
+
500,
|
|
232
|
+
"Internal Server Error",
|
|
233
|
+
"INTERNAL_SERVER_ERROR"
|
|
234
|
+
).toJSON()
|
|
235
|
+
);
|
|
97
236
|
}
|
|
98
|
-
|
|
99
|
-
}
|
|
237
|
+
};
|
|
100
238
|
};
|
|
101
239
|
|
|
102
240
|
// src/chmed16/router.ts
|
|
@@ -104,12 +242,17 @@ var CHMED16ARouter = class {
|
|
|
104
242
|
static getRouter(logger) {
|
|
105
243
|
const router = express2.Router();
|
|
106
244
|
const controller = new CHMED16AController(logger);
|
|
107
|
-
router.post(
|
|
245
|
+
router.post(
|
|
246
|
+
"/chmed",
|
|
247
|
+
validateBody(logger),
|
|
248
|
+
controller.process.bind(controller)
|
|
249
|
+
);
|
|
108
250
|
return router;
|
|
109
251
|
}
|
|
110
252
|
};
|
|
111
253
|
|
|
112
254
|
// src/index.ts
|
|
255
|
+
import { ZodError as ZodError2 } from "zod";
|
|
113
256
|
var App = class {
|
|
114
257
|
constructor(logger, appVersion) {
|
|
115
258
|
this.logger = logger;
|
|
@@ -127,15 +270,51 @@ var App = class {
|
|
|
127
270
|
});
|
|
128
271
|
}
|
|
129
272
|
initializeRoutes() {
|
|
130
|
-
this.app.use("/
|
|
273
|
+
this.app.use("/", HealthRouter.getRouter(this.logger, this.appVersion));
|
|
131
274
|
this.app.use("/v1", CHMED16ARouter.getRouter(this.logger));
|
|
132
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* General error handler for all errors, so we can throw from anywhere in the project
|
|
278
|
+
*/
|
|
133
279
|
initializeErrorHandling() {
|
|
134
280
|
this.app.use(
|
|
135
|
-
//eslint-disable-next-line
|
|
136
|
-
(
|
|
137
|
-
this.logger.
|
|
138
|
-
|
|
281
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
282
|
+
(e, req, res, next) => {
|
|
283
|
+
this.logger.debug("General Error Handler");
|
|
284
|
+
this.logger.debug(e);
|
|
285
|
+
if (e instanceof ZodError2) {
|
|
286
|
+
const details = e.issues.map((err) => ({
|
|
287
|
+
path: err.path.join("."),
|
|
288
|
+
message: err.message,
|
|
289
|
+
code: err.code
|
|
290
|
+
}));
|
|
291
|
+
return res.status(400).json(
|
|
292
|
+
new AppError(
|
|
293
|
+
400,
|
|
294
|
+
"Invalid request body",
|
|
295
|
+
"BAD_REQUEST",
|
|
296
|
+
details
|
|
297
|
+
).toJSON()
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (e instanceof AppError) {
|
|
301
|
+
if (e.code === "PARSE_ERROR") {
|
|
302
|
+
this.logger.warn(`Parse error: ${e.message}`);
|
|
303
|
+
} else if (e.code === "PDF_GENERATION_ERROR") {
|
|
304
|
+
this.logger.error(`PDF generation error: ${e.message}`);
|
|
305
|
+
} else {
|
|
306
|
+
this.logger.error(`App error: ${e.message}`);
|
|
307
|
+
}
|
|
308
|
+
return res.status(e.status).json(e.toJSON());
|
|
309
|
+
}
|
|
310
|
+
this.logger.error("Unexpected error:", e);
|
|
311
|
+
return res.status(500).json(
|
|
312
|
+
new AppError(
|
|
313
|
+
500,
|
|
314
|
+
"Internal Server Error",
|
|
315
|
+
"INTERNAL_SERVER_ERROR"
|
|
316
|
+
).toJSON()
|
|
317
|
+
);
|
|
139
318
|
}
|
|
140
319
|
);
|
|
141
320
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vereign/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "core api library for e prescription project",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"test": "vitest run --coverage",
|
|
27
27
|
"test:watch": "vitest --coverage",
|
|
28
28
|
"semantic-release": "semantic-release",
|
|
29
|
-
"dev": "ts-node-dev ./src/dev-server.ts"
|
|
29
|
+
"dev": "ts-node-dev ./src/dev-server.ts",
|
|
30
|
+
"generate:swagger": "npx ts-node ./src/generate-swagger.ts"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@commitlint/cli": "^19.8.1",
|
|
@@ -56,11 +57,11 @@
|
|
|
56
57
|
"typescript": "^5.8.3",
|
|
57
58
|
"typescript-eslint": "^8.37.0",
|
|
58
59
|
"vitest": "^3.2.4",
|
|
59
|
-
"zod-
|
|
60
|
+
"zod-openapi": "^5.3.1"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
|
62
|
-
"@vereign/chmed-parser": "^1.4.
|
|
63
|
-
"@vereign/pdf-generator": "^1.1
|
|
63
|
+
"@vereign/chmed-parser": "^1.4.6",
|
|
64
|
+
"@vereign/pdf-generator": "^1.2.1",
|
|
64
65
|
"express": "^5.1.0",
|
|
65
66
|
"pdfkit": "^0.17.1",
|
|
66
67
|
"winston": "^3.17.0",
|