@exyconn/common 2.1.0 → 2.3.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/README.md +969 -261
- package/dist/client/hooks/index.d.mts +1042 -0
- package/dist/client/hooks/index.d.ts +1042 -0
- package/dist/client/hooks/index.js +2276 -0
- package/dist/client/hooks/index.js.map +1 -0
- package/dist/client/hooks/index.mjs +2217 -0
- package/dist/client/hooks/index.mjs.map +1 -0
- package/dist/client/http/index.d.mts +217 -49
- package/dist/client/http/index.d.ts +217 -49
- package/dist/client/http/index.js +473 -94
- package/dist/client/http/index.js.map +1 -1
- package/dist/client/http/index.mjs +441 -84
- package/dist/client/http/index.mjs.map +1 -1
- package/dist/client/index.d.mts +6 -4
- package/dist/client/index.d.ts +6 -4
- package/dist/client/index.js +481 -319
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +449 -290
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/utils/index.d.mts +3 -279
- package/dist/client/utils/index.d.ts +3 -279
- package/dist/client/web/index.d.mts +1461 -0
- package/dist/client/web/index.d.ts +1461 -0
- package/dist/client/web/index.js +2681 -0
- package/dist/client/web/index.js.map +1 -0
- package/dist/client/web/index.mjs +2618 -0
- package/dist/client/web/index.mjs.map +1 -0
- package/dist/data/brand-identity.d.mts +149 -0
- package/dist/data/brand-identity.d.ts +149 -0
- package/dist/data/brand-identity.js +235 -0
- package/dist/data/brand-identity.js.map +1 -0
- package/dist/data/brand-identity.mjs +220 -0
- package/dist/data/brand-identity.mjs.map +1 -0
- package/dist/data/countries.d.mts +61 -0
- package/dist/data/countries.d.ts +61 -0
- package/dist/data/countries.js +987 -0
- package/dist/data/countries.js.map +1 -0
- package/dist/data/countries.mjs +971 -0
- package/dist/data/countries.mjs.map +1 -0
- package/dist/data/currencies.d.mts +19 -0
- package/dist/data/currencies.d.ts +19 -0
- package/dist/data/currencies.js +162 -0
- package/dist/data/currencies.js.map +1 -0
- package/dist/data/currencies.mjs +153 -0
- package/dist/data/currencies.mjs.map +1 -0
- package/dist/data/index.d.mts +7 -0
- package/dist/data/index.d.ts +7 -0
- package/dist/data/index.js +2087 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/index.mjs +1948 -0
- package/dist/data/index.mjs.map +1 -0
- package/dist/data/phone-codes.d.mts +15 -0
- package/dist/data/phone-codes.d.ts +15 -0
- package/dist/data/phone-codes.js +219 -0
- package/dist/data/phone-codes.js.map +1 -0
- package/dist/data/phone-codes.mjs +211 -0
- package/dist/data/phone-codes.mjs.map +1 -0
- package/dist/data/regex.d.mts +287 -0
- package/dist/data/regex.d.ts +287 -0
- package/dist/data/regex.js +306 -0
- package/dist/data/regex.js.map +1 -0
- package/dist/data/regex.mjs +208 -0
- package/dist/data/regex.mjs.map +1 -0
- package/dist/data/timezones.d.mts +16 -0
- package/dist/data/timezones.d.ts +16 -0
- package/dist/data/timezones.js +98 -0
- package/dist/data/timezones.js.map +1 -0
- package/dist/data/timezones.mjs +89 -0
- package/dist/data/timezones.mjs.map +1 -0
- package/dist/index-BZf42T3R.d.mts +305 -0
- package/dist/index-CF0D8PGE.d.ts +305 -0
- package/dist/index-Ckhm_HaX.d.mts +138 -0
- package/dist/index-DKn4raO7.d.ts +222 -0
- package/dist/index-NS8dS0p9.d.mts +222 -0
- package/dist/index-Nqm5_lwT.d.ts +188 -0
- package/dist/index-br6POSyA.d.ts +138 -0
- package/dist/index-jBi3V6e5.d.mts +188 -0
- package/dist/index.d.mts +21 -580
- package/dist/index.d.ts +21 -580
- package/dist/index.js +1839 -347
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1850 -359
- package/dist/index.mjs.map +1 -1
- package/dist/packageCheck-B_qfsD6R.d.ts +280 -0
- package/dist/packageCheck-C2_FT_Rl.d.mts +280 -0
- package/dist/server/configs/index.d.mts +602 -0
- package/dist/server/configs/index.d.ts +602 -0
- package/dist/server/configs/index.js +707 -0
- package/dist/server/configs/index.js.map +1 -0
- package/dist/server/configs/index.mjs +665 -0
- package/dist/server/configs/index.mjs.map +1 -0
- package/dist/server/index.d.mts +4 -1
- package/dist/server/index.d.ts +4 -1
- package/dist/server/index.js +1330 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1286 -2
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/middleware/index.d.mts +283 -2
- package/dist/server/middleware/index.d.ts +283 -2
- package/dist/server/middleware/index.js +761 -0
- package/dist/server/middleware/index.js.map +1 -1
- package/dist/server/middleware/index.mjs +751 -1
- package/dist/server/middleware/index.mjs.map +1 -1
- package/dist/shared/config/index.d.mts +40 -0
- package/dist/shared/config/index.d.ts +40 -0
- package/dist/shared/config/index.js +58 -0
- package/dist/shared/config/index.js.map +1 -0
- package/dist/shared/config/index.mjs +51 -0
- package/dist/shared/config/index.mjs.map +1 -0
- package/dist/shared/constants/index.d.mts +593 -0
- package/dist/shared/constants/index.d.ts +593 -0
- package/dist/shared/constants/index.js +391 -0
- package/dist/shared/constants/index.js.map +1 -0
- package/dist/shared/constants/index.mjs +360 -0
- package/dist/shared/constants/index.mjs.map +1 -0
- package/dist/shared/index.d.mts +5 -1
- package/dist/shared/index.d.ts +5 -1
- package/dist/shared/types/index.d.mts +140 -0
- package/dist/shared/types/index.d.ts +140 -0
- package/dist/shared/types/index.js +4 -0
- package/dist/shared/types/index.js.map +1 -0
- package/dist/shared/types/index.mjs +3 -0
- package/dist/shared/types/index.mjs.map +1 -0
- package/dist/shared/utils/index.d.mts +255 -0
- package/dist/shared/utils/index.d.ts +255 -0
- package/dist/shared/utils/index.js +623 -0
- package/dist/shared/utils/index.js.map +1 -0
- package/dist/shared/utils/index.mjs +324 -0
- package/dist/shared/utils/index.mjs.map +1 -0
- package/dist/shared/validation/index.d.mts +258 -0
- package/dist/shared/validation/index.d.ts +258 -0
- package/dist/shared/validation/index.js +185 -0
- package/dist/shared/validation/index.js.map +1 -0
- package/dist/shared/validation/index.mjs +172 -0
- package/dist/shared/validation/index.mjs.map +1 -0
- package/package.json +127 -62
- package/dist/index-BcxL4_V4.d.ts +0 -2946
- package/dist/index-DEzgM15j.d.ts +0 -67
- package/dist/index-DNFVgQx8.d.ts +0 -1375
- package/dist/index-DbV04Dx8.d.mts +0 -67
- package/dist/index-DfqEP6Oe.d.mts +0 -1375
- package/dist/index-bvvCev9Q.d.mts +0 -2946
package/dist/server/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var path = require('path');
|
|
|
6
6
|
var mongoose = require('mongoose');
|
|
7
7
|
var jwt = require('jsonwebtoken');
|
|
8
8
|
var fs = require('fs');
|
|
9
|
+
var rateLimit = require('express-rate-limit');
|
|
9
10
|
|
|
10
11
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
|
|
@@ -14,6 +15,7 @@ var DailyRotateFile__default = /*#__PURE__*/_interopDefault(DailyRotateFile);
|
|
|
14
15
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
15
16
|
var mongoose__default = /*#__PURE__*/_interopDefault(mongoose);
|
|
16
17
|
var jwt__default = /*#__PURE__*/_interopDefault(jwt);
|
|
18
|
+
var rateLimit__default = /*#__PURE__*/_interopDefault(rateLimit);
|
|
17
19
|
|
|
18
20
|
// src/server/enums/status.ts
|
|
19
21
|
var StatusCode = /* @__PURE__ */ ((StatusCode2) => {
|
|
@@ -426,6 +428,629 @@ var requireOrganization = (req, res, next) => {
|
|
|
426
428
|
next();
|
|
427
429
|
};
|
|
428
430
|
|
|
431
|
+
// src/server/middleware/queryParser.middleware.ts
|
|
432
|
+
var queryParser = (req, _, next) => {
|
|
433
|
+
const { page, limit, sort, sortBy, sortOrder, search, filter, ...otherParams } = req.query;
|
|
434
|
+
const parsed = {
|
|
435
|
+
page: Math.max(Number(page) || 1, 1),
|
|
436
|
+
limit: Math.min(Number(limit) || 10, 100),
|
|
437
|
+
filter: {}
|
|
438
|
+
};
|
|
439
|
+
if (typeof sort === "string") {
|
|
440
|
+
const [field, order] = sort.split(":");
|
|
441
|
+
parsed.sort = {
|
|
442
|
+
field,
|
|
443
|
+
order: order === "asc" ? "asc" : "desc"
|
|
444
|
+
};
|
|
445
|
+
} else if (typeof sortBy === "string") {
|
|
446
|
+
parsed.sort = {
|
|
447
|
+
field: sortBy,
|
|
448
|
+
order: sortOrder === "asc" ? "asc" : "desc"
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (typeof search === "string") {
|
|
452
|
+
parsed.search = search;
|
|
453
|
+
}
|
|
454
|
+
if (typeof filter === "object" && filter !== null) {
|
|
455
|
+
Object.entries(filter).forEach(([key, value]) => {
|
|
456
|
+
if (value !== "all") {
|
|
457
|
+
parsed.filter[key] = value;
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
Object.entries(otherParams).forEach(([key, value]) => {
|
|
462
|
+
if (typeof value === "string" && value !== "all" && !["page", "limit", "sort", "sortBy", "sortOrder", "search"].includes(key)) {
|
|
463
|
+
parsed.filter[key] = value;
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
req.parsedQuery = parsed;
|
|
467
|
+
next();
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/server/middleware/utils/schemaMeta.util.ts
|
|
471
|
+
var getZodTypeName = (schema) => {
|
|
472
|
+
const typeName = schema._def?.typeName;
|
|
473
|
+
switch (typeName) {
|
|
474
|
+
case "ZodString":
|
|
475
|
+
return "string";
|
|
476
|
+
case "ZodNumber":
|
|
477
|
+
return "number";
|
|
478
|
+
case "ZodBoolean":
|
|
479
|
+
return "boolean";
|
|
480
|
+
case "ZodDate":
|
|
481
|
+
return "date";
|
|
482
|
+
case "ZodArray":
|
|
483
|
+
return "array";
|
|
484
|
+
case "ZodObject":
|
|
485
|
+
return "object";
|
|
486
|
+
case "ZodOptional":
|
|
487
|
+
case "ZodNullable":
|
|
488
|
+
return schema._def?.innerType ? getZodTypeName(schema._def.innerType) : "unknown";
|
|
489
|
+
case "ZodDefault":
|
|
490
|
+
return schema._def?.innerType ? getZodTypeName(schema._def.innerType) : "unknown";
|
|
491
|
+
case "ZodEnum":
|
|
492
|
+
return "enum";
|
|
493
|
+
case "ZodUnion":
|
|
494
|
+
return "union";
|
|
495
|
+
default:
|
|
496
|
+
return "unknown";
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
var isZodRequired = (schema) => {
|
|
500
|
+
const typeName = schema._def?.typeName;
|
|
501
|
+
return typeName !== "ZodOptional" && typeName !== "ZodNullable";
|
|
502
|
+
};
|
|
503
|
+
var extractSchemaMeta = (model, zodSchema) => {
|
|
504
|
+
const columns = [];
|
|
505
|
+
if (zodSchema && zodSchema.shape) {
|
|
506
|
+
const shape = zodSchema.shape;
|
|
507
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
508
|
+
if (key.startsWith("_")) continue;
|
|
509
|
+
columns.push({
|
|
510
|
+
name: key,
|
|
511
|
+
datatype: getZodTypeName(value),
|
|
512
|
+
required: isZodRequired(value)
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
return columns;
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
const schema = model.schema;
|
|
519
|
+
const paths = schema.paths;
|
|
520
|
+
for (const [key, pathInfo] of Object.entries(paths)) {
|
|
521
|
+
if (key.startsWith("_") || key === "__v") continue;
|
|
522
|
+
const schemaType = pathInfo;
|
|
523
|
+
columns.push({
|
|
524
|
+
name: key,
|
|
525
|
+
datatype: (schemaType.instance || "unknown").toLowerCase(),
|
|
526
|
+
required: schemaType.isRequired || false
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
return columns;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/server/middleware/pagination.middleware.ts
|
|
535
|
+
var queryPagination = (model, options = {}, withOrgId = true) => {
|
|
536
|
+
return async (req, res, next) => {
|
|
537
|
+
try {
|
|
538
|
+
const { page, limit, sort, search, filter } = req.parsedQuery;
|
|
539
|
+
const query = {};
|
|
540
|
+
Object.entries(filter).forEach(([key, value]) => {
|
|
541
|
+
if (options.regexFilterFields?.includes(key)) {
|
|
542
|
+
query[key] = { $regex: value, $options: "i" };
|
|
543
|
+
} else {
|
|
544
|
+
query[key] = value;
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
const organizationId = req.headers["x-organization-id"];
|
|
548
|
+
if (organizationId && typeof organizationId === "string" && withOrgId) {
|
|
549
|
+
query.organizationId = organizationId;
|
|
550
|
+
}
|
|
551
|
+
if (search && options.searchFields?.length) {
|
|
552
|
+
query.$or = options.searchFields.map((field) => ({
|
|
553
|
+
[field]: { $regex: search, $options: "i" }
|
|
554
|
+
}));
|
|
555
|
+
}
|
|
556
|
+
const sortQuery = sort ? { [sort.field]: sort.order } : { createdAt: "desc" };
|
|
557
|
+
const skip = (page - 1) * limit;
|
|
558
|
+
const [data, total] = await Promise.all([
|
|
559
|
+
model.find(query).sort(sortQuery).skip(skip).limit(limit),
|
|
560
|
+
model.countDocuments(query)
|
|
561
|
+
]);
|
|
562
|
+
res.paginatedResult = {
|
|
563
|
+
data,
|
|
564
|
+
meta: {
|
|
565
|
+
page,
|
|
566
|
+
limit,
|
|
567
|
+
total,
|
|
568
|
+
totalPages: Math.ceil(total / limit)
|
|
569
|
+
},
|
|
570
|
+
columns: extractSchemaMeta(model, options.validatorSchema)
|
|
571
|
+
};
|
|
572
|
+
next();
|
|
573
|
+
} catch (error) {
|
|
574
|
+
next(error);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
};
|
|
578
|
+
var isZodError = (error) => {
|
|
579
|
+
return error !== null && typeof error === "object" && "errors" in error && Array.isArray(error.errors);
|
|
580
|
+
};
|
|
581
|
+
var getOrgId = (req, orgField = "organizationId") => {
|
|
582
|
+
const orgReq = req;
|
|
583
|
+
return orgReq.organizationId || req.headers["x-organization-id"] || req.query[orgField];
|
|
584
|
+
};
|
|
585
|
+
var buildOrgFilter = (req, config) => {
|
|
586
|
+
const filter = {};
|
|
587
|
+
if (config.withOrganization !== false) {
|
|
588
|
+
const orgId = getOrgId(req, config.orgField);
|
|
589
|
+
if (orgId) {
|
|
590
|
+
filter[config.orgField || "organizationId"] = orgId;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return filter;
|
|
594
|
+
};
|
|
595
|
+
var formatZodError = (error) => {
|
|
596
|
+
return error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
597
|
+
};
|
|
598
|
+
function createCrudControllers(config) {
|
|
599
|
+
const {
|
|
600
|
+
model,
|
|
601
|
+
resourceName,
|
|
602
|
+
createSchema,
|
|
603
|
+
updateSchema,
|
|
604
|
+
searchFields = [],
|
|
605
|
+
regexFilterFields = [],
|
|
606
|
+
withOrganization = true,
|
|
607
|
+
orgField = "organizationId",
|
|
608
|
+
transformCreate,
|
|
609
|
+
transformUpdate,
|
|
610
|
+
afterCreate,
|
|
611
|
+
afterUpdate,
|
|
612
|
+
afterDelete,
|
|
613
|
+
excludeFields = [],
|
|
614
|
+
populateFields = [],
|
|
615
|
+
buildQuery
|
|
616
|
+
} = config;
|
|
617
|
+
const getAll = async (req, res, _next) => {
|
|
618
|
+
try {
|
|
619
|
+
const paginatedRes = res;
|
|
620
|
+
if (paginatedRes.paginatedResult) {
|
|
621
|
+
successResponse(res, paginatedRes.paginatedResult, `${resourceName} list fetched successfully`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const page = parseInt(req.query.page) || 1;
|
|
625
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
626
|
+
const sortField = req.query.sortBy || "createdAt";
|
|
627
|
+
const sortOrder = req.query.sortOrder || "desc";
|
|
628
|
+
const search = req.query.search;
|
|
629
|
+
let query = {};
|
|
630
|
+
if (withOrganization) {
|
|
631
|
+
const orgId = getOrgId(req, orgField);
|
|
632
|
+
if (orgId) {
|
|
633
|
+
query[orgField] = orgId;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (search && searchFields.length > 0) {
|
|
637
|
+
query.$or = searchFields.map((field) => ({
|
|
638
|
+
[field]: { $regex: search, $options: "i" }
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
const filterableParams = Object.keys(req.query).filter(
|
|
642
|
+
(key) => !["page", "limit", "sortBy", "sortOrder", "search"].includes(key)
|
|
643
|
+
);
|
|
644
|
+
filterableParams.forEach((key) => {
|
|
645
|
+
const value = req.query[key];
|
|
646
|
+
if (value !== void 0 && value !== "" && value !== "all") {
|
|
647
|
+
if (regexFilterFields.includes(key)) {
|
|
648
|
+
query[key] = { $regex: value, $options: "i" };
|
|
649
|
+
} else {
|
|
650
|
+
query[key] = value;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
if (buildQuery) {
|
|
655
|
+
query = buildQuery(req, query);
|
|
656
|
+
}
|
|
657
|
+
const sortQuery = { [sortField]: sortOrder };
|
|
658
|
+
const skip = (page - 1) * limit;
|
|
659
|
+
let projection = {};
|
|
660
|
+
if (excludeFields.length > 0) {
|
|
661
|
+
projection = excludeFields.reduce(
|
|
662
|
+
(acc, field) => ({ ...acc, [field]: 0 }),
|
|
663
|
+
{}
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
let dbQuery = model.find(query, projection);
|
|
667
|
+
if (populateFields.length > 0) {
|
|
668
|
+
populateFields.forEach((field) => {
|
|
669
|
+
dbQuery = dbQuery.populate(field);
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
const [data, total] = await Promise.all([
|
|
673
|
+
dbQuery.sort(sortQuery).skip(skip).limit(limit),
|
|
674
|
+
model.countDocuments(query)
|
|
675
|
+
]);
|
|
676
|
+
successResponse(
|
|
677
|
+
res,
|
|
678
|
+
{
|
|
679
|
+
data,
|
|
680
|
+
meta: {
|
|
681
|
+
page,
|
|
682
|
+
limit,
|
|
683
|
+
total,
|
|
684
|
+
totalPages: Math.ceil(total / limit)
|
|
685
|
+
},
|
|
686
|
+
columns: extractSchemaMeta(model, createSchema)
|
|
687
|
+
},
|
|
688
|
+
`${resourceName} list fetched successfully`
|
|
689
|
+
);
|
|
690
|
+
} catch (error) {
|
|
691
|
+
logger.error(`Error in getAll ${resourceName}`, {
|
|
692
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
693
|
+
});
|
|
694
|
+
errorResponse(res, `Failed to fetch ${resourceName.toLowerCase()} list`);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
const getById = async (req, res, _next) => {
|
|
698
|
+
try {
|
|
699
|
+
const { id } = req.params;
|
|
700
|
+
if (!id || !mongoose.Types.ObjectId.isValid(id)) {
|
|
701
|
+
badRequestResponse(res, "Invalid ID format");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const query = {
|
|
705
|
+
_id: new mongoose.Types.ObjectId(id),
|
|
706
|
+
...buildOrgFilter(req, { ...config, withOrganization, orgField })
|
|
707
|
+
};
|
|
708
|
+
let dbQuery = model.findOne(query);
|
|
709
|
+
if (populateFields.length > 0) {
|
|
710
|
+
populateFields.forEach((field) => {
|
|
711
|
+
dbQuery = dbQuery.populate(field);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
const doc = await dbQuery;
|
|
715
|
+
if (!doc) {
|
|
716
|
+
notFoundResponse(res, `${resourceName} not found`);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
successResponse(res, doc, `${resourceName} fetched successfully`);
|
|
720
|
+
} catch (error) {
|
|
721
|
+
logger.error(`Error in getById ${resourceName}`, {
|
|
722
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
723
|
+
id: req.params.id
|
|
724
|
+
});
|
|
725
|
+
errorResponse(res, `Failed to fetch ${resourceName.toLowerCase()}`);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const create = async (req, res, _next) => {
|
|
729
|
+
try {
|
|
730
|
+
let input = req.body;
|
|
731
|
+
if (createSchema) {
|
|
732
|
+
try {
|
|
733
|
+
input = createSchema.parse(input);
|
|
734
|
+
} catch (error) {
|
|
735
|
+
if (isZodError(error)) {
|
|
736
|
+
badRequestResponse(res, formatZodError(error));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (transformCreate) {
|
|
743
|
+
input = transformCreate(input, req);
|
|
744
|
+
}
|
|
745
|
+
if (withOrganization) {
|
|
746
|
+
const orgId = getOrgId(req, orgField);
|
|
747
|
+
if (orgId) {
|
|
748
|
+
input[orgField] = orgId;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const doc = new model(input);
|
|
752
|
+
await doc.save();
|
|
753
|
+
if (afterCreate) {
|
|
754
|
+
await afterCreate(doc, req);
|
|
755
|
+
}
|
|
756
|
+
logger.info(`${resourceName} created successfully`, {
|
|
757
|
+
id: doc._id,
|
|
758
|
+
[orgField]: input[orgField]
|
|
759
|
+
});
|
|
760
|
+
createdResponse(res, doc, `${resourceName} created successfully`);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
logger.error(`Error in create ${resourceName}`, {
|
|
763
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
764
|
+
});
|
|
765
|
+
if (error.code === 11e3) {
|
|
766
|
+
badRequestResponse(res, `A ${resourceName.toLowerCase()} with this data already exists`);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
errorResponse(res, `Failed to create ${resourceName.toLowerCase()}`);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
const update = async (req, res, _next) => {
|
|
773
|
+
try {
|
|
774
|
+
const { id } = req.params;
|
|
775
|
+
if (!id || !mongoose.Types.ObjectId.isValid(id)) {
|
|
776
|
+
badRequestResponse(res, "Invalid ID format");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
let input = req.body;
|
|
780
|
+
if (updateSchema) {
|
|
781
|
+
try {
|
|
782
|
+
input = updateSchema.parse(input);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
if (isZodError(error)) {
|
|
785
|
+
badRequestResponse(res, formatZodError(error));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
throw error;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (transformUpdate) {
|
|
792
|
+
input = transformUpdate(input, req);
|
|
793
|
+
}
|
|
794
|
+
const query = {
|
|
795
|
+
_id: new mongoose.Types.ObjectId(id),
|
|
796
|
+
...buildOrgFilter(req, { ...config, withOrganization, orgField })
|
|
797
|
+
};
|
|
798
|
+
const doc = await model.findOneAndUpdate(query, { $set: input }, { new: true });
|
|
799
|
+
if (!doc) {
|
|
800
|
+
notFoundResponse(res, `${resourceName} not found`);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (afterUpdate) {
|
|
804
|
+
await afterUpdate(doc, req);
|
|
805
|
+
}
|
|
806
|
+
logger.info(`${resourceName} updated successfully`, { id });
|
|
807
|
+
successResponse(res, doc, `${resourceName} updated successfully`);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
logger.error(`Error in update ${resourceName}`, {
|
|
810
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
811
|
+
id: req.params.id
|
|
812
|
+
});
|
|
813
|
+
errorResponse(res, `Failed to update ${resourceName.toLowerCase()}`);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
const deleteOne = async (req, res, _next) => {
|
|
817
|
+
try {
|
|
818
|
+
const { id } = req.params;
|
|
819
|
+
if (!id || !mongoose.Types.ObjectId.isValid(id)) {
|
|
820
|
+
badRequestResponse(res, "Invalid ID format");
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const query = {
|
|
824
|
+
_id: new mongoose.Types.ObjectId(id),
|
|
825
|
+
...buildOrgFilter(req, { ...config, withOrganization, orgField })
|
|
826
|
+
};
|
|
827
|
+
const result = await model.deleteOne(query);
|
|
828
|
+
if (result.deletedCount === 0) {
|
|
829
|
+
notFoundResponse(res, `${resourceName} not found`);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (afterDelete) {
|
|
833
|
+
await afterDelete(id, req);
|
|
834
|
+
}
|
|
835
|
+
logger.info(`${resourceName} deleted successfully`, { id });
|
|
836
|
+
noContentResponse(res, null, `${resourceName} deleted successfully`);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
logger.error(`Error in delete ${resourceName}`, {
|
|
839
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
840
|
+
id: req.params.id
|
|
841
|
+
});
|
|
842
|
+
errorResponse(res, `Failed to delete ${resourceName.toLowerCase()}`);
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
const bulkDelete = async (req, res, _next) => {
|
|
846
|
+
try {
|
|
847
|
+
const bulkReq = req;
|
|
848
|
+
const { deleteIds = [], deleteAll = false } = bulkReq;
|
|
849
|
+
const baseFilter = buildOrgFilter(req, { ...config, withOrganization, orgField });
|
|
850
|
+
let filter;
|
|
851
|
+
if (deleteAll) {
|
|
852
|
+
filter = baseFilter;
|
|
853
|
+
} else if (deleteIds.length > 0) {
|
|
854
|
+
filter = {
|
|
855
|
+
...baseFilter,
|
|
856
|
+
_id: { $in: deleteIds.map((id) => new mongoose.Types.ObjectId(id)) }
|
|
857
|
+
};
|
|
858
|
+
} else {
|
|
859
|
+
badRequestResponse(res, "No IDs provided for deletion");
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const result = await model.deleteMany(filter);
|
|
863
|
+
if (afterDelete && deleteIds.length > 0) {
|
|
864
|
+
await Promise.all(deleteIds.map((id) => afterDelete(id, req)));
|
|
865
|
+
}
|
|
866
|
+
logger.info(`${resourceName}(s) bulk deleted successfully`, {
|
|
867
|
+
deletedCount: result.deletedCount,
|
|
868
|
+
deleteAll
|
|
869
|
+
});
|
|
870
|
+
successResponse(
|
|
871
|
+
res,
|
|
872
|
+
{ deletedCount: result.deletedCount },
|
|
873
|
+
`${result.deletedCount} ${resourceName.toLowerCase()}(s) deleted successfully`
|
|
874
|
+
);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
logger.error(`Error in bulkDelete ${resourceName}`, {
|
|
877
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
878
|
+
});
|
|
879
|
+
errorResponse(res, `Failed to delete ${resourceName.toLowerCase()}(s)`);
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
return {
|
|
883
|
+
getAll,
|
|
884
|
+
getById,
|
|
885
|
+
create,
|
|
886
|
+
update,
|
|
887
|
+
deleteOne,
|
|
888
|
+
bulkDelete
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
function createPaginationMiddleware(model, config = {}) {
|
|
892
|
+
const {
|
|
893
|
+
searchFields = [],
|
|
894
|
+
regexFilterFields = [],
|
|
895
|
+
withOrganization = true,
|
|
896
|
+
orgField = "organizationId"
|
|
897
|
+
} = config;
|
|
898
|
+
return async (req, res, next) => {
|
|
899
|
+
try {
|
|
900
|
+
const page = parseInt(req.query.page) || 1;
|
|
901
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
902
|
+
const sortField = req.query.sortBy || "createdAt";
|
|
903
|
+
const sortOrder = req.query.sortOrder || "desc";
|
|
904
|
+
const search = req.query.search;
|
|
905
|
+
const query = {};
|
|
906
|
+
if (withOrganization) {
|
|
907
|
+
const orgId = getOrgId(req, orgField);
|
|
908
|
+
if (orgId) {
|
|
909
|
+
query[orgField] = orgId;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (search && searchFields.length > 0) {
|
|
913
|
+
query.$or = searchFields.map((field) => ({
|
|
914
|
+
[field]: { $regex: search, $options: "i" }
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
const filterableParams = Object.keys(req.query).filter(
|
|
918
|
+
(key) => !["page", "limit", "sortBy", "sortOrder", "search"].includes(key)
|
|
919
|
+
);
|
|
920
|
+
filterableParams.forEach((key) => {
|
|
921
|
+
const value = req.query[key];
|
|
922
|
+
if (value !== void 0 && value !== "" && value !== "all") {
|
|
923
|
+
if (regexFilterFields.includes(key)) {
|
|
924
|
+
query[key] = { $regex: value, $options: "i" };
|
|
925
|
+
} else {
|
|
926
|
+
query[key] = value;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
const sortQuery = { [sortField]: sortOrder };
|
|
931
|
+
const skip = (page - 1) * limit;
|
|
932
|
+
const [data, total] = await Promise.all([
|
|
933
|
+
model.find(query).sort(sortQuery).skip(skip).limit(limit),
|
|
934
|
+
model.countDocuments(query)
|
|
935
|
+
]);
|
|
936
|
+
const paginatedRes = res;
|
|
937
|
+
paginatedRes.paginatedResult = {
|
|
938
|
+
data,
|
|
939
|
+
meta: {
|
|
940
|
+
page,
|
|
941
|
+
limit,
|
|
942
|
+
total,
|
|
943
|
+
totalPages: Math.ceil(total / limit)
|
|
944
|
+
},
|
|
945
|
+
columns: extractSchemaMeta(model, config.createSchema)
|
|
946
|
+
};
|
|
947
|
+
next();
|
|
948
|
+
} catch (error) {
|
|
949
|
+
next(error);
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
var parseBulkDelete = (req, res, next) => {
|
|
954
|
+
try {
|
|
955
|
+
const bulkReq = req;
|
|
956
|
+
let ids = [];
|
|
957
|
+
if (Array.isArray(req.body)) {
|
|
958
|
+
ids = req.body;
|
|
959
|
+
} else if (req.body && Array.isArray(req.body.ids)) {
|
|
960
|
+
ids = req.body.ids;
|
|
961
|
+
} else if (req.body && typeof req.body === "object") {
|
|
962
|
+
if (Array.isArray(req.body.data)) {
|
|
963
|
+
ids = req.body.data;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (ids.length === 0) {
|
|
967
|
+
return badRequestResponse(
|
|
968
|
+
res,
|
|
969
|
+
'Request body must contain an array of IDs. Use ["*"] to delete all records or ["id1", "id2"] to delete specific records.'
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
if (ids.length === 1 && ids[0] === "*") {
|
|
973
|
+
bulkReq.deleteAll = true;
|
|
974
|
+
bulkReq.deleteIds = [];
|
|
975
|
+
logger.info("Bulk delete: Deleting all records");
|
|
976
|
+
return next();
|
|
977
|
+
}
|
|
978
|
+
const validIds = [];
|
|
979
|
+
const invalidIds = [];
|
|
980
|
+
for (const id of ids) {
|
|
981
|
+
if (typeof id === "string" && mongoose.Types.ObjectId.isValid(id)) {
|
|
982
|
+
validIds.push(id);
|
|
983
|
+
} else {
|
|
984
|
+
invalidIds.push(id);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (invalidIds.length > 0) {
|
|
988
|
+
return badRequestResponse(
|
|
989
|
+
res,
|
|
990
|
+
`Invalid ID format(s): ${invalidIds.slice(0, 5).join(", ")}${invalidIds.length > 5 ? "..." : ""}. All IDs must be valid MongoDB ObjectIds.`
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
if (validIds.length === 0) {
|
|
994
|
+
return badRequestResponse(res, "No valid IDs provided for deletion.");
|
|
995
|
+
}
|
|
996
|
+
bulkReq.deleteAll = false;
|
|
997
|
+
bulkReq.deleteIds = validIds;
|
|
998
|
+
logger.info(`Bulk delete: Deleting ${validIds.length} record(s)`);
|
|
999
|
+
next();
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
logger.error("Error in parseBulkDelete middleware", error);
|
|
1002
|
+
return badRequestResponse(res, "Failed to parse delete request");
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
var buildDeleteFilter = (req, organizationId) => {
|
|
1006
|
+
const filter = {
|
|
1007
|
+
organizationId: new mongoose.Types.ObjectId(organizationId)
|
|
1008
|
+
};
|
|
1009
|
+
if (!req.deleteAll && req.deleteIds && req.deleteIds.length > 0) {
|
|
1010
|
+
filter._id = {
|
|
1011
|
+
$in: req.deleteIds.map((id) => new mongoose.Types.ObjectId(id))
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
return filter;
|
|
1015
|
+
};
|
|
1016
|
+
var createBulkDeleteHandler = (Model2, modelName) => {
|
|
1017
|
+
return async (req, res) => {
|
|
1018
|
+
const bulkReq = req;
|
|
1019
|
+
const organizationId = req.headers["x-organization-id"];
|
|
1020
|
+
if (!organizationId) {
|
|
1021
|
+
return badRequestResponse(res, "Organization ID is required");
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
const filter = buildDeleteFilter(bulkReq, organizationId);
|
|
1025
|
+
const result = await Model2.deleteMany(filter);
|
|
1026
|
+
const deletedCount = result.deletedCount || 0;
|
|
1027
|
+
logger.info(`Bulk delete completed: ${deletedCount} ${modelName}(s) deleted`, {
|
|
1028
|
+
organizationId,
|
|
1029
|
+
deleteAll: bulkReq.deleteAll,
|
|
1030
|
+
requestedIds: bulkReq.deleteIds?.length || "all",
|
|
1031
|
+
deletedCount
|
|
1032
|
+
});
|
|
1033
|
+
return res.status(200).json({
|
|
1034
|
+
message: `Successfully deleted ${deletedCount} ${modelName}(s)`,
|
|
1035
|
+
data: {
|
|
1036
|
+
deletedCount,
|
|
1037
|
+
deleteAll: bulkReq.deleteAll
|
|
1038
|
+
},
|
|
1039
|
+
status: "success",
|
|
1040
|
+
statusCode: 200
|
|
1041
|
+
});
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
logger.error(`Error in bulk delete ${modelName}`, error);
|
|
1044
|
+
return res.status(500).json({
|
|
1045
|
+
message: `Failed to delete ${modelName}(s)`,
|
|
1046
|
+
data: null,
|
|
1047
|
+
status: "error",
|
|
1048
|
+
statusCode: 500
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
};
|
|
1053
|
+
|
|
429
1054
|
// src/server/utils/filter-builder.ts
|
|
430
1055
|
var buildFilter = (options) => {
|
|
431
1056
|
const {
|
|
@@ -720,44 +1345,749 @@ var packageCheckServer = {
|
|
|
720
1345
|
print: printPackageCheckSummary
|
|
721
1346
|
};
|
|
722
1347
|
|
|
1348
|
+
// src/server/configs/cors.config.ts
|
|
1349
|
+
var DEFAULT_CORS_CONFIG = {
|
|
1350
|
+
productionOrigins: [],
|
|
1351
|
+
developmentOrigins: [
|
|
1352
|
+
"http://localhost:3000",
|
|
1353
|
+
"http://localhost:4000",
|
|
1354
|
+
"http://localhost:5000",
|
|
1355
|
+
"http://localhost:5173",
|
|
1356
|
+
"http://localhost:8080",
|
|
1357
|
+
"http://127.0.0.1:3000",
|
|
1358
|
+
"http://127.0.0.1:4000",
|
|
1359
|
+
"http://127.0.0.1:5000",
|
|
1360
|
+
"http://127.0.0.1:5173",
|
|
1361
|
+
"http://127.0.0.1:8080"
|
|
1362
|
+
],
|
|
1363
|
+
allowedSubdomains: [],
|
|
1364
|
+
originPatterns: [],
|
|
1365
|
+
allowNoOrigin: true,
|
|
1366
|
+
allowAllInDev: true,
|
|
1367
|
+
credentials: true,
|
|
1368
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
|
1369
|
+
allowedHeaders: [
|
|
1370
|
+
"Content-Type",
|
|
1371
|
+
"Authorization",
|
|
1372
|
+
"X-Requested-With",
|
|
1373
|
+
"Accept",
|
|
1374
|
+
"Origin",
|
|
1375
|
+
"X-API-Key",
|
|
1376
|
+
"X-Organization-Id",
|
|
1377
|
+
"X-Request-Id"
|
|
1378
|
+
],
|
|
1379
|
+
exposedHeaders: [
|
|
1380
|
+
"Content-Range",
|
|
1381
|
+
"X-Content-Range",
|
|
1382
|
+
"X-Total-Count",
|
|
1383
|
+
"X-Request-Id"
|
|
1384
|
+
],
|
|
1385
|
+
maxAge: 86400
|
|
1386
|
+
// 24 hours
|
|
1387
|
+
};
|
|
1388
|
+
var createCorsOptions = (config = {}) => {
|
|
1389
|
+
const finalConfig = { ...DEFAULT_CORS_CONFIG, ...config };
|
|
1390
|
+
const {
|
|
1391
|
+
productionOrigins,
|
|
1392
|
+
developmentOrigins,
|
|
1393
|
+
allowedSubdomains,
|
|
1394
|
+
originPatterns,
|
|
1395
|
+
allowNoOrigin,
|
|
1396
|
+
allowAllInDev,
|
|
1397
|
+
customValidator,
|
|
1398
|
+
credentials,
|
|
1399
|
+
methods,
|
|
1400
|
+
allowedHeaders,
|
|
1401
|
+
exposedHeaders,
|
|
1402
|
+
maxAge
|
|
1403
|
+
} = finalConfig;
|
|
1404
|
+
const allOrigins = /* @__PURE__ */ new Set([...productionOrigins, ...developmentOrigins]);
|
|
1405
|
+
const originHandler = (origin, callback) => {
|
|
1406
|
+
if (!origin) {
|
|
1407
|
+
callback(null, allowNoOrigin);
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
if (allOrigins.has(origin)) {
|
|
1411
|
+
callback(null, true);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (allowedSubdomains.some((subdomain) => origin.endsWith(subdomain))) {
|
|
1415
|
+
callback(null, true);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (originPatterns.some((pattern) => pattern.test(origin))) {
|
|
1419
|
+
callback(null, true);
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (customValidator && customValidator(origin)) {
|
|
1423
|
+
callback(null, true);
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
if (process.env.NODE_ENV !== "production" && allowAllInDev) {
|
|
1427
|
+
callback(null, true);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
if (process.env.NODE_ENV === "production") {
|
|
1431
|
+
callback(new Error(`Origin ${origin} not allowed by CORS`));
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
callback(null, true);
|
|
1435
|
+
};
|
|
1436
|
+
return {
|
|
1437
|
+
origin: originHandler,
|
|
1438
|
+
credentials,
|
|
1439
|
+
methods,
|
|
1440
|
+
allowedHeaders,
|
|
1441
|
+
exposedHeaders,
|
|
1442
|
+
maxAge
|
|
1443
|
+
};
|
|
1444
|
+
};
|
|
1445
|
+
var createBrandCorsOptions = (brandDomain, additionalConfig = {}) => {
|
|
1446
|
+
const productionOrigins = [
|
|
1447
|
+
`https://${brandDomain}`,
|
|
1448
|
+
`https://www.${brandDomain}`
|
|
1449
|
+
];
|
|
1450
|
+
const allowedSubdomains = [`.${brandDomain}`];
|
|
1451
|
+
return createCorsOptions({
|
|
1452
|
+
productionOrigins,
|
|
1453
|
+
allowedSubdomains,
|
|
1454
|
+
...additionalConfig
|
|
1455
|
+
});
|
|
1456
|
+
};
|
|
1457
|
+
var createMultiBrandCorsOptions = (domains, additionalConfig = {}) => {
|
|
1458
|
+
const productionOrigins = domains.flatMap((domain) => [
|
|
1459
|
+
`https://${domain}`,
|
|
1460
|
+
`https://www.${domain}`
|
|
1461
|
+
]);
|
|
1462
|
+
const allowedSubdomains = domains.map((domain) => `.${domain}`);
|
|
1463
|
+
return createCorsOptions({
|
|
1464
|
+
productionOrigins,
|
|
1465
|
+
allowedSubdomains,
|
|
1466
|
+
...additionalConfig
|
|
1467
|
+
});
|
|
1468
|
+
};
|
|
1469
|
+
var EXYCONN_CORS_CONFIG = {
|
|
1470
|
+
productionOrigins: [
|
|
1471
|
+
"https://exyconn.com",
|
|
1472
|
+
"https://www.exyconn.com",
|
|
1473
|
+
"https://botify.life",
|
|
1474
|
+
"https://www.botify.life",
|
|
1475
|
+
"https://partywings.fun",
|
|
1476
|
+
"https://www.partywings.fun",
|
|
1477
|
+
"https://sibera.work",
|
|
1478
|
+
"https://www.sibera.work",
|
|
1479
|
+
"https://spentiva.com",
|
|
1480
|
+
"https://www.spentiva.com"
|
|
1481
|
+
],
|
|
1482
|
+
allowedSubdomains: [
|
|
1483
|
+
".exyconn.com",
|
|
1484
|
+
".botify.life",
|
|
1485
|
+
".partywings.fun",
|
|
1486
|
+
".sibera.work",
|
|
1487
|
+
".spentiva.com"
|
|
1488
|
+
],
|
|
1489
|
+
developmentOrigins: [
|
|
1490
|
+
"http://localhost:3000",
|
|
1491
|
+
"http://localhost:4000",
|
|
1492
|
+
"http://localhost:4001",
|
|
1493
|
+
"http://localhost:4002",
|
|
1494
|
+
"http://localhost:4003",
|
|
1495
|
+
"http://localhost:4004",
|
|
1496
|
+
"http://localhost:4005",
|
|
1497
|
+
"http://localhost:5173",
|
|
1498
|
+
"http://127.0.0.1:3000",
|
|
1499
|
+
"http://127.0.0.1:4000",
|
|
1500
|
+
"http://127.0.0.1:5173"
|
|
1501
|
+
]
|
|
1502
|
+
};
|
|
1503
|
+
var STRICT_CORS_CONFIG = {
|
|
1504
|
+
allowNoOrigin: false,
|
|
1505
|
+
allowAllInDev: false,
|
|
1506
|
+
methods: ["GET", "POST", "PUT", "DELETE"]
|
|
1507
|
+
};
|
|
1508
|
+
var PERMISSIVE_CORS_CONFIG = {
|
|
1509
|
+
allowNoOrigin: true,
|
|
1510
|
+
allowAllInDev: true,
|
|
1511
|
+
originPatterns: [/localhost/, /127\.0\.0\.1/]
|
|
1512
|
+
};
|
|
1513
|
+
var corsOptions = createCorsOptions(EXYCONN_CORS_CONFIG);
|
|
1514
|
+
var DEFAULT_RATE_LIMIT_TIERS = {
|
|
1515
|
+
STANDARD: {
|
|
1516
|
+
windowMs: 15 * 60 * 1e3,
|
|
1517
|
+
// 15 minutes
|
|
1518
|
+
maxRequests: 100,
|
|
1519
|
+
message: "Too many requests, please try again later.",
|
|
1520
|
+
skipSuccessfulRequests: false,
|
|
1521
|
+
skipFailedRequests: false
|
|
1522
|
+
},
|
|
1523
|
+
STRICT: {
|
|
1524
|
+
windowMs: 15 * 60 * 1e3,
|
|
1525
|
+
// 15 minutes
|
|
1526
|
+
maxRequests: 20,
|
|
1527
|
+
message: "Too many requests, please try again later.",
|
|
1528
|
+
skipSuccessfulRequests: false,
|
|
1529
|
+
skipFailedRequests: false
|
|
1530
|
+
},
|
|
1531
|
+
DDOS: {
|
|
1532
|
+
windowMs: 60 * 1e3,
|
|
1533
|
+
// 1 minute
|
|
1534
|
+
maxRequests: 60,
|
|
1535
|
+
message: "Rate limit exceeded. Please slow down.",
|
|
1536
|
+
skipSuccessfulRequests: false,
|
|
1537
|
+
skipFailedRequests: false
|
|
1538
|
+
},
|
|
1539
|
+
// Additional presets
|
|
1540
|
+
VERY_STRICT: {
|
|
1541
|
+
windowMs: 60 * 60 * 1e3,
|
|
1542
|
+
// 1 hour
|
|
1543
|
+
maxRequests: 5,
|
|
1544
|
+
message: "Too many attempts. Please try again in an hour.",
|
|
1545
|
+
skipSuccessfulRequests: false,
|
|
1546
|
+
skipFailedRequests: false
|
|
1547
|
+
},
|
|
1548
|
+
RELAXED: {
|
|
1549
|
+
windowMs: 15 * 60 * 1e3,
|
|
1550
|
+
// 15 minutes
|
|
1551
|
+
maxRequests: 500,
|
|
1552
|
+
message: "Rate limit exceeded.",
|
|
1553
|
+
skipSuccessfulRequests: false,
|
|
1554
|
+
skipFailedRequests: false
|
|
1555
|
+
},
|
|
1556
|
+
API: {
|
|
1557
|
+
windowMs: 60 * 1e3,
|
|
1558
|
+
// 1 minute
|
|
1559
|
+
maxRequests: 30,
|
|
1560
|
+
message: "API rate limit exceeded.",
|
|
1561
|
+
skipSuccessfulRequests: false,
|
|
1562
|
+
skipFailedRequests: false
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
var defaultKeyGenerator = (req) => {
|
|
1566
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
1567
|
+
const ip = forwarded ? Array.isArray(forwarded) ? forwarded[0] : forwarded.split(",")[0].trim() : req.ip || req.socket.remoteAddress || "unknown";
|
|
1568
|
+
return ip;
|
|
1569
|
+
};
|
|
1570
|
+
var createPrefixedKeyGenerator = (prefix) => (req) => {
|
|
1571
|
+
return `${prefix}:${defaultKeyGenerator(req)}`;
|
|
1572
|
+
};
|
|
1573
|
+
var createUserKeyGenerator = (getUserId) => (req) => {
|
|
1574
|
+
const userId = getUserId(req);
|
|
1575
|
+
return userId || defaultKeyGenerator(req);
|
|
1576
|
+
};
|
|
1577
|
+
var createApiKeyGenerator = (headerName = "x-api-key") => (req) => {
|
|
1578
|
+
const apiKey = req.headers[headerName.toLowerCase()];
|
|
1579
|
+
return apiKey || defaultKeyGenerator(req);
|
|
1580
|
+
};
|
|
1581
|
+
var createRateLimitResponse = (message, retryAfter) => ({
|
|
1582
|
+
status: "error",
|
|
1583
|
+
statusCode: 429,
|
|
1584
|
+
message,
|
|
1585
|
+
...retryAfter
|
|
1586
|
+
});
|
|
1587
|
+
var createRateLimiter = (tierConfig, options = {}) => {
|
|
1588
|
+
const {
|
|
1589
|
+
standardHeaders = true,
|
|
1590
|
+
legacyHeaders = false,
|
|
1591
|
+
keyGenerator = defaultKeyGenerator,
|
|
1592
|
+
skip,
|
|
1593
|
+
handler
|
|
1594
|
+
} = options;
|
|
1595
|
+
return rateLimit__default.default({
|
|
1596
|
+
windowMs: tierConfig.windowMs,
|
|
1597
|
+
max: tierConfig.maxRequests,
|
|
1598
|
+
message: createRateLimitResponse(tierConfig.message),
|
|
1599
|
+
standardHeaders,
|
|
1600
|
+
legacyHeaders,
|
|
1601
|
+
keyGenerator,
|
|
1602
|
+
skip,
|
|
1603
|
+
handler,
|
|
1604
|
+
skipSuccessfulRequests: tierConfig.skipSuccessfulRequests,
|
|
1605
|
+
skipFailedRequests: tierConfig.skipFailedRequests
|
|
1606
|
+
});
|
|
1607
|
+
};
|
|
1608
|
+
var createStandardRateLimiter = (config = {}, options = {}) => {
|
|
1609
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.STANDARD, ...config };
|
|
1610
|
+
return createRateLimiter(tierConfig, options);
|
|
1611
|
+
};
|
|
1612
|
+
var createStrictRateLimiter = (config = {}, options = {}) => {
|
|
1613
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.STRICT, ...config };
|
|
1614
|
+
return createRateLimiter(tierConfig, options);
|
|
1615
|
+
};
|
|
1616
|
+
var createDdosRateLimiter = (config = {}, options = {}) => {
|
|
1617
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.DDOS, ...config };
|
|
1618
|
+
return createRateLimiter(tierConfig, options);
|
|
1619
|
+
};
|
|
1620
|
+
var createApiRateLimiter = (config = {}, options = {}) => {
|
|
1621
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.API, ...config };
|
|
1622
|
+
return createRateLimiter(tierConfig, {
|
|
1623
|
+
keyGenerator: createApiKeyGenerator(),
|
|
1624
|
+
...options
|
|
1625
|
+
});
|
|
1626
|
+
};
|
|
1627
|
+
var RateLimiterBuilder = class {
|
|
1628
|
+
constructor(preset = "STANDARD") {
|
|
1629
|
+
const presetConfig = DEFAULT_RATE_LIMIT_TIERS[preset];
|
|
1630
|
+
this.config = {
|
|
1631
|
+
windowMs: presetConfig.windowMs,
|
|
1632
|
+
maxRequests: presetConfig.maxRequests,
|
|
1633
|
+
message: presetConfig.message,
|
|
1634
|
+
skipSuccessfulRequests: presetConfig.skipSuccessfulRequests ?? false,
|
|
1635
|
+
skipFailedRequests: presetConfig.skipFailedRequests ?? false
|
|
1636
|
+
};
|
|
1637
|
+
this.options = {};
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Set window duration
|
|
1641
|
+
*/
|
|
1642
|
+
windowMs(ms) {
|
|
1643
|
+
this.config.windowMs = ms;
|
|
1644
|
+
return this;
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Set window duration in minutes
|
|
1648
|
+
*/
|
|
1649
|
+
windowMinutes(minutes) {
|
|
1650
|
+
this.config.windowMs = minutes * 60 * 1e3;
|
|
1651
|
+
return this;
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Set window duration in hours
|
|
1655
|
+
*/
|
|
1656
|
+
windowHours(hours) {
|
|
1657
|
+
this.config.windowMs = hours * 60 * 60 * 1e3;
|
|
1658
|
+
return this;
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Set maximum requests
|
|
1662
|
+
*/
|
|
1663
|
+
max(requests) {
|
|
1664
|
+
this.config.maxRequests = requests;
|
|
1665
|
+
return this;
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Set error message
|
|
1669
|
+
*/
|
|
1670
|
+
message(msg) {
|
|
1671
|
+
this.config.message = msg;
|
|
1672
|
+
return this;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Skip successful requests
|
|
1676
|
+
*/
|
|
1677
|
+
skipSuccessful(skip = true) {
|
|
1678
|
+
this.config.skipSuccessfulRequests = skip;
|
|
1679
|
+
return this;
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Skip failed requests
|
|
1683
|
+
*/
|
|
1684
|
+
skipFailed(skip = true) {
|
|
1685
|
+
this.config.skipFailedRequests = skip;
|
|
1686
|
+
return this;
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Set key generator
|
|
1690
|
+
*/
|
|
1691
|
+
keyBy(generator) {
|
|
1692
|
+
this.options.keyGenerator = generator;
|
|
1693
|
+
return this;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Key by IP (default)
|
|
1697
|
+
*/
|
|
1698
|
+
keyByIp() {
|
|
1699
|
+
this.options.keyGenerator = defaultKeyGenerator;
|
|
1700
|
+
return this;
|
|
1701
|
+
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Key by API key
|
|
1704
|
+
*/
|
|
1705
|
+
keyByApiKey(headerName) {
|
|
1706
|
+
this.options.keyGenerator = createApiKeyGenerator(headerName);
|
|
1707
|
+
return this;
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Skip certain requests
|
|
1711
|
+
*/
|
|
1712
|
+
skipWhen(predicate) {
|
|
1713
|
+
this.options.skip = predicate;
|
|
1714
|
+
return this;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Build the rate limiter
|
|
1718
|
+
*/
|
|
1719
|
+
build() {
|
|
1720
|
+
return createRateLimiter(this.config, this.options);
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
var rateLimiter = (preset) => {
|
|
1724
|
+
return new RateLimiterBuilder(preset);
|
|
1725
|
+
};
|
|
1726
|
+
var RATE_LIMIT_CONFIG = {
|
|
1727
|
+
STANDARD: DEFAULT_RATE_LIMIT_TIERS.STANDARD,
|
|
1728
|
+
STRICT: DEFAULT_RATE_LIMIT_TIERS.STRICT,
|
|
1729
|
+
DDOS: DEFAULT_RATE_LIMIT_TIERS.DDOS
|
|
1730
|
+
};
|
|
1731
|
+
var standardRateLimiter = createStandardRateLimiter();
|
|
1732
|
+
var strictRateLimiter = createStrictRateLimiter();
|
|
1733
|
+
var ddosProtectionLimiter = createDdosRateLimiter();
|
|
1734
|
+
|
|
1735
|
+
// src/server/configs/server.config.ts
|
|
1736
|
+
var DEFAULT_SERVER_CONFIG = {
|
|
1737
|
+
name: "app-server",
|
|
1738
|
+
version: "1.0.0",
|
|
1739
|
+
environment: process.env.NODE_ENV || "development",
|
|
1740
|
+
port: parseInt(process.env.PORT || "3000", 10),
|
|
1741
|
+
host: process.env.HOST || "0.0.0.0",
|
|
1742
|
+
basePath: "/api",
|
|
1743
|
+
debug: process.env.DEBUG === "true",
|
|
1744
|
+
trustProxy: true
|
|
1745
|
+
};
|
|
1746
|
+
var DEFAULT_DATABASE_CONFIG = {
|
|
1747
|
+
uri: process.env.DATABASE_URL || process.env.MONGODB_URI || "",
|
|
1748
|
+
name: process.env.DATABASE_NAME || "app_db",
|
|
1749
|
+
maxPoolSize: process.env.NODE_ENV === "production" ? 50 : 10,
|
|
1750
|
+
minPoolSize: process.env.NODE_ENV === "production" ? 10 : 5,
|
|
1751
|
+
socketTimeoutMS: 45e3,
|
|
1752
|
+
serverSelectionTimeoutMS: 1e4,
|
|
1753
|
+
maxIdleTimeMS: 1e4,
|
|
1754
|
+
retryWrites: true,
|
|
1755
|
+
retryReads: true,
|
|
1756
|
+
writeConcern: "majority"
|
|
1757
|
+
};
|
|
1758
|
+
var DEFAULT_AUTH_CONFIG = {
|
|
1759
|
+
jwtSecret: process.env.JWT_SECRET || "",
|
|
1760
|
+
jwtExpiresIn: process.env.JWT_EXPIRES_IN || "7d",
|
|
1761
|
+
refreshTokenExpiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN || "30d",
|
|
1762
|
+
enableRefreshTokens: true,
|
|
1763
|
+
apiKeyHeader: "x-api-key",
|
|
1764
|
+
orgHeader: "x-organization-id"
|
|
1765
|
+
};
|
|
1766
|
+
var DEFAULT_LOGGING_CONFIG = {
|
|
1767
|
+
level: process.env.LOG_LEVEL || "info",
|
|
1768
|
+
logsDir: process.env.LOGS_DIR || "logs",
|
|
1769
|
+
maxSize: "20m",
|
|
1770
|
+
maxFiles: "14d",
|
|
1771
|
+
errorMaxFiles: "30d",
|
|
1772
|
+
console: true,
|
|
1773
|
+
file: process.env.NODE_ENV === "production",
|
|
1774
|
+
json: process.env.NODE_ENV === "production"
|
|
1775
|
+
};
|
|
1776
|
+
var DEFAULT_CORS_ORIGINS = {
|
|
1777
|
+
production: [],
|
|
1778
|
+
development: [
|
|
1779
|
+
"http://localhost:3000",
|
|
1780
|
+
"http://localhost:4000",
|
|
1781
|
+
"http://localhost:5173",
|
|
1782
|
+
"http://127.0.0.1:3000",
|
|
1783
|
+
"http://127.0.0.1:4000",
|
|
1784
|
+
"http://127.0.0.1:5173"
|
|
1785
|
+
],
|
|
1786
|
+
patterns: []
|
|
1787
|
+
};
|
|
1788
|
+
var DEFAULT_RATE_LIMIT_CONFIG = {
|
|
1789
|
+
enabled: true,
|
|
1790
|
+
standard: {
|
|
1791
|
+
windowMs: 15 * 60 * 1e3,
|
|
1792
|
+
// 15 minutes
|
|
1793
|
+
maxRequests: 100,
|
|
1794
|
+
message: "Too many requests, please try again later."
|
|
1795
|
+
},
|
|
1796
|
+
strict: {
|
|
1797
|
+
windowMs: 15 * 60 * 1e3,
|
|
1798
|
+
// 15 minutes
|
|
1799
|
+
maxRequests: 20,
|
|
1800
|
+
message: "Too many requests, please try again later."
|
|
1801
|
+
},
|
|
1802
|
+
ddos: {
|
|
1803
|
+
windowMs: 60 * 1e3,
|
|
1804
|
+
// 1 minute
|
|
1805
|
+
maxRequests: 60,
|
|
1806
|
+
message: "Rate limit exceeded. Please slow down.",
|
|
1807
|
+
skipSuccessfulRequests: false
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
function deepMerge(target, source) {
|
|
1811
|
+
const result = { ...target };
|
|
1812
|
+
for (const key in source) {
|
|
1813
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1814
|
+
const sourceValue = source[key];
|
|
1815
|
+
const targetValue = target[key];
|
|
1816
|
+
if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
1817
|
+
result[key] = deepMerge(
|
|
1818
|
+
targetValue,
|
|
1819
|
+
sourceValue
|
|
1820
|
+
);
|
|
1821
|
+
} else if (sourceValue !== void 0) {
|
|
1822
|
+
result[key] = sourceValue;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return result;
|
|
1827
|
+
}
|
|
1828
|
+
var ConfigBuilder = class {
|
|
1829
|
+
constructor() {
|
|
1830
|
+
this.config = {
|
|
1831
|
+
server: { ...DEFAULT_SERVER_CONFIG },
|
|
1832
|
+
database: { ...DEFAULT_DATABASE_CONFIG },
|
|
1833
|
+
auth: { ...DEFAULT_AUTH_CONFIG },
|
|
1834
|
+
logging: { ...DEFAULT_LOGGING_CONFIG },
|
|
1835
|
+
cors: { ...DEFAULT_CORS_ORIGINS },
|
|
1836
|
+
rateLimit: { ...DEFAULT_RATE_LIMIT_CONFIG }
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Set server configuration
|
|
1841
|
+
*/
|
|
1842
|
+
setServer(config) {
|
|
1843
|
+
this.config.server = deepMerge(this.config.server, config);
|
|
1844
|
+
return this;
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Set database configuration
|
|
1848
|
+
*/
|
|
1849
|
+
setDatabase(config) {
|
|
1850
|
+
this.config.database = deepMerge(this.config.database, config);
|
|
1851
|
+
return this;
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Set auth configuration
|
|
1855
|
+
*/
|
|
1856
|
+
setAuth(config) {
|
|
1857
|
+
this.config.auth = deepMerge(this.config.auth, config);
|
|
1858
|
+
return this;
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Set logging configuration
|
|
1862
|
+
*/
|
|
1863
|
+
setLogging(config) {
|
|
1864
|
+
this.config.logging = deepMerge(this.config.logging, config);
|
|
1865
|
+
return this;
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Set CORS origins
|
|
1869
|
+
*/
|
|
1870
|
+
setCorsOrigins(config) {
|
|
1871
|
+
this.config.cors = deepMerge(this.config.cors, config);
|
|
1872
|
+
return this;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Add CORS production origin
|
|
1876
|
+
*/
|
|
1877
|
+
addProductionOrigin(origin) {
|
|
1878
|
+
if (!this.config.cors.production.includes(origin)) {
|
|
1879
|
+
this.config.cors.production.push(origin);
|
|
1880
|
+
}
|
|
1881
|
+
return this;
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Add CORS development origin
|
|
1885
|
+
*/
|
|
1886
|
+
addDevelopmentOrigin(origin) {
|
|
1887
|
+
if (!this.config.cors.development.includes(origin)) {
|
|
1888
|
+
this.config.cors.development.push(origin);
|
|
1889
|
+
}
|
|
1890
|
+
return this;
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Add CORS pattern
|
|
1894
|
+
*/
|
|
1895
|
+
addCorsPattern(pattern) {
|
|
1896
|
+
if (!this.config.cors.patterns.includes(pattern)) {
|
|
1897
|
+
this.config.cors.patterns.push(pattern);
|
|
1898
|
+
}
|
|
1899
|
+
return this;
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Set rate limit configuration
|
|
1903
|
+
*/
|
|
1904
|
+
setRateLimit(config) {
|
|
1905
|
+
this.config.rateLimit = deepMerge(this.config.rateLimit, config);
|
|
1906
|
+
return this;
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Add custom rate limit tier
|
|
1910
|
+
*/
|
|
1911
|
+
addRateLimitTier(name, tier) {
|
|
1912
|
+
if (!this.config.rateLimit.custom) {
|
|
1913
|
+
this.config.rateLimit.custom = {};
|
|
1914
|
+
}
|
|
1915
|
+
this.config.rateLimit.custom[name] = tier;
|
|
1916
|
+
return this;
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Set custom configuration
|
|
1920
|
+
*/
|
|
1921
|
+
setCustom(key, value) {
|
|
1922
|
+
if (!this.config.custom) {
|
|
1923
|
+
this.config.custom = {};
|
|
1924
|
+
}
|
|
1925
|
+
this.config.custom[key] = value;
|
|
1926
|
+
return this;
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* Load configuration from environment variables
|
|
1930
|
+
*/
|
|
1931
|
+
loadFromEnv() {
|
|
1932
|
+
if (process.env.SERVER_NAME) this.config.server.name = process.env.SERVER_NAME;
|
|
1933
|
+
if (process.env.SERVER_VERSION) this.config.server.version = process.env.SERVER_VERSION;
|
|
1934
|
+
if (process.env.PORT) this.config.server.port = parseInt(process.env.PORT, 10);
|
|
1935
|
+
if (process.env.HOST) this.config.server.host = process.env.HOST;
|
|
1936
|
+
if (process.env.BASE_PATH) this.config.server.basePath = process.env.BASE_PATH;
|
|
1937
|
+
if (process.env.DATABASE_URL) this.config.database.uri = process.env.DATABASE_URL;
|
|
1938
|
+
if (process.env.MONGODB_URI) this.config.database.uri = process.env.MONGODB_URI;
|
|
1939
|
+
if (process.env.DATABASE_NAME) this.config.database.name = process.env.DATABASE_NAME;
|
|
1940
|
+
if (process.env.JWT_SECRET) this.config.auth.jwtSecret = process.env.JWT_SECRET;
|
|
1941
|
+
if (process.env.JWT_EXPIRES_IN) this.config.auth.jwtExpiresIn = process.env.JWT_EXPIRES_IN;
|
|
1942
|
+
if (process.env.LOG_LEVEL) this.config.logging.level = process.env.LOG_LEVEL;
|
|
1943
|
+
if (process.env.LOGS_DIR) this.config.logging.logsDir = process.env.LOGS_DIR;
|
|
1944
|
+
if (process.env.CORS_ORIGINS) {
|
|
1945
|
+
const origins = process.env.CORS_ORIGINS.split(",").map((o) => o.trim());
|
|
1946
|
+
this.config.cors.production.push(...origins);
|
|
1947
|
+
}
|
|
1948
|
+
return this;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Validate configuration
|
|
1952
|
+
*/
|
|
1953
|
+
validate() {
|
|
1954
|
+
const errors = [];
|
|
1955
|
+
if (!this.config.server.name) errors.push("Server name is required");
|
|
1956
|
+
if (this.config.server.port < 1 || this.config.server.port > 65535) {
|
|
1957
|
+
errors.push("Server port must be between 1 and 65535");
|
|
1958
|
+
}
|
|
1959
|
+
if (this.config.server.environment === "production") {
|
|
1960
|
+
if (!this.config.auth.jwtSecret || this.config.auth.jwtSecret.length < 32) {
|
|
1961
|
+
errors.push("JWT secret must be at least 32 characters in production");
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
return { valid: errors.length === 0, errors };
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Build the final configuration
|
|
1968
|
+
*/
|
|
1969
|
+
build() {
|
|
1970
|
+
return { ...this.config };
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
var createConfig = () => {
|
|
1974
|
+
return new ConfigBuilder();
|
|
1975
|
+
};
|
|
1976
|
+
var buildConfig = (partial = {}) => {
|
|
1977
|
+
const builder = createConfig().loadFromEnv();
|
|
1978
|
+
if (partial.server) builder.setServer(partial.server);
|
|
1979
|
+
if (partial.database) builder.setDatabase(partial.database);
|
|
1980
|
+
if (partial.auth) builder.setAuth(partial.auth);
|
|
1981
|
+
if (partial.logging) builder.setLogging(partial.logging);
|
|
1982
|
+
if (partial.cors) builder.setCorsOrigins(partial.cors);
|
|
1983
|
+
if (partial.rateLimit) builder.setRateLimit(partial.rateLimit);
|
|
1984
|
+
return builder.build();
|
|
1985
|
+
};
|
|
1986
|
+
var isProduction = (config) => {
|
|
1987
|
+
return (config?.environment || process.env.NODE_ENV) === "production";
|
|
1988
|
+
};
|
|
1989
|
+
var isDevelopment = (config) => {
|
|
1990
|
+
return (config?.environment || process.env.NODE_ENV) === "development";
|
|
1991
|
+
};
|
|
1992
|
+
var isTest = (config) => {
|
|
1993
|
+
return (config?.environment || process.env.NODE_ENV) === "test";
|
|
1994
|
+
};
|
|
1995
|
+
var getDatabaseOptions = (config) => {
|
|
1996
|
+
return {
|
|
1997
|
+
maxPoolSize: config.maxPoolSize,
|
|
1998
|
+
minPoolSize: config.minPoolSize,
|
|
1999
|
+
socketTimeoutMS: config.socketTimeoutMS,
|
|
2000
|
+
serverSelectionTimeoutMS: config.serverSelectionTimeoutMS,
|
|
2001
|
+
maxIdleTimeMS: config.maxIdleTimeMS,
|
|
2002
|
+
retryWrites: config.retryWrites,
|
|
2003
|
+
retryReads: config.retryReads,
|
|
2004
|
+
w: config.writeConcern
|
|
2005
|
+
};
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
exports.ConfigBuilder = ConfigBuilder;
|
|
2009
|
+
exports.DEFAULT_AUTH_CONFIG = DEFAULT_AUTH_CONFIG;
|
|
2010
|
+
exports.DEFAULT_CORS_CONFIG = DEFAULT_CORS_CONFIG;
|
|
2011
|
+
exports.DEFAULT_CORS_ORIGINS = DEFAULT_CORS_ORIGINS;
|
|
2012
|
+
exports.DEFAULT_DATABASE_CONFIG = DEFAULT_DATABASE_CONFIG;
|
|
2013
|
+
exports.DEFAULT_LOGGING_CONFIG = DEFAULT_LOGGING_CONFIG;
|
|
2014
|
+
exports.DEFAULT_RATE_LIMIT_CONFIG = DEFAULT_RATE_LIMIT_CONFIG;
|
|
2015
|
+
exports.DEFAULT_RATE_LIMIT_TIERS = DEFAULT_RATE_LIMIT_TIERS;
|
|
2016
|
+
exports.DEFAULT_SERVER_CONFIG = DEFAULT_SERVER_CONFIG;
|
|
2017
|
+
exports.EXYCONN_CORS_CONFIG = EXYCONN_CORS_CONFIG;
|
|
2018
|
+
exports.PERMISSIVE_CORS_CONFIG = PERMISSIVE_CORS_CONFIG;
|
|
2019
|
+
exports.RATE_LIMIT_CONFIG = RATE_LIMIT_CONFIG;
|
|
2020
|
+
exports.RateLimiterBuilder = RateLimiterBuilder;
|
|
2021
|
+
exports.STRICT_CORS_CONFIG = STRICT_CORS_CONFIG;
|
|
723
2022
|
exports.StatusCode = StatusCode;
|
|
724
2023
|
exports.StatusMessage = StatusMessage;
|
|
725
2024
|
exports.authenticateApiKey = authenticateApiKey;
|
|
726
2025
|
exports.authenticateJWT = authenticateJWT;
|
|
727
2026
|
exports.badRequestResponse = badRequestResponse;
|
|
2027
|
+
exports.buildConfig = buildConfig;
|
|
2028
|
+
exports.buildDeleteFilter = buildDeleteFilter;
|
|
728
2029
|
exports.buildFilter = buildFilter;
|
|
729
2030
|
exports.buildPagination = buildPagination;
|
|
730
2031
|
exports.buildPaginationMeta = buildPaginationMeta;
|
|
731
2032
|
exports.checkPackageServer = checkPackageServer;
|
|
732
2033
|
exports.conflictResponse = conflictResponse;
|
|
733
2034
|
exports.connectDB = connectDB;
|
|
2035
|
+
exports.corsOptions = corsOptions;
|
|
2036
|
+
exports.createApiKeyGenerator = createApiKeyGenerator;
|
|
2037
|
+
exports.createApiRateLimiter = createApiRateLimiter;
|
|
2038
|
+
exports.createBrandCorsOptions = createBrandCorsOptions;
|
|
2039
|
+
exports.createBulkDeleteHandler = createBulkDeleteHandler;
|
|
2040
|
+
exports.createConfig = createConfig;
|
|
2041
|
+
exports.createCorsOptions = createCorsOptions;
|
|
2042
|
+
exports.createCrudControllers = createCrudControllers;
|
|
2043
|
+
exports.createDdosRateLimiter = createDdosRateLimiter;
|
|
734
2044
|
exports.createLogger = createLogger;
|
|
735
2045
|
exports.createMorganStream = createMorganStream;
|
|
2046
|
+
exports.createMultiBrandCorsOptions = createMultiBrandCorsOptions;
|
|
2047
|
+
exports.createPaginationMiddleware = createPaginationMiddleware;
|
|
2048
|
+
exports.createPrefixedKeyGenerator = createPrefixedKeyGenerator;
|
|
2049
|
+
exports.createRateLimiter = createRateLimiter;
|
|
2050
|
+
exports.createStandardRateLimiter = createStandardRateLimiter;
|
|
2051
|
+
exports.createStrictRateLimiter = createStrictRateLimiter;
|
|
2052
|
+
exports.createUserKeyGenerator = createUserKeyGenerator;
|
|
736
2053
|
exports.createdResponse = createdResponse;
|
|
2054
|
+
exports.ddosProtectionLimiter = ddosProtectionLimiter;
|
|
2055
|
+
exports.defaultKeyGenerator = defaultKeyGenerator;
|
|
737
2056
|
exports.disconnectDB = disconnectDB;
|
|
738
2057
|
exports.errorResponse = errorResponse;
|
|
739
2058
|
exports.extractColumns = extractColumns;
|
|
740
2059
|
exports.extractOrganization = extractOrganization;
|
|
2060
|
+
exports.extractSchemaMeta = extractSchemaMeta;
|
|
741
2061
|
exports.forbiddenResponse = forbiddenResponse;
|
|
742
2062
|
exports.formatPackageCheckResult = formatPackageCheckResult;
|
|
743
2063
|
exports.generateNcuCommand = generateNcuCommand;
|
|
744
2064
|
exports.getConnectionStatus = getConnectionStatus;
|
|
2065
|
+
exports.getDatabaseOptions = getDatabaseOptions;
|
|
2066
|
+
exports.isDevelopment = isDevelopment;
|
|
2067
|
+
exports.isProduction = isProduction;
|
|
2068
|
+
exports.isTest = isTest;
|
|
745
2069
|
exports.logger = logger;
|
|
746
2070
|
exports.noContentResponse = noContentResponse;
|
|
747
2071
|
exports.notFoundResponse = notFoundResponse;
|
|
748
2072
|
exports.omitFields = omitFields;
|
|
749
2073
|
exports.optionalAuthenticateJWT = optionalAuthenticateJWT;
|
|
750
2074
|
exports.packageCheckServer = packageCheckServer;
|
|
2075
|
+
exports.parseBulkDelete = parseBulkDelete;
|
|
751
2076
|
exports.pickFields = pickFields;
|
|
752
2077
|
exports.printPackageCheckSummary = printPackageCheckSummary;
|
|
2078
|
+
exports.queryPagination = queryPagination;
|
|
2079
|
+
exports.queryParser = queryParser;
|
|
753
2080
|
exports.rateLimitResponse = rateLimitResponse;
|
|
2081
|
+
exports.rateLimiter = rateLimiter;
|
|
754
2082
|
exports.requireOrganization = requireOrganization;
|
|
755
2083
|
exports.sanitizeDocument = sanitizeDocument;
|
|
756
2084
|
exports.sanitizeUser = sanitizeUser;
|
|
757
2085
|
exports.simpleLogger = simpleLogger;
|
|
2086
|
+
exports.standardRateLimiter = standardRateLimiter;
|
|
758
2087
|
exports.statusCode = statusCode;
|
|
759
2088
|
exports.statusMessage = statusMessage;
|
|
760
2089
|
exports.stream = stream;
|
|
2090
|
+
exports.strictRateLimiter = strictRateLimiter;
|
|
761
2091
|
exports.successResponse = successResponse;
|
|
762
2092
|
exports.successResponseArr = successResponseArr;
|
|
763
2093
|
exports.unauthorizedResponse = unauthorizedResponse;
|