tsledge 0.1.30 → 0.1.32
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 +238 -366
- package/dist/index.js.map +4 -4
- package/dist/tests/main.js +419 -283
- package/dist/tests/main.js.map +7 -0
- package/package.json +2 -2
- package/dist/core/http.d.ts +0 -15
- package/dist/core/http.d.ts.map +0 -1
- package/dist/core/index.d.ts +0 -4
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/query-builder.d.ts +0 -96
- package/dist/core/query-builder.d.ts.map +0 -1
- package/dist/core/types.d.ts +0 -58
- package/dist/core/types.d.ts.map +0 -1
- package/dist/exitCodes.d.ts +0 -4
- package/dist/exitCodes.d.ts.map +0 -1
- package/dist/middleware/authentication/types.d.ts +0 -34
- package/dist/middleware/authentication/types.d.ts.map +0 -1
- package/dist/scripts/repl.js +0 -1089
- package/dist/scripts/repl.js.map +0 -7
- package/dist/utils/date.d.ts +0 -2
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/encoding.d.ts +0 -13
- package/dist/utils/encoding.d.ts.map +0 -1
- package/dist/utils/validation.d.ts +0 -16
- package/dist/utils/validation.d.ts.map +0 -1
package/dist/tests/main.js
CHANGED
|
@@ -1,37 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// src/utils/validation.ts
|
|
18
|
-
function validateString(value, fallback = null) {
|
|
19
|
-
if (value !== void 0 && value !== null && value !== "" && value !== "undefined" && value !== "null") {
|
|
20
|
-
return value;
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
14
|
}
|
|
22
|
-
return
|
|
23
|
-
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
24
18
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
var JwtRefreshSecret = process.env.JWT_REFRESH_SECRET || "refresh_secret";
|
|
19
|
+
// tests/main.ts
|
|
20
|
+
import dotenv from "dotenv";
|
|
28
21
|
|
|
29
|
-
// src/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
// src/index.ts
|
|
23
|
+
var src_exports = {};
|
|
24
|
+
__export(src_exports, {
|
|
25
|
+
AuthTokenBlocklistModel: () => AuthTokenBlocklistModel,
|
|
26
|
+
AuthUserModel: () => AuthUserModel,
|
|
27
|
+
FluentPatternHandler: () => FluentPatternHandler,
|
|
28
|
+
JoinRelation: () => JoinRelation,
|
|
29
|
+
JwtRefreshSecret: () => JwtRefreshSecret,
|
|
30
|
+
JwtSecret: () => JwtSecret,
|
|
31
|
+
QueryBuilder: () => QueryBuilder,
|
|
32
|
+
authLogin: () => authLogin,
|
|
33
|
+
authLogout: () => authLogout,
|
|
34
|
+
authRefresh: () => authRefresh,
|
|
35
|
+
authRegister: () => authRegister,
|
|
36
|
+
connectMongoDB: () => connectMongoDB,
|
|
37
|
+
createApp: () => createApp,
|
|
38
|
+
diskFileUpload: () => diskFileUpload,
|
|
39
|
+
errorLogger: () => errorLogger,
|
|
40
|
+
fluentRequestQueryAttributes: () => fluentRequestQueryAttributes,
|
|
41
|
+
insertCollectionRelations: () => insertCollectionRelations,
|
|
42
|
+
isDebug: () => isDebug,
|
|
43
|
+
jwtRefreshRequired: () => jwtRefreshRequired,
|
|
44
|
+
jwtRequired: () => jwtRequired,
|
|
45
|
+
memoryFileUpload: () => memoryFileUpload,
|
|
46
|
+
mergeCollectionRelations: () => mergeCollectionRelations,
|
|
47
|
+
requestLogger: () => requestLogger,
|
|
48
|
+
socketToken: () => socketToken,
|
|
49
|
+
verifyToken: () => verifyToken
|
|
50
|
+
});
|
|
51
|
+
__reExport(src_exports, tsledge_core_star);
|
|
52
|
+
import * as tsledge_core_star from "tsledge-core";
|
|
33
53
|
|
|
34
54
|
// src/db/mongodb.ts
|
|
55
|
+
import mongoose from "mongoose";
|
|
56
|
+
import { EXIT_CODE_GENERAL_ERROR, EXIT_CODE_INVALID_CONFIG, getCurrentDateString } from "tsledge-core";
|
|
35
57
|
async function connectMongoDB(uri) {
|
|
36
58
|
if (uri == null || uri == void 0 || uri.length == 0) {
|
|
37
59
|
console.error(`\u{1F6D1} [${getCurrentDateString()}] Error: MongoDB URI is not provided`);
|
|
@@ -50,24 +72,27 @@ async function connectMongoDB(uri) {
|
|
|
50
72
|
import multer from "multer";
|
|
51
73
|
import path from "node:path";
|
|
52
74
|
import fs from "node:fs";
|
|
53
|
-
var uploadDir = path.resolve(process.cwd(), "src", "files");
|
|
54
|
-
fs.mkdirSync(uploadDir, { recursive: true });
|
|
55
75
|
var sanitizeFilename = (name) => name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
56
|
-
var diskStorage =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
var diskStorage = (directory = "files") => {
|
|
77
|
+
let uploadDir = path.resolve(process.cwd(), "src", directory);
|
|
78
|
+
fs.mkdirSync(uploadDir, { recursive: true });
|
|
79
|
+
return multer.diskStorage({
|
|
80
|
+
destination: (_req, _file, next) => next(null, uploadDir),
|
|
81
|
+
filename: (_req, file, next) => {
|
|
82
|
+
const ext = path.extname(file.originalname);
|
|
83
|
+
const base = path.basename(file.originalname, ext);
|
|
84
|
+
const safe = sanitizeFilename(base);
|
|
85
|
+
const unique = Date.now() + "-" + Math.round(Math.random() * 1e9);
|
|
86
|
+
next(null, `${safe}-${unique}${ext}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
};
|
|
66
90
|
var memoryStorage = multer.memoryStorage();
|
|
67
|
-
var diskFileUpload = multer({ storage: diskStorage });
|
|
91
|
+
var diskFileUpload = (directory = "files") => multer({ storage: diskStorage(directory) });
|
|
68
92
|
var memoryFileUpload = multer({ storage: memoryStorage });
|
|
69
93
|
|
|
70
94
|
// src/middleware/logger.ts
|
|
95
|
+
import { getCurrentDateString as getCurrentDateString2 } from "tsledge-core";
|
|
71
96
|
function requestLogger(req, res, next) {
|
|
72
97
|
res.on("finish", () => {
|
|
73
98
|
let emoji = "";
|
|
@@ -78,19 +103,99 @@ function requestLogger(req, res, next) {
|
|
|
78
103
|
else if (res.statusCode >= 400 && res.statusCode < 500) emoji = "\u26A0\uFE0F";
|
|
79
104
|
else if (res.statusCode >= 500) emoji = "\u{1F525}";
|
|
80
105
|
console.log(
|
|
81
|
-
`${emoji} [${
|
|
106
|
+
`${emoji} [${getCurrentDateString2()}] ${req.method} ${req.originalUrl} - ${res.statusCode}`
|
|
82
107
|
);
|
|
83
108
|
});
|
|
84
109
|
next();
|
|
85
110
|
}
|
|
86
111
|
function errorLogger(err, req, res, next) {
|
|
87
|
-
console.error(`\u{1F6D1} [${
|
|
112
|
+
console.error(`\u{1F6D1} [${getCurrentDateString2()}] Error in ${req.method} ${req.originalUrl}:`, err);
|
|
88
113
|
res.status(500).json();
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
// src/middleware/authentication/session.ts
|
|
92
117
|
import express from "express";
|
|
93
118
|
import bcrypt from "bcrypt";
|
|
119
|
+
|
|
120
|
+
// src/utils/mongo-relation.ts
|
|
121
|
+
function insertCollectionRelations(model, relations, compareFunc, validateFunc = void 0) {
|
|
122
|
+
if (relations == void 0) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
let relationStack = [];
|
|
126
|
+
for (let relation of relations) {
|
|
127
|
+
if (validateFunc) {
|
|
128
|
+
relation = validateFunc(relation);
|
|
129
|
+
}
|
|
130
|
+
let inStack = false;
|
|
131
|
+
for (let duplicateRelationCheck of relationStack) {
|
|
132
|
+
if (compareFunc(relation, duplicateRelationCheck)) {
|
|
133
|
+
inStack = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!inStack) {
|
|
137
|
+
relationStack.push(relation);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (relationStack.length > 0) {
|
|
141
|
+
model.insertMany(relationStack);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function mergeCollectionRelations(model, relationsToMerge, match, compareFunc, validateFunc) {
|
|
145
|
+
const relationStack = [];
|
|
146
|
+
if (relationsToMerge != null) {
|
|
147
|
+
for (const relation of relationsToMerge) {
|
|
148
|
+
const validatedRelation = validateFunc(relation);
|
|
149
|
+
if (!relationStack.some(
|
|
150
|
+
(duplicateRelationCheck) => compareFunc(validatedRelation, duplicateRelationCheck)
|
|
151
|
+
)) {
|
|
152
|
+
relationStack.push(validatedRelation);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (relationStack.length === 0) {
|
|
157
|
+
await model.deleteMany(match);
|
|
158
|
+
} else {
|
|
159
|
+
const existingRels = await model.find(match).lean();
|
|
160
|
+
const toRemoveRels = [];
|
|
161
|
+
for (const rel of existingRels) {
|
|
162
|
+
if (!relationStack.some((relationFromStack) => compareFunc(rel, relationFromStack))) {
|
|
163
|
+
toRemoveRels.push(rel);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const toCreateRels = [];
|
|
167
|
+
for (const relationFromStack of relationStack) {
|
|
168
|
+
if (!existingRels.some((rel) => compareFunc(rel, relationFromStack))) {
|
|
169
|
+
toCreateRels.push(relationFromStack);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (toCreateRels.length > 0) {
|
|
173
|
+
await model.insertMany(toCreateRels);
|
|
174
|
+
}
|
|
175
|
+
if (toRemoveRels.length > 0) {
|
|
176
|
+
const idsToRemove = toRemoveRels.map((rel) => rel._id);
|
|
177
|
+
await model.deleteMany({ _id: { $in: idsToRemove } });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/utils/env.ts
|
|
183
|
+
var JwtSecret = process.env.JWT_SECRET || "secret";
|
|
184
|
+
var JwtRefreshSecret = process.env.JWT_REFRESH_SECRET || "refresh_secret";
|
|
185
|
+
function isDebug(func = void 0) {
|
|
186
|
+
const dbg = process.env.DEBUG;
|
|
187
|
+
if (dbg !== void 0) {
|
|
188
|
+
const v = dbg.toString().trim().toLowerCase();
|
|
189
|
+
let validation = v === "1" || v === "true" || v === "yes" || v === "on";
|
|
190
|
+
if (validation && func != void 0) {
|
|
191
|
+
func();
|
|
192
|
+
}
|
|
193
|
+
return validation;
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/middleware/authentication/session.ts
|
|
94
199
|
import jwt2 from "jsonwebtoken";
|
|
95
200
|
|
|
96
201
|
// src/middleware/authentication/validation.ts
|
|
@@ -198,7 +303,7 @@ async function validateJwt(req, res, next, jwtSecret) {
|
|
|
198
303
|
console.log("[WARN] JWT token for blocked user");
|
|
199
304
|
return res.sendStatus(FORBIDDEN);
|
|
200
305
|
}
|
|
201
|
-
res.locals.
|
|
306
|
+
res.locals.authUserPayload = result.payload;
|
|
202
307
|
res.locals.token = token;
|
|
203
308
|
next();
|
|
204
309
|
} catch (err) {
|
|
@@ -206,8 +311,30 @@ async function validateJwt(req, res, next, jwtSecret) {
|
|
|
206
311
|
return res.sendStatus(FORBIDDEN);
|
|
207
312
|
}
|
|
208
313
|
}
|
|
314
|
+
async function socketToken(_socket, _next) {
|
|
315
|
+
const token = _socket.handshake.auth?.token;
|
|
316
|
+
if (!token) {
|
|
317
|
+
return _next();
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
const verificationResult = await verifyToken(token, JwtSecret);
|
|
321
|
+
const isValidChar = verificationResult.isTokenValid && !verificationResult.isTokenExpired ? "\u{1F7E2}" : "\u{1F534}";
|
|
322
|
+
console.log(`${isValidChar} Socket verification result: ${JSON.stringify(verificationResult)}`);
|
|
323
|
+
if (!verificationResult.isTokenValid) {
|
|
324
|
+
if (verificationResult.isTokenExpired) {
|
|
325
|
+
return _next(new Error("expired_token"));
|
|
326
|
+
}
|
|
327
|
+
return _next(new Error("invalid_token"));
|
|
328
|
+
}
|
|
329
|
+
_socket.user = verificationResult.payload;
|
|
330
|
+
_next();
|
|
331
|
+
} catch (err) {
|
|
332
|
+
return _next(new Error("invalid_token"));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
209
335
|
|
|
210
336
|
// src/middleware/authentication/session.ts
|
|
337
|
+
import { encodeToBase64, validateString } from "tsledge-core";
|
|
211
338
|
var router = express.Router();
|
|
212
339
|
var FORBIDDEN2 = 403;
|
|
213
340
|
var BAD_REQUEST = 400;
|
|
@@ -245,12 +372,14 @@ async function generateCredentials(auth) {
|
|
|
245
372
|
async function authRegister(req, res, next) {
|
|
246
373
|
let { identifier = void 0, secret = void 0 } = req.body || {};
|
|
247
374
|
if (!identifier || !secret) {
|
|
248
|
-
|
|
375
|
+
res.sendStatus(FORBIDDEN2);
|
|
376
|
+
return;
|
|
249
377
|
}
|
|
250
378
|
identifier = identifier.toLowerCase();
|
|
251
379
|
let user = await AuthUserModel.findOne({ identifier });
|
|
252
380
|
if (user) {
|
|
253
|
-
|
|
381
|
+
res.sendStatus(BAD_REQUEST);
|
|
382
|
+
return;
|
|
254
383
|
}
|
|
255
384
|
res.locals.authUser = new AuthUserModel({
|
|
256
385
|
identifier,
|
|
@@ -261,32 +390,47 @@ async function authRegister(req, res, next) {
|
|
|
261
390
|
async function authLogin(req, res, next) {
|
|
262
391
|
let { identifier = void 0, secret = void 0 } = req.body || {};
|
|
263
392
|
if (!identifier || !secret) {
|
|
264
|
-
|
|
393
|
+
res.sendStatus(FORBIDDEN2);
|
|
394
|
+
return;
|
|
265
395
|
}
|
|
266
396
|
identifier = identifier.toLowerCase();
|
|
267
397
|
let user = await AuthUserModel.findOne({ identifier }).select("+secretHash");
|
|
268
398
|
if (!user || !user.secretHash) {
|
|
269
|
-
|
|
399
|
+
res.sendStatus(BAD_REQUEST);
|
|
400
|
+
return;
|
|
270
401
|
}
|
|
271
402
|
if (user.blockedSince) {
|
|
272
|
-
|
|
403
|
+
res.sendStatus(FORBIDDEN2);
|
|
404
|
+
return;
|
|
273
405
|
}
|
|
274
406
|
let isMatch = await bcrypt.compare(secret, user.secretHash);
|
|
275
407
|
if (!isMatch) {
|
|
276
|
-
|
|
408
|
+
res.sendStatus(BAD_REQUEST);
|
|
409
|
+
return;
|
|
277
410
|
}
|
|
278
411
|
let credentials = await generateCredentials(user);
|
|
279
412
|
if (!credentials) {
|
|
280
|
-
|
|
413
|
+
res.sendStatus(BAD_REQUEST);
|
|
414
|
+
return;
|
|
281
415
|
}
|
|
282
416
|
res.locals.credentials = credentials;
|
|
417
|
+
res.locals.authUser = user;
|
|
283
418
|
next();
|
|
284
419
|
}
|
|
285
420
|
async function authLogout(req, res, next) {
|
|
286
421
|
await jwtRefreshRequired(req, res, async () => {
|
|
422
|
+
let authUserPayload = res.locals.authUserPayload;
|
|
423
|
+
let user = await AuthUserModel.findOne({ identifier: authUserPayload.identifier }).select(
|
|
424
|
+
"+secretHash"
|
|
425
|
+
);
|
|
426
|
+
if (!user || !user.secretHash) {
|
|
427
|
+
res.sendStatus(BAD_REQUEST);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
287
430
|
const refreshToken = res.locals.token;
|
|
288
431
|
if (!refreshToken) {
|
|
289
|
-
|
|
432
|
+
res.sendStatus(BAD_REQUEST);
|
|
433
|
+
return;
|
|
290
434
|
}
|
|
291
435
|
const decoded = jwt2.decode(refreshToken);
|
|
292
436
|
const jti = decoded?.jti;
|
|
@@ -307,53 +451,66 @@ async function authLogout(req, res, next) {
|
|
|
307
451
|
}
|
|
308
452
|
}
|
|
309
453
|
}
|
|
454
|
+
res.locals.authUser = user;
|
|
310
455
|
next();
|
|
311
456
|
});
|
|
312
457
|
}
|
|
313
458
|
async function authRefresh(req, res, next) {
|
|
314
459
|
await jwtRefreshRequired(req, res, async () => {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const jti = decoded?.jti;
|
|
323
|
-
if (jti) {
|
|
324
|
-
const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
|
|
325
|
-
if (!existingBlock) {
|
|
326
|
-
await new AuthTokenBlocklistModel({ jti }).save();
|
|
327
|
-
}
|
|
460
|
+
let authUserPayload = res.locals.authUserPayload;
|
|
461
|
+
let user = await AuthUserModel.findOne({ identifier: authUserPayload.identifier }).select(
|
|
462
|
+
"+secretHash"
|
|
463
|
+
);
|
|
464
|
+
if (!user || !user.secretHash) {
|
|
465
|
+
res.sendStatus(BAD_REQUEST);
|
|
466
|
+
return;
|
|
328
467
|
}
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
468
|
+
const refreshToken = res.locals.token;
|
|
469
|
+
if (!refreshToken) {
|
|
470
|
+
res.sendStatus(BAD_REQUEST);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const decoded = jwt2.decode(refreshToken);
|
|
475
|
+
const jti = decoded?.jti;
|
|
476
|
+
if (jti) {
|
|
477
|
+
const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
|
|
478
|
+
if (!existingBlock) {
|
|
479
|
+
await new AuthTokenBlocklistModel({ jti }).save();
|
|
339
480
|
}
|
|
340
481
|
}
|
|
482
|
+
let accessToken = validateString(req.body?.access_token);
|
|
483
|
+
if (accessToken) {
|
|
484
|
+
const accessTokenDecoded = jwt2.decode(accessToken);
|
|
485
|
+
let accessTokenJti = accessTokenDecoded?.jti;
|
|
486
|
+
if (accessTokenJti) {
|
|
487
|
+
const existing = await AuthTokenBlocklistModel.findOne({
|
|
488
|
+
jti: accessTokenJti
|
|
489
|
+
});
|
|
490
|
+
if (!existing) {
|
|
491
|
+
await new AuthTokenBlocklistModel({ jti: accessTokenJti }).save();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const payload = jwt2.verify(refreshToken, JwtRefreshSecret);
|
|
496
|
+
let credentials = await generateCredentials(payload);
|
|
497
|
+
if (!credentials) {
|
|
498
|
+
res.sendStatus(BAD_REQUEST);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
res.locals.authUser = user;
|
|
502
|
+
res.locals.credentials = credentials;
|
|
503
|
+
next();
|
|
504
|
+
} catch (err) {
|
|
505
|
+
console.log("[WARN] refreshing JWT:", err);
|
|
506
|
+
res.sendStatus(BAD_REQUEST);
|
|
507
|
+
return;
|
|
341
508
|
}
|
|
342
|
-
|
|
343
|
-
let credentials = await generateCredentials(payload);
|
|
344
|
-
if (!credentials) {
|
|
345
|
-
return res.sendStatus(BAD_REQUEST);
|
|
346
|
-
}
|
|
347
|
-
res.locals.credentials = credentials;
|
|
348
|
-
next();
|
|
349
|
-
} catch (err) {
|
|
350
|
-
console.log("[WARN] refreshing JWT:", err);
|
|
351
|
-
return res.sendStatus(BAD_REQUEST);
|
|
352
|
-
}
|
|
509
|
+
});
|
|
353
510
|
}
|
|
354
511
|
|
|
355
512
|
// src/fluent-interface/fluent-pattern-handler.ts
|
|
356
|
-
import
|
|
513
|
+
import mongoose4 from "mongoose";
|
|
357
514
|
|
|
358
515
|
// src/fluent-interface/types.ts
|
|
359
516
|
var fluentRequestQueryAttributes = {
|
|
@@ -366,34 +523,180 @@ var fluentRequestQueryAttributes = {
|
|
|
366
523
|
excluded: ""
|
|
367
524
|
};
|
|
368
525
|
|
|
369
|
-
// src/
|
|
370
|
-
import
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
526
|
+
// src/fluent-interface/fluent-pattern-handler.ts
|
|
527
|
+
import { Codec } from "tsledge-core";
|
|
528
|
+
var FluentPatternHandler = class _FluentPatternHandler {
|
|
529
|
+
/**
|
|
530
|
+
* Constructor for FluentPatternHandler.
|
|
531
|
+
* @param execMiddleware - Optional array of middleware functions to be executed before the main query execution in the exec method.
|
|
532
|
+
*/
|
|
533
|
+
constructor(execMiddleware = []) {
|
|
534
|
+
/**
|
|
535
|
+
* Array of middleware functions to be executed before the main query execution in the exec method. Each function receives the execution parameters and can modify them as needed.
|
|
536
|
+
*/
|
|
537
|
+
this._execMiddlewareFunctions = [];
|
|
538
|
+
if (_FluentPatternHandler._singleton) {
|
|
539
|
+
throw new Error(
|
|
540
|
+
"FluentPatternHandler is a singleton class. Use FluentPatternHandler.getInstance() to access the instance."
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
this._execMiddlewareFunctions = execMiddleware;
|
|
544
|
+
_FluentPatternHandler._singleton = this;
|
|
377
545
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Initializes the singleton instance of FluentPatternHandler with the provided options.
|
|
548
|
+
* @param execMiddleware - Optional array of middleware functions to be executed before the main query execution in the exec method.
|
|
549
|
+
* @returns Singleton instance of FluentPatternHandler.
|
|
550
|
+
*/
|
|
551
|
+
static init(execMiddleware = []) {
|
|
552
|
+
if (_FluentPatternHandler._singleton != void 0) {
|
|
553
|
+
throw new Error("FluentPatternHandler is already initialized");
|
|
381
554
|
}
|
|
382
|
-
|
|
555
|
+
_FluentPatternHandler._singleton = new _FluentPatternHandler(execMiddleware);
|
|
556
|
+
return _FluentPatternHandler._singleton;
|
|
383
557
|
}
|
|
384
|
-
|
|
385
|
-
|
|
558
|
+
/**
|
|
559
|
+
* Returns the singleton instance of FluentPatternHandler.
|
|
560
|
+
* @returns Singleton instance of FluentPatternHandler.
|
|
561
|
+
*/
|
|
562
|
+
static getInstance() {
|
|
563
|
+
if (_FluentPatternHandler._singleton == void 0) {
|
|
564
|
+
throw new Error(
|
|
565
|
+
"FluentPatternHandler instance has not been created yet. Please create an instance before calling getInstance()."
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
return _FluentPatternHandler._singleton;
|
|
386
569
|
}
|
|
387
|
-
|
|
388
|
-
|
|
570
|
+
/**
|
|
571
|
+
* Parses and validates query parameters from the request.
|
|
572
|
+
* @param query - The query object from the request.
|
|
573
|
+
* @returns Parsed query parameters.
|
|
574
|
+
*/
|
|
575
|
+
_parseFluentRequestQuery(query) {
|
|
576
|
+
const { filter, limit = "5", offset = "0", id, excluded: excludedJSON, ids: idsJSON } = query;
|
|
577
|
+
const queryKeys = Object.keys(fluentRequestQueryAttributes);
|
|
578
|
+
let filterFields = {};
|
|
579
|
+
for (const [key, value] of Object.entries(query)) {
|
|
580
|
+
if (queryKeys.includes(key)) continue;
|
|
581
|
+
if (value == void 0) continue;
|
|
582
|
+
try {
|
|
583
|
+
filterFields[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
584
|
+
} catch (e) {
|
|
585
|
+
filterFields[key] = String(value);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
let excluded;
|
|
589
|
+
if (excludedJSON) {
|
|
590
|
+
try {
|
|
591
|
+
excluded = JSON.parse(excludedJSON);
|
|
592
|
+
if (!Array.isArray(excluded)) throw new Error("Excluded must be an array");
|
|
593
|
+
} catch (error) {
|
|
594
|
+
console.warn("[FluentPatternHandler] Invalid excluded parameter:", error);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
let ids;
|
|
598
|
+
if (idsJSON) {
|
|
599
|
+
try {
|
|
600
|
+
ids = JSON.parse(idsJSON);
|
|
601
|
+
if (!Array.isArray(ids)) throw new Error("Ids must be an array");
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.warn("[FluentPatternHandler] Invalid ids parameter:", error);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return { filter, limit, offset, id, excluded, ids, filterFields };
|
|
389
607
|
}
|
|
390
|
-
|
|
391
|
-
|
|
608
|
+
/**
|
|
609
|
+
* Applies filters to the query builder based on parsed parameters and options.
|
|
610
|
+
* @param queryBuilder - The QueryBuilder instance.
|
|
611
|
+
* @param params - Parsed query parameters.
|
|
612
|
+
* @param config - Query request configuration.
|
|
613
|
+
*/
|
|
614
|
+
_applyParameters(queryBuilder, params) {
|
|
615
|
+
const { id, ids, filter, excluded } = params;
|
|
616
|
+
if (id) {
|
|
617
|
+
queryBuilder.match({ _id: new mongoose4.Types.ObjectId(id) });
|
|
618
|
+
} else if (ids && ids.length > 0) {
|
|
619
|
+
const objectIds = ids.map((id2) => new mongoose4.Types.ObjectId(id2));
|
|
620
|
+
queryBuilder.match({ _id: { $in: objectIds } });
|
|
621
|
+
} else {
|
|
622
|
+
const modelFilterFields = this._getFilterFieldsForModel(queryBuilder.getConfig().model);
|
|
623
|
+
if (filter && modelFilterFields.length > 0) {
|
|
624
|
+
const ors = modelFilterFields.map((field) => ({
|
|
625
|
+
[field]: { $regex: filter, $options: "i" }
|
|
626
|
+
}));
|
|
627
|
+
queryBuilder.match({ $or: ors });
|
|
628
|
+
}
|
|
629
|
+
if (params.filterFields && Object.keys(params.filterFields).length > 0) {
|
|
630
|
+
for (const [field, value] of Object.entries(params.filterFields)) {
|
|
631
|
+
if (!modelFilterFields.includes(field)) {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const match = {};
|
|
635
|
+
match[field] = { $regex: value, $options: "i" };
|
|
636
|
+
queryBuilder.match(match);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (excluded && excluded.length > 0) {
|
|
640
|
+
const objectIds = excluded.map((id2) => new mongoose4.Types.ObjectId(id2));
|
|
641
|
+
queryBuilder.match({ _id: { $nin: objectIds } });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
392
644
|
}
|
|
393
|
-
|
|
394
|
-
|
|
645
|
+
/**
|
|
646
|
+
* Generates filter fields for the given Mongoose model based on schema options.
|
|
647
|
+
* @param model - The Mongoose model.
|
|
648
|
+
* @returns Array of filter fields.
|
|
649
|
+
*/
|
|
650
|
+
_getFilterFieldsForModel(model) {
|
|
651
|
+
let filterFields = [];
|
|
652
|
+
let schema = model.schema;
|
|
653
|
+
schema.eachPath((path2, type) => {
|
|
654
|
+
if (type?.options?.filter == true) {
|
|
655
|
+
filterFields.push(path2);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
return filterFields;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Builds execution parameters for the query builder.
|
|
662
|
+
* @param params - Parsed query parameters.
|
|
663
|
+
* @returns Execution parameters.
|
|
664
|
+
*/
|
|
665
|
+
_buildExecutionConfig(params) {
|
|
666
|
+
const { id, limit, offset } = params;
|
|
667
|
+
return {
|
|
668
|
+
isOne: Boolean(id),
|
|
669
|
+
limit: limit === "full" ? void 0 : parseInt(limit || "5", 10),
|
|
670
|
+
skip: parseInt(offset || "0", 10)
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Executes the query builder with applied filters and returns the result.
|
|
675
|
+
* @param params Execution parameters including the query builder and request query.
|
|
676
|
+
* @returns
|
|
677
|
+
*/
|
|
678
|
+
async exec(params) {
|
|
679
|
+
try {
|
|
680
|
+
if (this._execMiddlewareFunctions && this._execMiddlewareFunctions.length > 0) {
|
|
681
|
+
for (const func of this._execMiddlewareFunctions) {
|
|
682
|
+
await func(params);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const queryParams = this._parseFluentRequestQuery(params.req.query);
|
|
686
|
+
this._applyParameters(params.queryBuilder, queryParams);
|
|
687
|
+
const execConfig = this._buildExecutionConfig(queryParams);
|
|
688
|
+
return await params.queryBuilder.exec(execConfig);
|
|
689
|
+
} catch (err) {
|
|
690
|
+
console.error("[ERROR - FluentPatternHandler]", err);
|
|
691
|
+
return new Codec({ data: [], meta: { total: 0 } }, 500);
|
|
692
|
+
}
|
|
395
693
|
}
|
|
396
694
|
};
|
|
695
|
+
|
|
696
|
+
// src/query-builder/query-builder.ts
|
|
697
|
+
import mongoose5 from "mongoose";
|
|
698
|
+
|
|
699
|
+
// src/query-builder/types.ts
|
|
397
700
|
var JoinRelation = class {
|
|
398
701
|
constructor(localField, ref, alias = void 0) {
|
|
399
702
|
this.ref = ref;
|
|
@@ -402,7 +705,8 @@ var JoinRelation = class {
|
|
|
402
705
|
}
|
|
403
706
|
};
|
|
404
707
|
|
|
405
|
-
// src/
|
|
708
|
+
// src/query-builder/query-builder.ts
|
|
709
|
+
import { Codec as Codec2 } from "tsledge-core";
|
|
406
710
|
var QueryBuilder = class {
|
|
407
711
|
constructor(config) {
|
|
408
712
|
this._matchConditions = {};
|
|
@@ -505,7 +809,7 @@ var QueryBuilder = class {
|
|
|
505
809
|
if (schematype.options?.ref) {
|
|
506
810
|
const refModelName = schematype.options.ref;
|
|
507
811
|
try {
|
|
508
|
-
const refModel =
|
|
812
|
+
const refModel = mongoose5.model(refModelName);
|
|
509
813
|
let alias = schematype.options?.alias ?? refModel.collection.name;
|
|
510
814
|
this.join(new JoinRelation(fullPath, refModel, alias));
|
|
511
815
|
} catch (err) {
|
|
@@ -514,10 +818,10 @@ var QueryBuilder = class {
|
|
|
514
818
|
);
|
|
515
819
|
}
|
|
516
820
|
}
|
|
517
|
-
if (schematype instanceof
|
|
821
|
+
if (schematype instanceof mongoose5.Schema.Types.Array && schematype.caster?.options?.ref) {
|
|
518
822
|
const refModelName = schematype.caster.options.ref;
|
|
519
823
|
try {
|
|
520
|
-
const refModel =
|
|
824
|
+
const refModel = mongoose5.model(refModelName);
|
|
521
825
|
let alias = schematype.caster.options?.alias ?? refModel.collection.name;
|
|
522
826
|
this.join(new JoinRelation(fullPath, refModel, alias));
|
|
523
827
|
} catch (err) {
|
|
@@ -599,10 +903,10 @@ var QueryBuilder = class {
|
|
|
599
903
|
]);
|
|
600
904
|
const totalCount = countRes && countRes[0] ? countRes[0].n : 0;
|
|
601
905
|
const documents = config && config.isOne ? await this._processSingleDocument(res) : await this._processMultipleDocuments(res);
|
|
602
|
-
return new
|
|
906
|
+
return new Codec2({ data: documents, meta: { total: totalCount } }, 200);
|
|
603
907
|
} catch (err) {
|
|
604
908
|
console.error("[ERROR - QueryBuilder]", err);
|
|
605
|
-
return new
|
|
909
|
+
return new Codec2({ data: [], meta: { total: 0 } }, 500);
|
|
606
910
|
}
|
|
607
911
|
}
|
|
608
912
|
/**
|
|
@@ -643,175 +947,6 @@ var QueryBuilder = class {
|
|
|
643
947
|
}
|
|
644
948
|
};
|
|
645
949
|
|
|
646
|
-
// src/fluent-interface/fluent-pattern-handler.ts
|
|
647
|
-
var FluentPatternHandler = class _FluentPatternHandler {
|
|
648
|
-
/**
|
|
649
|
-
* Constructor for FluentPatternHandler.
|
|
650
|
-
* @param execMiddleware - Optional array of middleware functions to be executed before the main query execution in the exec method.
|
|
651
|
-
*/
|
|
652
|
-
constructor(execMiddleware = []) {
|
|
653
|
-
/**
|
|
654
|
-
* Array of middleware functions to be executed before the main query execution in the exec method. Each function receives the execution parameters and can modify them as needed.
|
|
655
|
-
*/
|
|
656
|
-
this._execMiddlewareFunctions = [];
|
|
657
|
-
if (_FluentPatternHandler._singleton) {
|
|
658
|
-
throw new Error(
|
|
659
|
-
"FluentPatternHandler is a singleton class. Use FluentPatternHandler.getInstance() to access the instance."
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
this._execMiddlewareFunctions = execMiddleware;
|
|
663
|
-
_FluentPatternHandler._singleton = this;
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Initializes the singleton instance of FluentPatternHandler with the provided options.
|
|
667
|
-
* @param execMiddleware - Optional array of middleware functions to be executed before the main query execution in the exec method.
|
|
668
|
-
* @returns Singleton instance of FluentPatternHandler.
|
|
669
|
-
*/
|
|
670
|
-
static init(execMiddleware = []) {
|
|
671
|
-
if (_FluentPatternHandler._singleton != void 0) {
|
|
672
|
-
throw new Error("FluentPatternHandler is already initialized");
|
|
673
|
-
}
|
|
674
|
-
_FluentPatternHandler._singleton = new _FluentPatternHandler(execMiddleware);
|
|
675
|
-
return _FluentPatternHandler._singleton;
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Returns the singleton instance of FluentPatternHandler.
|
|
679
|
-
* @returns Singleton instance of FluentPatternHandler.
|
|
680
|
-
*/
|
|
681
|
-
static getInstance() {
|
|
682
|
-
if (_FluentPatternHandler._singleton == void 0) {
|
|
683
|
-
throw new Error(
|
|
684
|
-
"FluentPatternHandler instance has not been created yet. Please create an instance before calling getInstance()."
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
return _FluentPatternHandler._singleton;
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* Parses and validates query parameters from the request.
|
|
691
|
-
* @param query - The query object from the request.
|
|
692
|
-
* @returns Parsed query parameters.
|
|
693
|
-
*/
|
|
694
|
-
_parseFluentRequestQuery(query) {
|
|
695
|
-
const { filter, limit = "5", offset = "0", id, excluded: excludedJSON, ids: idsJSON } = query;
|
|
696
|
-
const queryKeys = Object.keys(fluentRequestQueryAttributes);
|
|
697
|
-
let filterFields = {};
|
|
698
|
-
for (const [key, value] of Object.entries(query)) {
|
|
699
|
-
if (queryKeys.includes(key)) continue;
|
|
700
|
-
if (value == void 0) continue;
|
|
701
|
-
try {
|
|
702
|
-
filterFields[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
703
|
-
} catch (e) {
|
|
704
|
-
filterFields[key] = String(value);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
let excluded;
|
|
708
|
-
if (excludedJSON) {
|
|
709
|
-
try {
|
|
710
|
-
excluded = JSON.parse(excludedJSON);
|
|
711
|
-
if (!Array.isArray(excluded)) throw new Error("Excluded must be an array");
|
|
712
|
-
} catch (error) {
|
|
713
|
-
console.warn("[FluentPatternHandler] Invalid excluded parameter:", error);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
let ids;
|
|
717
|
-
if (idsJSON) {
|
|
718
|
-
try {
|
|
719
|
-
ids = JSON.parse(idsJSON);
|
|
720
|
-
if (!Array.isArray(ids)) throw new Error("Ids must be an array");
|
|
721
|
-
} catch (error) {
|
|
722
|
-
console.warn("[FluentPatternHandler] Invalid ids parameter:", error);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
return { filter, limit, offset, id, excluded, ids, filterFields };
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Applies filters to the query builder based on parsed parameters and options.
|
|
729
|
-
* @param queryBuilder - The QueryBuilder instance.
|
|
730
|
-
* @param params - Parsed query parameters.
|
|
731
|
-
* @param config - Query request configuration.
|
|
732
|
-
*/
|
|
733
|
-
_applyParameters(queryBuilder, params) {
|
|
734
|
-
const { id, ids, filter, excluded } = params;
|
|
735
|
-
if (id) {
|
|
736
|
-
queryBuilder.match({ _id: new mongoose5.Types.ObjectId(id) });
|
|
737
|
-
} else if (ids && ids.length > 0) {
|
|
738
|
-
const objectIds = ids.map((id2) => new mongoose5.Types.ObjectId(id2));
|
|
739
|
-
queryBuilder.match({ _id: { $in: objectIds } });
|
|
740
|
-
} else {
|
|
741
|
-
const modelFilterFields = this._getFilterFieldsForModel(queryBuilder.getConfig().model);
|
|
742
|
-
if (filter && modelFilterFields.length > 0) {
|
|
743
|
-
const ors = modelFilterFields.map((field) => ({
|
|
744
|
-
[field]: { $regex: filter, $options: "i" }
|
|
745
|
-
}));
|
|
746
|
-
queryBuilder.match({ $or: ors });
|
|
747
|
-
}
|
|
748
|
-
if (params.filterFields && Object.keys(params.filterFields).length > 0) {
|
|
749
|
-
for (const [field, value] of Object.entries(params.filterFields)) {
|
|
750
|
-
if (!modelFilterFields.includes(field)) {
|
|
751
|
-
continue;
|
|
752
|
-
}
|
|
753
|
-
const match = {};
|
|
754
|
-
match[field] = { $regex: value, $options: "i" };
|
|
755
|
-
queryBuilder.match(match);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
if (excluded && excluded.length > 0) {
|
|
759
|
-
const objectIds = excluded.map((id2) => new mongoose5.Types.ObjectId(id2));
|
|
760
|
-
queryBuilder.match({ _id: { $nin: objectIds } });
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Generates filter fields for the given Mongoose model based on schema options.
|
|
766
|
-
* @param model - The Mongoose model.
|
|
767
|
-
* @returns Array of filter fields.
|
|
768
|
-
*/
|
|
769
|
-
_getFilterFieldsForModel(model) {
|
|
770
|
-
let filterFields = [];
|
|
771
|
-
let schema = model.schema;
|
|
772
|
-
schema.eachPath((path2, type) => {
|
|
773
|
-
if (type?.options?.filter == true) {
|
|
774
|
-
filterFields.push(path2);
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
return filterFields;
|
|
778
|
-
}
|
|
779
|
-
/**
|
|
780
|
-
* Builds execution parameters for the query builder.
|
|
781
|
-
* @param params - Parsed query parameters.
|
|
782
|
-
* @returns Execution parameters.
|
|
783
|
-
*/
|
|
784
|
-
_buildExecutionConfig(params) {
|
|
785
|
-
const { id, limit, offset } = params;
|
|
786
|
-
return {
|
|
787
|
-
isOne: Boolean(id),
|
|
788
|
-
limit: limit === "full" ? void 0 : parseInt(limit || "5", 10),
|
|
789
|
-
skip: parseInt(offset || "0", 10)
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
/**
|
|
793
|
-
* Executes the query builder with applied filters and returns the result.
|
|
794
|
-
* @param params Execution parameters including the query builder and request query.
|
|
795
|
-
* @returns
|
|
796
|
-
*/
|
|
797
|
-
async exec(params) {
|
|
798
|
-
try {
|
|
799
|
-
if (this._execMiddlewareFunctions && this._execMiddlewareFunctions.length > 0) {
|
|
800
|
-
this._execMiddlewareFunctions.forEach((func) => {
|
|
801
|
-
func(params);
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
const queryParams = this._parseFluentRequestQuery(params.req.query);
|
|
805
|
-
this._applyParameters(params.queryBuilder, queryParams);
|
|
806
|
-
const execConfig = this._buildExecutionConfig(queryParams);
|
|
807
|
-
return await params.queryBuilder.exec(execConfig);
|
|
808
|
-
} catch (err) {
|
|
809
|
-
console.error("[ERROR - FluentPatternHandler]", err);
|
|
810
|
-
return new Codec({ data: [], meta: { total: 0 } }, 500);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
|
|
815
950
|
// src/app.ts
|
|
816
951
|
import express2 from "express";
|
|
817
952
|
import cors from "cors";
|
|
@@ -1036,3 +1171,4 @@ setup();
|
|
|
1036
1171
|
export {
|
|
1037
1172
|
setup
|
|
1038
1173
|
};
|
|
1174
|
+
//# sourceMappingURL=main.js.map
|