@starklabs/backend-core 1.1.1 → 1.2.1
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/.env.example +21 -0
- package/dist/js/config/cloudinary.js +18 -0
- package/dist/js/config/config.js +11 -0
- package/dist/js/config/duration.js +22 -0
- package/dist/js/core/app.js +122 -0
- package/dist/js/core/auth/OTP.js +115 -0
- package/dist/js/core/auth/auth.controller.js +63 -0
- package/dist/js/core/auth/auth.service.js +290 -0
- package/dist/js/core/auth/auth.validation.js +95 -0
- package/dist/js/core/crud/crud.controller.js +95 -0
- package/dist/js/core/crud/crud.service.js +296 -0
- package/dist/js/core/index.js +3 -0
- package/dist/js/index.js +44 -55
- package/dist/js/lib/db.js +40 -0
- package/dist/js/lib/field.types.js +174 -0
- package/dist/js/lib/model.factory.js +19 -0
- package/dist/js/lib/model.registry.js +4 -0
- package/dist/js/lib/schema.builder.js +35 -0
- package/dist/js/lib/zod.validations.js +247 -0
- package/dist/js/middleware/auth.middleware.js +51 -0
- package/dist/js/middleware/error.middleware.js +28 -0
- package/dist/js/middleware/socket.middleware.js +29 -0
- package/dist/js/utils/AppLog.js +2 -1
- package/dist/js/utils/deleteFile.js +22 -0
- package/dist/js/utils/index.js +10 -1
- package/dist/js/utils/jwt.js +12 -20
- package/dist/js/utils/libsodium.js +19 -3
- package/dist/js/utils/rateLimiter.js +25 -0
- package/dist/js/utils/uploadFile.js +43 -0
- package/handlerMap.js +33 -0
- package/package.json +17 -4
- package/dist/cjs/db.cjs +0 -17
- package/dist/cjs/index.cjs +0 -59
- package/dist/cjs/utils/AppError.cjs +0 -13
- package/dist/cjs/utils/AppLog.cjs +0 -13
- package/dist/cjs/utils/asyncHandler.cjs +0 -6
- package/dist/cjs/utils/jwt.cjs +0 -38
- package/dist/cjs/utils/libsodium.cjs +0 -145
- package/dist/cjs/utils/successResponse.cjs +0 -13
- package/dist/js/db.js +0 -19
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { userIdSchema } from "../../utils/userIdValidation.js";
|
|
3
|
+
|
|
4
|
+
const emailSchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.trim()
|
|
7
|
+
.min(1, "Email is required")
|
|
8
|
+
.email("Invalid email");
|
|
9
|
+
|
|
10
|
+
const passwordSchema = z
|
|
11
|
+
.string()
|
|
12
|
+
.min(12, "Password must be at least 12 characters");
|
|
13
|
+
|
|
14
|
+
const providerSchema = z.string().min(1, "Provider is required");
|
|
15
|
+
|
|
16
|
+
const nameSchema = (field) =>
|
|
17
|
+
z
|
|
18
|
+
.string()
|
|
19
|
+
.trim()
|
|
20
|
+
.min(1, `${field} is required`)
|
|
21
|
+
.min(3, `${field} must be at least 3 characters`)
|
|
22
|
+
.max(50, `${field} too long`);
|
|
23
|
+
|
|
24
|
+
// signup schema
|
|
25
|
+
const signupSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
firstName: nameSchema("FirstName"),
|
|
28
|
+
|
|
29
|
+
lastName: nameSchema("LastName"),
|
|
30
|
+
|
|
31
|
+
email: emailSchema,
|
|
32
|
+
|
|
33
|
+
password: passwordSchema,
|
|
34
|
+
|
|
35
|
+
role: z.string().default("user"),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
|
|
39
|
+
// 2fa schema
|
|
40
|
+
const twoFactorAuthSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
email: emailSchema,
|
|
43
|
+
|
|
44
|
+
otp: z.string().length(6, "OTP must be exactly 6 characters"),
|
|
45
|
+
})
|
|
46
|
+
.strict();
|
|
47
|
+
|
|
48
|
+
// resend OTP schema
|
|
49
|
+
const resendOTPSchema = z
|
|
50
|
+
.object({
|
|
51
|
+
email: emailSchema,
|
|
52
|
+
})
|
|
53
|
+
.strict();
|
|
54
|
+
|
|
55
|
+
// login schema
|
|
56
|
+
const loginSchema = z
|
|
57
|
+
.object({
|
|
58
|
+
email: emailSchema,
|
|
59
|
+
password: passwordSchema,
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
62
|
+
|
|
63
|
+
// forgot password schema
|
|
64
|
+
const forgotPasswordSchema = z
|
|
65
|
+
.object({
|
|
66
|
+
email: emailSchema,
|
|
67
|
+
})
|
|
68
|
+
.strict();
|
|
69
|
+
|
|
70
|
+
// reset password schema
|
|
71
|
+
const resetPasswordSchema = z
|
|
72
|
+
.object({
|
|
73
|
+
email: emailSchema,
|
|
74
|
+
password: passwordSchema,
|
|
75
|
+
})
|
|
76
|
+
.strict();
|
|
77
|
+
|
|
78
|
+
// logout schema
|
|
79
|
+
const logoutSchema = z
|
|
80
|
+
.object({
|
|
81
|
+
email: emailSchema,
|
|
82
|
+
provider: providerSchema,
|
|
83
|
+
userId: userIdSchema,
|
|
84
|
+
})
|
|
85
|
+
.strict();
|
|
86
|
+
|
|
87
|
+
export default {
|
|
88
|
+
loginSchema,
|
|
89
|
+
signupSchema,
|
|
90
|
+
twoFactorAuthSchema,
|
|
91
|
+
resendOTPSchema,
|
|
92
|
+
forgotPasswordSchema,
|
|
93
|
+
resetPasswordSchema,
|
|
94
|
+
logoutSchema,
|
|
95
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { app } from "../app.js";
|
|
2
|
+
import mongoose, { model } from "mongoose";
|
|
3
|
+
import asyncHandler from "../../utils/asyncHandler.js";
|
|
4
|
+
import crudService from "./crud.service.js";
|
|
5
|
+
import registerModel from "../../lib/model.registry.js";
|
|
6
|
+
import AppError from "../../utils/AppError.js";
|
|
7
|
+
import zodValidations from "../../lib/zod.validations.js";
|
|
8
|
+
import z from "zod";
|
|
9
|
+
import protect from "../../middleware/auth.middleware.js";
|
|
10
|
+
|
|
11
|
+
const validateCookie = async (validations, user, isValidCookie) => {
|
|
12
|
+
const zodCookieObj = z.object(validations.cookieValidation);
|
|
13
|
+
isValidCookie = await zodCookieObj.safeParse(user);
|
|
14
|
+
|
|
15
|
+
if (!isValidCookie.success) {
|
|
16
|
+
const issue = isValidCookie.error.issues[0];
|
|
17
|
+
if (issue.code === "invalid_type")
|
|
18
|
+
throw new AppError(`${issue.path.join(".")} is required`);
|
|
19
|
+
|
|
20
|
+
throw new AppError(issue.message, 409);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return isValidCookie;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const crud = (route, routes, modelName, validations, apiVersion) => {
|
|
27
|
+
routes.forEach((el) => {
|
|
28
|
+
const middlewares = [];
|
|
29
|
+
if (el.method !== "get" || el.path !== "/") middlewares.push(protect);
|
|
30
|
+
if (el.middleware) middlewares.push(el.middleware);
|
|
31
|
+
if (el.middlewares) middlewares.push(...el.middlewares);
|
|
32
|
+
|
|
33
|
+
app[el.method](
|
|
34
|
+
`/api/v${apiVersion}/${route}${el.path}`,
|
|
35
|
+
...middlewares,
|
|
36
|
+
asyncHandler(async (req, res) => {
|
|
37
|
+
const Model = registerModel[modelName];
|
|
38
|
+
if (!Model && el.modelName)
|
|
39
|
+
throw new Error(`Model not found for endpoint: ${el.path}`);
|
|
40
|
+
|
|
41
|
+
// if not getAll and cookie - validation
|
|
42
|
+
let isValidCookie = undefined;
|
|
43
|
+
if (el.method !== "get" || el.path !== "/") {
|
|
44
|
+
isValidCookie = await validateCookie(
|
|
45
|
+
validations,
|
|
46
|
+
req.user,
|
|
47
|
+
isValidCookie,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// req.body - validation
|
|
52
|
+
const payload = {
|
|
53
|
+
...req.body,
|
|
54
|
+
image: req.file,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const validationObj =
|
|
58
|
+
validations && validations[el.handler]
|
|
59
|
+
? z.object(validations[el.handler])
|
|
60
|
+
: z.object({});
|
|
61
|
+
|
|
62
|
+
const zodBodyObj = validationObj;
|
|
63
|
+
|
|
64
|
+
const isValidBody = zodBodyObj.safeParse(payload || {});
|
|
65
|
+
|
|
66
|
+
if (!isValidBody.success) {
|
|
67
|
+
const issue = isValidBody.error.issues[0];
|
|
68
|
+
if (issue.code === "invalid_type")
|
|
69
|
+
throw new AppError(`${issue.path.join(".")} is required`);
|
|
70
|
+
|
|
71
|
+
throw new AppError(issue.message, 409);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// send data to service
|
|
75
|
+
const result = await crudService[el.handler]({
|
|
76
|
+
Model,
|
|
77
|
+
modelName,
|
|
78
|
+
body: isValidBody?.data,
|
|
79
|
+
userData: isValidCookie?.data,
|
|
80
|
+
id: req?.params?.id,
|
|
81
|
+
fileType: el?.fileType,
|
|
82
|
+
metaDataName: el?.metaDataName,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return res.json({
|
|
86
|
+
success: true,
|
|
87
|
+
data: result?.data,
|
|
88
|
+
message: result?.msg,
|
|
89
|
+
});
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default crud;
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { getConfig } from "../../config/config.js";
|
|
2
|
+
import { hash } from "../../utils/libsodium.js";
|
|
3
|
+
import { AppError, uploadFile, deleteFile } from "../../utils/index.js";
|
|
4
|
+
|
|
5
|
+
const sanitize = (doc) => {
|
|
6
|
+
if (!doc) return doc;
|
|
7
|
+
const { __v, password, ...clean } = doc;
|
|
8
|
+
return clean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const authorizeAccess = ({ ownerEmail, dataEmail, role }) => {
|
|
12
|
+
const { internalRoles } = getConfig();
|
|
13
|
+
|
|
14
|
+
if (!internalRoles?.length)
|
|
15
|
+
throw new AppError("internalRoles is missing in StarkCore({})");
|
|
16
|
+
|
|
17
|
+
const isInternalRuler = internalRoles.includes(role);
|
|
18
|
+
const isOwner = ownerEmail === dataEmail;
|
|
19
|
+
|
|
20
|
+
if (!isInternalRuler && !isOwner) throw new AppError("Unauthorized", 401);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const assertInternalRole = (role) => {
|
|
24
|
+
const { internalRoles } = getConfig();
|
|
25
|
+
|
|
26
|
+
const isInternalRuler = internalRoles.includes(role);
|
|
27
|
+
if (!isInternalRuler) throw new AppError("Unauthorized", 401);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const showHealth = () => {
|
|
31
|
+
return {
|
|
32
|
+
msg: "All clear!",
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getAll = async ({ Model, modelName }) => {
|
|
37
|
+
const data = await Model.find().lean();
|
|
38
|
+
|
|
39
|
+
if (!data.length) {
|
|
40
|
+
throw new AppError(`${modelName} not found`, 404);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
data: data.map(sanitize),
|
|
45
|
+
msg: "Fetched all successfully",
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getById = async ({ Model, modelName, id, userData, body }) => {
|
|
50
|
+
const data = await Model.findById(id).lean();
|
|
51
|
+
|
|
52
|
+
if (!data) {
|
|
53
|
+
throw new AppError(`${modelName} with id ${id} not found`, 404);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
authorizeAccess({
|
|
57
|
+
ownerEmail: userData.email,
|
|
58
|
+
dataEmail: data.email,
|
|
59
|
+
role: userData.role,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
data: sanitize(data),
|
|
64
|
+
msg: "Fetched successfully",
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const create = async ({ Model, modelName, userData, body }) => {
|
|
69
|
+
assertInternalRole(userData.role);
|
|
70
|
+
|
|
71
|
+
const { cloudinaryFolderName } = getConfig();
|
|
72
|
+
|
|
73
|
+
// if image
|
|
74
|
+
let imageMetaData = undefined;
|
|
75
|
+
if (body.image) {
|
|
76
|
+
imageMetaData = await uploadFile(body.image, cloudinaryFolderName);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// if user
|
|
80
|
+
if (body.email) {
|
|
81
|
+
const exists = await Model.findOne({ email: body.email });
|
|
82
|
+
if (exists) {
|
|
83
|
+
throw new AppError(`${modelName} with this email already exists`, 409);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (body.password) body.password = await hash(body.password);
|
|
88
|
+
|
|
89
|
+
const newItem = await Model.create({
|
|
90
|
+
...body,
|
|
91
|
+
role: "user",
|
|
92
|
+
imageMetaData,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
data: sanitize(newItem.toObject()),
|
|
97
|
+
msg: "Created successfully",
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const update = async ({ Model, modelName, id, body, userData }) => {
|
|
102
|
+
assertInternalRole(userData.role);
|
|
103
|
+
|
|
104
|
+
if (body.password) {
|
|
105
|
+
body.password = await hash(body.password);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const existing = await Model.findById(id);
|
|
109
|
+
|
|
110
|
+
if (!existing) {
|
|
111
|
+
throw new AppError(`${modelName} not found for update`, 404);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { cloudinaryFolderName } = getConfig();
|
|
115
|
+
|
|
116
|
+
let imageMetaData;
|
|
117
|
+
if (body.image) {
|
|
118
|
+
imageMetaData = await uploadFile(body.image, cloudinaryFolderName);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const replacementDoc = {
|
|
123
|
+
...body,
|
|
124
|
+
...(imageMetaData && { imageMetaData }),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Prevent storing raw file object/base64/etc.
|
|
128
|
+
delete replacementDoc.image;
|
|
129
|
+
|
|
130
|
+
const updated = await Model.findOneAndReplace({ _id: id }, replacementDoc, {
|
|
131
|
+
returnDocument: "after",
|
|
132
|
+
}).lean();
|
|
133
|
+
|
|
134
|
+
// Delete old image after successful replacement
|
|
135
|
+
if (imageMetaData && existing.imageMetaData?.public_id) {
|
|
136
|
+
await deleteFile(existing.imageMetaData.public_id);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
data: sanitize(updated),
|
|
141
|
+
msg: "Updated successfully",
|
|
142
|
+
};
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Rollback newly uploaded image
|
|
145
|
+
if (imageMetaData?.public_id) {
|
|
146
|
+
await deleteFile(imageMetaData.public_id).catch(() => {});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (error.message.includes("duplicate key error")) {
|
|
150
|
+
throw new AppError(`${modelName} with these detail already exists`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const patch = async ({ Model, modelName, id, body, userData }) => {
|
|
158
|
+
assertInternalRole(userData.role);
|
|
159
|
+
|
|
160
|
+
if (body.password) {
|
|
161
|
+
body.password = await hash(body.password);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const existing = await Model.findById(id);
|
|
165
|
+
|
|
166
|
+
if (!existing) {
|
|
167
|
+
throw new AppError(`${modelName} not found for patch`, 404);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { cloudinaryFolderName } = getConfig();
|
|
171
|
+
|
|
172
|
+
let imageMetaData;
|
|
173
|
+
if (body.image) {
|
|
174
|
+
// Upload new image first
|
|
175
|
+
imageMetaData = await uploadFile(body.image, cloudinaryFolderName);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const updated = await Model.findByIdAndUpdate(
|
|
180
|
+
id,
|
|
181
|
+
{
|
|
182
|
+
$set: {
|
|
183
|
+
...body,
|
|
184
|
+
...(imageMetaData && { imageMetaData }),
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{ returnDocument: "after" },
|
|
188
|
+
).lean();
|
|
189
|
+
|
|
190
|
+
// Delete old image only after successful DB update
|
|
191
|
+
if (imageMetaData && existing.imageMetaData?.public_id) {
|
|
192
|
+
await deleteFile(existing.imageMetaData.public_id);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
data: sanitize(updated),
|
|
197
|
+
msg: "Updated successfully!",
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// Rollback newly uploaded image if DB update fails
|
|
201
|
+
if (imageMetaData?.public_id) {
|
|
202
|
+
await deleteFile(imageMetaData.public_id).catch(() => {});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (error.message.includes("duplicate key error")) {
|
|
206
|
+
throw new AppError(`${modelName} with these detail already exists`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const remove = async ({ Model, modelName, id, userData }) => {
|
|
214
|
+
assertInternalRole(userData.role);
|
|
215
|
+
|
|
216
|
+
const deleted = await Model.findByIdAndDelete(id).lean();
|
|
217
|
+
|
|
218
|
+
if (!deleted) {
|
|
219
|
+
throw new AppError(`${modelName} not found for delete`, 404);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
data: sanitize(deleted),
|
|
224
|
+
msg: "Deleted successfully",
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const removeFile = async ({
|
|
229
|
+
Model,
|
|
230
|
+
modelName,
|
|
231
|
+
id,
|
|
232
|
+
userData,
|
|
233
|
+
fileType,
|
|
234
|
+
metaDataName,
|
|
235
|
+
}) => {
|
|
236
|
+
if (!fileType)
|
|
237
|
+
throw new AppError(
|
|
238
|
+
`fileType is required in ${modelName.toLowerCase()} collection`,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (!metaDataName)
|
|
242
|
+
throw new AppError(
|
|
243
|
+
`metaDataName is required in ${modelName.toLowerCase()} collection`,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
assertInternalRole(userData.role);
|
|
247
|
+
|
|
248
|
+
const item = await Model.findById(id);
|
|
249
|
+
if (!item) throw new AppError(`${modelName} not found to delete file`);
|
|
250
|
+
|
|
251
|
+
if (Object.keys(item[metaDataName]).length === 0)
|
|
252
|
+
throw new AppError(`${fileType} not found to delete`);
|
|
253
|
+
|
|
254
|
+
if (Object.keys(item[metaDataName].toObject()).length === 0)
|
|
255
|
+
throw new AppError(`${fileType} not found to delete`);
|
|
256
|
+
|
|
257
|
+
const deleted = await deleteFile(item[metaDataName].public_id, fileType);
|
|
258
|
+
|
|
259
|
+
item[metaDataName] = undefined;
|
|
260
|
+
|
|
261
|
+
item.save();
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
data: item,
|
|
265
|
+
msg: "File deleted successfully",
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const removeAll = async ({ Model, modelName, id, userData }) => {
|
|
270
|
+
assertInternalRole(userData.role);
|
|
271
|
+
|
|
272
|
+
const deleted = await Model.deleteMany();
|
|
273
|
+
|
|
274
|
+
if (!deleted) {
|
|
275
|
+
throw new AppError(`${modelName} not found for delete`, 404);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
data: sanitize(deleted),
|
|
280
|
+
msg: "Deleted all successfully",
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// by deleting all, also delete the images from cloudinary 1/1
|
|
285
|
+
|
|
286
|
+
export default {
|
|
287
|
+
showHealth,
|
|
288
|
+
getAll,
|
|
289
|
+
getById,
|
|
290
|
+
create,
|
|
291
|
+
update,
|
|
292
|
+
patch,
|
|
293
|
+
remove,
|
|
294
|
+
removeFile,
|
|
295
|
+
removeAll,
|
|
296
|
+
};
|
package/dist/js/index.js
CHANGED
|
@@ -1,66 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { setConfig } from "./config/config.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} StarkCoreConfig
|
|
5
|
+
* @property {number} port - *Server listening port number*
|
|
6
|
+
* @property {Array<object>} collections - *Routes detail*
|
|
7
|
+
* @property {string} apiVersion - *Current API version*
|
|
8
|
+
* @property {string} jwtSecret - *Used for JSON Web Token functionalities*
|
|
9
|
+
* @property {boolean} isOffline - *Connects to local MongoDB instance*
|
|
10
|
+
* @property {string} masterKey - *Used for encryption/decryption operations*
|
|
11
|
+
* @property {string} ENV - *Current application environment (e.g. development, production)*
|
|
12
|
+
* @property {string} tokenExpiry - *JWT/token expiration duration*
|
|
13
|
+
* @property {Array<string>} internalRoles - *List of internal roles with elevated permissions*
|
|
14
|
+
* @property {string} resendAPIKey - *API key used for Resend email services*
|
|
15
|
+
* @property {string|number} rateLimitDuration - *Duration for rate limiting window*
|
|
16
|
+
* @property {number} maxReqLimit - *Maximum allowed requests within rate limit duration*
|
|
17
|
+
* @property {string} rateLimitMsg - *Message returned when rate limit is exceeded*
|
|
18
|
+
* @property {string} cloudinaryAPIKey - *Cloudinary API key*
|
|
19
|
+
* @property {string} cloudinaryCloudName - *Cloudinary cloud name*
|
|
20
|
+
* @property {string} cloudinaryAPISecret - *Cloudinary API secret*
|
|
21
|
+
* @property {string} cloudinaryFolderName - *Default Cloudinary folder for uploads*
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
class StarkCore {
|
|
25
|
+
/**
|
|
26
|
+
* @param {StarkCoreConfig} config
|
|
27
|
+
*/
|
|
3
28
|
|
|
4
|
-
// seal/unSeal - encrypt and decrypt data
|
|
5
|
-
import { seal, unSeal } from "./utils/libsodium.js";
|
|
6
|
-
import { signJWT, verifyJWT } from "./utils/jwt.js";
|
|
7
|
-
|
|
8
|
-
class StarkAuth {
|
|
9
|
-
#_masterKey;
|
|
10
|
-
#_JWT_SECRET;
|
|
11
|
-
#_JWT_EXPIRY;
|
|
12
|
-
|
|
13
|
-
// constructor - store keys etc.
|
|
14
29
|
constructor(config) {
|
|
15
|
-
|
|
16
|
-
this.#_JWT_SECRET = config.JWT_SECRET;
|
|
17
|
-
this.#_JWT_EXPIRY = config.JWT_EXPIRY;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// create - initializes DB and auth instance
|
|
21
|
-
static async create(config) {
|
|
22
|
-
await connectDB(config.MONGODB_URI, config.DB_NAME);
|
|
23
|
-
return new StarkAuth(config);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// encrypt - encrypts string using master key
|
|
27
|
-
async encrypt(str) {
|
|
28
|
-
return seal(str, this.#_masterKey);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// decrypt - decrypts string using master key
|
|
32
|
-
async decrypt(str, nonce, publicKey, securedPrivateKey) {
|
|
33
|
-
return unSeal(str, nonce, publicKey, securedPrivateKey, this.#_masterKey);
|
|
30
|
+
setConfig(config);
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
/**
|
|
34
|
+
* @param {StarkCoreConfig} config
|
|
35
|
+
* @returns {StarkCore}
|
|
36
|
+
*/
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return verifyJWT(token, this.#_JWT_SECRET);
|
|
38
|
+
static create(config) {
|
|
39
|
+
return new StarkCore(config);
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
export default
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
generateMasterKey,
|
|
43
|
+
export default StarkCore;
|
|
44
|
+
export { startServer } from "./core/index.js";
|
|
45
|
+
export { default as fieldTypes } from "./lib/field.types.js";
|
|
46
|
+
export { default as zodValidations } from "./lib/zod.validations.js";
|
|
47
|
+
export {
|
|
48
|
+
AppLog,
|
|
54
49
|
generateJWTSecret,
|
|
50
|
+
generateMasterKey,
|
|
55
51
|
hash,
|
|
52
|
+
seal,
|
|
53
|
+
unSeal,
|
|
56
54
|
verifyHash,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// AppError - custom application error class
|
|
60
|
-
export { default as AppError } from "./utils/AppError.js";
|
|
61
|
-
|
|
62
|
-
// AppLog - structured logging utility
|
|
63
|
-
export { default as AppLog } from "./utils/AppLog.js";
|
|
64
|
-
|
|
65
|
-
// async handler - AppError in async functions
|
|
66
|
-
export { default as asyncHandler } from "./utils/asyncHandler.js";
|
|
55
|
+
} from "./utils/index.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// module imports
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
|
|
4
|
+
import AppLog from "../utils/AppLog.js";
|
|
5
|
+
|
|
6
|
+
const connectLocally = async () => {
|
|
7
|
+
await mongoose.connect(`mongodb://localhost:27017/offline-db`);
|
|
8
|
+
AppLog("check", "db", "Connected to OFFLINE-DB!");
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// connecting to db
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {"MONGODB_URI"} uri
|
|
15
|
+
* @param {"DATABASE_NAME"} database
|
|
16
|
+
* @param {true | false} NETWORK
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
const connectDB = async (
|
|
20
|
+
MONGODB_URI = "",
|
|
21
|
+
DATABASE_NAME = "starklabs",
|
|
22
|
+
NETWORK = false,
|
|
23
|
+
) => {
|
|
24
|
+
const mongodbUri = MONGODB_URI || "mongodb://localhost:27017";
|
|
25
|
+
try {
|
|
26
|
+
if (!NETWORK) {
|
|
27
|
+
await connectLocally();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await mongoose.connect(`${mongodbUri}/${DATABASE_NAME}`);
|
|
32
|
+
AppLog("check", "db", "Connected successfully!");
|
|
33
|
+
} catch (error) {
|
|
34
|
+
AppLog("X", "db", "Error while connecting!");
|
|
35
|
+
AppLog("X", "db", error.message);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// export
|
|
40
|
+
export default connectDB;
|