@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/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
|
var axios = require('axios');
|
|
10
11
|
var react = require('react');
|
|
11
12
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -39,6 +40,7 @@ var DailyRotateFile__default = /*#__PURE__*/_interopDefault(DailyRotateFile);
|
|
|
39
40
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
40
41
|
var mongoose__default = /*#__PURE__*/_interopDefault(mongoose);
|
|
41
42
|
var jwt__default = /*#__PURE__*/_interopDefault(jwt);
|
|
43
|
+
var rateLimit__default = /*#__PURE__*/_interopDefault(rateLimit);
|
|
42
44
|
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
43
45
|
var Yup__namespace = /*#__PURE__*/_interopNamespace(Yup);
|
|
44
46
|
|
|
@@ -51,44 +53,89 @@ var __export = (target, all) => {
|
|
|
51
53
|
// src/server/index.ts
|
|
52
54
|
var server_exports = {};
|
|
53
55
|
__export(server_exports, {
|
|
56
|
+
ConfigBuilder: () => ConfigBuilder,
|
|
57
|
+
DEFAULT_AUTH_CONFIG: () => DEFAULT_AUTH_CONFIG,
|
|
58
|
+
DEFAULT_CORS_CONFIG: () => DEFAULT_CORS_CONFIG,
|
|
59
|
+
DEFAULT_CORS_ORIGINS: () => DEFAULT_CORS_ORIGINS,
|
|
60
|
+
DEFAULT_DATABASE_CONFIG: () => DEFAULT_DATABASE_CONFIG,
|
|
61
|
+
DEFAULT_LOGGING_CONFIG: () => DEFAULT_LOGGING_CONFIG,
|
|
62
|
+
DEFAULT_RATE_LIMIT_CONFIG: () => DEFAULT_RATE_LIMIT_CONFIG,
|
|
63
|
+
DEFAULT_RATE_LIMIT_TIERS: () => DEFAULT_RATE_LIMIT_TIERS,
|
|
64
|
+
DEFAULT_SERVER_CONFIG: () => DEFAULT_SERVER_CONFIG,
|
|
65
|
+
EXYCONN_CORS_CONFIG: () => EXYCONN_CORS_CONFIG,
|
|
66
|
+
PERMISSIVE_CORS_CONFIG: () => PERMISSIVE_CORS_CONFIG,
|
|
67
|
+
RATE_LIMIT_CONFIG: () => RATE_LIMIT_CONFIG,
|
|
68
|
+
RateLimiterBuilder: () => RateLimiterBuilder,
|
|
69
|
+
STRICT_CORS_CONFIG: () => STRICT_CORS_CONFIG,
|
|
54
70
|
StatusCode: () => StatusCode,
|
|
55
71
|
StatusMessage: () => StatusMessage,
|
|
56
72
|
authenticateApiKey: () => authenticateApiKey,
|
|
57
73
|
authenticateJWT: () => authenticateJWT,
|
|
58
74
|
badRequestResponse: () => badRequestResponse,
|
|
75
|
+
buildConfig: () => buildConfig,
|
|
76
|
+
buildDeleteFilter: () => buildDeleteFilter,
|
|
59
77
|
buildFilter: () => buildFilter,
|
|
60
78
|
buildPagination: () => buildPagination,
|
|
61
79
|
buildPaginationMeta: () => buildPaginationMeta,
|
|
62
80
|
checkPackageServer: () => checkPackageServer,
|
|
63
81
|
conflictResponse: () => conflictResponse,
|
|
64
82
|
connectDB: () => connectDB,
|
|
83
|
+
corsOptions: () => corsOptions,
|
|
84
|
+
createApiKeyGenerator: () => createApiKeyGenerator,
|
|
85
|
+
createApiRateLimiter: () => createApiRateLimiter,
|
|
86
|
+
createBrandCorsOptions: () => createBrandCorsOptions,
|
|
87
|
+
createBulkDeleteHandler: () => createBulkDeleteHandler,
|
|
88
|
+
createConfig: () => createConfig,
|
|
89
|
+
createCorsOptions: () => createCorsOptions,
|
|
90
|
+
createCrudControllers: () => createCrudControllers,
|
|
91
|
+
createDdosRateLimiter: () => createDdosRateLimiter,
|
|
65
92
|
createLogger: () => createLogger,
|
|
66
93
|
createMorganStream: () => createMorganStream,
|
|
94
|
+
createMultiBrandCorsOptions: () => createMultiBrandCorsOptions,
|
|
95
|
+
createPaginationMiddleware: () => createPaginationMiddleware,
|
|
96
|
+
createPrefixedKeyGenerator: () => createPrefixedKeyGenerator,
|
|
97
|
+
createRateLimiter: () => createRateLimiter,
|
|
98
|
+
createStandardRateLimiter: () => createStandardRateLimiter,
|
|
99
|
+
createStrictRateLimiter: () => createStrictRateLimiter,
|
|
100
|
+
createUserKeyGenerator: () => createUserKeyGenerator,
|
|
67
101
|
createdResponse: () => createdResponse,
|
|
102
|
+
ddosProtectionLimiter: () => ddosProtectionLimiter,
|
|
103
|
+
defaultKeyGenerator: () => defaultKeyGenerator,
|
|
68
104
|
disconnectDB: () => disconnectDB,
|
|
69
105
|
errorResponse: () => errorResponse,
|
|
70
106
|
extractColumns: () => extractColumns,
|
|
71
107
|
extractOrganization: () => extractOrganization,
|
|
108
|
+
extractSchemaMeta: () => extractSchemaMeta,
|
|
72
109
|
forbiddenResponse: () => forbiddenResponse,
|
|
73
110
|
formatPackageCheckResult: () => formatPackageCheckResult,
|
|
74
111
|
generateNcuCommand: () => generateNcuCommand,
|
|
75
112
|
getConnectionStatus: () => getConnectionStatus,
|
|
113
|
+
getDatabaseOptions: () => getDatabaseOptions,
|
|
114
|
+
isDevelopment: () => isDevelopment,
|
|
115
|
+
isProduction: () => isProduction,
|
|
116
|
+
isTest: () => isTest,
|
|
76
117
|
logger: () => logger,
|
|
77
118
|
noContentResponse: () => noContentResponse,
|
|
78
119
|
notFoundResponse: () => notFoundResponse,
|
|
79
120
|
omitFields: () => omitFields,
|
|
80
121
|
optionalAuthenticateJWT: () => optionalAuthenticateJWT,
|
|
81
122
|
packageCheckServer: () => packageCheckServer,
|
|
123
|
+
parseBulkDelete: () => parseBulkDelete,
|
|
82
124
|
pickFields: () => pickFields,
|
|
83
125
|
printPackageCheckSummary: () => printPackageCheckSummary,
|
|
126
|
+
queryPagination: () => queryPagination,
|
|
127
|
+
queryParser: () => queryParser,
|
|
84
128
|
rateLimitResponse: () => rateLimitResponse,
|
|
129
|
+
rateLimiter: () => rateLimiter,
|
|
85
130
|
requireOrganization: () => requireOrganization,
|
|
86
131
|
sanitizeDocument: () => sanitizeDocument,
|
|
87
132
|
sanitizeUser: () => sanitizeUser,
|
|
88
133
|
simpleLogger: () => simpleLogger,
|
|
134
|
+
standardRateLimiter: () => standardRateLimiter,
|
|
89
135
|
statusCode: () => statusCode,
|
|
90
136
|
statusMessage: () => statusMessage,
|
|
91
137
|
stream: () => stream,
|
|
138
|
+
strictRateLimiter: () => strictRateLimiter,
|
|
92
139
|
successResponse: () => successResponse,
|
|
93
140
|
successResponseArr: () => successResponseArr,
|
|
94
141
|
unauthorizedResponse: () => unauthorizedResponse,
|
|
@@ -371,37 +418,37 @@ var defaultOptions = {
|
|
|
371
418
|
retryReads: true,
|
|
372
419
|
w: "majority"
|
|
373
420
|
};
|
|
374
|
-
var connectDB = async (mongoUri, options = {},
|
|
421
|
+
var connectDB = async (mongoUri, options = {}, logger3) => {
|
|
375
422
|
if (mongoose__default.default.connection.readyState === 1) {
|
|
376
|
-
|
|
423
|
+
logger3?.info("Database already connected, reusing connection");
|
|
377
424
|
return mongoose__default.default;
|
|
378
425
|
}
|
|
379
426
|
const finalOptions = { ...defaultOptions, ...options };
|
|
380
427
|
try {
|
|
381
428
|
await mongoose__default.default.connect(mongoUri, finalOptions);
|
|
382
|
-
|
|
429
|
+
logger3?.info("MongoDB connected successfully");
|
|
383
430
|
mongoose__default.default.connection.on("error", (err) => {
|
|
384
|
-
|
|
431
|
+
logger3?.error("MongoDB connection error", err);
|
|
385
432
|
});
|
|
386
433
|
mongoose__default.default.connection.on("disconnected", () => {
|
|
387
|
-
|
|
434
|
+
logger3?.info("MongoDB disconnected");
|
|
388
435
|
});
|
|
389
436
|
mongoose__default.default.connection.on("reconnected", () => {
|
|
390
|
-
|
|
437
|
+
logger3?.info("MongoDB reconnected");
|
|
391
438
|
});
|
|
392
439
|
return mongoose__default.default;
|
|
393
440
|
} catch (error) {
|
|
394
441
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
395
|
-
|
|
442
|
+
logger3?.error("MongoDB connection failed", { error: errorMessage });
|
|
396
443
|
throw error;
|
|
397
444
|
}
|
|
398
445
|
};
|
|
399
|
-
var disconnectDB = async (
|
|
446
|
+
var disconnectDB = async (logger3) => {
|
|
400
447
|
try {
|
|
401
448
|
await mongoose__default.default.disconnect();
|
|
402
|
-
|
|
449
|
+
logger3?.info("MongoDB disconnected successfully");
|
|
403
450
|
} catch (error) {
|
|
404
|
-
|
|
451
|
+
logger3?.error("Error disconnecting from MongoDB", error);
|
|
405
452
|
throw error;
|
|
406
453
|
}
|
|
407
454
|
};
|
|
@@ -506,6 +553,629 @@ var requireOrganization = (req, res, next) => {
|
|
|
506
553
|
next();
|
|
507
554
|
};
|
|
508
555
|
|
|
556
|
+
// src/server/middleware/queryParser.middleware.ts
|
|
557
|
+
var queryParser = (req, _, next) => {
|
|
558
|
+
const { page, limit, sort, sortBy, sortOrder, search, filter, ...otherParams } = req.query;
|
|
559
|
+
const parsed = {
|
|
560
|
+
page: Math.max(Number(page) || 1, 1),
|
|
561
|
+
limit: Math.min(Number(limit) || 10, 100),
|
|
562
|
+
filter: {}
|
|
563
|
+
};
|
|
564
|
+
if (typeof sort === "string") {
|
|
565
|
+
const [field, order] = sort.split(":");
|
|
566
|
+
parsed.sort = {
|
|
567
|
+
field,
|
|
568
|
+
order: order === "asc" ? "asc" : "desc"
|
|
569
|
+
};
|
|
570
|
+
} else if (typeof sortBy === "string") {
|
|
571
|
+
parsed.sort = {
|
|
572
|
+
field: sortBy,
|
|
573
|
+
order: sortOrder === "asc" ? "asc" : "desc"
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
if (typeof search === "string") {
|
|
577
|
+
parsed.search = search;
|
|
578
|
+
}
|
|
579
|
+
if (typeof filter === "object" && filter !== null) {
|
|
580
|
+
Object.entries(filter).forEach(([key, value]) => {
|
|
581
|
+
if (value !== "all") {
|
|
582
|
+
parsed.filter[key] = value;
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
Object.entries(otherParams).forEach(([key, value]) => {
|
|
587
|
+
if (typeof value === "string" && value !== "all" && !["page", "limit", "sort", "sortBy", "sortOrder", "search"].includes(key)) {
|
|
588
|
+
parsed.filter[key] = value;
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
req.parsedQuery = parsed;
|
|
592
|
+
next();
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// src/server/middleware/utils/schemaMeta.util.ts
|
|
596
|
+
var getZodTypeName = (schema) => {
|
|
597
|
+
const typeName = schema._def?.typeName;
|
|
598
|
+
switch (typeName) {
|
|
599
|
+
case "ZodString":
|
|
600
|
+
return "string";
|
|
601
|
+
case "ZodNumber":
|
|
602
|
+
return "number";
|
|
603
|
+
case "ZodBoolean":
|
|
604
|
+
return "boolean";
|
|
605
|
+
case "ZodDate":
|
|
606
|
+
return "date";
|
|
607
|
+
case "ZodArray":
|
|
608
|
+
return "array";
|
|
609
|
+
case "ZodObject":
|
|
610
|
+
return "object";
|
|
611
|
+
case "ZodOptional":
|
|
612
|
+
case "ZodNullable":
|
|
613
|
+
return schema._def?.innerType ? getZodTypeName(schema._def.innerType) : "unknown";
|
|
614
|
+
case "ZodDefault":
|
|
615
|
+
return schema._def?.innerType ? getZodTypeName(schema._def.innerType) : "unknown";
|
|
616
|
+
case "ZodEnum":
|
|
617
|
+
return "enum";
|
|
618
|
+
case "ZodUnion":
|
|
619
|
+
return "union";
|
|
620
|
+
default:
|
|
621
|
+
return "unknown";
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
var isZodRequired = (schema) => {
|
|
625
|
+
const typeName = schema._def?.typeName;
|
|
626
|
+
return typeName !== "ZodOptional" && typeName !== "ZodNullable";
|
|
627
|
+
};
|
|
628
|
+
var extractSchemaMeta = (model, zodSchema) => {
|
|
629
|
+
const columns = [];
|
|
630
|
+
if (zodSchema && zodSchema.shape) {
|
|
631
|
+
const shape = zodSchema.shape;
|
|
632
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
633
|
+
if (key.startsWith("_")) continue;
|
|
634
|
+
columns.push({
|
|
635
|
+
name: key,
|
|
636
|
+
datatype: getZodTypeName(value),
|
|
637
|
+
required: isZodRequired(value)
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return columns;
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
const schema = model.schema;
|
|
644
|
+
const paths = schema.paths;
|
|
645
|
+
for (const [key, pathInfo] of Object.entries(paths)) {
|
|
646
|
+
if (key.startsWith("_") || key === "__v") continue;
|
|
647
|
+
const schemaType = pathInfo;
|
|
648
|
+
columns.push({
|
|
649
|
+
name: key,
|
|
650
|
+
datatype: (schemaType.instance || "unknown").toLowerCase(),
|
|
651
|
+
required: schemaType.isRequired || false
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
} catch {
|
|
655
|
+
}
|
|
656
|
+
return columns;
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
// src/server/middleware/pagination.middleware.ts
|
|
660
|
+
var queryPagination = (model, options = {}, withOrgId = true) => {
|
|
661
|
+
return async (req, res, next) => {
|
|
662
|
+
try {
|
|
663
|
+
const { page, limit, sort, search, filter } = req.parsedQuery;
|
|
664
|
+
const query = {};
|
|
665
|
+
Object.entries(filter).forEach(([key, value]) => {
|
|
666
|
+
if (options.regexFilterFields?.includes(key)) {
|
|
667
|
+
query[key] = { $regex: value, $options: "i" };
|
|
668
|
+
} else {
|
|
669
|
+
query[key] = value;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
const organizationId = req.headers["x-organization-id"];
|
|
673
|
+
if (organizationId && typeof organizationId === "string" && withOrgId) {
|
|
674
|
+
query.organizationId = organizationId;
|
|
675
|
+
}
|
|
676
|
+
if (search && options.searchFields?.length) {
|
|
677
|
+
query.$or = options.searchFields.map((field) => ({
|
|
678
|
+
[field]: { $regex: search, $options: "i" }
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
const sortQuery = sort ? { [sort.field]: sort.order } : { createdAt: "desc" };
|
|
682
|
+
const skip = (page - 1) * limit;
|
|
683
|
+
const [data, total] = await Promise.all([
|
|
684
|
+
model.find(query).sort(sortQuery).skip(skip).limit(limit),
|
|
685
|
+
model.countDocuments(query)
|
|
686
|
+
]);
|
|
687
|
+
res.paginatedResult = {
|
|
688
|
+
data,
|
|
689
|
+
meta: {
|
|
690
|
+
page,
|
|
691
|
+
limit,
|
|
692
|
+
total,
|
|
693
|
+
totalPages: Math.ceil(total / limit)
|
|
694
|
+
},
|
|
695
|
+
columns: extractSchemaMeta(model, options.validatorSchema)
|
|
696
|
+
};
|
|
697
|
+
next();
|
|
698
|
+
} catch (error) {
|
|
699
|
+
next(error);
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
};
|
|
703
|
+
var isZodError = (error) => {
|
|
704
|
+
return error !== null && typeof error === "object" && "errors" in error && Array.isArray(error.errors);
|
|
705
|
+
};
|
|
706
|
+
var getOrgId = (req, orgField = "organizationId") => {
|
|
707
|
+
const orgReq = req;
|
|
708
|
+
return orgReq.organizationId || req.headers["x-organization-id"] || req.query[orgField];
|
|
709
|
+
};
|
|
710
|
+
var buildOrgFilter = (req, config) => {
|
|
711
|
+
const filter = {};
|
|
712
|
+
if (config.withOrganization !== false) {
|
|
713
|
+
const orgId = getOrgId(req, config.orgField);
|
|
714
|
+
if (orgId) {
|
|
715
|
+
filter[config.orgField || "organizationId"] = orgId;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return filter;
|
|
719
|
+
};
|
|
720
|
+
var formatZodError = (error) => {
|
|
721
|
+
return error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
722
|
+
};
|
|
723
|
+
function createCrudControllers(config) {
|
|
724
|
+
const {
|
|
725
|
+
model,
|
|
726
|
+
resourceName,
|
|
727
|
+
createSchema,
|
|
728
|
+
updateSchema,
|
|
729
|
+
searchFields = [],
|
|
730
|
+
regexFilterFields = [],
|
|
731
|
+
withOrganization = true,
|
|
732
|
+
orgField = "organizationId",
|
|
733
|
+
transformCreate,
|
|
734
|
+
transformUpdate,
|
|
735
|
+
afterCreate,
|
|
736
|
+
afterUpdate,
|
|
737
|
+
afterDelete,
|
|
738
|
+
excludeFields = [],
|
|
739
|
+
populateFields = [],
|
|
740
|
+
buildQuery
|
|
741
|
+
} = config;
|
|
742
|
+
const getAll = async (req, res, _next) => {
|
|
743
|
+
try {
|
|
744
|
+
const paginatedRes = res;
|
|
745
|
+
if (paginatedRes.paginatedResult) {
|
|
746
|
+
successResponse(res, paginatedRes.paginatedResult, `${resourceName} list fetched successfully`);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const page = parseInt(req.query.page) || 1;
|
|
750
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
751
|
+
const sortField = req.query.sortBy || "createdAt";
|
|
752
|
+
const sortOrder = req.query.sortOrder || "desc";
|
|
753
|
+
const search = req.query.search;
|
|
754
|
+
let query = {};
|
|
755
|
+
if (withOrganization) {
|
|
756
|
+
const orgId = getOrgId(req, orgField);
|
|
757
|
+
if (orgId) {
|
|
758
|
+
query[orgField] = orgId;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (search && searchFields.length > 0) {
|
|
762
|
+
query.$or = searchFields.map((field) => ({
|
|
763
|
+
[field]: { $regex: search, $options: "i" }
|
|
764
|
+
}));
|
|
765
|
+
}
|
|
766
|
+
const filterableParams = Object.keys(req.query).filter(
|
|
767
|
+
(key) => !["page", "limit", "sortBy", "sortOrder", "search"].includes(key)
|
|
768
|
+
);
|
|
769
|
+
filterableParams.forEach((key) => {
|
|
770
|
+
const value = req.query[key];
|
|
771
|
+
if (value !== void 0 && value !== "" && value !== "all") {
|
|
772
|
+
if (regexFilterFields.includes(key)) {
|
|
773
|
+
query[key] = { $regex: value, $options: "i" };
|
|
774
|
+
} else {
|
|
775
|
+
query[key] = value;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
if (buildQuery) {
|
|
780
|
+
query = buildQuery(req, query);
|
|
781
|
+
}
|
|
782
|
+
const sortQuery = { [sortField]: sortOrder };
|
|
783
|
+
const skip = (page - 1) * limit;
|
|
784
|
+
let projection = {};
|
|
785
|
+
if (excludeFields.length > 0) {
|
|
786
|
+
projection = excludeFields.reduce(
|
|
787
|
+
(acc, field) => ({ ...acc, [field]: 0 }),
|
|
788
|
+
{}
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
let dbQuery = model.find(query, projection);
|
|
792
|
+
if (populateFields.length > 0) {
|
|
793
|
+
populateFields.forEach((field) => {
|
|
794
|
+
dbQuery = dbQuery.populate(field);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
const [data, total] = await Promise.all([
|
|
798
|
+
dbQuery.sort(sortQuery).skip(skip).limit(limit),
|
|
799
|
+
model.countDocuments(query)
|
|
800
|
+
]);
|
|
801
|
+
successResponse(
|
|
802
|
+
res,
|
|
803
|
+
{
|
|
804
|
+
data,
|
|
805
|
+
meta: {
|
|
806
|
+
page,
|
|
807
|
+
limit,
|
|
808
|
+
total,
|
|
809
|
+
totalPages: Math.ceil(total / limit)
|
|
810
|
+
},
|
|
811
|
+
columns: extractSchemaMeta(model, createSchema)
|
|
812
|
+
},
|
|
813
|
+
`${resourceName} list fetched successfully`
|
|
814
|
+
);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
logger.error(`Error in getAll ${resourceName}`, {
|
|
817
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
818
|
+
});
|
|
819
|
+
errorResponse(res, `Failed to fetch ${resourceName.toLowerCase()} list`);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
const getById = async (req, res, _next) => {
|
|
823
|
+
try {
|
|
824
|
+
const { id } = req.params;
|
|
825
|
+
if (!id || !mongoose.Types.ObjectId.isValid(id)) {
|
|
826
|
+
badRequestResponse(res, "Invalid ID format");
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const query = {
|
|
830
|
+
_id: new mongoose.Types.ObjectId(id),
|
|
831
|
+
...buildOrgFilter(req, { ...config, withOrganization, orgField })
|
|
832
|
+
};
|
|
833
|
+
let dbQuery = model.findOne(query);
|
|
834
|
+
if (populateFields.length > 0) {
|
|
835
|
+
populateFields.forEach((field) => {
|
|
836
|
+
dbQuery = dbQuery.populate(field);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
const doc = await dbQuery;
|
|
840
|
+
if (!doc) {
|
|
841
|
+
notFoundResponse(res, `${resourceName} not found`);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
successResponse(res, doc, `${resourceName} fetched successfully`);
|
|
845
|
+
} catch (error) {
|
|
846
|
+
logger.error(`Error in getById ${resourceName}`, {
|
|
847
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
848
|
+
id: req.params.id
|
|
849
|
+
});
|
|
850
|
+
errorResponse(res, `Failed to fetch ${resourceName.toLowerCase()}`);
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
const create = async (req, res, _next) => {
|
|
854
|
+
try {
|
|
855
|
+
let input = req.body;
|
|
856
|
+
if (createSchema) {
|
|
857
|
+
try {
|
|
858
|
+
input = createSchema.parse(input);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
if (isZodError(error)) {
|
|
861
|
+
badRequestResponse(res, formatZodError(error));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (transformCreate) {
|
|
868
|
+
input = transformCreate(input, req);
|
|
869
|
+
}
|
|
870
|
+
if (withOrganization) {
|
|
871
|
+
const orgId = getOrgId(req, orgField);
|
|
872
|
+
if (orgId) {
|
|
873
|
+
input[orgField] = orgId;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
const doc = new model(input);
|
|
877
|
+
await doc.save();
|
|
878
|
+
if (afterCreate) {
|
|
879
|
+
await afterCreate(doc, req);
|
|
880
|
+
}
|
|
881
|
+
logger.info(`${resourceName} created successfully`, {
|
|
882
|
+
id: doc._id,
|
|
883
|
+
[orgField]: input[orgField]
|
|
884
|
+
});
|
|
885
|
+
createdResponse(res, doc, `${resourceName} created successfully`);
|
|
886
|
+
} catch (error) {
|
|
887
|
+
logger.error(`Error in create ${resourceName}`, {
|
|
888
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
889
|
+
});
|
|
890
|
+
if (error.code === 11e3) {
|
|
891
|
+
badRequestResponse(res, `A ${resourceName.toLowerCase()} with this data already exists`);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
errorResponse(res, `Failed to create ${resourceName.toLowerCase()}`);
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
const update = async (req, res, _next) => {
|
|
898
|
+
try {
|
|
899
|
+
const { id } = req.params;
|
|
900
|
+
if (!id || !mongoose.Types.ObjectId.isValid(id)) {
|
|
901
|
+
badRequestResponse(res, "Invalid ID format");
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
let input = req.body;
|
|
905
|
+
if (updateSchema) {
|
|
906
|
+
try {
|
|
907
|
+
input = updateSchema.parse(input);
|
|
908
|
+
} catch (error) {
|
|
909
|
+
if (isZodError(error)) {
|
|
910
|
+
badRequestResponse(res, formatZodError(error));
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
throw error;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (transformUpdate) {
|
|
917
|
+
input = transformUpdate(input, req);
|
|
918
|
+
}
|
|
919
|
+
const query = {
|
|
920
|
+
_id: new mongoose.Types.ObjectId(id),
|
|
921
|
+
...buildOrgFilter(req, { ...config, withOrganization, orgField })
|
|
922
|
+
};
|
|
923
|
+
const doc = await model.findOneAndUpdate(query, { $set: input }, { new: true });
|
|
924
|
+
if (!doc) {
|
|
925
|
+
notFoundResponse(res, `${resourceName} not found`);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
if (afterUpdate) {
|
|
929
|
+
await afterUpdate(doc, req);
|
|
930
|
+
}
|
|
931
|
+
logger.info(`${resourceName} updated successfully`, { id });
|
|
932
|
+
successResponse(res, doc, `${resourceName} updated successfully`);
|
|
933
|
+
} catch (error) {
|
|
934
|
+
logger.error(`Error in update ${resourceName}`, {
|
|
935
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
936
|
+
id: req.params.id
|
|
937
|
+
});
|
|
938
|
+
errorResponse(res, `Failed to update ${resourceName.toLowerCase()}`);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
const deleteOne = async (req, res, _next) => {
|
|
942
|
+
try {
|
|
943
|
+
const { id } = req.params;
|
|
944
|
+
if (!id || !mongoose.Types.ObjectId.isValid(id)) {
|
|
945
|
+
badRequestResponse(res, "Invalid ID format");
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const query = {
|
|
949
|
+
_id: new mongoose.Types.ObjectId(id),
|
|
950
|
+
...buildOrgFilter(req, { ...config, withOrganization, orgField })
|
|
951
|
+
};
|
|
952
|
+
const result = await model.deleteOne(query);
|
|
953
|
+
if (result.deletedCount === 0) {
|
|
954
|
+
notFoundResponse(res, `${resourceName} not found`);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (afterDelete) {
|
|
958
|
+
await afterDelete(id, req);
|
|
959
|
+
}
|
|
960
|
+
logger.info(`${resourceName} deleted successfully`, { id });
|
|
961
|
+
noContentResponse(res, null, `${resourceName} deleted successfully`);
|
|
962
|
+
} catch (error) {
|
|
963
|
+
logger.error(`Error in delete ${resourceName}`, {
|
|
964
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
965
|
+
id: req.params.id
|
|
966
|
+
});
|
|
967
|
+
errorResponse(res, `Failed to delete ${resourceName.toLowerCase()}`);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
const bulkDelete = async (req, res, _next) => {
|
|
971
|
+
try {
|
|
972
|
+
const bulkReq = req;
|
|
973
|
+
const { deleteIds = [], deleteAll = false } = bulkReq;
|
|
974
|
+
const baseFilter = buildOrgFilter(req, { ...config, withOrganization, orgField });
|
|
975
|
+
let filter;
|
|
976
|
+
if (deleteAll) {
|
|
977
|
+
filter = baseFilter;
|
|
978
|
+
} else if (deleteIds.length > 0) {
|
|
979
|
+
filter = {
|
|
980
|
+
...baseFilter,
|
|
981
|
+
_id: { $in: deleteIds.map((id) => new mongoose.Types.ObjectId(id)) }
|
|
982
|
+
};
|
|
983
|
+
} else {
|
|
984
|
+
badRequestResponse(res, "No IDs provided for deletion");
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const result = await model.deleteMany(filter);
|
|
988
|
+
if (afterDelete && deleteIds.length > 0) {
|
|
989
|
+
await Promise.all(deleteIds.map((id) => afterDelete(id, req)));
|
|
990
|
+
}
|
|
991
|
+
logger.info(`${resourceName}(s) bulk deleted successfully`, {
|
|
992
|
+
deletedCount: result.deletedCount,
|
|
993
|
+
deleteAll
|
|
994
|
+
});
|
|
995
|
+
successResponse(
|
|
996
|
+
res,
|
|
997
|
+
{ deletedCount: result.deletedCount },
|
|
998
|
+
`${result.deletedCount} ${resourceName.toLowerCase()}(s) deleted successfully`
|
|
999
|
+
);
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
logger.error(`Error in bulkDelete ${resourceName}`, {
|
|
1002
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1003
|
+
});
|
|
1004
|
+
errorResponse(res, `Failed to delete ${resourceName.toLowerCase()}(s)`);
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
return {
|
|
1008
|
+
getAll,
|
|
1009
|
+
getById,
|
|
1010
|
+
create,
|
|
1011
|
+
update,
|
|
1012
|
+
deleteOne,
|
|
1013
|
+
bulkDelete
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
function createPaginationMiddleware(model, config = {}) {
|
|
1017
|
+
const {
|
|
1018
|
+
searchFields = [],
|
|
1019
|
+
regexFilterFields = [],
|
|
1020
|
+
withOrganization = true,
|
|
1021
|
+
orgField = "organizationId"
|
|
1022
|
+
} = config;
|
|
1023
|
+
return async (req, res, next) => {
|
|
1024
|
+
try {
|
|
1025
|
+
const page = parseInt(req.query.page) || 1;
|
|
1026
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
1027
|
+
const sortField = req.query.sortBy || "createdAt";
|
|
1028
|
+
const sortOrder = req.query.sortOrder || "desc";
|
|
1029
|
+
const search = req.query.search;
|
|
1030
|
+
const query = {};
|
|
1031
|
+
if (withOrganization) {
|
|
1032
|
+
const orgId = getOrgId(req, orgField);
|
|
1033
|
+
if (orgId) {
|
|
1034
|
+
query[orgField] = orgId;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (search && searchFields.length > 0) {
|
|
1038
|
+
query.$or = searchFields.map((field) => ({
|
|
1039
|
+
[field]: { $regex: search, $options: "i" }
|
|
1040
|
+
}));
|
|
1041
|
+
}
|
|
1042
|
+
const filterableParams = Object.keys(req.query).filter(
|
|
1043
|
+
(key) => !["page", "limit", "sortBy", "sortOrder", "search"].includes(key)
|
|
1044
|
+
);
|
|
1045
|
+
filterableParams.forEach((key) => {
|
|
1046
|
+
const value = req.query[key];
|
|
1047
|
+
if (value !== void 0 && value !== "" && value !== "all") {
|
|
1048
|
+
if (regexFilterFields.includes(key)) {
|
|
1049
|
+
query[key] = { $regex: value, $options: "i" };
|
|
1050
|
+
} else {
|
|
1051
|
+
query[key] = value;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
const sortQuery = { [sortField]: sortOrder };
|
|
1056
|
+
const skip = (page - 1) * limit;
|
|
1057
|
+
const [data, total] = await Promise.all([
|
|
1058
|
+
model.find(query).sort(sortQuery).skip(skip).limit(limit),
|
|
1059
|
+
model.countDocuments(query)
|
|
1060
|
+
]);
|
|
1061
|
+
const paginatedRes = res;
|
|
1062
|
+
paginatedRes.paginatedResult = {
|
|
1063
|
+
data,
|
|
1064
|
+
meta: {
|
|
1065
|
+
page,
|
|
1066
|
+
limit,
|
|
1067
|
+
total,
|
|
1068
|
+
totalPages: Math.ceil(total / limit)
|
|
1069
|
+
},
|
|
1070
|
+
columns: extractSchemaMeta(model, config.createSchema)
|
|
1071
|
+
};
|
|
1072
|
+
next();
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
next(error);
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
var parseBulkDelete = (req, res, next) => {
|
|
1079
|
+
try {
|
|
1080
|
+
const bulkReq = req;
|
|
1081
|
+
let ids = [];
|
|
1082
|
+
if (Array.isArray(req.body)) {
|
|
1083
|
+
ids = req.body;
|
|
1084
|
+
} else if (req.body && Array.isArray(req.body.ids)) {
|
|
1085
|
+
ids = req.body.ids;
|
|
1086
|
+
} else if (req.body && typeof req.body === "object") {
|
|
1087
|
+
if (Array.isArray(req.body.data)) {
|
|
1088
|
+
ids = req.body.data;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (ids.length === 0) {
|
|
1092
|
+
return badRequestResponse(
|
|
1093
|
+
res,
|
|
1094
|
+
'Request body must contain an array of IDs. Use ["*"] to delete all records or ["id1", "id2"] to delete specific records.'
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
if (ids.length === 1 && ids[0] === "*") {
|
|
1098
|
+
bulkReq.deleteAll = true;
|
|
1099
|
+
bulkReq.deleteIds = [];
|
|
1100
|
+
logger.info("Bulk delete: Deleting all records");
|
|
1101
|
+
return next();
|
|
1102
|
+
}
|
|
1103
|
+
const validIds = [];
|
|
1104
|
+
const invalidIds = [];
|
|
1105
|
+
for (const id of ids) {
|
|
1106
|
+
if (typeof id === "string" && mongoose.Types.ObjectId.isValid(id)) {
|
|
1107
|
+
validIds.push(id);
|
|
1108
|
+
} else {
|
|
1109
|
+
invalidIds.push(id);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
if (invalidIds.length > 0) {
|
|
1113
|
+
return badRequestResponse(
|
|
1114
|
+
res,
|
|
1115
|
+
`Invalid ID format(s): ${invalidIds.slice(0, 5).join(", ")}${invalidIds.length > 5 ? "..." : ""}. All IDs must be valid MongoDB ObjectIds.`
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
if (validIds.length === 0) {
|
|
1119
|
+
return badRequestResponse(res, "No valid IDs provided for deletion.");
|
|
1120
|
+
}
|
|
1121
|
+
bulkReq.deleteAll = false;
|
|
1122
|
+
bulkReq.deleteIds = validIds;
|
|
1123
|
+
logger.info(`Bulk delete: Deleting ${validIds.length} record(s)`);
|
|
1124
|
+
next();
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
logger.error("Error in parseBulkDelete middleware", error);
|
|
1127
|
+
return badRequestResponse(res, "Failed to parse delete request");
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
var buildDeleteFilter = (req, organizationId) => {
|
|
1131
|
+
const filter = {
|
|
1132
|
+
organizationId: new mongoose.Types.ObjectId(organizationId)
|
|
1133
|
+
};
|
|
1134
|
+
if (!req.deleteAll && req.deleteIds && req.deleteIds.length > 0) {
|
|
1135
|
+
filter._id = {
|
|
1136
|
+
$in: req.deleteIds.map((id) => new mongoose.Types.ObjectId(id))
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
return filter;
|
|
1140
|
+
};
|
|
1141
|
+
var createBulkDeleteHandler = (Model2, modelName) => {
|
|
1142
|
+
return async (req, res) => {
|
|
1143
|
+
const bulkReq = req;
|
|
1144
|
+
const organizationId = req.headers["x-organization-id"];
|
|
1145
|
+
if (!organizationId) {
|
|
1146
|
+
return badRequestResponse(res, "Organization ID is required");
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
const filter = buildDeleteFilter(bulkReq, organizationId);
|
|
1150
|
+
const result = await Model2.deleteMany(filter);
|
|
1151
|
+
const deletedCount = result.deletedCount || 0;
|
|
1152
|
+
logger.info(`Bulk delete completed: ${deletedCount} ${modelName}(s) deleted`, {
|
|
1153
|
+
organizationId,
|
|
1154
|
+
deleteAll: bulkReq.deleteAll,
|
|
1155
|
+
requestedIds: bulkReq.deleteIds?.length || "all",
|
|
1156
|
+
deletedCount
|
|
1157
|
+
});
|
|
1158
|
+
return res.status(200).json({
|
|
1159
|
+
message: `Successfully deleted ${deletedCount} ${modelName}(s)`,
|
|
1160
|
+
data: {
|
|
1161
|
+
deletedCount,
|
|
1162
|
+
deleteAll: bulkReq.deleteAll
|
|
1163
|
+
},
|
|
1164
|
+
status: "success",
|
|
1165
|
+
statusCode: 200
|
|
1166
|
+
});
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
logger.error(`Error in bulk delete ${modelName}`, error);
|
|
1169
|
+
return res.status(500).json({
|
|
1170
|
+
message: `Failed to delete ${modelName}(s)`,
|
|
1171
|
+
data: null,
|
|
1172
|
+
status: "error",
|
|
1173
|
+
statusCode: 500
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
};
|
|
1178
|
+
|
|
509
1179
|
// src/server/utils/filter-builder.ts
|
|
510
1180
|
var buildFilter = (options) => {
|
|
511
1181
|
const {
|
|
@@ -800,45 +1470,704 @@ var packageCheckServer = {
|
|
|
800
1470
|
print: printPackageCheckSummary
|
|
801
1471
|
};
|
|
802
1472
|
|
|
1473
|
+
// src/server/configs/cors.config.ts
|
|
1474
|
+
var DEFAULT_CORS_CONFIG = {
|
|
1475
|
+
productionOrigins: [],
|
|
1476
|
+
developmentOrigins: [
|
|
1477
|
+
"http://localhost:3000",
|
|
1478
|
+
"http://localhost:4000",
|
|
1479
|
+
"http://localhost:5000",
|
|
1480
|
+
"http://localhost:5173",
|
|
1481
|
+
"http://localhost:8080",
|
|
1482
|
+
"http://127.0.0.1:3000",
|
|
1483
|
+
"http://127.0.0.1:4000",
|
|
1484
|
+
"http://127.0.0.1:5000",
|
|
1485
|
+
"http://127.0.0.1:5173",
|
|
1486
|
+
"http://127.0.0.1:8080"
|
|
1487
|
+
],
|
|
1488
|
+
allowedSubdomains: [],
|
|
1489
|
+
originPatterns: [],
|
|
1490
|
+
allowNoOrigin: true,
|
|
1491
|
+
allowAllInDev: true,
|
|
1492
|
+
credentials: true,
|
|
1493
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"],
|
|
1494
|
+
allowedHeaders: [
|
|
1495
|
+
"Content-Type",
|
|
1496
|
+
"Authorization",
|
|
1497
|
+
"X-Requested-With",
|
|
1498
|
+
"Accept",
|
|
1499
|
+
"Origin",
|
|
1500
|
+
"X-API-Key",
|
|
1501
|
+
"X-Organization-Id",
|
|
1502
|
+
"X-Request-Id"
|
|
1503
|
+
],
|
|
1504
|
+
exposedHeaders: [
|
|
1505
|
+
"Content-Range",
|
|
1506
|
+
"X-Content-Range",
|
|
1507
|
+
"X-Total-Count",
|
|
1508
|
+
"X-Request-Id"
|
|
1509
|
+
],
|
|
1510
|
+
maxAge: 86400
|
|
1511
|
+
// 24 hours
|
|
1512
|
+
};
|
|
1513
|
+
var createCorsOptions = (config = {}) => {
|
|
1514
|
+
const finalConfig = { ...DEFAULT_CORS_CONFIG, ...config };
|
|
1515
|
+
const {
|
|
1516
|
+
productionOrigins,
|
|
1517
|
+
developmentOrigins,
|
|
1518
|
+
allowedSubdomains,
|
|
1519
|
+
originPatterns,
|
|
1520
|
+
allowNoOrigin,
|
|
1521
|
+
allowAllInDev,
|
|
1522
|
+
customValidator,
|
|
1523
|
+
credentials,
|
|
1524
|
+
methods,
|
|
1525
|
+
allowedHeaders,
|
|
1526
|
+
exposedHeaders,
|
|
1527
|
+
maxAge
|
|
1528
|
+
} = finalConfig;
|
|
1529
|
+
const allOrigins = /* @__PURE__ */ new Set([...productionOrigins, ...developmentOrigins]);
|
|
1530
|
+
const originHandler = (origin, callback) => {
|
|
1531
|
+
if (!origin) {
|
|
1532
|
+
callback(null, allowNoOrigin);
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (allOrigins.has(origin)) {
|
|
1536
|
+
callback(null, true);
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
if (allowedSubdomains.some((subdomain) => origin.endsWith(subdomain))) {
|
|
1540
|
+
callback(null, true);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (originPatterns.some((pattern) => pattern.test(origin))) {
|
|
1544
|
+
callback(null, true);
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
if (customValidator && customValidator(origin)) {
|
|
1548
|
+
callback(null, true);
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
if (process.env.NODE_ENV !== "production" && allowAllInDev) {
|
|
1552
|
+
callback(null, true);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
if (process.env.NODE_ENV === "production") {
|
|
1556
|
+
callback(new Error(`Origin ${origin} not allowed by CORS`));
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
callback(null, true);
|
|
1560
|
+
};
|
|
1561
|
+
return {
|
|
1562
|
+
origin: originHandler,
|
|
1563
|
+
credentials,
|
|
1564
|
+
methods,
|
|
1565
|
+
allowedHeaders,
|
|
1566
|
+
exposedHeaders,
|
|
1567
|
+
maxAge
|
|
1568
|
+
};
|
|
1569
|
+
};
|
|
1570
|
+
var createBrandCorsOptions = (brandDomain, additionalConfig = {}) => {
|
|
1571
|
+
const productionOrigins = [
|
|
1572
|
+
`https://${brandDomain}`,
|
|
1573
|
+
`https://www.${brandDomain}`
|
|
1574
|
+
];
|
|
1575
|
+
const allowedSubdomains = [`.${brandDomain}`];
|
|
1576
|
+
return createCorsOptions({
|
|
1577
|
+
productionOrigins,
|
|
1578
|
+
allowedSubdomains,
|
|
1579
|
+
...additionalConfig
|
|
1580
|
+
});
|
|
1581
|
+
};
|
|
1582
|
+
var createMultiBrandCorsOptions = (domains, additionalConfig = {}) => {
|
|
1583
|
+
const productionOrigins = domains.flatMap((domain) => [
|
|
1584
|
+
`https://${domain}`,
|
|
1585
|
+
`https://www.${domain}`
|
|
1586
|
+
]);
|
|
1587
|
+
const allowedSubdomains = domains.map((domain) => `.${domain}`);
|
|
1588
|
+
return createCorsOptions({
|
|
1589
|
+
productionOrigins,
|
|
1590
|
+
allowedSubdomains,
|
|
1591
|
+
...additionalConfig
|
|
1592
|
+
});
|
|
1593
|
+
};
|
|
1594
|
+
var EXYCONN_CORS_CONFIG = {
|
|
1595
|
+
productionOrigins: [
|
|
1596
|
+
"https://exyconn.com",
|
|
1597
|
+
"https://www.exyconn.com",
|
|
1598
|
+
"https://botify.life",
|
|
1599
|
+
"https://www.botify.life",
|
|
1600
|
+
"https://partywings.fun",
|
|
1601
|
+
"https://www.partywings.fun",
|
|
1602
|
+
"https://sibera.work",
|
|
1603
|
+
"https://www.sibera.work",
|
|
1604
|
+
"https://spentiva.com",
|
|
1605
|
+
"https://www.spentiva.com"
|
|
1606
|
+
],
|
|
1607
|
+
allowedSubdomains: [
|
|
1608
|
+
".exyconn.com",
|
|
1609
|
+
".botify.life",
|
|
1610
|
+
".partywings.fun",
|
|
1611
|
+
".sibera.work",
|
|
1612
|
+
".spentiva.com"
|
|
1613
|
+
],
|
|
1614
|
+
developmentOrigins: [
|
|
1615
|
+
"http://localhost:3000",
|
|
1616
|
+
"http://localhost:4000",
|
|
1617
|
+
"http://localhost:4001",
|
|
1618
|
+
"http://localhost:4002",
|
|
1619
|
+
"http://localhost:4003",
|
|
1620
|
+
"http://localhost:4004",
|
|
1621
|
+
"http://localhost:4005",
|
|
1622
|
+
"http://localhost:5173",
|
|
1623
|
+
"http://127.0.0.1:3000",
|
|
1624
|
+
"http://127.0.0.1:4000",
|
|
1625
|
+
"http://127.0.0.1:5173"
|
|
1626
|
+
]
|
|
1627
|
+
};
|
|
1628
|
+
var STRICT_CORS_CONFIG = {
|
|
1629
|
+
allowNoOrigin: false,
|
|
1630
|
+
allowAllInDev: false,
|
|
1631
|
+
methods: ["GET", "POST", "PUT", "DELETE"]
|
|
1632
|
+
};
|
|
1633
|
+
var PERMISSIVE_CORS_CONFIG = {
|
|
1634
|
+
allowNoOrigin: true,
|
|
1635
|
+
allowAllInDev: true,
|
|
1636
|
+
originPatterns: [/localhost/, /127\.0\.0\.1/]
|
|
1637
|
+
};
|
|
1638
|
+
var corsOptions = createCorsOptions(EXYCONN_CORS_CONFIG);
|
|
1639
|
+
var DEFAULT_RATE_LIMIT_TIERS = {
|
|
1640
|
+
STANDARD: {
|
|
1641
|
+
windowMs: 15 * 60 * 1e3,
|
|
1642
|
+
// 15 minutes
|
|
1643
|
+
maxRequests: 100,
|
|
1644
|
+
message: "Too many requests, please try again later.",
|
|
1645
|
+
skipSuccessfulRequests: false,
|
|
1646
|
+
skipFailedRequests: false
|
|
1647
|
+
},
|
|
1648
|
+
STRICT: {
|
|
1649
|
+
windowMs: 15 * 60 * 1e3,
|
|
1650
|
+
// 15 minutes
|
|
1651
|
+
maxRequests: 20,
|
|
1652
|
+
message: "Too many requests, please try again later.",
|
|
1653
|
+
skipSuccessfulRequests: false,
|
|
1654
|
+
skipFailedRequests: false
|
|
1655
|
+
},
|
|
1656
|
+
DDOS: {
|
|
1657
|
+
windowMs: 60 * 1e3,
|
|
1658
|
+
// 1 minute
|
|
1659
|
+
maxRequests: 60,
|
|
1660
|
+
message: "Rate limit exceeded. Please slow down.",
|
|
1661
|
+
skipSuccessfulRequests: false,
|
|
1662
|
+
skipFailedRequests: false
|
|
1663
|
+
},
|
|
1664
|
+
// Additional presets
|
|
1665
|
+
VERY_STRICT: {
|
|
1666
|
+
windowMs: 60 * 60 * 1e3,
|
|
1667
|
+
// 1 hour
|
|
1668
|
+
maxRequests: 5,
|
|
1669
|
+
message: "Too many attempts. Please try again in an hour.",
|
|
1670
|
+
skipSuccessfulRequests: false,
|
|
1671
|
+
skipFailedRequests: false
|
|
1672
|
+
},
|
|
1673
|
+
RELAXED: {
|
|
1674
|
+
windowMs: 15 * 60 * 1e3,
|
|
1675
|
+
// 15 minutes
|
|
1676
|
+
maxRequests: 500,
|
|
1677
|
+
message: "Rate limit exceeded.",
|
|
1678
|
+
skipSuccessfulRequests: false,
|
|
1679
|
+
skipFailedRequests: false
|
|
1680
|
+
},
|
|
1681
|
+
API: {
|
|
1682
|
+
windowMs: 60 * 1e3,
|
|
1683
|
+
// 1 minute
|
|
1684
|
+
maxRequests: 30,
|
|
1685
|
+
message: "API rate limit exceeded.",
|
|
1686
|
+
skipSuccessfulRequests: false,
|
|
1687
|
+
skipFailedRequests: false
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
var defaultKeyGenerator = (req) => {
|
|
1691
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
1692
|
+
const ip = forwarded ? Array.isArray(forwarded) ? forwarded[0] : forwarded.split(",")[0].trim() : req.ip || req.socket.remoteAddress || "unknown";
|
|
1693
|
+
return ip;
|
|
1694
|
+
};
|
|
1695
|
+
var createPrefixedKeyGenerator = (prefix) => (req) => {
|
|
1696
|
+
return `${prefix}:${defaultKeyGenerator(req)}`;
|
|
1697
|
+
};
|
|
1698
|
+
var createUserKeyGenerator = (getUserId) => (req) => {
|
|
1699
|
+
const userId = getUserId(req);
|
|
1700
|
+
return userId || defaultKeyGenerator(req);
|
|
1701
|
+
};
|
|
1702
|
+
var createApiKeyGenerator = (headerName = "x-api-key") => (req) => {
|
|
1703
|
+
const apiKey = req.headers[headerName.toLowerCase()];
|
|
1704
|
+
return apiKey || defaultKeyGenerator(req);
|
|
1705
|
+
};
|
|
1706
|
+
var createRateLimitResponse = (message, retryAfter) => ({
|
|
1707
|
+
status: "error",
|
|
1708
|
+
statusCode: 429,
|
|
1709
|
+
message,
|
|
1710
|
+
...retryAfter
|
|
1711
|
+
});
|
|
1712
|
+
var createRateLimiter = (tierConfig, options = {}) => {
|
|
1713
|
+
const {
|
|
1714
|
+
standardHeaders = true,
|
|
1715
|
+
legacyHeaders = false,
|
|
1716
|
+
keyGenerator = defaultKeyGenerator,
|
|
1717
|
+
skip,
|
|
1718
|
+
handler
|
|
1719
|
+
} = options;
|
|
1720
|
+
return rateLimit__default.default({
|
|
1721
|
+
windowMs: tierConfig.windowMs,
|
|
1722
|
+
max: tierConfig.maxRequests,
|
|
1723
|
+
message: createRateLimitResponse(tierConfig.message),
|
|
1724
|
+
standardHeaders,
|
|
1725
|
+
legacyHeaders,
|
|
1726
|
+
keyGenerator,
|
|
1727
|
+
skip,
|
|
1728
|
+
handler,
|
|
1729
|
+
skipSuccessfulRequests: tierConfig.skipSuccessfulRequests,
|
|
1730
|
+
skipFailedRequests: tierConfig.skipFailedRequests
|
|
1731
|
+
});
|
|
1732
|
+
};
|
|
1733
|
+
var createStandardRateLimiter = (config = {}, options = {}) => {
|
|
1734
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.STANDARD, ...config };
|
|
1735
|
+
return createRateLimiter(tierConfig, options);
|
|
1736
|
+
};
|
|
1737
|
+
var createStrictRateLimiter = (config = {}, options = {}) => {
|
|
1738
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.STRICT, ...config };
|
|
1739
|
+
return createRateLimiter(tierConfig, options);
|
|
1740
|
+
};
|
|
1741
|
+
var createDdosRateLimiter = (config = {}, options = {}) => {
|
|
1742
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.DDOS, ...config };
|
|
1743
|
+
return createRateLimiter(tierConfig, options);
|
|
1744
|
+
};
|
|
1745
|
+
var createApiRateLimiter = (config = {}, options = {}) => {
|
|
1746
|
+
const tierConfig = { ...DEFAULT_RATE_LIMIT_TIERS.API, ...config };
|
|
1747
|
+
return createRateLimiter(tierConfig, {
|
|
1748
|
+
keyGenerator: createApiKeyGenerator(),
|
|
1749
|
+
...options
|
|
1750
|
+
});
|
|
1751
|
+
};
|
|
1752
|
+
var RateLimiterBuilder = class {
|
|
1753
|
+
constructor(preset = "STANDARD") {
|
|
1754
|
+
const presetConfig = DEFAULT_RATE_LIMIT_TIERS[preset];
|
|
1755
|
+
this.config = {
|
|
1756
|
+
windowMs: presetConfig.windowMs,
|
|
1757
|
+
maxRequests: presetConfig.maxRequests,
|
|
1758
|
+
message: presetConfig.message,
|
|
1759
|
+
skipSuccessfulRequests: presetConfig.skipSuccessfulRequests ?? false,
|
|
1760
|
+
skipFailedRequests: presetConfig.skipFailedRequests ?? false
|
|
1761
|
+
};
|
|
1762
|
+
this.options = {};
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Set window duration
|
|
1766
|
+
*/
|
|
1767
|
+
windowMs(ms) {
|
|
1768
|
+
this.config.windowMs = ms;
|
|
1769
|
+
return this;
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Set window duration in minutes
|
|
1773
|
+
*/
|
|
1774
|
+
windowMinutes(minutes) {
|
|
1775
|
+
this.config.windowMs = minutes * 60 * 1e3;
|
|
1776
|
+
return this;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Set window duration in hours
|
|
1780
|
+
*/
|
|
1781
|
+
windowHours(hours) {
|
|
1782
|
+
this.config.windowMs = hours * 60 * 60 * 1e3;
|
|
1783
|
+
return this;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Set maximum requests
|
|
1787
|
+
*/
|
|
1788
|
+
max(requests) {
|
|
1789
|
+
this.config.maxRequests = requests;
|
|
1790
|
+
return this;
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Set error message
|
|
1794
|
+
*/
|
|
1795
|
+
message(msg) {
|
|
1796
|
+
this.config.message = msg;
|
|
1797
|
+
return this;
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Skip successful requests
|
|
1801
|
+
*/
|
|
1802
|
+
skipSuccessful(skip = true) {
|
|
1803
|
+
this.config.skipSuccessfulRequests = skip;
|
|
1804
|
+
return this;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Skip failed requests
|
|
1808
|
+
*/
|
|
1809
|
+
skipFailed(skip = true) {
|
|
1810
|
+
this.config.skipFailedRequests = skip;
|
|
1811
|
+
return this;
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Set key generator
|
|
1815
|
+
*/
|
|
1816
|
+
keyBy(generator) {
|
|
1817
|
+
this.options.keyGenerator = generator;
|
|
1818
|
+
return this;
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Key by IP (default)
|
|
1822
|
+
*/
|
|
1823
|
+
keyByIp() {
|
|
1824
|
+
this.options.keyGenerator = defaultKeyGenerator;
|
|
1825
|
+
return this;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Key by API key
|
|
1829
|
+
*/
|
|
1830
|
+
keyByApiKey(headerName) {
|
|
1831
|
+
this.options.keyGenerator = createApiKeyGenerator(headerName);
|
|
1832
|
+
return this;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Skip certain requests
|
|
1836
|
+
*/
|
|
1837
|
+
skipWhen(predicate) {
|
|
1838
|
+
this.options.skip = predicate;
|
|
1839
|
+
return this;
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Build the rate limiter
|
|
1843
|
+
*/
|
|
1844
|
+
build() {
|
|
1845
|
+
return createRateLimiter(this.config, this.options);
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
var rateLimiter = (preset) => {
|
|
1849
|
+
return new RateLimiterBuilder(preset);
|
|
1850
|
+
};
|
|
1851
|
+
var RATE_LIMIT_CONFIG = {
|
|
1852
|
+
STANDARD: DEFAULT_RATE_LIMIT_TIERS.STANDARD,
|
|
1853
|
+
STRICT: DEFAULT_RATE_LIMIT_TIERS.STRICT,
|
|
1854
|
+
DDOS: DEFAULT_RATE_LIMIT_TIERS.DDOS
|
|
1855
|
+
};
|
|
1856
|
+
var standardRateLimiter = createStandardRateLimiter();
|
|
1857
|
+
var strictRateLimiter = createStrictRateLimiter();
|
|
1858
|
+
var ddosProtectionLimiter = createDdosRateLimiter();
|
|
1859
|
+
|
|
1860
|
+
// src/server/configs/server.config.ts
|
|
1861
|
+
var DEFAULT_SERVER_CONFIG = {
|
|
1862
|
+
name: "app-server",
|
|
1863
|
+
version: "1.0.0",
|
|
1864
|
+
environment: process.env.NODE_ENV || "development",
|
|
1865
|
+
port: parseInt(process.env.PORT || "3000", 10),
|
|
1866
|
+
host: process.env.HOST || "0.0.0.0",
|
|
1867
|
+
basePath: "/api",
|
|
1868
|
+
debug: process.env.DEBUG === "true",
|
|
1869
|
+
trustProxy: true
|
|
1870
|
+
};
|
|
1871
|
+
var DEFAULT_DATABASE_CONFIG = {
|
|
1872
|
+
uri: process.env.DATABASE_URL || process.env.MONGODB_URI || "",
|
|
1873
|
+
name: process.env.DATABASE_NAME || "app_db",
|
|
1874
|
+
maxPoolSize: process.env.NODE_ENV === "production" ? 50 : 10,
|
|
1875
|
+
minPoolSize: process.env.NODE_ENV === "production" ? 10 : 5,
|
|
1876
|
+
socketTimeoutMS: 45e3,
|
|
1877
|
+
serverSelectionTimeoutMS: 1e4,
|
|
1878
|
+
maxIdleTimeMS: 1e4,
|
|
1879
|
+
retryWrites: true,
|
|
1880
|
+
retryReads: true,
|
|
1881
|
+
writeConcern: "majority"
|
|
1882
|
+
};
|
|
1883
|
+
var DEFAULT_AUTH_CONFIG = {
|
|
1884
|
+
jwtSecret: process.env.JWT_SECRET || "",
|
|
1885
|
+
jwtExpiresIn: process.env.JWT_EXPIRES_IN || "7d",
|
|
1886
|
+
refreshTokenExpiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN || "30d",
|
|
1887
|
+
enableRefreshTokens: true,
|
|
1888
|
+
apiKeyHeader: "x-api-key",
|
|
1889
|
+
orgHeader: "x-organization-id"
|
|
1890
|
+
};
|
|
1891
|
+
var DEFAULT_LOGGING_CONFIG = {
|
|
1892
|
+
level: process.env.LOG_LEVEL || "info",
|
|
1893
|
+
logsDir: process.env.LOGS_DIR || "logs",
|
|
1894
|
+
maxSize: "20m",
|
|
1895
|
+
maxFiles: "14d",
|
|
1896
|
+
errorMaxFiles: "30d",
|
|
1897
|
+
console: true,
|
|
1898
|
+
file: process.env.NODE_ENV === "production",
|
|
1899
|
+
json: process.env.NODE_ENV === "production"
|
|
1900
|
+
};
|
|
1901
|
+
var DEFAULT_CORS_ORIGINS = {
|
|
1902
|
+
production: [],
|
|
1903
|
+
development: [
|
|
1904
|
+
"http://localhost:3000",
|
|
1905
|
+
"http://localhost:4000",
|
|
1906
|
+
"http://localhost:5173",
|
|
1907
|
+
"http://127.0.0.1:3000",
|
|
1908
|
+
"http://127.0.0.1:4000",
|
|
1909
|
+
"http://127.0.0.1:5173"
|
|
1910
|
+
],
|
|
1911
|
+
patterns: []
|
|
1912
|
+
};
|
|
1913
|
+
var DEFAULT_RATE_LIMIT_CONFIG = {
|
|
1914
|
+
enabled: true,
|
|
1915
|
+
standard: {
|
|
1916
|
+
windowMs: 15 * 60 * 1e3,
|
|
1917
|
+
// 15 minutes
|
|
1918
|
+
maxRequests: 100,
|
|
1919
|
+
message: "Too many requests, please try again later."
|
|
1920
|
+
},
|
|
1921
|
+
strict: {
|
|
1922
|
+
windowMs: 15 * 60 * 1e3,
|
|
1923
|
+
// 15 minutes
|
|
1924
|
+
maxRequests: 20,
|
|
1925
|
+
message: "Too many requests, please try again later."
|
|
1926
|
+
},
|
|
1927
|
+
ddos: {
|
|
1928
|
+
windowMs: 60 * 1e3,
|
|
1929
|
+
// 1 minute
|
|
1930
|
+
maxRequests: 60,
|
|
1931
|
+
message: "Rate limit exceeded. Please slow down.",
|
|
1932
|
+
skipSuccessfulRequests: false
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
function deepMerge(target, source) {
|
|
1936
|
+
const result = { ...target };
|
|
1937
|
+
for (const key in source) {
|
|
1938
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1939
|
+
const sourceValue = source[key];
|
|
1940
|
+
const targetValue = target[key];
|
|
1941
|
+
if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
1942
|
+
result[key] = deepMerge(
|
|
1943
|
+
targetValue,
|
|
1944
|
+
sourceValue
|
|
1945
|
+
);
|
|
1946
|
+
} else if (sourceValue !== void 0) {
|
|
1947
|
+
result[key] = sourceValue;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
return result;
|
|
1952
|
+
}
|
|
1953
|
+
var ConfigBuilder = class {
|
|
1954
|
+
constructor() {
|
|
1955
|
+
this.config = {
|
|
1956
|
+
server: { ...DEFAULT_SERVER_CONFIG },
|
|
1957
|
+
database: { ...DEFAULT_DATABASE_CONFIG },
|
|
1958
|
+
auth: { ...DEFAULT_AUTH_CONFIG },
|
|
1959
|
+
logging: { ...DEFAULT_LOGGING_CONFIG },
|
|
1960
|
+
cors: { ...DEFAULT_CORS_ORIGINS },
|
|
1961
|
+
rateLimit: { ...DEFAULT_RATE_LIMIT_CONFIG }
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Set server configuration
|
|
1966
|
+
*/
|
|
1967
|
+
setServer(config) {
|
|
1968
|
+
this.config.server = deepMerge(this.config.server, config);
|
|
1969
|
+
return this;
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Set database configuration
|
|
1973
|
+
*/
|
|
1974
|
+
setDatabase(config) {
|
|
1975
|
+
this.config.database = deepMerge(this.config.database, config);
|
|
1976
|
+
return this;
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Set auth configuration
|
|
1980
|
+
*/
|
|
1981
|
+
setAuth(config) {
|
|
1982
|
+
this.config.auth = deepMerge(this.config.auth, config);
|
|
1983
|
+
return this;
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Set logging configuration
|
|
1987
|
+
*/
|
|
1988
|
+
setLogging(config) {
|
|
1989
|
+
this.config.logging = deepMerge(this.config.logging, config);
|
|
1990
|
+
return this;
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Set CORS origins
|
|
1994
|
+
*/
|
|
1995
|
+
setCorsOrigins(config) {
|
|
1996
|
+
this.config.cors = deepMerge(this.config.cors, config);
|
|
1997
|
+
return this;
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Add CORS production origin
|
|
2001
|
+
*/
|
|
2002
|
+
addProductionOrigin(origin) {
|
|
2003
|
+
if (!this.config.cors.production.includes(origin)) {
|
|
2004
|
+
this.config.cors.production.push(origin);
|
|
2005
|
+
}
|
|
2006
|
+
return this;
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Add CORS development origin
|
|
2010
|
+
*/
|
|
2011
|
+
addDevelopmentOrigin(origin) {
|
|
2012
|
+
if (!this.config.cors.development.includes(origin)) {
|
|
2013
|
+
this.config.cors.development.push(origin);
|
|
2014
|
+
}
|
|
2015
|
+
return this;
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Add CORS pattern
|
|
2019
|
+
*/
|
|
2020
|
+
addCorsPattern(pattern) {
|
|
2021
|
+
if (!this.config.cors.patterns.includes(pattern)) {
|
|
2022
|
+
this.config.cors.patterns.push(pattern);
|
|
2023
|
+
}
|
|
2024
|
+
return this;
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Set rate limit configuration
|
|
2028
|
+
*/
|
|
2029
|
+
setRateLimit(config) {
|
|
2030
|
+
this.config.rateLimit = deepMerge(this.config.rateLimit, config);
|
|
2031
|
+
return this;
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Add custom rate limit tier
|
|
2035
|
+
*/
|
|
2036
|
+
addRateLimitTier(name, tier) {
|
|
2037
|
+
if (!this.config.rateLimit.custom) {
|
|
2038
|
+
this.config.rateLimit.custom = {};
|
|
2039
|
+
}
|
|
2040
|
+
this.config.rateLimit.custom[name] = tier;
|
|
2041
|
+
return this;
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Set custom configuration
|
|
2045
|
+
*/
|
|
2046
|
+
setCustom(key, value) {
|
|
2047
|
+
if (!this.config.custom) {
|
|
2048
|
+
this.config.custom = {};
|
|
2049
|
+
}
|
|
2050
|
+
this.config.custom[key] = value;
|
|
2051
|
+
return this;
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Load configuration from environment variables
|
|
2055
|
+
*/
|
|
2056
|
+
loadFromEnv() {
|
|
2057
|
+
if (process.env.SERVER_NAME) this.config.server.name = process.env.SERVER_NAME;
|
|
2058
|
+
if (process.env.SERVER_VERSION) this.config.server.version = process.env.SERVER_VERSION;
|
|
2059
|
+
if (process.env.PORT) this.config.server.port = parseInt(process.env.PORT, 10);
|
|
2060
|
+
if (process.env.HOST) this.config.server.host = process.env.HOST;
|
|
2061
|
+
if (process.env.BASE_PATH) this.config.server.basePath = process.env.BASE_PATH;
|
|
2062
|
+
if (process.env.DATABASE_URL) this.config.database.uri = process.env.DATABASE_URL;
|
|
2063
|
+
if (process.env.MONGODB_URI) this.config.database.uri = process.env.MONGODB_URI;
|
|
2064
|
+
if (process.env.DATABASE_NAME) this.config.database.name = process.env.DATABASE_NAME;
|
|
2065
|
+
if (process.env.JWT_SECRET) this.config.auth.jwtSecret = process.env.JWT_SECRET;
|
|
2066
|
+
if (process.env.JWT_EXPIRES_IN) this.config.auth.jwtExpiresIn = process.env.JWT_EXPIRES_IN;
|
|
2067
|
+
if (process.env.LOG_LEVEL) this.config.logging.level = process.env.LOG_LEVEL;
|
|
2068
|
+
if (process.env.LOGS_DIR) this.config.logging.logsDir = process.env.LOGS_DIR;
|
|
2069
|
+
if (process.env.CORS_ORIGINS) {
|
|
2070
|
+
const origins = process.env.CORS_ORIGINS.split(",").map((o) => o.trim());
|
|
2071
|
+
this.config.cors.production.push(...origins);
|
|
2072
|
+
}
|
|
2073
|
+
return this;
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Validate configuration
|
|
2077
|
+
*/
|
|
2078
|
+
validate() {
|
|
2079
|
+
const errors = [];
|
|
2080
|
+
if (!this.config.server.name) errors.push("Server name is required");
|
|
2081
|
+
if (this.config.server.port < 1 || this.config.server.port > 65535) {
|
|
2082
|
+
errors.push("Server port must be between 1 and 65535");
|
|
2083
|
+
}
|
|
2084
|
+
if (this.config.server.environment === "production") {
|
|
2085
|
+
if (!this.config.auth.jwtSecret || this.config.auth.jwtSecret.length < 32) {
|
|
2086
|
+
errors.push("JWT secret must be at least 32 characters in production");
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
return { valid: errors.length === 0, errors };
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Build the final configuration
|
|
2093
|
+
*/
|
|
2094
|
+
build() {
|
|
2095
|
+
return { ...this.config };
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
var createConfig = () => {
|
|
2099
|
+
return new ConfigBuilder();
|
|
2100
|
+
};
|
|
2101
|
+
var buildConfig = (partial = {}) => {
|
|
2102
|
+
const builder = createConfig().loadFromEnv();
|
|
2103
|
+
if (partial.server) builder.setServer(partial.server);
|
|
2104
|
+
if (partial.database) builder.setDatabase(partial.database);
|
|
2105
|
+
if (partial.auth) builder.setAuth(partial.auth);
|
|
2106
|
+
if (partial.logging) builder.setLogging(partial.logging);
|
|
2107
|
+
if (partial.cors) builder.setCorsOrigins(partial.cors);
|
|
2108
|
+
if (partial.rateLimit) builder.setRateLimit(partial.rateLimit);
|
|
2109
|
+
return builder.build();
|
|
2110
|
+
};
|
|
2111
|
+
var isProduction = (config) => {
|
|
2112
|
+
return (config?.environment || process.env.NODE_ENV) === "production";
|
|
2113
|
+
};
|
|
2114
|
+
var isDevelopment = (config) => {
|
|
2115
|
+
return (config?.environment || process.env.NODE_ENV) === "development";
|
|
2116
|
+
};
|
|
2117
|
+
var isTest = (config) => {
|
|
2118
|
+
return (config?.environment || process.env.NODE_ENV) === "test";
|
|
2119
|
+
};
|
|
2120
|
+
var getDatabaseOptions = (config) => {
|
|
2121
|
+
return {
|
|
2122
|
+
maxPoolSize: config.maxPoolSize,
|
|
2123
|
+
minPoolSize: config.minPoolSize,
|
|
2124
|
+
socketTimeoutMS: config.socketTimeoutMS,
|
|
2125
|
+
serverSelectionTimeoutMS: config.serverSelectionTimeoutMS,
|
|
2126
|
+
maxIdleTimeMS: config.maxIdleTimeMS,
|
|
2127
|
+
retryWrites: config.retryWrites,
|
|
2128
|
+
retryReads: config.retryReads,
|
|
2129
|
+
w: config.writeConcern
|
|
2130
|
+
};
|
|
2131
|
+
};
|
|
2132
|
+
|
|
803
2133
|
// src/client/index.ts
|
|
804
2134
|
var client_exports = {};
|
|
805
2135
|
__export(client_exports, {
|
|
806
|
-
|
|
2136
|
+
API_BASE_URL: () => API_BASE_URL,
|
|
2137
|
+
API_PREFIX: () => API_PREFIX,
|
|
807
2138
|
ClientLogger: () => ClientLogger,
|
|
808
2139
|
ContactForm: () => ContactForm,
|
|
809
|
-
|
|
2140
|
+
ERROR_CODES: () => ERROR_CODES,
|
|
810
2141
|
LoginForm: () => LoginForm,
|
|
811
2142
|
NewsletterForm: () => NewsletterForm,
|
|
812
2143
|
RegisterForm: () => RegisterForm,
|
|
2144
|
+
STATUS_CODES: () => STATUS_CODES,
|
|
2145
|
+
STATUS_MESSAGES: () => STATUS_MESSAGES,
|
|
2146
|
+
SUCCESS_CODES: () => SUCCESS_CODES,
|
|
813
2147
|
ThemeContext: () => ThemeContext,
|
|
814
2148
|
ThemeProvider: () => ThemeProvider,
|
|
815
2149
|
ThemeToggle: () => ThemeToggle,
|
|
816
2150
|
VALIDATION_MESSAGES: () => VALIDATION_MESSAGES,
|
|
817
|
-
addDays: () => addDays,
|
|
818
2151
|
adjustColor: () => adjustColor,
|
|
819
|
-
|
|
2152
|
+
axios: () => axiosInstance,
|
|
820
2153
|
camelToKebab: () => camelToKebab,
|
|
821
2154
|
capitalize: () => capitalize,
|
|
822
2155
|
capitalizeWords: () => capitalizeWords,
|
|
823
|
-
checkPackage: () => checkPackage,
|
|
824
2156
|
clientLogger: () => clientLogger,
|
|
825
2157
|
contactFormSchema: () => contactFormSchema,
|
|
826
2158
|
copyToClipboard: () => copyToClipboard,
|
|
827
|
-
createApiEndpoints: () => createApiEndpoints,
|
|
828
|
-
createApiUrlBuilder: () => createApiUrlBuilder,
|
|
829
2159
|
createClientLogger: () => createClientLogger,
|
|
830
2160
|
createEmptyPaginationMeta: () => createEmptyPaginationMeta,
|
|
831
2161
|
createErrorResponse: () => createErrorResponse,
|
|
832
|
-
createEventEmitter: () => createEventEmitter,
|
|
833
|
-
createHttpClient: () => createHttpClient,
|
|
834
2162
|
createRegisterFormSchema: () => createRegisterFormSchema,
|
|
835
2163
|
createSuccessResponse: () => createSuccessResponse,
|
|
836
2164
|
createTheme: () => createTheme,
|
|
837
2165
|
createThemeFromBrand: () => createThemeFromBrand,
|
|
838
2166
|
cssVar: () => cssVar,
|
|
839
|
-
deepMerge: () =>
|
|
2167
|
+
deepMerge: () => deepMerge2,
|
|
840
2168
|
defaultDarkTheme: () => defaultDarkTheme,
|
|
841
2169
|
defaultLightTheme: () => defaultLightTheme,
|
|
2170
|
+
deleteRequest: () => deleteRequest,
|
|
842
2171
|
dummyBannerData: () => dummyBannerData,
|
|
843
2172
|
dummyFaqItems: () => dummyFaqItems,
|
|
844
2173
|
dummyFeatures: () => dummyFeatures,
|
|
@@ -847,59 +2176,66 @@ __export(client_exports, {
|
|
|
847
2176
|
dummyImage: () => dummyImage,
|
|
848
2177
|
dummyPricingPlans: () => dummyPricingPlans,
|
|
849
2178
|
dummyTestimonials: () => dummyTestimonials,
|
|
850
|
-
|
|
2179
|
+
extractData: () => extractData,
|
|
2180
|
+
extractMessage: () => extractMessage,
|
|
2181
|
+
extractNestedData: () => extractNestedData,
|
|
2182
|
+
extractPaginatedData: () => extractPaginatedData,
|
|
851
2183
|
flattenToCssVars: () => flattenToCssVars,
|
|
852
2184
|
formatDate: () => formatDate,
|
|
853
|
-
formatDateForInput: () => formatDateForInput,
|
|
854
2185
|
formatDateTime: () => formatDateTime,
|
|
855
|
-
formatDateTimeForInput: () => formatDateTimeForInput,
|
|
856
|
-
formatPackageCheckResult: () => formatPackageCheckResult2,
|
|
857
2186
|
formatRelativeTime: () => formatRelativeTime,
|
|
858
2187
|
generateCssVars: () => generateCssVars,
|
|
859
|
-
|
|
2188
|
+
generateSlug: () => generateSlug,
|
|
2189
|
+
generateSnakeSlug: () => generateSnakeSlug,
|
|
2190
|
+
generateUrlSlug: () => generateUrlSlug,
|
|
860
2191
|
getContrastColor: () => getContrastColor,
|
|
861
2192
|
getErrorMessage: () => getErrorMessage,
|
|
862
2193
|
getNextPage: () => getNextPage,
|
|
863
2194
|
getPrevPage: () => getPrevPage,
|
|
2195
|
+
getRequest: () => getRequest,
|
|
864
2196
|
getResponseData: () => getResponseData,
|
|
865
2197
|
getSystemColorScheme: () => getSystemColorScheme,
|
|
866
2198
|
hasData: () => hasData,
|
|
867
2199
|
hasMorePages: () => hasMorePages,
|
|
868
2200
|
hexToRgba: () => hexToRgba,
|
|
869
2201
|
injectCssVars: () => injectCssVars,
|
|
870
|
-
isClipboardAvailable: () => isClipboardAvailable,
|
|
871
2202
|
isErrorResponse: () => isErrorResponse,
|
|
872
|
-
isForbidden: () => isForbidden,
|
|
873
|
-
isFuture: () => isFuture,
|
|
874
|
-
isNotFound: () => isNotFound,
|
|
875
|
-
isPast: () => isPast,
|
|
876
|
-
isServerError: () => isServerError,
|
|
877
|
-
isStatusError: () => isStatusError,
|
|
878
2203
|
isSuccess: () => isSuccess,
|
|
879
2204
|
isSuccessResponse: () => isSuccessResponse,
|
|
880
|
-
|
|
881
|
-
|
|
2205
|
+
isUtilErrorResponse: () => isErrorResponse2,
|
|
2206
|
+
isUtilSuccessResponse: () => isSuccessResponse2,
|
|
882
2207
|
kebabToCamel: () => kebabToCamel,
|
|
883
2208
|
loadThemeFromUrl: () => loadThemeFromUrl,
|
|
884
2209
|
loadThemeMode: () => loadThemeMode,
|
|
2210
|
+
logger: () => logger2,
|
|
885
2211
|
loginFormSchema: () => loginFormSchema,
|
|
886
2212
|
loremIpsum: () => loremIpsum,
|
|
887
2213
|
newsletterFormSchema: () => newsletterFormSchema,
|
|
888
2214
|
packageCheck: () => packageCheck,
|
|
2215
|
+
parseAxiosErrorMessage: () => parseAxiosErrorMessage,
|
|
889
2216
|
parseError: () => parseError,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
2217
|
+
parsePaginatedResponse: () => parsePaginatedResponse,
|
|
2218
|
+
parseResponseData: () => parseResponseData,
|
|
2219
|
+
parseResponseMessage: () => parseResponseMessage,
|
|
2220
|
+
parseResponseStatus: () => parseResponseStatus,
|
|
2221
|
+
parseResponseStatusMessage: () => parseResponseStatusMessage,
|
|
2222
|
+
patchRequest: () => patchRequest,
|
|
2223
|
+
postRequest: () => postRequest,
|
|
2224
|
+
putRequest: () => putRequest,
|
|
893
2225
|
registerFormSchema: () => registerFormSchema,
|
|
894
2226
|
removeCssVars: () => removeCssVars,
|
|
895
2227
|
resolveThemeMode: () => resolveThemeMode,
|
|
2228
|
+
safeJsonParse: () => safeJsonParse,
|
|
896
2229
|
saveThemeMode: () => saveThemeMode,
|
|
2230
|
+
simpleMetaParseResponse: () => simpleMetaParseResponse,
|
|
2231
|
+
simpleParseDualDataResponse: () => simpleParseDualDataResponse,
|
|
2232
|
+
simpleParseResponse: () => simpleParseResponse,
|
|
897
2233
|
slugify: () => slugify,
|
|
898
2234
|
slugifyUnique: () => slugifyUnique,
|
|
899
|
-
startOfDay: () => startOfDay,
|
|
900
2235
|
truncate: () => truncate,
|
|
901
2236
|
truncateWords: () => truncateWords,
|
|
902
2237
|
unslugify: () => unslugify,
|
|
2238
|
+
uploadFile: () => uploadFile,
|
|
903
2239
|
useBattery: () => useBattery_default,
|
|
904
2240
|
useClickAway: () => useClickAway_default,
|
|
905
2241
|
useContinuousRetry: () => useContinuousRetry_default,
|
|
@@ -959,112 +2295,466 @@ __export(client_exports, {
|
|
|
959
2295
|
useToggle: () => useToggle_default,
|
|
960
2296
|
useVisibilityChange: () => useVisibilityChange_default,
|
|
961
2297
|
useWindowScroll: () => useWindowScroll_default,
|
|
962
|
-
useWindowSize: () => useWindowSize_default
|
|
963
|
-
withAbortSignal: () => withAbortSignal,
|
|
964
|
-
withFormData: () => withFormData,
|
|
965
|
-
withTimeout: () => withTimeout
|
|
2298
|
+
useWindowSize: () => useWindowSize_default
|
|
966
2299
|
});
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
headers: {
|
|
981
|
-
"Content-Type": "application/json"
|
|
2300
|
+
|
|
2301
|
+
// src/client/http/logger.ts
|
|
2302
|
+
var Logger = class {
|
|
2303
|
+
constructor() {
|
|
2304
|
+
this.isDevelopment = typeof window !== "undefined" && window.location.hostname === "localhost";
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Log informational messages
|
|
2308
|
+
*/
|
|
2309
|
+
info(message, data, options) {
|
|
2310
|
+
if (this.isDevelopment) {
|
|
2311
|
+
const prefix = options?.context ? `[${options.context}]` : "";
|
|
2312
|
+
console.log(`${prefix} ${message}`, data ?? "");
|
|
982
2313
|
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Log warning messages
|
|
2317
|
+
*/
|
|
2318
|
+
warn(message, data, options) {
|
|
2319
|
+
if (this.isDevelopment) {
|
|
2320
|
+
const prefix = options?.context ? `[${options.context}]` : "";
|
|
2321
|
+
console.warn(`${prefix} ${message}`, data ?? "");
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Log error messages
|
|
2326
|
+
*/
|
|
2327
|
+
error(message, error, options) {
|
|
2328
|
+
const prefix = options?.context ? `[${options.context}]` : "";
|
|
2329
|
+
if (this.isDevelopment) {
|
|
2330
|
+
console.error(`${prefix} ${message}`, error, options?.metadata || "");
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Log debug messages (only in development)
|
|
2335
|
+
*/
|
|
2336
|
+
debug(message, data, options) {
|
|
2337
|
+
if (this.isDevelopment) {
|
|
2338
|
+
const prefix = options?.context ? `[${options.context}]` : "";
|
|
2339
|
+
console.debug(`${prefix} ${message}`, data || "");
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Log API errors with structured information
|
|
2344
|
+
*/
|
|
2345
|
+
apiError(endpoint, error, metadata) {
|
|
2346
|
+
this.error(`API Error: ${endpoint}`, error, {
|
|
2347
|
+
context: "API",
|
|
2348
|
+
metadata: {
|
|
2349
|
+
endpoint,
|
|
2350
|
+
...metadata
|
|
991
2351
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
};
|
|
2355
|
+
var logger2 = new Logger();
|
|
2356
|
+
|
|
2357
|
+
// src/client/http/response-parser.ts
|
|
2358
|
+
var STATUS_CODES = {
|
|
2359
|
+
SUCCESS: 200,
|
|
2360
|
+
CREATED: 201,
|
|
2361
|
+
NO_CONTENT: 204,
|
|
2362
|
+
BAD_REQUEST: 400,
|
|
2363
|
+
UNAUTHORIZED: 401,
|
|
2364
|
+
FORBIDDEN: 403,
|
|
2365
|
+
NOT_FOUND: 404,
|
|
2366
|
+
CONFLICT: 409,
|
|
2367
|
+
ERROR: 500
|
|
2368
|
+
};
|
|
2369
|
+
var STATUS_MESSAGES = {
|
|
2370
|
+
SUCCESS: "success",
|
|
2371
|
+
CREATED: "created",
|
|
2372
|
+
NO_CONTENT: "no_content",
|
|
2373
|
+
BAD_REQUEST: "bad_request",
|
|
2374
|
+
UNAUTHORIZED: "unauthorized",
|
|
2375
|
+
FORBIDDEN: "forbidden",
|
|
2376
|
+
NOT_FOUND: "not_found",
|
|
2377
|
+
CONFLICT: "conflict",
|
|
2378
|
+
ERROR: "error"
|
|
2379
|
+
};
|
|
2380
|
+
var SUCCESS_CODES = [200, 201, 204];
|
|
2381
|
+
var ERROR_CODES = [400, 401, 403, 404, 409, 500];
|
|
2382
|
+
var parseResponseData = (response, fallback = null) => {
|
|
2383
|
+
try {
|
|
2384
|
+
if (!response || typeof response !== "object") {
|
|
2385
|
+
return fallback;
|
|
2386
|
+
}
|
|
2387
|
+
const resp = response;
|
|
2388
|
+
if ("data" in resp) {
|
|
2389
|
+
return resp["data"] ?? fallback;
|
|
2390
|
+
}
|
|
2391
|
+
return response;
|
|
2392
|
+
} catch (error) {
|
|
2393
|
+
logger2.error("Error parsing response data", error);
|
|
2394
|
+
return fallback;
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
var parseResponseMessage = (response, fallback = "") => {
|
|
2398
|
+
try {
|
|
2399
|
+
if (!response || typeof response !== "object") {
|
|
2400
|
+
return fallback;
|
|
2401
|
+
}
|
|
2402
|
+
const resp = response;
|
|
2403
|
+
if ("message" in resp && typeof resp["message"] === "string") {
|
|
2404
|
+
return resp["message"];
|
|
2405
|
+
}
|
|
2406
|
+
return fallback;
|
|
2407
|
+
} catch (error) {
|
|
2408
|
+
logger2.error("Error parsing response message", error);
|
|
2409
|
+
return fallback;
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
var parseResponseStatus = (response) => {
|
|
2413
|
+
try {
|
|
2414
|
+
if (!response || typeof response !== "object") {
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
const resp = response;
|
|
2418
|
+
if ("statusCode" in resp && typeof resp["statusCode"] === "number") {
|
|
2419
|
+
return resp["statusCode"];
|
|
2420
|
+
}
|
|
2421
|
+
if ("status" in resp && typeof resp["status"] === "number") {
|
|
2422
|
+
return resp["status"];
|
|
2423
|
+
}
|
|
2424
|
+
return null;
|
|
2425
|
+
} catch (error) {
|
|
2426
|
+
logger2.error("Error parsing response status", error);
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
var parseResponseStatusMessage = (response, fallback = "") => {
|
|
2431
|
+
try {
|
|
2432
|
+
if (!response || typeof response !== "object") {
|
|
2433
|
+
return fallback;
|
|
2434
|
+
}
|
|
2435
|
+
const resp = response;
|
|
2436
|
+
if ("status" in resp && typeof resp["status"] === "string") {
|
|
2437
|
+
return resp["status"];
|
|
2438
|
+
}
|
|
2439
|
+
return fallback;
|
|
2440
|
+
} catch (error) {
|
|
2441
|
+
logger2.error("Error parsing response status message", error);
|
|
2442
|
+
return fallback;
|
|
2443
|
+
}
|
|
2444
|
+
};
|
|
2445
|
+
var isSuccessResponse = (response) => {
|
|
2446
|
+
try {
|
|
2447
|
+
const statusCode2 = parseResponseStatus(response);
|
|
2448
|
+
if (statusCode2 !== null) {
|
|
2449
|
+
return SUCCESS_CODES.includes(statusCode2);
|
|
2450
|
+
}
|
|
2451
|
+
const status = parseResponseStatusMessage(response);
|
|
2452
|
+
return [STATUS_MESSAGES.SUCCESS, STATUS_MESSAGES.CREATED, STATUS_MESSAGES.NO_CONTENT].includes(
|
|
2453
|
+
status
|
|
2454
|
+
);
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
logger2.error("Error checking response success", error);
|
|
2457
|
+
return false;
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
var isErrorResponse = (response) => {
|
|
2461
|
+
try {
|
|
2462
|
+
const statusCode2 = parseResponseStatus(response);
|
|
2463
|
+
if (statusCode2 !== null) {
|
|
2464
|
+
return ERROR_CODES.includes(statusCode2);
|
|
2465
|
+
}
|
|
2466
|
+
return false;
|
|
2467
|
+
} catch (error) {
|
|
2468
|
+
logger2.error("Error checking response error", error);
|
|
2469
|
+
return false;
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
var parsePaginatedResponse = (response) => {
|
|
2473
|
+
try {
|
|
2474
|
+
if (!response || typeof response !== "object") {
|
|
2475
|
+
return { items: [], total: 0, page: 1, limit: 10 };
|
|
2476
|
+
}
|
|
2477
|
+
const resp = response;
|
|
2478
|
+
let items = [];
|
|
2479
|
+
if ("data" in resp && Array.isArray(resp["data"])) {
|
|
2480
|
+
items = resp["data"];
|
|
2481
|
+
}
|
|
2482
|
+
let total = items.length;
|
|
2483
|
+
let page = 1;
|
|
2484
|
+
let limit = 10;
|
|
2485
|
+
let totalPages;
|
|
2486
|
+
if ("paginationData" in resp && resp["paginationData"] && typeof resp["paginationData"] === "object") {
|
|
2487
|
+
const paginationData = resp["paginationData"];
|
|
2488
|
+
if ("total" in paginationData && typeof paginationData["total"] === "number") {
|
|
2489
|
+
total = paginationData["total"];
|
|
2490
|
+
}
|
|
2491
|
+
if ("page" in paginationData && typeof paginationData["page"] === "number") {
|
|
2492
|
+
page = paginationData["page"];
|
|
2493
|
+
}
|
|
2494
|
+
if ("limit" in paginationData && typeof paginationData["limit"] === "number") {
|
|
2495
|
+
limit = paginationData["limit"];
|
|
2496
|
+
}
|
|
2497
|
+
if ("totalPages" in paginationData && typeof paginationData["totalPages"] === "number") {
|
|
2498
|
+
totalPages = paginationData["totalPages"];
|
|
1007
2499
|
}
|
|
1008
|
-
return Promise.reject(error);
|
|
1009
2500
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
2501
|
+
let columns;
|
|
2502
|
+
if ("columns" in resp && Array.isArray(resp["columns"])) {
|
|
2503
|
+
columns = resp["columns"];
|
|
2504
|
+
}
|
|
2505
|
+
return {
|
|
2506
|
+
items,
|
|
2507
|
+
total,
|
|
2508
|
+
page,
|
|
2509
|
+
limit,
|
|
2510
|
+
...totalPages !== void 0 && { totalPages },
|
|
2511
|
+
...columns !== void 0 && { columns }
|
|
2512
|
+
};
|
|
2513
|
+
} catch (error) {
|
|
2514
|
+
logger2.error("Error parsing paginated response", error);
|
|
2515
|
+
return { items: [], total: 0, page: 1, limit: 10 };
|
|
2516
|
+
}
|
|
1012
2517
|
};
|
|
1013
|
-
var
|
|
1014
|
-
|
|
1015
|
-
|
|
2518
|
+
var extractNestedData = (response, path2, fallback = null) => {
|
|
2519
|
+
try {
|
|
2520
|
+
const keys = path2.split(".");
|
|
2521
|
+
let current = response;
|
|
2522
|
+
for (const key of keys) {
|
|
2523
|
+
if (current && typeof current === "object" && key in current) {
|
|
2524
|
+
current = current[key];
|
|
2525
|
+
} else {
|
|
2526
|
+
return fallback;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
return current;
|
|
2530
|
+
} catch (error) {
|
|
2531
|
+
logger2.error("Error extracting nested data", error);
|
|
2532
|
+
return fallback;
|
|
1016
2533
|
}
|
|
1017
|
-
}
|
|
1018
|
-
var
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
// src/client/http/response-parser.ts
|
|
1026
|
-
var parseResponse = (response) => {
|
|
1027
|
-
if (response.data?.success && response.data?.data !== void 0) {
|
|
1028
|
-
return response.data.data;
|
|
2534
|
+
};
|
|
2535
|
+
var safeJsonParse = (json, fallback = null) => {
|
|
2536
|
+
try {
|
|
2537
|
+
return JSON.parse(json);
|
|
2538
|
+
} catch (error) {
|
|
2539
|
+
logger2.error("Error parsing JSON", error);
|
|
2540
|
+
return fallback;
|
|
1029
2541
|
}
|
|
1030
|
-
return null;
|
|
1031
2542
|
};
|
|
1032
|
-
var
|
|
1033
|
-
|
|
2543
|
+
var parseAxiosErrorMessage = (error) => {
|
|
2544
|
+
try {
|
|
2545
|
+
if (!error || typeof error !== "object") {
|
|
2546
|
+
return "An unexpected error occurred";
|
|
2547
|
+
}
|
|
2548
|
+
const err = error;
|
|
2549
|
+
if ("response" in err && err["response"] && typeof err["response"] === "object") {
|
|
2550
|
+
const response = err["response"];
|
|
2551
|
+
if ("data" in response && response["data"] && typeof response["data"] === "object") {
|
|
2552
|
+
const data = response["data"];
|
|
2553
|
+
if ("data" in data && data["data"] && typeof data["data"] === "object") {
|
|
2554
|
+
const nestedData = data["data"];
|
|
2555
|
+
if ("message" in nestedData && typeof nestedData["message"] === "string") {
|
|
2556
|
+
return nestedData["message"];
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
if ("message" in data && typeof data["message"] === "string") {
|
|
2560
|
+
return data["message"];
|
|
2561
|
+
}
|
|
2562
|
+
if ("error" in data && typeof data["error"] === "string") {
|
|
2563
|
+
return data["error"];
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
if ("message" in err && typeof err["message"] === "string") {
|
|
2568
|
+
return err["message"];
|
|
2569
|
+
}
|
|
2570
|
+
if (typeof error === "string") {
|
|
2571
|
+
return error;
|
|
2572
|
+
}
|
|
2573
|
+
return "An unexpected error occurred";
|
|
2574
|
+
} catch (parseError2) {
|
|
2575
|
+
logger2.error("Error parsing axios error message", parseError2);
|
|
2576
|
+
return "An unexpected error occurred";
|
|
2577
|
+
}
|
|
1034
2578
|
};
|
|
1035
2579
|
var parseError = (error) => {
|
|
1036
|
-
|
|
1037
|
-
|
|
2580
|
+
try {
|
|
2581
|
+
if (!error || typeof error !== "object") {
|
|
2582
|
+
return {
|
|
2583
|
+
message: "An unexpected error occurred",
|
|
2584
|
+
statusCode: null,
|
|
2585
|
+
data: null
|
|
2586
|
+
};
|
|
2587
|
+
}
|
|
2588
|
+
const err = error;
|
|
2589
|
+
let statusCode2 = null;
|
|
2590
|
+
let data = null;
|
|
2591
|
+
let status;
|
|
2592
|
+
if ("response" in err && err["response"] && typeof err["response"] === "object") {
|
|
2593
|
+
const response = err["response"];
|
|
2594
|
+
if ("status" in response && typeof response["status"] === "number") {
|
|
2595
|
+
statusCode2 = response["status"];
|
|
2596
|
+
}
|
|
2597
|
+
if ("data" in response && response["data"] !== void 0) {
|
|
2598
|
+
data = response["data"];
|
|
2599
|
+
if (data && typeof data === "object" && "status" in data) {
|
|
2600
|
+
const dataObj = data;
|
|
2601
|
+
if (typeof dataObj["status"] === "string") {
|
|
2602
|
+
status = dataObj["status"];
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
if (statusCode2 === null && "statusCode" in err && typeof err["statusCode"] === "number") {
|
|
2608
|
+
statusCode2 = err["statusCode"];
|
|
2609
|
+
}
|
|
2610
|
+
if (data === null && "data" in err && err["data"] !== void 0) {
|
|
2611
|
+
data = err["data"];
|
|
2612
|
+
}
|
|
2613
|
+
if (!status && "status" in err && typeof err["status"] === "string") {
|
|
2614
|
+
status = err["status"];
|
|
2615
|
+
}
|
|
2616
|
+
return {
|
|
2617
|
+
message: parseAxiosErrorMessage(error),
|
|
2618
|
+
statusCode: statusCode2,
|
|
2619
|
+
data,
|
|
2620
|
+
...status !== void 0 && { status }
|
|
2621
|
+
};
|
|
2622
|
+
} catch (err) {
|
|
2623
|
+
logger2.error("Error parsing error object", err);
|
|
2624
|
+
return {
|
|
2625
|
+
message: "An unexpected error occurred",
|
|
2626
|
+
statusCode: null,
|
|
2627
|
+
data: null
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
};
|
|
2631
|
+
var simpleParseResponse = (response) => {
|
|
2632
|
+
return response?.data?.data?.data;
|
|
2633
|
+
};
|
|
2634
|
+
var simpleMetaParseResponse = (response) => {
|
|
2635
|
+
return response?.data?.data?.meta;
|
|
2636
|
+
};
|
|
2637
|
+
var simpleParseDualDataResponse = (response) => {
|
|
2638
|
+
return response?.data?.data;
|
|
2639
|
+
};
|
|
2640
|
+
|
|
2641
|
+
// src/client/http/http.ts
|
|
2642
|
+
var isDevelopment2 = typeof window !== "undefined" && window.location.hostname === "localhost";
|
|
2643
|
+
var API_BASE_URL = isDevelopment2 ? "http://localhost:4002" : "https://service-api.exyconn.com";
|
|
2644
|
+
var API_PREFIX = "/v1/api";
|
|
2645
|
+
var axiosInstance = axios__default.default.create({
|
|
2646
|
+
baseURL: API_BASE_URL,
|
|
2647
|
+
timeout: 3e4,
|
|
2648
|
+
// 30 seconds
|
|
2649
|
+
headers: {
|
|
2650
|
+
"Content-Type": "application/json"
|
|
1038
2651
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
2652
|
+
});
|
|
2653
|
+
axiosInstance.interceptors.request.use(
|
|
2654
|
+
(config) => {
|
|
2655
|
+
try {
|
|
2656
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
2657
|
+
const selectedOrg = localStorage.getItem("selectedOrganization");
|
|
2658
|
+
if (selectedOrg) {
|
|
2659
|
+
const org = JSON.parse(selectedOrg);
|
|
2660
|
+
if (org && org._id) {
|
|
2661
|
+
config.headers["x-organization-id"] = org._id;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
} catch (error) {
|
|
2666
|
+
logger2.warn("Failed to read organization from localStorage", error);
|
|
2667
|
+
}
|
|
2668
|
+
return config;
|
|
2669
|
+
},
|
|
2670
|
+
(error) => {
|
|
2671
|
+
return Promise.reject(error);
|
|
1041
2672
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
2673
|
+
);
|
|
2674
|
+
axiosInstance.interceptors.response.use(
|
|
2675
|
+
(response) => response,
|
|
2676
|
+
(error) => {
|
|
2677
|
+
const parsedError = parseError(error);
|
|
2678
|
+
logger2.error("API Error", parsedError);
|
|
2679
|
+
return Promise.reject(parsedError);
|
|
1044
2680
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
2681
|
+
);
|
|
2682
|
+
var buildHeaders = (customHeaders) => {
|
|
2683
|
+
const headers = {
|
|
2684
|
+
"Content-Type": "application/json",
|
|
2685
|
+
...customHeaders
|
|
2686
|
+
};
|
|
2687
|
+
return headers;
|
|
2688
|
+
};
|
|
2689
|
+
var buildConfig2 = (params, customHeaders) => {
|
|
2690
|
+
const config = {
|
|
2691
|
+
headers: buildHeaders(customHeaders)
|
|
2692
|
+
};
|
|
2693
|
+
if (params) {
|
|
2694
|
+
config.params = params;
|
|
1047
2695
|
}
|
|
1048
|
-
return
|
|
2696
|
+
return config;
|
|
1049
2697
|
};
|
|
1050
|
-
var
|
|
1051
|
-
|
|
2698
|
+
var getRequest = async (url, params, customHeaders) => {
|
|
2699
|
+
const config = buildConfig2(params, customHeaders);
|
|
2700
|
+
return axiosInstance.get(url, config);
|
|
2701
|
+
};
|
|
2702
|
+
var postRequest = async (url, data, customHeaders) => {
|
|
2703
|
+
const config = buildConfig2(void 0, customHeaders);
|
|
2704
|
+
return axiosInstance.post(url, data, config);
|
|
2705
|
+
};
|
|
2706
|
+
var putRequest = async (url, data, customHeaders) => {
|
|
2707
|
+
const config = buildConfig2(void 0, customHeaders);
|
|
2708
|
+
return axiosInstance.put(url, data, config);
|
|
1052
2709
|
};
|
|
1053
|
-
var
|
|
1054
|
-
|
|
2710
|
+
var patchRequest = async (url, data, customHeaders) => {
|
|
2711
|
+
const config = buildConfig2(void 0, customHeaders);
|
|
2712
|
+
return axiosInstance.patch(url, data, config);
|
|
1055
2713
|
};
|
|
1056
|
-
var
|
|
1057
|
-
|
|
2714
|
+
var deleteRequest = async (url, params, customHeaders) => {
|
|
2715
|
+
const config = buildConfig2(params, customHeaders);
|
|
2716
|
+
return axiosInstance.delete(url, config);
|
|
1058
2717
|
};
|
|
1059
|
-
var
|
|
1060
|
-
|
|
2718
|
+
var uploadFile = async (url, file, additionalData) => {
|
|
2719
|
+
const formData = new FormData();
|
|
2720
|
+
formData.append("file", file);
|
|
2721
|
+
if (additionalData) {
|
|
2722
|
+
Object.entries(additionalData).forEach(([key, value]) => {
|
|
2723
|
+
formData.append(key, String(value));
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
const config = {
|
|
2727
|
+
headers: {
|
|
2728
|
+
"Content-Type": "multipart/form-data"
|
|
2729
|
+
}
|
|
2730
|
+
};
|
|
2731
|
+
return axiosInstance.post(url, formData, config);
|
|
2732
|
+
};
|
|
2733
|
+
var extractData = (response) => {
|
|
2734
|
+
return parseResponseData(response.data);
|
|
2735
|
+
};
|
|
2736
|
+
var extractMessage = (response) => {
|
|
2737
|
+
return parseResponseMessage(response, "");
|
|
2738
|
+
};
|
|
2739
|
+
var isSuccess = (response) => {
|
|
2740
|
+
return response.status >= 200 && response.status < 300;
|
|
2741
|
+
};
|
|
2742
|
+
var extractPaginatedData = (response) => {
|
|
2743
|
+
return parsePaginatedResponse(response.data);
|
|
2744
|
+
};
|
|
2745
|
+
|
|
2746
|
+
// src/client/http/slug.ts
|
|
2747
|
+
var generateSlug = (text) => {
|
|
2748
|
+
if (!text) return "";
|
|
2749
|
+
return text.trim().replace(/[^\w\s]/g, "").replace(/\s+(.)/g, (_, char) => char.toUpperCase()).replace(/\s+/g, "").replace(/^(.)/, (_, char) => char.toLowerCase());
|
|
1061
2750
|
};
|
|
1062
|
-
var
|
|
1063
|
-
|
|
2751
|
+
var generateUrlSlug = (text) => {
|
|
2752
|
+
if (!text) return "";
|
|
2753
|
+
return text.trim().toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1064
2754
|
};
|
|
1065
|
-
var
|
|
1066
|
-
|
|
1067
|
-
return
|
|
2755
|
+
var generateSnakeSlug = (text) => {
|
|
2756
|
+
if (!text) return "";
|
|
2757
|
+
return text.trim().toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
1068
2758
|
};
|
|
1069
2759
|
|
|
1070
2760
|
// src/client/logger/client-logger.ts
|
|
@@ -1220,40 +2910,6 @@ var formatRelativeTime = (date) => {
|
|
|
1220
2910
|
}
|
|
1221
2911
|
return "just now";
|
|
1222
2912
|
};
|
|
1223
|
-
var formatDateForInput = (date) => {
|
|
1224
|
-
const dateObj = new Date(date);
|
|
1225
|
-
return dateObj.toISOString().split("T")[0];
|
|
1226
|
-
};
|
|
1227
|
-
var formatDateTimeForInput = (date) => {
|
|
1228
|
-
const dateObj = new Date(date);
|
|
1229
|
-
return dateObj.toISOString().slice(0, 16);
|
|
1230
|
-
};
|
|
1231
|
-
var isToday = (date) => {
|
|
1232
|
-
const dateObj = new Date(date);
|
|
1233
|
-
const today = /* @__PURE__ */ new Date();
|
|
1234
|
-
return dateObj.getDate() === today.getDate() && dateObj.getMonth() === today.getMonth() && dateObj.getFullYear() === today.getFullYear();
|
|
1235
|
-
};
|
|
1236
|
-
var isPast = (date) => {
|
|
1237
|
-
return new Date(date).getTime() < Date.now();
|
|
1238
|
-
};
|
|
1239
|
-
var isFuture = (date) => {
|
|
1240
|
-
return new Date(date).getTime() > Date.now();
|
|
1241
|
-
};
|
|
1242
|
-
var addDays = (date, days) => {
|
|
1243
|
-
const dateObj = new Date(date);
|
|
1244
|
-
dateObj.setDate(dateObj.getDate() + days);
|
|
1245
|
-
return dateObj;
|
|
1246
|
-
};
|
|
1247
|
-
var startOfDay = (date) => {
|
|
1248
|
-
const dateObj = new Date(date);
|
|
1249
|
-
dateObj.setHours(0, 0, 0, 0);
|
|
1250
|
-
return dateObj;
|
|
1251
|
-
};
|
|
1252
|
-
var endOfDay = (date) => {
|
|
1253
|
-
const dateObj = new Date(date);
|
|
1254
|
-
dateObj.setHours(23, 59, 59, 999);
|
|
1255
|
-
return dateObj;
|
|
1256
|
-
};
|
|
1257
2913
|
|
|
1258
2914
|
// src/client/utils/clipboard.ts
|
|
1259
2915
|
var copyToClipboard = async (text) => {
|
|
@@ -1278,20 +2934,6 @@ var copyToClipboard = async (text) => {
|
|
|
1278
2934
|
return false;
|
|
1279
2935
|
}
|
|
1280
2936
|
};
|
|
1281
|
-
var readFromClipboard = async () => {
|
|
1282
|
-
try {
|
|
1283
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
1284
|
-
return await navigator.clipboard.readText();
|
|
1285
|
-
}
|
|
1286
|
-
return null;
|
|
1287
|
-
} catch (error) {
|
|
1288
|
-
console.error("Failed to read from clipboard:", error);
|
|
1289
|
-
return null;
|
|
1290
|
-
}
|
|
1291
|
-
};
|
|
1292
|
-
var isClipboardAvailable = () => {
|
|
1293
|
-
return !!(navigator.clipboard && window.isSecureContext);
|
|
1294
|
-
};
|
|
1295
2937
|
|
|
1296
2938
|
// src/client/utils/slug.ts
|
|
1297
2939
|
var slugify = (text) => {
|
|
@@ -1328,165 +2970,15 @@ var kebabToCamel = (text) => {
|
|
|
1328
2970
|
return text.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
1329
2971
|
};
|
|
1330
2972
|
|
|
1331
|
-
// src/client/utils/events.ts
|
|
1332
|
-
var EventEmitter = class {
|
|
1333
|
-
constructor() {
|
|
1334
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Subscribe to an event
|
|
1338
|
-
* @returns Unsubscribe function
|
|
1339
|
-
*/
|
|
1340
|
-
on(event, handler) {
|
|
1341
|
-
if (!this.handlers.has(event)) {
|
|
1342
|
-
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
1343
|
-
}
|
|
1344
|
-
this.handlers.get(event).add(handler);
|
|
1345
|
-
return () => this.off(event, handler);
|
|
1346
|
-
}
|
|
1347
|
-
/**
|
|
1348
|
-
* Subscribe to an event once
|
|
1349
|
-
*/
|
|
1350
|
-
once(event, handler) {
|
|
1351
|
-
const wrappedHandler = (data) => {
|
|
1352
|
-
this.off(event, wrappedHandler);
|
|
1353
|
-
handler(data);
|
|
1354
|
-
};
|
|
1355
|
-
return this.on(event, wrappedHandler);
|
|
1356
|
-
}
|
|
1357
|
-
/**
|
|
1358
|
-
* Unsubscribe from an event
|
|
1359
|
-
*/
|
|
1360
|
-
off(event, handler) {
|
|
1361
|
-
const eventHandlers = this.handlers.get(event);
|
|
1362
|
-
if (eventHandlers) {
|
|
1363
|
-
eventHandlers.delete(handler);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Emit an event
|
|
1368
|
-
*/
|
|
1369
|
-
emit(event, data) {
|
|
1370
|
-
const eventHandlers = this.handlers.get(event);
|
|
1371
|
-
if (eventHandlers) {
|
|
1372
|
-
eventHandlers.forEach((handler) => {
|
|
1373
|
-
try {
|
|
1374
|
-
handler(data);
|
|
1375
|
-
} catch (error) {
|
|
1376
|
-
console.error(`Error in event handler for "${String(event)}":`, error);
|
|
1377
|
-
}
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
/**
|
|
1382
|
-
* Remove all handlers for an event (or all events)
|
|
1383
|
-
*/
|
|
1384
|
-
removeAllListeners(event) {
|
|
1385
|
-
if (event) {
|
|
1386
|
-
this.handlers.delete(event);
|
|
1387
|
-
} else {
|
|
1388
|
-
this.handlers.clear();
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
/**
|
|
1392
|
-
* Get count of listeners for an event
|
|
1393
|
-
*/
|
|
1394
|
-
listenerCount(event) {
|
|
1395
|
-
return this.handlers.get(event)?.size ?? 0;
|
|
1396
|
-
}
|
|
1397
|
-
};
|
|
1398
|
-
var createEventEmitter = () => {
|
|
1399
|
-
return new EventEmitter();
|
|
1400
|
-
};
|
|
1401
|
-
var appEvents = new EventEmitter();
|
|
1402
|
-
|
|
1403
|
-
// src/client/utils/api-urls.ts
|
|
1404
|
-
var ApiUrlBuilder = class {
|
|
1405
|
-
constructor(config) {
|
|
1406
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
1407
|
-
this.version = config.version || "";
|
|
1408
|
-
}
|
|
1409
|
-
/**
|
|
1410
|
-
* Build full URL from path
|
|
1411
|
-
*/
|
|
1412
|
-
build(path2) {
|
|
1413
|
-
const normalizedPath = path2.startsWith("/") ? path2 : `/${path2}`;
|
|
1414
|
-
const versionPath = this.version ? `/${this.version}` : "";
|
|
1415
|
-
return `${this.baseUrl}${versionPath}${normalizedPath}`;
|
|
1416
|
-
}
|
|
1417
|
-
/**
|
|
1418
|
-
* Build URL with query parameters
|
|
1419
|
-
*/
|
|
1420
|
-
buildWithParams(path2, params) {
|
|
1421
|
-
const url = this.build(path2);
|
|
1422
|
-
const filteredParams = Object.entries(params).filter(([, value]) => value !== void 0).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
|
|
1423
|
-
return filteredParams ? `${url}?${filteredParams}` : url;
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* Build URL with path parameters
|
|
1427
|
-
*/
|
|
1428
|
-
buildWithPathParams(template, params) {
|
|
1429
|
-
let path2 = template;
|
|
1430
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
1431
|
-
path2 = path2.replace(`:${key}`, String(value));
|
|
1432
|
-
path2 = path2.replace(`{${key}}`, String(value));
|
|
1433
|
-
});
|
|
1434
|
-
return this.build(path2);
|
|
1435
|
-
}
|
|
1436
|
-
/**
|
|
1437
|
-
* Get base URL
|
|
1438
|
-
*/
|
|
1439
|
-
getBaseUrl() {
|
|
1440
|
-
return this.baseUrl;
|
|
1441
|
-
}
|
|
1442
|
-
/**
|
|
1443
|
-
* Set new base URL
|
|
1444
|
-
*/
|
|
1445
|
-
setBaseUrl(baseUrl) {
|
|
1446
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1447
|
-
}
|
|
1448
|
-
};
|
|
1449
|
-
var createApiUrlBuilder = (config) => {
|
|
1450
|
-
return new ApiUrlBuilder(config);
|
|
1451
|
-
};
|
|
1452
|
-
var createApiEndpoints = (builder) => ({
|
|
1453
|
-
// Auth endpoints
|
|
1454
|
-
auth: {
|
|
1455
|
-
login: () => builder.build("/auth/login"),
|
|
1456
|
-
register: () => builder.build("/auth/register"),
|
|
1457
|
-
logout: () => builder.build("/auth/logout"),
|
|
1458
|
-
refresh: () => builder.build("/auth/refresh"),
|
|
1459
|
-
me: () => builder.build("/auth/me"),
|
|
1460
|
-
forgotPassword: () => builder.build("/auth/forgot-password"),
|
|
1461
|
-
resetPassword: () => builder.build("/auth/reset-password")
|
|
1462
|
-
},
|
|
1463
|
-
// User endpoints
|
|
1464
|
-
users: {
|
|
1465
|
-
list: () => builder.build("/users"),
|
|
1466
|
-
get: (id) => builder.buildWithPathParams("/users/:id", { id }),
|
|
1467
|
-
create: () => builder.build("/users"),
|
|
1468
|
-
update: (id) => builder.buildWithPathParams("/users/:id", { id }),
|
|
1469
|
-
delete: (id) => builder.buildWithPathParams("/users/:id", { id })
|
|
1470
|
-
},
|
|
1471
|
-
// Generic CRUD factory
|
|
1472
|
-
crud: (resource) => ({
|
|
1473
|
-
list: () => builder.build(`/${resource}`),
|
|
1474
|
-
get: (id) => builder.buildWithPathParams(`/${resource}/:id`, { id }),
|
|
1475
|
-
create: () => builder.build(`/${resource}`),
|
|
1476
|
-
update: (id) => builder.buildWithPathParams(`/${resource}/:id`, { id }),
|
|
1477
|
-
delete: (id) => builder.buildWithPathParams(`/${resource}/:id`, { id })
|
|
1478
|
-
})
|
|
1479
|
-
});
|
|
1480
|
-
|
|
1481
2973
|
// src/client/utils/response-parser.ts
|
|
1482
|
-
var
|
|
2974
|
+
var isSuccessResponse2 = (response) => {
|
|
1483
2975
|
return response.success === true;
|
|
1484
2976
|
};
|
|
1485
|
-
var
|
|
2977
|
+
var isErrorResponse2 = (response) => {
|
|
1486
2978
|
return response.success === false;
|
|
1487
2979
|
};
|
|
1488
2980
|
var getResponseData = (response, defaultValue) => {
|
|
1489
|
-
if (
|
|
2981
|
+
if (isSuccessResponse2(response) && response.data !== void 0) {
|
|
1490
2982
|
return response.data;
|
|
1491
2983
|
}
|
|
1492
2984
|
return defaultValue;
|
|
@@ -3727,18 +5219,18 @@ function useLogger(componentName, props, options = {}) {
|
|
|
3727
5219
|
const {
|
|
3728
5220
|
logProps = true,
|
|
3729
5221
|
logLifecycle = true,
|
|
3730
|
-
logger:
|
|
5222
|
+
logger: logger3 = console.log
|
|
3731
5223
|
} = options;
|
|
3732
5224
|
const previousProps = react.useRef(props);
|
|
3733
5225
|
const renderCount = react.useRef(0);
|
|
3734
5226
|
renderCount.current++;
|
|
3735
5227
|
react.useEffect(() => {
|
|
3736
5228
|
if (logLifecycle) {
|
|
3737
|
-
|
|
5229
|
+
logger3(`[${componentName}] Mounted`);
|
|
3738
5230
|
}
|
|
3739
5231
|
return () => {
|
|
3740
5232
|
if (logLifecycle) {
|
|
3741
|
-
|
|
5233
|
+
logger3(`[${componentName}] Unmounted (rendered ${renderCount.current} times)`);
|
|
3742
5234
|
}
|
|
3743
5235
|
};
|
|
3744
5236
|
}, []);
|
|
@@ -3770,12 +5262,12 @@ function useLogger(componentName, props, options = {}) {
|
|
|
3770
5262
|
});
|
|
3771
5263
|
}
|
|
3772
5264
|
if (hasChanges) {
|
|
3773
|
-
|
|
5265
|
+
logger3(`[${componentName}] Props changed:`, changedProps);
|
|
3774
5266
|
}
|
|
3775
5267
|
previousProps.current = props;
|
|
3776
|
-
}, [componentName, props, logProps,
|
|
5268
|
+
}, [componentName, props, logProps, logger3]);
|
|
3777
5269
|
if (process.env.NODE_ENV === "development") {
|
|
3778
|
-
|
|
5270
|
+
logger3(`[${componentName}] Render #${renderCount.current}`);
|
|
3779
5271
|
}
|
|
3780
5272
|
}
|
|
3781
5273
|
var useLogger_default = useLogger;
|
|
@@ -4973,14 +6465,14 @@ var defaultDarkTheme = {
|
|
|
4973
6465
|
};
|
|
4974
6466
|
|
|
4975
6467
|
// src/client/web/theme/theme-utils.ts
|
|
4976
|
-
function
|
|
6468
|
+
function deepMerge2(target, source) {
|
|
4977
6469
|
const output = { ...target };
|
|
4978
6470
|
for (const key in source) {
|
|
4979
6471
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
4980
6472
|
const sourceValue = source[key];
|
|
4981
6473
|
const targetValue = target[key];
|
|
4982
6474
|
if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
|
|
4983
|
-
output[key] =
|
|
6475
|
+
output[key] = deepMerge2(
|
|
4984
6476
|
targetValue,
|
|
4985
6477
|
sourceValue
|
|
4986
6478
|
);
|
|
@@ -5046,7 +6538,7 @@ function createThemeFromBrand(brand, baseTheme = defaultLightTheme) {
|
|
|
5046
6538
|
}
|
|
5047
6539
|
}
|
|
5048
6540
|
};
|
|
5049
|
-
return
|
|
6541
|
+
return deepMerge2(baseTheme, brandOverrides);
|
|
5050
6542
|
}
|
|
5051
6543
|
function adjustColor(hex, percent) {
|
|
5052
6544
|
hex = hex.replace(/^#/, "");
|
|
@@ -5140,8 +6632,8 @@ async function createTheme(config = {}) {
|
|
|
5140
6632
|
if (config.themeUrl) {
|
|
5141
6633
|
const urlTheme = await loadThemeFromUrl(config.themeUrl);
|
|
5142
6634
|
if (urlTheme) {
|
|
5143
|
-
lightTheme =
|
|
5144
|
-
darkTheme =
|
|
6635
|
+
lightTheme = deepMerge2(lightTheme, urlTheme);
|
|
6636
|
+
darkTheme = deepMerge2(darkTheme, urlTheme);
|
|
5145
6637
|
}
|
|
5146
6638
|
}
|
|
5147
6639
|
if (config.brandIdentity) {
|
|
@@ -5149,10 +6641,10 @@ async function createTheme(config = {}) {
|
|
|
5149
6641
|
darkTheme = createThemeFromBrand(config.brandIdentity, darkTheme);
|
|
5150
6642
|
}
|
|
5151
6643
|
if (config.light) {
|
|
5152
|
-
lightTheme =
|
|
6644
|
+
lightTheme = deepMerge2(lightTheme, config.light);
|
|
5153
6645
|
}
|
|
5154
6646
|
if (config.dark) {
|
|
5155
|
-
darkTheme =
|
|
6647
|
+
darkTheme = deepMerge2(darkTheme, config.dark);
|
|
5156
6648
|
}
|
|
5157
6649
|
return { light: lightTheme, dark: darkTheme };
|
|
5158
6650
|
}
|
|
@@ -5226,7 +6718,7 @@ function ThemeProvider({
|
|
|
5226
6718
|
theme2 = createThemeFromBrand(brandIdentity, theme2);
|
|
5227
6719
|
}
|
|
5228
6720
|
if (lightOverrides) {
|
|
5229
|
-
theme2 =
|
|
6721
|
+
theme2 = deepMerge2(theme2, lightOverrides);
|
|
5230
6722
|
}
|
|
5231
6723
|
return theme2;
|
|
5232
6724
|
});
|
|
@@ -5237,7 +6729,7 @@ function ThemeProvider({
|
|
|
5237
6729
|
theme2 = createThemeFromBrand(brandIdentity, theme2);
|
|
5238
6730
|
}
|
|
5239
6731
|
if (darkOverrides) {
|
|
5240
|
-
theme2 =
|
|
6732
|
+
theme2 = deepMerge2(theme2, darkOverrides);
|
|
5241
6733
|
}
|
|
5242
6734
|
return theme2;
|
|
5243
6735
|
});
|
|
@@ -5256,8 +6748,8 @@ function ThemeProvider({
|
|
|
5256
6748
|
setError(null);
|
|
5257
6749
|
loadThemeFromUrl(themeUrl).then((urlTheme) => {
|
|
5258
6750
|
if (urlTheme) {
|
|
5259
|
-
setLightTheme((prev) =>
|
|
5260
|
-
setDarkTheme((prev) =>
|
|
6751
|
+
setLightTheme((prev) => deepMerge2(prev, urlTheme));
|
|
6752
|
+
setDarkTheme((prev) => deepMerge2(prev, urlTheme));
|
|
5261
6753
|
}
|
|
5262
6754
|
}).catch((err) => {
|
|
5263
6755
|
setError(err instanceof Error ? err.message : "Failed to load theme");
|
|
@@ -5302,8 +6794,8 @@ function ThemeProvider({
|
|
|
5302
6794
|
});
|
|
5303
6795
|
}, []);
|
|
5304
6796
|
const updateTheme = react.useCallback((updates) => {
|
|
5305
|
-
setLightTheme((prev) =>
|
|
5306
|
-
setDarkTheme((prev) =>
|
|
6797
|
+
setLightTheme((prev) => deepMerge2(prev, updates));
|
|
6798
|
+
setDarkTheme((prev) => deepMerge2(prev, updates));
|
|
5307
6799
|
}, []);
|
|
5308
6800
|
const resetTheme = react.useCallback(() => {
|
|
5309
6801
|
let light = defaultLightTheme;
|
|
@@ -5313,10 +6805,10 @@ function ThemeProvider({
|
|
|
5313
6805
|
dark = createThemeFromBrand(brandIdentity, dark);
|
|
5314
6806
|
}
|
|
5315
6807
|
if (lightOverrides) {
|
|
5316
|
-
light =
|
|
6808
|
+
light = deepMerge2(light, lightOverrides);
|
|
5317
6809
|
}
|
|
5318
6810
|
if (darkOverrides) {
|
|
5319
|
-
dark =
|
|
6811
|
+
dark = deepMerge2(dark, darkOverrides);
|
|
5320
6812
|
}
|
|
5321
6813
|
setLightTheme(light);
|
|
5322
6814
|
setDarkTheme(dark);
|
|
@@ -6671,7 +8163,7 @@ __export(shared_exports, {
|
|
|
6671
8163
|
isSameMonth: () => dateFns.isSameMonth,
|
|
6672
8164
|
isSameWeek: () => dateFns.isSameWeek,
|
|
6673
8165
|
isSameYear: () => dateFns.isSameYear,
|
|
6674
|
-
isTest: () =>
|
|
8166
|
+
isTest: () => isTest2,
|
|
6675
8167
|
isThisMonth: () => dateFns.isThisMonth,
|
|
6676
8168
|
isThisWeek: () => dateFns.isThisWeek,
|
|
6677
8169
|
isThisYear: () => dateFns.isThisYear,
|
|
@@ -6750,7 +8242,7 @@ var isProd = () => {
|
|
|
6750
8242
|
var isDev = () => {
|
|
6751
8243
|
return process.env.NODE_ENV === "development";
|
|
6752
8244
|
};
|
|
6753
|
-
var
|
|
8245
|
+
var isTest2 = () => {
|
|
6754
8246
|
return process.env.NODE_ENV === "test";
|
|
6755
8247
|
};
|
|
6756
8248
|
var validateEnv = (requiredVars) => {
|