elseware-nodejs 1.8.6 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +560 -305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +229 -104
- package/dist/index.d.ts +229 -104
- package/dist/index.js +543 -298
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import mongoose from 'mongoose';
|
|
2
1
|
import dotenv from 'dotenv';
|
|
2
|
+
import mongoose from 'mongoose';
|
|
3
3
|
import { BlobServiceClient, StorageSharedKeyCredential, generateBlobSASQueryParameters, BlobSASPermissions } from '@azure/storage-blob';
|
|
4
4
|
import fs3 from 'fs';
|
|
5
5
|
import cloudinaryModule from 'cloudinary';
|
|
@@ -10,252 +10,7 @@ import juice from 'juice';
|
|
|
10
10
|
import jwt from 'jsonwebtoken';
|
|
11
11
|
import multer from 'multer';
|
|
12
12
|
|
|
13
|
-
// src/
|
|
14
|
-
var AppError = class extends Error {
|
|
15
|
-
statusCode;
|
|
16
|
-
status;
|
|
17
|
-
isOperational;
|
|
18
|
-
code;
|
|
19
|
-
details;
|
|
20
|
-
constructor(message, statusCode = 500, options) {
|
|
21
|
-
super(message);
|
|
22
|
-
this.statusCode = statusCode;
|
|
23
|
-
this.status = `${statusCode}`.startsWith("4") ? "fail" : "error";
|
|
24
|
-
this.isOperational = true;
|
|
25
|
-
this.code = options?.code;
|
|
26
|
-
this.details = options?.details;
|
|
27
|
-
Error.captureStackTrace(this, this.constructor);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// src/api/apiResponse.ts
|
|
32
|
-
var APIResponse = class _APIResponse {
|
|
33
|
-
static send(res, options) {
|
|
34
|
-
const {
|
|
35
|
-
statusCode = 200,
|
|
36
|
-
success = true,
|
|
37
|
-
message = "Success",
|
|
38
|
-
data,
|
|
39
|
-
meta
|
|
40
|
-
} = options;
|
|
41
|
-
if (statusCode === 204) {
|
|
42
|
-
return res.status(204).send();
|
|
43
|
-
}
|
|
44
|
-
const body = {
|
|
45
|
-
success,
|
|
46
|
-
message,
|
|
47
|
-
...data !== void 0 && { data },
|
|
48
|
-
...meta !== void 0 && { meta }
|
|
49
|
-
};
|
|
50
|
-
return res.status(statusCode).json(body);
|
|
51
|
-
}
|
|
52
|
-
static ok(res, data, message = "Success", meta) {
|
|
53
|
-
return _APIResponse.send(res, { statusCode: 200, data, message, meta });
|
|
54
|
-
}
|
|
55
|
-
static created(res, data, message = "Resource created successfully") {
|
|
56
|
-
return _APIResponse.send(res, { statusCode: 201, data, message });
|
|
57
|
-
}
|
|
58
|
-
static noContent(res) {
|
|
59
|
-
return _APIResponse.send(res, { statusCode: 204 });
|
|
60
|
-
}
|
|
61
|
-
static error(res, message = "Internal Server Error", statusCode = 500, meta) {
|
|
62
|
-
return _APIResponse.send(res, {
|
|
63
|
-
success: false,
|
|
64
|
-
statusCode,
|
|
65
|
-
message,
|
|
66
|
-
meta
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
static paginated(res, data, options, message = "Data fetched successfully") {
|
|
70
|
-
return _APIResponse.ok(res, data, message, {
|
|
71
|
-
total: options.total,
|
|
72
|
-
page: options.page,
|
|
73
|
-
limit: options.limit,
|
|
74
|
-
totalPages: Math.ceil(options.total / options.limit)
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// src/api/asyncHandler.ts
|
|
80
|
-
var asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
|
81
|
-
|
|
82
|
-
// src/api/apiFeatures.ts
|
|
83
|
-
var APIFeatures = class {
|
|
84
|
-
query;
|
|
85
|
-
queryString;
|
|
86
|
-
page;
|
|
87
|
-
limit;
|
|
88
|
-
constructor(query, queryString) {
|
|
89
|
-
this.query = query;
|
|
90
|
-
this.queryString = queryString;
|
|
91
|
-
this.page = 1;
|
|
92
|
-
this.limit = 100;
|
|
93
|
-
}
|
|
94
|
-
filter() {
|
|
95
|
-
const queryObj = { ...this.queryString };
|
|
96
|
-
["page", "sort", "limit", "fields"].forEach((el) => delete queryObj[el]);
|
|
97
|
-
let queryStr = JSON.stringify(queryObj);
|
|
98
|
-
queryStr = queryStr.replace(
|
|
99
|
-
/\b(gte|gt|lte|lt|in)\b/g,
|
|
100
|
-
(match) => `$${match}`
|
|
101
|
-
);
|
|
102
|
-
this.query = this.query.find(JSON.parse(queryStr));
|
|
103
|
-
return this;
|
|
104
|
-
}
|
|
105
|
-
sort() {
|
|
106
|
-
if (this.queryString.sort) {
|
|
107
|
-
const sortBy = String(this.queryString.sort).split(",").join(" ");
|
|
108
|
-
this.query = this.query.sort(sortBy);
|
|
109
|
-
} else {
|
|
110
|
-
this.query = this.query.sort("-createdAt");
|
|
111
|
-
}
|
|
112
|
-
return this;
|
|
113
|
-
}
|
|
114
|
-
limitFields() {
|
|
115
|
-
if (this.queryString.fields) {
|
|
116
|
-
const fields = String(this.queryString.fields).split(",").join(" ");
|
|
117
|
-
this.query = this.query.select(fields);
|
|
118
|
-
} else {
|
|
119
|
-
this.query = this.query.select("-__v");
|
|
120
|
-
}
|
|
121
|
-
return this;
|
|
122
|
-
}
|
|
123
|
-
paginate() {
|
|
124
|
-
this.page = Number(this.queryString.page) || 1;
|
|
125
|
-
this.limit = Number(this.queryString.limit) || 100;
|
|
126
|
-
const skip = (this.page - 1) * this.limit;
|
|
127
|
-
this.query = this.query.skip(skip).limit(this.limit);
|
|
128
|
-
return this;
|
|
129
|
-
}
|
|
130
|
-
populate(populateOptions) {
|
|
131
|
-
if (populateOptions) {
|
|
132
|
-
this.query = this.query.populate(populateOptions);
|
|
133
|
-
}
|
|
134
|
-
return this;
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
var apiFeatures_default = APIFeatures;
|
|
138
|
-
|
|
139
|
-
// src/api/apiFactory.ts
|
|
140
|
-
var APIFactory = class {
|
|
141
|
-
static getAll(Model, options = {}) {
|
|
142
|
-
return asyncHandler(async (req, res) => {
|
|
143
|
-
const filter = options.filter ? options.filter(req) : {};
|
|
144
|
-
const features = new apiFeatures_default(Model.find(filter), req.query).filter().sort().limitFields().paginate().populate(options.populate);
|
|
145
|
-
const docs = await features.query;
|
|
146
|
-
APIResponse.ok(
|
|
147
|
-
res,
|
|
148
|
-
docs,
|
|
149
|
-
options.message ?? "Records fetched successfully",
|
|
150
|
-
{
|
|
151
|
-
count: docs.length,
|
|
152
|
-
page: features.page,
|
|
153
|
-
limit: features.limit
|
|
154
|
-
}
|
|
155
|
-
);
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
static getOne(Model, options = {}) {
|
|
159
|
-
return asyncHandler(async (req, res) => {
|
|
160
|
-
let query = Model.findById(req.params.id);
|
|
161
|
-
if (options.populate) {
|
|
162
|
-
query = query.populate(
|
|
163
|
-
options.populate
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
const doc = await query;
|
|
167
|
-
if (!doc) {
|
|
168
|
-
throw new AppError(
|
|
169
|
-
options.notFoundMessage ?? "Resource not found",
|
|
170
|
-
404,
|
|
171
|
-
{ code: "RESOURCE_NOT_FOUND" }
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
APIResponse.ok(res, doc);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
static createOne(Model, options = {}) {
|
|
178
|
-
return asyncHandler(async (req, res) => {
|
|
179
|
-
const doc = await Model.create(req.body);
|
|
180
|
-
APIResponse.created(
|
|
181
|
-
res,
|
|
182
|
-
doc,
|
|
183
|
-
options.message ?? "Resource created successfully"
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
static updateOne(Model, options = {}) {
|
|
188
|
-
return asyncHandler(async (req, res) => {
|
|
189
|
-
const doc = await Model.findByIdAndUpdate(
|
|
190
|
-
req.params.id,
|
|
191
|
-
req.body,
|
|
192
|
-
{
|
|
193
|
-
new: true,
|
|
194
|
-
runValidators: true
|
|
195
|
-
}
|
|
196
|
-
);
|
|
197
|
-
if (!doc) {
|
|
198
|
-
throw new AppError(
|
|
199
|
-
options.notFoundMessage ?? "Resource not found",
|
|
200
|
-
404
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
APIResponse.ok(
|
|
204
|
-
res,
|
|
205
|
-
doc,
|
|
206
|
-
options.message ?? "Resource updated successfully"
|
|
207
|
-
);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
static deleteOne(Model, options = {}) {
|
|
211
|
-
return asyncHandler(async (req, res) => {
|
|
212
|
-
const doc = await Model.findByIdAndDelete(req.params.id);
|
|
213
|
-
if (!doc) {
|
|
214
|
-
throw new AppError(
|
|
215
|
-
options.notFoundMessage ?? "Resource not found",
|
|
216
|
-
404
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
APIResponse.noContent(res);
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// src/api/pickFields.ts
|
|
225
|
-
function pickFields(obj, fields, options = {}) {
|
|
226
|
-
const removeUndefined = options.removeUndefined ?? false;
|
|
227
|
-
const removeNull = options.removeNull ?? false;
|
|
228
|
-
const removeEmptyString = options.removeEmptyString ?? false;
|
|
229
|
-
const strict = options.strict ?? false;
|
|
230
|
-
const defaults = options.defaults ?? {};
|
|
231
|
-
const rename = options.rename ?? {};
|
|
232
|
-
const transform = options.transform ?? {};
|
|
233
|
-
const result = {};
|
|
234
|
-
if (strict) {
|
|
235
|
-
for (const key of Object.keys(obj)) {
|
|
236
|
-
if (!fields.includes(key)) {
|
|
237
|
-
throw new Error(`Unknown field: ${key}`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
for (const field of fields) {
|
|
242
|
-
let value = obj[field];
|
|
243
|
-
if (value === void 0 && field in defaults) {
|
|
244
|
-
value = defaults[field];
|
|
245
|
-
}
|
|
246
|
-
if (removeUndefined && value === void 0) continue;
|
|
247
|
-
if (removeNull && value === null) continue;
|
|
248
|
-
if (removeEmptyString && value === "") continue;
|
|
249
|
-
if (transform[field]) {
|
|
250
|
-
value = transform[field](value);
|
|
251
|
-
}
|
|
252
|
-
const outputKey = rename[field] ?? field;
|
|
253
|
-
if (Object.prototype.hasOwnProperty.call(obj, field) || field in defaults) {
|
|
254
|
-
result[outputKey] = value;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return result;
|
|
258
|
-
}
|
|
13
|
+
// src/configs/env.ts
|
|
259
14
|
|
|
260
15
|
// src/utils/errorToString.ts
|
|
261
16
|
function errorToString(error) {
|
|
@@ -314,25 +69,7 @@ ${errorToString(error)}`);
|
|
|
314
69
|
};
|
|
315
70
|
var logger = new Logger();
|
|
316
71
|
|
|
317
|
-
// src/configs/
|
|
318
|
-
async function connectMongoDB(config) {
|
|
319
|
-
try {
|
|
320
|
-
mongoose.set("strictQuery", true);
|
|
321
|
-
await mongoose.connect(config.uri);
|
|
322
|
-
logger.success("Database connected successfully");
|
|
323
|
-
process.on("SIGINT", async () => {
|
|
324
|
-
await mongoose.connection.close();
|
|
325
|
-
logger.success("Database connection closed");
|
|
326
|
-
process.exit(0);
|
|
327
|
-
});
|
|
328
|
-
} catch (error) {
|
|
329
|
-
logger.danger("Database connection failed");
|
|
330
|
-
if (error instanceof Error) {
|
|
331
|
-
logger.danger(error.message);
|
|
332
|
-
}
|
|
333
|
-
process.exit(1);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
72
|
+
// src/configs/env.ts
|
|
336
73
|
dotenv.config({ quiet: true });
|
|
337
74
|
function loadEnv(options) {
|
|
338
75
|
const result = options.schema.safeParse(process.env);
|
|
@@ -2167,6 +1904,7 @@ var CircularQueue = class {
|
|
|
2167
1904
|
}
|
|
2168
1905
|
this.data = new Array(capacity);
|
|
2169
1906
|
}
|
|
1907
|
+
capacity;
|
|
2170
1908
|
data;
|
|
2171
1909
|
head = 0;
|
|
2172
1910
|
tail = 0;
|
|
@@ -5087,31 +4825,418 @@ var SegmentTree = class {
|
|
|
5087
4825
|
}
|
|
5088
4826
|
};
|
|
5089
4827
|
|
|
5090
|
-
// src/
|
|
4828
|
+
// src/database/DatabaseManager.ts
|
|
4829
|
+
var DatabaseManager = class {
|
|
4830
|
+
constructor(provider, options = {
|
|
4831
|
+
enableShutdownHooks: true
|
|
4832
|
+
}) {
|
|
4833
|
+
this.provider = provider;
|
|
4834
|
+
this.options = options;
|
|
4835
|
+
}
|
|
4836
|
+
provider;
|
|
4837
|
+
options;
|
|
4838
|
+
async connect() {
|
|
4839
|
+
try {
|
|
4840
|
+
await this.provider.connect();
|
|
4841
|
+
logger.success("Database connected successfully");
|
|
4842
|
+
if (this.options.enableShutdownHooks) {
|
|
4843
|
+
this.registerShutdownHooks();
|
|
4844
|
+
}
|
|
4845
|
+
} catch (error) {
|
|
4846
|
+
logger.danger("Database connection failed", error);
|
|
4847
|
+
throw error;
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
async disconnect() {
|
|
4851
|
+
try {
|
|
4852
|
+
await this.provider.disconnect();
|
|
4853
|
+
logger.success("Database connection closed");
|
|
4854
|
+
} catch (error) {
|
|
4855
|
+
logger.danger("Database disconnect failed", error);
|
|
4856
|
+
throw error;
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4859
|
+
isConnected() {
|
|
4860
|
+
return this.provider.isConnected();
|
|
4861
|
+
}
|
|
4862
|
+
getConnectionState() {
|
|
4863
|
+
return this.provider.getConnectionState();
|
|
4864
|
+
}
|
|
4865
|
+
registerShutdownHooks() {
|
|
4866
|
+
process.once("SIGINT", async () => {
|
|
4867
|
+
await this.disconnect();
|
|
4868
|
+
process.exit(0);
|
|
4869
|
+
});
|
|
4870
|
+
process.once("SIGTERM", async () => {
|
|
4871
|
+
await this.disconnect();
|
|
4872
|
+
process.exit(0);
|
|
4873
|
+
});
|
|
4874
|
+
}
|
|
4875
|
+
};
|
|
4876
|
+
var MongoDatabaseProvider = class {
|
|
4877
|
+
constructor(config) {
|
|
4878
|
+
this.config = config;
|
|
4879
|
+
}
|
|
4880
|
+
config;
|
|
4881
|
+
async connect() {
|
|
4882
|
+
mongoose.set("strictQuery", this.config.strictQuery ?? true);
|
|
4883
|
+
await mongoose.connect(this.config.uri);
|
|
4884
|
+
}
|
|
4885
|
+
async disconnect() {
|
|
4886
|
+
await mongoose.disconnect();
|
|
4887
|
+
}
|
|
4888
|
+
isConnected() {
|
|
4889
|
+
return mongoose.connection.readyState === 1;
|
|
4890
|
+
}
|
|
4891
|
+
getConnectionState() {
|
|
4892
|
+
switch (mongoose.connection.readyState) {
|
|
4893
|
+
case 0:
|
|
4894
|
+
return "disconnected";
|
|
4895
|
+
case 1:
|
|
4896
|
+
return "connected";
|
|
4897
|
+
case 2:
|
|
4898
|
+
return "connecting";
|
|
4899
|
+
case 3:
|
|
4900
|
+
return "disconnecting";
|
|
4901
|
+
default:
|
|
4902
|
+
return "unknown";
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
};
|
|
4906
|
+
|
|
4907
|
+
// src/errors/appError.ts
|
|
4908
|
+
var AppError = class extends Error {
|
|
4909
|
+
statusCode;
|
|
4910
|
+
status;
|
|
4911
|
+
isOperational;
|
|
4912
|
+
code;
|
|
4913
|
+
details;
|
|
4914
|
+
constructor(message, statusCode = 500, options) {
|
|
4915
|
+
super(message);
|
|
4916
|
+
this.statusCode = statusCode;
|
|
4917
|
+
this.status = `${statusCode}`.startsWith("4") ? "fail" : "error";
|
|
4918
|
+
this.isOperational = true;
|
|
4919
|
+
this.code = options?.code;
|
|
4920
|
+
this.details = options?.details;
|
|
4921
|
+
Error.captureStackTrace(this, this.constructor);
|
|
4922
|
+
}
|
|
4923
|
+
};
|
|
4924
|
+
|
|
4925
|
+
// src/http/handlers/AsyncHandler.ts
|
|
4926
|
+
var AsyncHandler = (fn) => (req, res, next) => {
|
|
4927
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
4928
|
+
};
|
|
4929
|
+
|
|
4930
|
+
// src/http/query/QueryConstants.ts
|
|
4931
|
+
var QUERY_RESERVED_FIELDS = [
|
|
4932
|
+
"page",
|
|
4933
|
+
"limit",
|
|
4934
|
+
"sort",
|
|
4935
|
+
"fields",
|
|
4936
|
+
"populate"
|
|
4937
|
+
];
|
|
4938
|
+
var DEFAULT_PAGE = 1;
|
|
4939
|
+
var DEFAULT_LIMIT = 100;
|
|
4940
|
+
var DEFAULT_SORT_DIRECTION = "asc";
|
|
4941
|
+
var SUPPORTED_OPERATORS = [
|
|
4942
|
+
"eq",
|
|
4943
|
+
"ne",
|
|
4944
|
+
"gt",
|
|
4945
|
+
"gte",
|
|
4946
|
+
"lt",
|
|
4947
|
+
"lte",
|
|
4948
|
+
"in",
|
|
4949
|
+
"nin",
|
|
4950
|
+
"contains",
|
|
4951
|
+
"startsWith",
|
|
4952
|
+
"endsWith"
|
|
4953
|
+
];
|
|
4954
|
+
|
|
4955
|
+
// src/http/query/ApiFeatures.ts
|
|
4956
|
+
var ApiFeatures = class {
|
|
4957
|
+
static parse(query) {
|
|
4958
|
+
return {
|
|
4959
|
+
filters: this.parseFilters(query),
|
|
4960
|
+
sort: this.parseSort(query),
|
|
4961
|
+
fields: this.parseFields(query),
|
|
4962
|
+
pagination: this.parsePagination(query),
|
|
4963
|
+
populate: this.parsePopulate(query)
|
|
4964
|
+
};
|
|
4965
|
+
}
|
|
4966
|
+
static parseFilters(query) {
|
|
4967
|
+
const filters = [];
|
|
4968
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
4969
|
+
if (QUERY_RESERVED_FIELDS.includes(key)) {
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
const match = key.match(
|
|
4973
|
+
/^(.+)\[(eq|ne|gt|gte|lt|lte|in|nin|contains|startsWith|endsWith)\]$/
|
|
4974
|
+
);
|
|
4975
|
+
if (match) {
|
|
4976
|
+
filters.push({
|
|
4977
|
+
field: match[1],
|
|
4978
|
+
operator: match[2],
|
|
4979
|
+
value
|
|
4980
|
+
});
|
|
4981
|
+
return;
|
|
4982
|
+
}
|
|
4983
|
+
filters.push({
|
|
4984
|
+
field: key,
|
|
4985
|
+
operator: "eq",
|
|
4986
|
+
value
|
|
4987
|
+
});
|
|
4988
|
+
});
|
|
4989
|
+
return filters;
|
|
4990
|
+
}
|
|
4991
|
+
static parseSort(query) {
|
|
4992
|
+
const sort = query.sort?.toString();
|
|
4993
|
+
if (!sort) {
|
|
4994
|
+
return [];
|
|
4995
|
+
}
|
|
4996
|
+
return sort.split(",").filter(Boolean).map((item) => {
|
|
4997
|
+
if (item.startsWith("-")) {
|
|
4998
|
+
return {
|
|
4999
|
+
field: item.substring(1),
|
|
5000
|
+
direction: "desc"
|
|
5001
|
+
};
|
|
5002
|
+
}
|
|
5003
|
+
return {
|
|
5004
|
+
field: item,
|
|
5005
|
+
direction: "asc"
|
|
5006
|
+
};
|
|
5007
|
+
});
|
|
5008
|
+
}
|
|
5009
|
+
static parseFields(query) {
|
|
5010
|
+
const fields = query.fields?.toString();
|
|
5011
|
+
if (!fields) {
|
|
5012
|
+
return [];
|
|
5013
|
+
}
|
|
5014
|
+
return fields.split(",").map((field) => field.trim()).filter(Boolean);
|
|
5015
|
+
}
|
|
5016
|
+
static parsePopulate(query) {
|
|
5017
|
+
const populate = query.populate?.toString();
|
|
5018
|
+
if (!populate) {
|
|
5019
|
+
return [];
|
|
5020
|
+
}
|
|
5021
|
+
return populate.split(",").map((field) => field.trim()).filter(Boolean);
|
|
5022
|
+
}
|
|
5023
|
+
static parsePagination(query) {
|
|
5024
|
+
const page = Number(query.page) || DEFAULT_PAGE;
|
|
5025
|
+
const limit = Number(query.limit) || DEFAULT_LIMIT;
|
|
5026
|
+
return {
|
|
5027
|
+
page,
|
|
5028
|
+
limit
|
|
5029
|
+
};
|
|
5030
|
+
}
|
|
5031
|
+
};
|
|
5032
|
+
|
|
5033
|
+
// src/http/responses/ApiResponse.ts
|
|
5034
|
+
var ApiResponse = class _ApiResponse {
|
|
5035
|
+
static send(res, options) {
|
|
5036
|
+
const {
|
|
5037
|
+
statusCode = 200,
|
|
5038
|
+
success = true,
|
|
5039
|
+
message = "Success",
|
|
5040
|
+
data,
|
|
5041
|
+
meta
|
|
5042
|
+
} = options;
|
|
5043
|
+
if (statusCode === 204) {
|
|
5044
|
+
return res.status(204).send();
|
|
5045
|
+
}
|
|
5046
|
+
const body = {
|
|
5047
|
+
success,
|
|
5048
|
+
message
|
|
5049
|
+
};
|
|
5050
|
+
if (data !== void 0) {
|
|
5051
|
+
body.data = data;
|
|
5052
|
+
}
|
|
5053
|
+
if (meta !== void 0) {
|
|
5054
|
+
body.meta = meta;
|
|
5055
|
+
}
|
|
5056
|
+
return res.status(statusCode).json(body);
|
|
5057
|
+
}
|
|
5058
|
+
static ok(res, data, message = "Success", meta) {
|
|
5059
|
+
return _ApiResponse.send(res, {
|
|
5060
|
+
statusCode: 200,
|
|
5061
|
+
success: true,
|
|
5062
|
+
message,
|
|
5063
|
+
data,
|
|
5064
|
+
meta
|
|
5065
|
+
});
|
|
5066
|
+
}
|
|
5067
|
+
static created(res, data, message = "Resource created successfully") {
|
|
5068
|
+
return _ApiResponse.send(res, {
|
|
5069
|
+
statusCode: 201,
|
|
5070
|
+
success: true,
|
|
5071
|
+
message,
|
|
5072
|
+
data
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
5075
|
+
static accepted(res, data, message = "Request accepted") {
|
|
5076
|
+
return _ApiResponse.send(res, {
|
|
5077
|
+
statusCode: 202,
|
|
5078
|
+
success: true,
|
|
5079
|
+
message,
|
|
5080
|
+
data
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
static noContent(res) {
|
|
5084
|
+
return _ApiResponse.send(res, {
|
|
5085
|
+
statusCode: 204
|
|
5086
|
+
});
|
|
5087
|
+
}
|
|
5088
|
+
static error(res, message = "Internal Server Error", statusCode = 500, meta) {
|
|
5089
|
+
return _ApiResponse.send(res, {
|
|
5090
|
+
success: false,
|
|
5091
|
+
statusCode,
|
|
5092
|
+
message,
|
|
5093
|
+
meta
|
|
5094
|
+
});
|
|
5095
|
+
}
|
|
5096
|
+
static paginated(res, data, options, message = "Data fetched successfully") {
|
|
5097
|
+
const meta = {
|
|
5098
|
+
total: options.total,
|
|
5099
|
+
page: options.page,
|
|
5100
|
+
limit: options.limit,
|
|
5101
|
+
totalPages: Math.ceil(options.total / options.limit)
|
|
5102
|
+
};
|
|
5103
|
+
return _ApiResponse.ok(res, data, message, meta);
|
|
5104
|
+
}
|
|
5105
|
+
};
|
|
5106
|
+
|
|
5107
|
+
// src/http/controllers/CrudControllerFactory.ts
|
|
5108
|
+
var CrudControllerFactory = class {
|
|
5109
|
+
static create(repository, options = {}) {
|
|
5110
|
+
const resourceName = options.resourceName ?? "Resource";
|
|
5111
|
+
return {
|
|
5112
|
+
/**
|
|
5113
|
+
* GET /
|
|
5114
|
+
*/
|
|
5115
|
+
getAll: AsyncHandler(async (req, res) => {
|
|
5116
|
+
const parsed = ApiFeatures.parse(req.query);
|
|
5117
|
+
const baseFilter = options.filter?.(req) ?? {};
|
|
5118
|
+
const filter = {
|
|
5119
|
+
...baseFilter
|
|
5120
|
+
};
|
|
5121
|
+
parsed.filters.forEach((item) => {
|
|
5122
|
+
if (item.operator === "eq") {
|
|
5123
|
+
filter[item.field] = item.value;
|
|
5124
|
+
}
|
|
5125
|
+
});
|
|
5126
|
+
const queryOptions = {
|
|
5127
|
+
limit: parsed.pagination.limit,
|
|
5128
|
+
skip: (parsed.pagination.page - 1) * parsed.pagination.limit,
|
|
5129
|
+
populate: parsed.populate
|
|
5130
|
+
};
|
|
5131
|
+
if (parsed.sort.length > 0) {
|
|
5132
|
+
queryOptions.sort = Object.fromEntries(
|
|
5133
|
+
parsed.sort.map((item) => [
|
|
5134
|
+
item.field,
|
|
5135
|
+
item.direction === "asc" ? 1 : -1
|
|
5136
|
+
])
|
|
5137
|
+
);
|
|
5138
|
+
}
|
|
5139
|
+
if (parsed.fields.length > 0) {
|
|
5140
|
+
queryOptions.select = parsed.fields;
|
|
5141
|
+
}
|
|
5142
|
+
const docs = await repository.findAll(filter, queryOptions);
|
|
5143
|
+
const total = await repository.count(filter);
|
|
5144
|
+
return ApiResponse.paginated(
|
|
5145
|
+
res,
|
|
5146
|
+
docs,
|
|
5147
|
+
{
|
|
5148
|
+
total,
|
|
5149
|
+
page: parsed.pagination.page,
|
|
5150
|
+
limit: parsed.pagination.limit
|
|
5151
|
+
},
|
|
5152
|
+
`${resourceName} list fetched successfully`
|
|
5153
|
+
);
|
|
5154
|
+
}),
|
|
5155
|
+
/**
|
|
5156
|
+
* GET /:id
|
|
5157
|
+
*/
|
|
5158
|
+
getOne: AsyncHandler(async (req, res) => {
|
|
5159
|
+
const id = String(req.params.id);
|
|
5160
|
+
const doc = await repository.findById(id);
|
|
5161
|
+
if (!doc) {
|
|
5162
|
+
throw new AppError(`${resourceName} not found`, 404, {
|
|
5163
|
+
code: "RESOURCE_NOT_FOUND"
|
|
5164
|
+
});
|
|
5165
|
+
}
|
|
5166
|
+
return ApiResponse.ok(res, doc, `${resourceName} fetched successfully`);
|
|
5167
|
+
}),
|
|
5168
|
+
/**
|
|
5169
|
+
* POST /
|
|
5170
|
+
*/
|
|
5171
|
+
create: AsyncHandler(async (req, res) => {
|
|
5172
|
+
const doc = await repository.create(req.body);
|
|
5173
|
+
return ApiResponse.created(
|
|
5174
|
+
res,
|
|
5175
|
+
doc,
|
|
5176
|
+
`${resourceName} created successfully`
|
|
5177
|
+
);
|
|
5178
|
+
}),
|
|
5179
|
+
/**
|
|
5180
|
+
* PATCH /:id
|
|
5181
|
+
*/
|
|
5182
|
+
update: AsyncHandler(async (req, res) => {
|
|
5183
|
+
const id = String(req.params.id);
|
|
5184
|
+
const doc = await repository.updateById(id, req.body);
|
|
5185
|
+
if (!doc) {
|
|
5186
|
+
throw new AppError(`${resourceName} not found`, 404, {
|
|
5187
|
+
code: "RESOURCE_NOT_FOUND"
|
|
5188
|
+
});
|
|
5189
|
+
}
|
|
5190
|
+
return ApiResponse.ok(res, doc, `${resourceName} updated successfully`);
|
|
5191
|
+
}),
|
|
5192
|
+
/**
|
|
5193
|
+
* DELETE /:id
|
|
5194
|
+
*/
|
|
5195
|
+
delete: AsyncHandler(async (req, res) => {
|
|
5196
|
+
const id = String(req.params.id);
|
|
5197
|
+
const doc = await repository.deleteById(id);
|
|
5198
|
+
if (!doc) {
|
|
5199
|
+
throw new AppError(`${resourceName} not found`, 404, {
|
|
5200
|
+
code: "RESOURCE_NOT_FOUND"
|
|
5201
|
+
});
|
|
5202
|
+
}
|
|
5203
|
+
return ApiResponse.noContent(res);
|
|
5204
|
+
})
|
|
5205
|
+
};
|
|
5206
|
+
}
|
|
5207
|
+
};
|
|
5208
|
+
|
|
5209
|
+
// src/http/middleware/AuthMiddleware.ts
|
|
5091
5210
|
var authMiddleware = (_req, _res, next) => {
|
|
5092
5211
|
next();
|
|
5093
5212
|
};
|
|
5094
5213
|
|
|
5095
|
-
// src/
|
|
5096
|
-
var
|
|
5214
|
+
// src/http/middleware/ErrorMiddleware.ts
|
|
5215
|
+
var handleCastError = (err) => new AppError(`Invalid ${err.path}: ${err.value}`, 400, {
|
|
5097
5216
|
code: "INVALID_ID"
|
|
5098
5217
|
});
|
|
5099
|
-
var
|
|
5100
|
-
const
|
|
5101
|
-
return new AppError(
|
|
5218
|
+
var handleDuplicateKey = (err) => {
|
|
5219
|
+
const field = err.keyValue ? Object.keys(err.keyValue)[0] : "field";
|
|
5220
|
+
return new AppError(`${field} already exists`, 400, {
|
|
5102
5221
|
code: "DUPLICATE_FIELD"
|
|
5103
5222
|
});
|
|
5104
5223
|
};
|
|
5105
|
-
var
|
|
5106
|
-
const errors = Object.
|
|
5107
|
-
|
|
5224
|
+
var handleValidationError = (err) => {
|
|
5225
|
+
const errors = Object.entries(err.errors).reduce(
|
|
5226
|
+
(acc, [key, value]) => {
|
|
5227
|
+
acc[key] = value.message;
|
|
5228
|
+
return acc;
|
|
5229
|
+
},
|
|
5230
|
+
{}
|
|
5231
|
+
);
|
|
5232
|
+
return new AppError("Validation failed", 400, {
|
|
5108
5233
|
code: "VALIDATION_ERROR",
|
|
5109
5234
|
details: errors
|
|
5110
5235
|
});
|
|
5111
5236
|
};
|
|
5112
|
-
var handleJWTError = () => new AppError("Invalid token
|
|
5113
|
-
var handleJWTExpiredError = () => new AppError("
|
|
5114
|
-
var
|
|
5237
|
+
var handleJWTError = () => new AppError("Invalid token", 401);
|
|
5238
|
+
var handleJWTExpiredError = () => new AppError("Token expired", 401);
|
|
5239
|
+
var ErrorMiddleware = (isProduction = false) => (err, _req, res, _next) => {
|
|
5115
5240
|
let error;
|
|
5116
5241
|
if (err instanceof AppError) {
|
|
5117
5242
|
error = err;
|
|
@@ -5123,15 +5248,15 @@ var GlobalErrorHandler = (isProd = false) => (err, _req, res, _next) => {
|
|
|
5123
5248
|
error.isOperational = false;
|
|
5124
5249
|
}
|
|
5125
5250
|
if (err && err.name === "CastError") {
|
|
5126
|
-
error =
|
|
5251
|
+
error = handleCastError(err);
|
|
5127
5252
|
}
|
|
5128
5253
|
if (err && typeof err === "object" && err.code === 11e3) {
|
|
5129
|
-
error =
|
|
5254
|
+
error = handleDuplicateKey(
|
|
5130
5255
|
err
|
|
5131
5256
|
);
|
|
5132
5257
|
}
|
|
5133
5258
|
if (err && err.name === "ValidationError") {
|
|
5134
|
-
error =
|
|
5259
|
+
error = handleValidationError(err);
|
|
5135
5260
|
}
|
|
5136
5261
|
if (err?.name === "JsonWebTokenError") {
|
|
5137
5262
|
error = handleJWTError();
|
|
@@ -5142,37 +5267,157 @@ var GlobalErrorHandler = (isProd = false) => (err, _req, res, _next) => {
|
|
|
5142
5267
|
if (!error.isOperational) {
|
|
5143
5268
|
logger.danger("Programming error", err);
|
|
5144
5269
|
} else {
|
|
5145
|
-
logger.danger("Operational error", {
|
|
5270
|
+
logger.danger("Operational error", {
|
|
5271
|
+
message: error.message
|
|
5272
|
+
});
|
|
5146
5273
|
}
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5274
|
+
const response = {
|
|
5275
|
+
success: false,
|
|
5276
|
+
message: error.isOperational ? error.message : "Something went wrong",
|
|
5277
|
+
...error.details && {
|
|
5278
|
+
meta: {
|
|
5279
|
+
errors: error.details
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
};
|
|
5283
|
+
if (!isProduction) {
|
|
5284
|
+
return ApiResponse.send(res, {
|
|
5150
5285
|
statusCode: error.statusCode,
|
|
5151
|
-
|
|
5286
|
+
...response,
|
|
5152
5287
|
data: {
|
|
5153
|
-
stack: err?.stack
|
|
5154
|
-
details: error.details
|
|
5288
|
+
stack: err?.stack
|
|
5155
5289
|
}
|
|
5156
5290
|
});
|
|
5157
5291
|
}
|
|
5158
|
-
return
|
|
5159
|
-
success: false,
|
|
5292
|
+
return ApiResponse.send(res, {
|
|
5160
5293
|
statusCode: error.statusCode,
|
|
5161
|
-
|
|
5294
|
+
...response
|
|
5162
5295
|
});
|
|
5163
5296
|
};
|
|
5164
5297
|
|
|
5165
|
-
// src/
|
|
5298
|
+
// src/http/validation/Validator.ts
|
|
5299
|
+
var isJoiSchema = (schema) => {
|
|
5300
|
+
return typeof schema === "object" && schema !== null && "validate" in schema;
|
|
5301
|
+
};
|
|
5302
|
+
var isZodSchema = (schema) => {
|
|
5303
|
+
return typeof schema === "object" && schema !== null && "safeParse" in schema;
|
|
5304
|
+
};
|
|
5305
|
+
|
|
5306
|
+
// src/http/validation/JoiValidator.ts
|
|
5307
|
+
var JoiValidator = class {
|
|
5308
|
+
constructor(schema) {
|
|
5309
|
+
this.schema = schema;
|
|
5310
|
+
}
|
|
5311
|
+
schema;
|
|
5312
|
+
validate(data) {
|
|
5313
|
+
const result = this.schema.validate(data, {
|
|
5314
|
+
abortEarly: false,
|
|
5315
|
+
stripUnknown: true
|
|
5316
|
+
});
|
|
5317
|
+
if (result.error) {
|
|
5318
|
+
const errors = {};
|
|
5319
|
+
result.error.details.forEach((detail) => {
|
|
5320
|
+
const field = detail.path.join(".");
|
|
5321
|
+
errors[field] = detail.message;
|
|
5322
|
+
});
|
|
5323
|
+
return {
|
|
5324
|
+
success: false,
|
|
5325
|
+
errors
|
|
5326
|
+
};
|
|
5327
|
+
}
|
|
5328
|
+
return {
|
|
5329
|
+
success: true,
|
|
5330
|
+
data: result.value
|
|
5331
|
+
};
|
|
5332
|
+
}
|
|
5333
|
+
};
|
|
5334
|
+
|
|
5335
|
+
// src/http/validation/ZodValidator.ts
|
|
5336
|
+
var ZodValidator = class {
|
|
5337
|
+
constructor(schema) {
|
|
5338
|
+
this.schema = schema;
|
|
5339
|
+
}
|
|
5340
|
+
schema;
|
|
5341
|
+
validate(data) {
|
|
5342
|
+
const result = this.schema.safeParse(data);
|
|
5343
|
+
if (!result.success) {
|
|
5344
|
+
const errors = {};
|
|
5345
|
+
result.error.issues.forEach((issue) => {
|
|
5346
|
+
const field = issue.path.join(".");
|
|
5347
|
+
errors[field] = issue.message;
|
|
5348
|
+
});
|
|
5349
|
+
return {
|
|
5350
|
+
success: false,
|
|
5351
|
+
errors
|
|
5352
|
+
};
|
|
5353
|
+
}
|
|
5354
|
+
return {
|
|
5355
|
+
success: true,
|
|
5356
|
+
data: result.data
|
|
5357
|
+
};
|
|
5358
|
+
}
|
|
5359
|
+
};
|
|
5360
|
+
|
|
5361
|
+
// src/http/middleware/ValidationMiddleware.ts
|
|
5166
5362
|
var validate = (schema) => (req, _res, next) => {
|
|
5167
|
-
|
|
5168
|
-
if (
|
|
5169
|
-
|
|
5363
|
+
let result;
|
|
5364
|
+
if (isJoiSchema(schema)) {
|
|
5365
|
+
result = new JoiValidator(schema).validate(req.body);
|
|
5366
|
+
} else if (isZodSchema(schema)) {
|
|
5367
|
+
result = new ZodValidator(schema).validate(req.body);
|
|
5368
|
+
} else {
|
|
5369
|
+
return next(new AppError("Unsupported validation schema", 500));
|
|
5370
|
+
}
|
|
5371
|
+
if (!result.success) {
|
|
5372
|
+
return next(
|
|
5373
|
+
new AppError("Validation failed", 400, {
|
|
5374
|
+
code: "VALIDATION_ERROR",
|
|
5375
|
+
details: result.errors
|
|
5376
|
+
})
|
|
5377
|
+
);
|
|
5170
5378
|
}
|
|
5379
|
+
req.body = result.data;
|
|
5171
5380
|
next();
|
|
5172
5381
|
};
|
|
5173
5382
|
|
|
5174
|
-
// src/
|
|
5175
|
-
|
|
5383
|
+
// src/http/utils/PickFields.ts
|
|
5384
|
+
function pickFields(obj, fields, options = {}) {
|
|
5385
|
+
const removeUndefined = options.removeUndefined ?? false;
|
|
5386
|
+
const removeNull = options.removeNull ?? false;
|
|
5387
|
+
const removeEmptyString = options.removeEmptyString ?? false;
|
|
5388
|
+
const strict = options.strict ?? false;
|
|
5389
|
+
const defaults = options.defaults ?? {};
|
|
5390
|
+
const rename = options.rename ?? {};
|
|
5391
|
+
const transform = options.transform ?? {};
|
|
5392
|
+
const result = {};
|
|
5393
|
+
if (strict) {
|
|
5394
|
+
for (const key of Object.keys(obj)) {
|
|
5395
|
+
if (!fields.includes(key)) {
|
|
5396
|
+
throw new Error(`Unknown field: ${key}`);
|
|
5397
|
+
}
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
for (const field of fields) {
|
|
5401
|
+
let value = obj[field];
|
|
5402
|
+
if (value === void 0 && field in defaults) {
|
|
5403
|
+
value = defaults[field];
|
|
5404
|
+
}
|
|
5405
|
+
if (removeUndefined && value === void 0) continue;
|
|
5406
|
+
if (removeNull && value === null) continue;
|
|
5407
|
+
if (removeEmptyString && value === "") continue;
|
|
5408
|
+
if (transform[field]) {
|
|
5409
|
+
value = transform[field](value);
|
|
5410
|
+
}
|
|
5411
|
+
const outputKey = rename[field] ?? field;
|
|
5412
|
+
if (Object.prototype.hasOwnProperty.call(obj, field) || field in defaults) {
|
|
5413
|
+
result[outputKey] = value;
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
return result;
|
|
5417
|
+
}
|
|
5418
|
+
|
|
5419
|
+
// src/repositories/providers/MongoRepository.ts
|
|
5420
|
+
var MongoRepository = class {
|
|
5176
5421
|
model;
|
|
5177
5422
|
constructor(model) {
|
|
5178
5423
|
this.model = model;
|
|
@@ -5726,6 +5971,6 @@ function sleep(ms) {
|
|
|
5726
5971
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5727
5972
|
}
|
|
5728
5973
|
|
|
5729
|
-
export {
|
|
5974
|
+
export { AVLTree, AdjacencyList, AdjacencyMatrix, ApiFeatures, ApiResponse, AppError, AsyncHandler, AzureBlobStorageService, BPlusTree, BTree, BinaryHeap, BinarySearchTree, BinaryTree, BloomFilter, CircularArray, CircularLinkedList, CircularQueue, CloudinaryService, ConsistentHash, CountMinSketch, CrudControllerFactory, DEFAULT_LIMIT, DEFAULT_PAGE, DEFAULT_SORT_DIRECTION, DatabaseManager, Deque, DirectedGraph, DisjointSetUnion, DoublyLinkedList, DynamicArray, EmailService, ErrorMiddleware, FenwickTree, FibNode, FibonacciHeap, Graph, HashMap, HashSet, HyperLogLog, IntervalTree, JWTService, JoiValidator, KDTree, LFUCache, LRUCache, MaxHeap, MaxStack, MinHeap, MinStack, MongoDatabaseProvider, MongoRepository, MulterFileHandlerService, MultiSet, Node, OrderedSet, PairingHeap, PairingNode, PriorityQueue, QUERY_RESERVED_FIELDS, QuadTree, Queue, RadixTree, RedBlackTree, SMTPProvider, SUPPORTED_OPERATORS, SegmentTree, Set2 as Set, SinglyLinkedList, SparseTable, SplayTree, Stack, StaticArray, SuffixArray, SuffixTree, TemplateEngine, TernarySearchTree, TreeNode, Trie, ZodValidator, authMiddleware, createAllowedOrigins, createCorsOptions, days, errorToString, hours, isJoiSchema, isZodSchema, loadEnv, logger, milliseconds, minutes, pickFields, seconds, sleep, toHours, toMinutes, toSeconds, validate };
|
|
5730
5975
|
//# sourceMappingURL=index.js.map
|
|
5731
5976
|
//# sourceMappingURL=index.js.map
|