@starklabs/backend-core 1.1.0 → 1.2.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.
Files changed (42) hide show
  1. package/.env.example +21 -0
  2. package/README.md +7 -7
  3. package/dist/js/config/cloudinary.js +18 -0
  4. package/dist/js/config/config.js +11 -0
  5. package/dist/js/config/duration.js +22 -0
  6. package/dist/js/core/app.js +122 -0
  7. package/dist/js/core/auth/OTP.js +115 -0
  8. package/dist/js/core/auth/auth.controller.js +63 -0
  9. package/dist/js/core/auth/auth.service.js +290 -0
  10. package/dist/js/core/auth/auth.validation.js +95 -0
  11. package/dist/js/core/crud/crud.controller.js +95 -0
  12. package/dist/js/core/crud/crud.service.js +296 -0
  13. package/dist/js/core/index.js +3 -0
  14. package/dist/js/index.js +44 -55
  15. package/dist/js/lib/db.js +40 -0
  16. package/dist/js/lib/field.types.js +174 -0
  17. package/dist/js/lib/model.factory.js +19 -0
  18. package/dist/js/lib/model.registry.js +4 -0
  19. package/dist/js/lib/schema.builder.js +35 -0
  20. package/dist/js/lib/zod.validations.js +247 -0
  21. package/dist/js/middleware/auth.middleware.js +51 -0
  22. package/dist/js/middleware/error.middleware.js +28 -0
  23. package/dist/js/middleware/socket.middleware.js +29 -0
  24. package/dist/js/utils/AppLog.js +2 -1
  25. package/dist/js/utils/deleteFile.js +22 -0
  26. package/dist/js/utils/index.js +10 -1
  27. package/dist/js/utils/jwt.js +12 -20
  28. package/dist/js/utils/libsodium.js +19 -3
  29. package/dist/js/utils/rateLimiter.js +25 -0
  30. package/dist/js/utils/uploadFile.js +43 -0
  31. package/handlerMap.js +33 -0
  32. package/package.json +18 -5
  33. package/test.js +36 -0
  34. package/dist/cjs/db.cjs +0 -17
  35. package/dist/cjs/index.cjs +0 -59
  36. package/dist/cjs/utils/AppError.cjs +0 -13
  37. package/dist/cjs/utils/AppLog.cjs +0 -13
  38. package/dist/cjs/utils/asyncHandler.cjs +0 -6
  39. package/dist/cjs/utils/jwt.cjs +0 -38
  40. package/dist/cjs/utils/libsodium.cjs +0 -145
  41. package/dist/cjs/utils/successResponse.cjs +0 -13
  42. package/dist/js/db.js +0 -19
package/.env.example ADDED
@@ -0,0 +1,21 @@
1
+ # development@v3
2
+ PORT=4000
3
+ API_VERSION=1 # must be an integer
4
+
5
+ # Duration format: 1m | 5m | 10m | 15m | 30m | 1h | 6h | 12h | 1d | 3d | 7d | 14d | 30d
6
+ TOKEN_EXPIRY=7d
7
+ JWT_SECRET=
8
+ MASTER_KEY=
9
+ ISOFFLINE=true
10
+ ENV=development
11
+ INTERNAL_ROLES=["developer","admin"]
12
+ RESEND_API_KEY=
13
+
14
+ # Duration format: 1m | 5m | 10m | 15m | 30m | 1h | 6h | 12h | 1d | 3d | 7d | 14d | 30d
15
+ RATE_LIMIT_DURATION=15m
16
+ RATE_LIMIT_REQ=200
17
+ RATE_LIMIT_MSG="Too many requests, please try again later."
18
+ CLOUDINARY_API_SECRET=
19
+ CLOUDINARY_API_KEY=5
20
+ CLOUDINARY_CLOUD_NAME=
21
+ CLOUDINARY_FOLDER_NAME=uploads
package/README.md CHANGED
@@ -23,7 +23,7 @@ npm install @starklabs/backend-core
23
23
  The following environment variables are required:
24
24
 
25
25
  ```env
26
- MONGO_URI=mongodb://localhost:27017
26
+ MONGODB_URI=mongodb://localhost:27017
27
27
  DB_NAME=your_database_name
28
28
  MASTER_KEY=your_base64_encoded_32_byte_key
29
29
  JWT_SECRET=your_jwt_secret_string
@@ -62,7 +62,7 @@ import starkAuth, { crypto, AppError, AppLog, asyncHandler } from '@starklabs/ba
62
62
 
63
63
  // Initialize
64
64
  const auth = await starkAuth.create({
65
- MONGO_URI: process.env.MONGO_URI,
65
+ MONGODB_URI: process.env.MONGODB_URI,
66
66
  DB_NAME: process.env.DB_NAME,
67
67
  MASTER_KEY: process.env.MASTER_KEY,
68
68
  JWT_SECRET: process.env.JWT_SECRET,
@@ -97,7 +97,7 @@ const starkAuth = require('@starklabs/backend-core');
97
97
 
98
98
  // Initialize
99
99
  const auth = await starkAuth.create({
100
- MONGO_URI: process.env.MONGO_URI,
100
+ MONGODB_URI: process.env.MONGODB_URI,
101
101
  DB_NAME: process.env.DB_NAME,
102
102
  MASTER_KEY: process.env.MASTER_KEY,
103
103
  JWT_SECRET: process.env.JWT_SECRET,
@@ -118,7 +118,7 @@ const payload = auth.verifyJWT(token);
118
118
  Initializes database connection and returns a new StarkAuth instance.
119
119
 
120
120
  **Parameters:**
121
- - `config.MONGO_URI` (string): MongoDB connection URI
121
+ - `config.MONGODB_URI` (string): MongoDB connection URI
122
122
  - `config.DB_NAME` (string): Database name
123
123
  - `config.MASTER_KEY` (string): Base64-encoded 32-byte encryption key
124
124
  - `config.JWT_SECRET` (string): Secret for JWT signing
@@ -128,7 +128,7 @@ Initializes database connection and returns a new StarkAuth instance.
128
128
 
129
129
  ```javascript
130
130
  const auth = await starkAuth.create({
131
- MONGO_URI: 'mongodb://localhost:27017',
131
+ MONGODB_URI: 'mongodb://localhost:27017',
132
132
  DB_NAME: 'myapp',
133
133
  MASTER_KEY: 'eZkZDoy2s+DvGe44QGa7AdU41nKhglEaIjFsxfQCKao=',
134
134
  JWT_SECRET: 'your-secret-key',
@@ -335,7 +335,7 @@ let auth;
335
335
  app.use(async (req, res, next) => {
336
336
  if (!auth) {
337
337
  auth = await starkAuth.create({
338
- MONGO_URI: process.env.MONGO_URI,
338
+ MONGODB_URI: process.env.MONGODB_URI,
339
339
  DB_NAME: process.env.DB_NAME,
340
340
  MASTER_KEY: process.env.MASTER_KEY,
341
341
  JWT_SECRET: process.env.JWT_SECRET,
@@ -456,7 +456,7 @@ Ensure `JWT_EXPIRY` is one of the supported values: `"2m"`, `"10m"`, `"1h"`, `"6
456
456
  ### "Error while connecting!"
457
457
  Check that:
458
458
  - MongoDB is running and accessible
459
- - `MONGO_URI` is correct
459
+ - `MONGODB_URI` is correct
460
460
  - Network/firewall allows connection
461
461
 
462
462
  ### "Invalid or expired token"
@@ -0,0 +1,18 @@
1
+ import multer from "multer";
2
+ import { v2 as cloudinary } from "cloudinary";
3
+
4
+ const upload = multer({ storage: multer.memoryStorage() });
5
+
6
+ const configureCloudinary = ({
7
+ cloudinaryAPIKey,
8
+ cloudinaryCloudName,
9
+ cloudinaryAPISecret,
10
+ }) => {
11
+ cloudinary.config({
12
+ cloud_name: cloudinaryCloudName,
13
+ api_key: cloudinaryAPIKey,
14
+ api_secret: cloudinaryAPISecret,
15
+ });
16
+ };
17
+
18
+ export { cloudinary, configureCloudinary, upload };
@@ -0,0 +1,11 @@
1
+ let config = {};
2
+
3
+ const setConfig = (options) => {
4
+ config = options;
5
+ };
6
+
7
+ const getConfig = () => {
8
+ return config;
9
+ };
10
+
11
+ export { setConfig, getConfig, config };
@@ -0,0 +1,22 @@
1
+ export const DURATIONS = {
2
+ "1m": 60 * 1000,
3
+ "5m": 5 * 60 * 1000,
4
+ "10m": 10 * 60 * 1000,
5
+ "15m": 15 * 60 * 1000,
6
+ "30m": 30 * 60 * 1000,
7
+ "1h": 60 * 60 * 1000,
8
+ "6h": 6 * 60 * 60 * 1000,
9
+ "12h": 12 * 60 * 60 * 1000,
10
+ "1d": 24 * 60 * 60 * 1000,
11
+ "3d": 3 * 24 * 60 * 60 * 1000,
12
+ "7d": 7 * 24 * 60 * 60 * 1000,
13
+ "14d": 14 * 24 * 60 * 60 * 1000,
14
+ "30d": 30 * 24 * 60 * 60 * 1000,
15
+ };
16
+
17
+ const getDuration = (key) => {
18
+ if (!DURATIONS[key]) throw new Error("Invalid expiry key");
19
+ return DURATIONS[key];
20
+ };
21
+
22
+ export default getDuration;
@@ -0,0 +1,122 @@
1
+ import express from "express";
2
+ import AppLog from "../utils/AppLog.js";
3
+ import connectDB from "../lib/db.js";
4
+ import { crud, auth } from "./index.js";
5
+ import mongoose from "mongoose";
6
+ import createModel from "../lib/model.factory.js";
7
+ import registerModel from "../lib/model.registry.js";
8
+ import errorMiddleware from "../middleware/error.middleware.js";
9
+ import { getConfig, config } from "../config/config.js";
10
+ import AppError from "../utils/AppError.js";
11
+ import helmet from "helmet";
12
+ import cors from "cors";
13
+ import cookieParser from "cookie-parser";
14
+ import { apiLimiter } from "../utils/rateLimiter.js";
15
+ import { configureCloudinary } from "../config/cloudinary.js";
16
+
17
+ const app = express();
18
+ app.use(express.json());
19
+ app.use(express.urlencoded({ extended: true }));
20
+
21
+ app.use(express.json());
22
+
23
+ const registerCollections = (collections, apiVersion) => {
24
+ collections.forEach(
25
+ ({
26
+ route,
27
+ modelName,
28
+ mongooseSchema,
29
+ otpSchema = false,
30
+ routes,
31
+ validations,
32
+ }) => {
33
+ // 1. create model dynamically
34
+ if (modelName && mongooseSchema) {
35
+ const model = createModel(modelName, mongooseSchema);
36
+
37
+ // register model
38
+ registerModel[modelName] = model;
39
+
40
+ if (otpSchema) {
41
+ const otpModel = createModel("otpUser", otpSchema);
42
+ registerModel["otpModel"] = otpModel;
43
+ }
44
+ }
45
+
46
+ // 3. register CRUD routes
47
+ if (route === "auth") {
48
+ auth(route, routes, modelName, validations, apiVersion);
49
+ config.userModel = modelName;
50
+ } else {
51
+ crud(route, routes, modelName, validations, apiVersion);
52
+ }
53
+ },
54
+ );
55
+ };
56
+
57
+ const configValidation = ({ port, collections }) => {
58
+ if (!port) {
59
+ throw new Error("Port is missing in StarkCore.create({})");
60
+ }
61
+
62
+ if (!collections) {
63
+ throw new Error("Collections array is missing in StarkCore.create({})");
64
+ } else if (!Array.isArray(collections)) {
65
+ throw new Error("Collections must be an array datatype");
66
+ } else if (collections.length === 0) {
67
+ throw new Error("Collections shouldn't be empty in StarkCore.create({})");
68
+ }
69
+ };
70
+
71
+ const startServer = async () => {
72
+ try {
73
+ const config = getConfig();
74
+ configValidation(config);
75
+ const {
76
+ port,
77
+ collections,
78
+ rateLimitDuration,
79
+ maxReqLimit,
80
+ rateLimitMsg,
81
+ cloudinaryAPIKey,
82
+ cloudinaryCloudName,
83
+ cloudinaryAPISecret,
84
+ apiVersion,
85
+ } = config;
86
+
87
+ app.use(cors());
88
+ app.use(helmet());
89
+ app.use(cookieParser());
90
+ app.use(apiLimiter({ rateLimitDuration, maxReqLimit, rateLimitMsg }));
91
+
92
+ connectDB();
93
+
94
+ configureCloudinary({
95
+ cloudinaryAPIKey,
96
+ cloudinaryCloudName,
97
+ cloudinaryAPISecret,
98
+ });
99
+
100
+ registerCollections(collections, apiVersion);
101
+
102
+ app.use((req, res) => {
103
+ res.status(404).json({
104
+ success: false,
105
+ message: `Cannot ${req.method} ${req.originalUrl}`,
106
+ });
107
+ });
108
+
109
+ app.use(errorMiddleware);
110
+
111
+ app.listen(port, () => {
112
+ console.log(
113
+ `Server running at http://localhost:${port}/api/v${apiVersion}`,
114
+ );
115
+ });
116
+ } catch (err) {
117
+ console.log(err);
118
+ AppLog("X", "startServer", err.message);
119
+ }
120
+ };
121
+
122
+ export { startServer, app };
@@ -0,0 +1,115 @@
1
+ // module imports
2
+ import { Resend } from "resend";
3
+ import crypto from "crypto";
4
+
5
+ // custom imports
6
+ import AppError from "../../utils/AppError.js";
7
+ import { getConfig } from "../../config/config.js";
8
+
9
+ // sendOTP()
10
+ const sendOTP = async (email) => {
11
+ // OTP
12
+ const OTP = crypto.randomInt(100000, 1000000).toString();
13
+ const otpExpiry = Date.now() + 1000 * 60 * 5;
14
+
15
+ // config
16
+ const { isOffline, resendAPIKey } = getConfig();
17
+ if (isOffline) return { OTP, otpExpiry };
18
+
19
+ if (!resendAPIKey)
20
+ throw new AppError("resendAPIKey is missing in StarkCore.create({})");
21
+
22
+ // html
23
+ const htmlTemp = `<!DOCTYPE html>
24
+ <html>
25
+ <head>
26
+ <meta charset="UTF-8" />
27
+ <title>Email Verification</title>
28
+ </head>
29
+ <body style="margin:0; padding:0; font-family:Arial, sans-serif;">
30
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" style="padding:40px 0; padding-top: 0px;">
31
+ <tr>
32
+ <td align="center">
33
+ <table width="500" cellpadding="0" cellspacing="0" border="0" style="border-radius:12px; box-shadow:0 10px 25px rgba(0,0,0,0.08); padding:40px; padding-top: 10px;">
34
+
35
+ <tr>
36
+ <td align="center" style="padding-bottom:20px;">
37
+ <h2 style="margin:0; color:#111827; font-weight:600;">Stark Vault</h2>
38
+ </td>
39
+ </tr>
40
+
41
+ <tr>
42
+ <td align="center" style="padding-bottom:20px;">
43
+ <h1 style="margin:0; font-size:22px; color:#111827;">
44
+ Email Verification Code
45
+ </h1>
46
+ </td>
47
+ </tr>
48
+
49
+ <tr>
50
+ <td align="center" style="padding-bottom:30px; color:#6b7280; font-size:15px; line-height:1.6;">
51
+ Use the verification code below to complete your sign-in process.
52
+ This code will expire in <strong>5 minutes</strong>.
53
+ </td>
54
+ </tr>
55
+
56
+ <tr>
57
+ <td align="center" style="padding-bottom:30px;">
58
+ <div style="
59
+ display:inline-block;
60
+ padding:18px 32px;
61
+ font-size:28px;
62
+ letter-spacing:8px;
63
+ font-weight:bold;
64
+ color:#111827;
65
+ background:#f3f4f6;
66
+ border-radius:8px;
67
+ border:1px solid #e5e7eb;">
68
+ ${OTP}
69
+ </div>
70
+ </td>
71
+ </tr>
72
+
73
+ <tr>
74
+ <td align="center" style="color:#9ca3af; font-size:13px; line-height:1.5;">
75
+ If you didn’t request this code, you can safely ignore this email.
76
+ Never share your verification code with anyone.
77
+ </td>
78
+ </tr>
79
+
80
+ <tr>
81
+ <td style="padding-top:30px;">
82
+ <hr style="border:none; border-top:1px solid #e5e7eb;" />
83
+ </td>
84
+ </tr>
85
+
86
+ <tr>
87
+ <td align="center" style="padding-top:15px; font-size:12px; color:#9ca3af;">
88
+ © 2026 Your Company. All rights reserved.
89
+ </td>
90
+ </tr>
91
+
92
+ </table>
93
+ </td>
94
+ </tr>
95
+ </table>
96
+ </body>
97
+ </html>`;
98
+
99
+ const resend = new Resend(resendAPIKey);
100
+ const { data, error } = await resend.emails.send({
101
+ from: "Stark Vault <noreply@vault.musastark.space>",
102
+ to: email,
103
+ subject: "OTP - StarkVault",
104
+ html: htmlTemp,
105
+ });
106
+
107
+ if (error) {
108
+ console.log("[OTP.js] 102: ", error);
109
+ throw new AppError(error.message, 409);
110
+ }
111
+
112
+ return { OTP, otpExpiry };
113
+ };
114
+
115
+ export default sendOTP;
@@ -0,0 +1,63 @@
1
+ import { app } from "../app.js";
2
+ import mongoose, { model } from "mongoose";
3
+ import asyncHandler from "../../utils/asyncHandler.js";
4
+ import authService from "./auth.service.js";
5
+ import registerModel from "../../lib/model.registry.js";
6
+ import getDuration from "../../config/duration.js";
7
+ import AppError from "../../utils/AppError.js";
8
+ import zodValidations from "../../lib/zod.validations.js";
9
+ import z from "zod";
10
+ import { getConfig } from "../../config/config.js";
11
+
12
+ const auth = (route, routes, modelName, validations, apiVersion) => {
13
+ const { ENV, tokenExpiry } = getConfig();
14
+
15
+ routes.forEach((el) => {
16
+ app[el.method](
17
+ `/api/v${apiVersion}/${route}${el.path}`,
18
+ asyncHandler(async (req, res) => {
19
+ const Model = registerModel[modelName];
20
+ if (!Model) throw new Error(`Model not found for endpoint: ${el.path}`);
21
+ const OTPModel = registerModel["otpModel"];
22
+ if (!OTPModel)
23
+ throw new AppError("otpSchema is missing in auth collection");
24
+
25
+ const zodObj = z.object(validations[el.handler]);
26
+ const isValid = await zodObj.safeParse(req.body || {});
27
+
28
+ if (!isValid.success) {
29
+ const issue = isValid.error.issues[0];
30
+
31
+ if (issue.code === "invalid_type")
32
+ throw new AppError(`${issue.path.join(".")} is required`);
33
+
34
+ throw new AppError(issue.message, 409);
35
+ }
36
+
37
+ // send data to service
38
+ const result = await authService[el.handler]({
39
+ body: isValid.data,
40
+ Model,
41
+ OTPModel,
42
+ });
43
+ if (result?.token) {
44
+ res.cookie("authToken", result.token, {
45
+ httpOnly: true,
46
+ maxAge: getDuration(tokenExpiry),
47
+ sameSite: ENV === "production" ? "none" : "lax",
48
+ secure: ENV === "production" ? true : false,
49
+ domain: ENV === "production" ? ".musastark.space" : undefined,
50
+ });
51
+ }
52
+
53
+ return res.json({
54
+ success: true,
55
+ user: result?.user,
56
+ message: result?.msg,
57
+ });
58
+ }),
59
+ );
60
+ });
61
+ };
62
+
63
+ export default auth;