exp-node-server 1.1.7 → 2.0.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/README.md +82 -20
- package/dist/cli.js +525 -0
- package/package.json +29 -9
- package/templates/mongoose/.dockerignore +10 -0
- package/templates/mongoose/.env.example +56 -7
- package/templates/mongoose/.prettierignore +3 -0
- package/templates/mongoose/.prettierrc +7 -0
- package/templates/mongoose/Dockerfile +33 -0
- package/templates/mongoose/_gitignore +32 -0
- package/templates/mongoose/docker-compose.yml +21 -0
- package/templates/mongoose/eslint.config.js +35 -0
- package/templates/mongoose/package-lock.json +6377 -0
- package/templates/mongoose/package.json +49 -10
- package/templates/mongoose/readme.md +85 -0
- package/templates/mongoose/src/app/configs/index.ts +178 -26
- package/templates/mongoose/src/app/errors/zodError.ts +16 -16
- package/templates/mongoose/src/app/lib/db.ts +18 -0
- package/templates/mongoose/src/app/lib/logger.ts +12 -0
- package/templates/mongoose/src/app/middlewares/auth.ts +48 -44
- package/templates/mongoose/src/app/middlewares/global_error_handler.ts +59 -65
- package/templates/mongoose/src/app/middlewares/not_found_api.ts +7 -7
- package/templates/mongoose/src/app/middlewares/rate_limiter.ts +153 -0
- package/templates/mongoose/src/app/middlewares/request_validator.ts +11 -10
- package/templates/mongoose/src/app/middlewares/uploader.ts +17 -13
- package/templates/mongoose/src/app/modules/account/account.controller.ts +156 -0
- package/templates/mongoose/src/app/modules/account/account.interface.ts +65 -0
- package/templates/mongoose/src/app/modules/account/account.route.ts +101 -0
- package/templates/mongoose/src/app/modules/account/account.schema.ts +97 -0
- package/templates/mongoose/src/app/modules/account/account.service.ts +422 -0
- package/templates/mongoose/src/app/modules/account/account.swagger.ts +278 -0
- package/templates/mongoose/src/app/modules/account/account.validation.ts +63 -0
- package/templates/mongoose/src/app/modules/profile/profile.controller.ts +17 -0
- package/templates/mongoose/src/app/modules/profile/profile.interface.ts +7 -0
- package/templates/mongoose/src/app/modules/profile/profile.route.ts +28 -0
- package/templates/mongoose/src/app/modules/profile/profile.schema.ts +13 -0
- package/templates/mongoose/src/app/modules/profile/profile.service.ts +28 -0
- package/templates/mongoose/src/app/modules/profile/profile.swagger.ts +34 -0
- package/templates/mongoose/src/app/modules/profile/profile.validation.ts +8 -0
- package/templates/mongoose/src/app/passport-js/index.ts +87 -0
- package/templates/mongoose/src/app/passport-js/oauth-state.ts +150 -0
- package/templates/mongoose/src/app/passport-js/providers.ts +258 -0
- package/templates/mongoose/src/app/passport-js/social-account.ts +141 -0
- package/templates/mongoose/src/app/queues/agenda.ts +24 -0
- package/templates/mongoose/src/app/queues/email.queue.ts +53 -0
- package/templates/mongoose/src/app/queues/index.ts +13 -0
- package/templates/mongoose/src/app/templates/otpEmailTemplate.ts +185 -0
- package/templates/mongoose/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
- package/templates/mongoose/src/app/types/error.d.ts +7 -7
- package/templates/mongoose/src/app/types/express.d.ts +11 -0
- package/templates/mongoose/src/app/types/passport-oauth1.d.ts +51 -0
- package/templates/mongoose/src/app/utils/JWT.ts +38 -18
- package/templates/mongoose/src/app/utils/app_error.ts +10 -10
- package/templates/mongoose/src/app/utils/auth_cookie.ts +27 -0
- package/templates/mongoose/src/app/utils/catch_async.ts +8 -8
- package/templates/mongoose/src/app/utils/cloudinary.ts +26 -59
- package/templates/mongoose/src/app/utils/mail_sender.ts +26 -92
- package/templates/mongoose/src/app/utils/manage_response.ts +19 -19
- package/templates/mongoose/src/app/utils/otp.ts +20 -0
- package/templates/mongoose/src/app/utils/session.ts +55 -0
- package/templates/mongoose/src/app.ts +73 -42
- package/templates/mongoose/src/routes.ts +13 -13
- package/templates/mongoose/src/server.ts +38 -18
- package/templates/mongoose/src/swaggerOptions.ts +23 -23
- package/templates/mongoose/tests/app.test.ts +97 -0
- package/templates/mongoose/tests/global-setup.ts +15 -0
- package/templates/mongoose/tests/helpers/fixtures.ts +42 -0
- package/templates/mongoose/tests/routes/account.test.ts +614 -0
- package/templates/mongoose/tests/routes/profile.test.ts +95 -0
- package/templates/mongoose/tests/routes/social.test.ts +310 -0
- package/templates/mongoose/tests/setup.ts +25 -0
- package/templates/mongoose/tsconfig.json +16 -20
- package/templates/mongoose/vitest.config.ts +43 -0
- package/templates/prisma/.dockerignore +2 -0
- package/templates/prisma/.env.example +62 -6
- package/templates/prisma/.prettierignore +3 -0
- package/templates/prisma/.prettierrc +7 -0
- package/templates/prisma/.projectrc.json +1 -1
- package/templates/prisma/Dockerfile +21 -7
- package/templates/prisma/_gitignore +35 -0
- package/templates/prisma/docker-compose.yml +8 -7
- package/templates/prisma/eslint.config.js +35 -0
- package/templates/prisma/package-lock.json +7327 -0
- package/templates/prisma/package.json +47 -9
- package/templates/prisma/prisma/schema/account.schema.prisma +92 -10
- package/templates/prisma/prisma/schema/profile.schema.prisma +8 -3
- package/templates/prisma/prisma/schema/schema.prisma +1 -2
- package/templates/prisma/prisma.config.ts +5 -5
- package/templates/prisma/readme.md +81 -0
- package/templates/prisma/src/app/configs/index.ts +175 -26
- package/templates/prisma/src/app/errors/zodError.ts +16 -16
- package/templates/prisma/src/app/lib/logger.ts +12 -0
- package/templates/prisma/src/app/lib/prisma.ts +4 -6
- package/templates/prisma/src/app/middlewares/auth.ts +37 -16
- package/templates/prisma/src/app/middlewares/global_error_handler.ts +42 -35
- package/templates/prisma/src/app/middlewares/not_found_api.ts +7 -7
- package/templates/prisma/src/app/middlewares/rate_limiter.ts +124 -0
- package/templates/prisma/src/app/middlewares/request_validator.ts +4 -3
- package/templates/prisma/src/app/middlewares/uploader.ts +17 -13
- package/templates/prisma/src/app/modules/account/account.controller.ts +74 -34
- package/templates/prisma/src/app/modules/account/account.route.ts +68 -22
- package/templates/prisma/src/app/modules/account/account.service.ts +329 -309
- package/templates/prisma/src/app/modules/account/account.swagger.ts +167 -76
- package/templates/prisma/src/app/modules/account/account.validation.ts +41 -19
- package/templates/prisma/src/app/modules/profile/profile.controller.ts +4 -4
- package/templates/prisma/src/app/modules/profile/profile.route.ts +18 -12
- package/templates/prisma/src/app/modules/profile/profile.service.ts +11 -18
- package/templates/prisma/src/app/modules/profile/profile.swagger.ts +11 -11
- package/templates/prisma/src/app/modules/profile/profile.validation.ts +2 -2
- package/templates/prisma/src/app/passport-js/index.ts +87 -0
- package/templates/prisma/src/app/passport-js/oauth-state.ts +150 -0
- package/templates/prisma/src/app/passport-js/providers.ts +263 -0
- package/templates/prisma/src/app/passport-js/social-account.ts +147 -0
- package/templates/prisma/src/app/queues/boss.ts +8 -0
- package/templates/prisma/src/app/queues/email.queue.ts +56 -0
- package/templates/prisma/src/app/queues/index.ts +14 -0
- package/templates/prisma/src/app/queues/maintenance.queue.ts +30 -0
- package/templates/prisma/src/app/templates/otpEmailTemplate.ts +185 -0
- package/templates/prisma/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
- package/templates/prisma/src/app/types/error.d.ts +7 -7
- package/templates/prisma/src/app/types/express.d.ts +11 -0
- package/templates/prisma/src/app/types/passport-oauth1.d.ts +51 -0
- package/templates/prisma/src/app/utils/JWT.ts +36 -17
- package/templates/prisma/src/app/utils/app_error.ts +10 -10
- package/templates/prisma/src/app/utils/auth_cookie.ts +27 -0
- package/templates/prisma/src/app/utils/catch_async.ts +8 -8
- package/templates/prisma/src/app/utils/cloudinary.ts +26 -59
- package/templates/prisma/src/app/utils/mail_sender.ts +26 -92
- package/templates/prisma/src/app/utils/manage_response.ts +19 -19
- package/templates/prisma/src/app/utils/otp.ts +20 -0
- package/templates/prisma/src/app/utils/session.ts +57 -0
- package/templates/prisma/src/app.ts +73 -42
- package/templates/prisma/src/routes.ts +12 -10
- package/templates/prisma/src/server.ts +37 -19
- package/templates/prisma/src/swaggerOptions.ts +22 -26
- package/templates/prisma/tests/app.test.ts +86 -0
- package/templates/prisma/tests/helpers/fixtures.ts +35 -0
- package/templates/prisma/tests/helpers/prisma.ts +32 -0
- package/templates/prisma/tests/routes/account.test.ts +627 -0
- package/templates/prisma/tests/routes/profile.test.ts +91 -0
- package/templates/prisma/tests/routes/social.test.ts +317 -0
- package/templates/prisma/tests/setup.ts +10 -0
- package/templates/prisma/tsconfig.json +16 -23
- package/templates/prisma/vitest.config.ts +38 -0
- package/bin/cli.js +0 -41
- package/src/createProject.js +0 -61
- package/src/generateModule.js +0 -93
- package/src/utils/getProjectType.js +0 -13
- package/src/utils/mongoose/controllerGenerator.js +0 -70
- package/src/utils/mongoose/routeGenerator.js +0 -27
- package/src/utils/mongoose/schemaGenerator.js +0 -11
- package/src/utils/mongoose/serviceGenerator.js +0 -49
- package/src/utils/mongoose/swaggerGenerator.js +0 -112
- package/src/utils/mongoose/validationGenerator.js +0 -13
- package/src/utils/prisma/prismaSchemaGenerator.js +0 -10
- package/src/utils/prisma/prismaServiceGenerator.js +0 -47
- package/src/utils/routeInjector.js +0 -40
- package/src/utils/swaggerInjector.js +0 -52
- package/src/utils/validateModuleName.js +0 -17
- package/src/utils/validateProjectName.js +0 -15
- package/src/utils.js +0 -26
- package/templates/mongoose/src/app/errors/castError.ts +0 -23
- package/templates/mongoose/src/app/errors/duplicateError.ts +0 -26
- package/templates/mongoose/src/app/errors/validationError.ts +0 -25
- package/templates/mongoose/src/app/modules/auth/auth.controller.ts +0 -127
- package/templates/mongoose/src/app/modules/auth/auth.interface.ts +0 -24
- package/templates/mongoose/src/app/modules/auth/auth.route.ts +0 -46
- package/templates/mongoose/src/app/modules/auth/auth.schema.ts +0 -19
- package/templates/mongoose/src/app/modules/auth/auth.service.ts +0 -324
- package/templates/mongoose/src/app/modules/auth/auth.swagger.ts +0 -214
- package/templates/mongoose/src/app/modules/auth/auth.validation.ts +0 -37
- package/templates/mongoose/src/app/modules/user/user.controller.ts +0 -17
- package/templates/mongoose/src/app/modules/user/user.interface.ts +0 -15
- package/templates/mongoose/src/app/modules/user/user.route.ts +0 -20
- package/templates/mongoose/src/app/modules/user/user.schema.ts +0 -22
- package/templates/mongoose/src/app/modules/user/user.service.ts +0 -22
- package/templates/mongoose/src/app/modules/user/user.swagger.ts +0 -75
- package/templates/mongoose/src/app/modules/user/user.validation.ts +0 -18
- package/templates/mongoose/src/app/types/index.d.ts +0 -12
- package/templates/mongoose/src/app/utils/isAccountExist.ts +0 -31
- package/templates/prisma/src/app/types/index.d.ts +0 -12
- package/templates/prisma/src/app/utils/otpGenerator.ts +0 -29
|
@@ -1,66 +1,33 @@
|
|
|
1
|
-
import { v2 as cloudinary } from 'cloudinary';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { v2 as cloudinary, type UploadApiResponse } from 'cloudinary';
|
|
2
|
+
import { configs } from '../configs/index.js';
|
|
3
|
+
import { AppError } from './app_error.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
public_id: string;
|
|
8
|
-
version: number;
|
|
9
|
-
version_id: string;
|
|
10
|
-
signature: string;
|
|
11
|
-
width: number;
|
|
12
|
-
height: number;
|
|
13
|
-
format: string;
|
|
14
|
-
resource_type: string;
|
|
15
|
-
created_at: string;
|
|
16
|
-
tags: string[];
|
|
17
|
-
bytes: number;
|
|
18
|
-
type: string;
|
|
19
|
-
etag: string;
|
|
20
|
-
placeholder: boolean;
|
|
21
|
-
url: string;
|
|
22
|
-
secure_url: string;
|
|
23
|
-
folder: string;
|
|
24
|
-
overwritten: boolean;
|
|
25
|
-
original_filename: string;
|
|
26
|
-
original_extension: string;
|
|
27
|
-
api_key: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
type IFile = {
|
|
31
|
-
fieldname: string;
|
|
32
|
-
originalname: string;
|
|
33
|
-
encoding: string;
|
|
34
|
-
mimetype: string;
|
|
35
|
-
destination: string;
|
|
36
|
-
filename: string;
|
|
37
|
-
path: string;
|
|
38
|
-
size: number;
|
|
39
|
-
};
|
|
5
|
+
const { cloud_name, cloud_api_key, cloud_api_secret } = configs.cloudinary;
|
|
6
|
+
const isConfigured = Boolean(cloud_name && cloud_api_key && cloud_api_secret);
|
|
40
7
|
|
|
41
|
-
// Configuration
|
|
42
8
|
cloudinary.config({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
9
|
+
cloud_name,
|
|
10
|
+
api_key: cloud_api_key,
|
|
11
|
+
api_secret: cloud_api_secret,
|
|
46
12
|
});
|
|
47
13
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Streams an in-memory image to Cloudinary. `resource_type: 'image'` makes
|
|
16
|
+
* Cloudinary reject anything that isn't really an image, whatever its mimetype said.
|
|
17
|
+
*/
|
|
18
|
+
const uploadCloud = (file: Express.Multer.File, folder = 'uploads'): Promise<UploadApiResponse> => {
|
|
19
|
+
if (!isConfigured) {
|
|
20
|
+
return Promise.reject(new AppError('Image upload is not configured', 503));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
cloudinary.uploader
|
|
25
|
+
.upload_stream({ resource_type: 'image', folder }, (error, result) => {
|
|
26
|
+
if (error || !result) return reject(error ?? new Error('Upload failed'));
|
|
27
|
+
resolve(result);
|
|
28
|
+
})
|
|
29
|
+
.end(file.buffer);
|
|
30
|
+
});
|
|
64
31
|
};
|
|
65
32
|
|
|
66
|
-
export default uploadCloud;
|
|
33
|
+
export default uploadCloud;
|
|
@@ -1,102 +1,36 @@
|
|
|
1
1
|
import nodemailer from 'nodemailer';
|
|
2
|
-
import { configs } from '../configs';
|
|
2
|
+
import { configs } from '../configs/index.js';
|
|
3
|
+
|
|
3
4
|
type TMailContent = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
5
|
+
to: string;
|
|
6
|
+
subject: string;
|
|
7
|
+
textBody: string;
|
|
8
|
+
htmlBody: string;
|
|
9
|
+
};
|
|
10
10
|
|
|
11
11
|
const transporter = nodemailer.createTransport({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
host: 'smtp.gmail.com',
|
|
13
|
+
port: 465,
|
|
14
|
+
secure: true, // true for 465, false for other ports
|
|
15
|
+
auth: {
|
|
16
|
+
user: configs.email.app_email,
|
|
17
|
+
pass: configs.email.app_password,
|
|
18
|
+
},
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Email Sender Function
|
|
22
22
|
const sendMail = async (payload: TMailContent) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<title>Welcome Email</title>
|
|
35
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
36
|
-
<style>
|
|
37
|
-
* {
|
|
38
|
-
margin: 0;
|
|
39
|
-
padding: 0;
|
|
40
|
-
box-sizing: border-box;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/* Fallback styles for unsupported clients (some email clients ignore <style> tags) */
|
|
44
|
-
@media only screen and (max-width: 600px) {
|
|
45
|
-
.container {
|
|
46
|
-
padding: 20px !important;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.btn {
|
|
50
|
-
padding: 12px 18px !important;
|
|
51
|
-
font-size: 16px !important;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
</style>
|
|
55
|
-
</head>
|
|
56
|
-
|
|
57
|
-
<body
|
|
58
|
-
style="margin: 0; padding: 0; font-family: Arial, sans-serif;">
|
|
59
|
-
|
|
60
|
-
<div style="max-width: 600px; margin: 40px auto; background-color: #f4f4f4; padding: 40px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);"
|
|
61
|
-
class="container">
|
|
62
|
-
|
|
63
|
-
<div style="font-size: 16px; color: #555555; line-height: 1.6;">
|
|
64
|
-
<p style="margin-bottom: 30px;">Hi <strong>${payload?.name || ""}</strong>,</p>
|
|
65
|
-
|
|
66
|
-
${payload?.htmlBody}
|
|
67
|
-
|
|
68
|
-
<div
|
|
69
|
-
style=" margin-top: 60px; text-align: center;">
|
|
70
|
-
|
|
71
|
-
<img style="width: 50px; height: 50px; border-radius: 50%;"
|
|
72
|
-
src="https://imgs.search.brave.com/IZoN38NQxnIIuB1I9E70bW6q5OvbEtz68YaxTe1j-o0/rs:fit:860:0:0:0/g:ce/aHR0cHM6Ly9lbGVt/ZW50cy1yZXNpemVk/LmVudmF0b3VzZXJj/b250ZW50LmNvbS9l/bGVtZW50cy1jb3Zl/ci1pbWFnZXMvMjhi/NmVjMTQtMGMwOS00/NGY1LWE5NGUtNmIy/OTM5NTZkMDM2P3c9/NDMzJmNmX2ZpdD1z/Y2FsZS1kb3duJnE9/ODUmZm9ybWF0PWF1/dG8mcz04Mjc0OWYy/ZDUyMmJiM2NlMjNi/OWNhNjhlZmFhNjdk/MTg5OGI4NWIwNzBh/MjQ1NjM4NmI1ZmFj/NWVmNmM5ZTNl"
|
|
73
|
-
alt="">
|
|
74
|
-
|
|
75
|
-
<p style="font-size: 12px;">The Support Team</p>
|
|
76
|
-
<h3>Company Name</h3>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
<p style="font-size: 14px; color: #999999; margin-top: 20px; margin-bottom: 10px; text-align: center;">
|
|
80
|
-
This is an automated message — please do not reply to this email.
|
|
81
|
-
<br>
|
|
82
|
-
If you need assistance, feel free to contact our support team.
|
|
83
|
-
<br><br>
|
|
84
|
-
Thank you for choosing us!
|
|
85
|
-
</p>
|
|
86
|
-
|
|
87
|
-
<hr>
|
|
88
|
-
<div style="text-align: center; font-size: 12px; color: #999999; margin-top: 20px;">
|
|
89
|
-
© {{year}} Your Company. All rights reserved.
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
</div>
|
|
93
|
-
</body>
|
|
94
|
-
|
|
95
|
-
</html>
|
|
96
|
-
|
|
97
|
-
`,
|
|
98
|
-
});
|
|
99
|
-
return info
|
|
23
|
+
if (!configs.email.app_email || !configs.email.app_password) {
|
|
24
|
+
throw new Error('Email is not configured. Set APP_USER_EMAIL and APP_PASSWORD.');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return transporter.sendMail({
|
|
28
|
+
from: configs.email.from,
|
|
29
|
+
to: payload.to,
|
|
30
|
+
subject: payload.subject,
|
|
31
|
+
text: payload.textBody,
|
|
32
|
+
html: payload.htmlBody,
|
|
33
|
+
});
|
|
100
34
|
};
|
|
101
35
|
|
|
102
36
|
export default sendMail;
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { Response } from
|
|
1
|
+
import { Response } from 'express';
|
|
2
2
|
interface IResponse<T> {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
success: boolean;
|
|
4
|
+
statusCode: number;
|
|
5
|
+
message: string;
|
|
6
|
+
data?: T;
|
|
7
|
+
meta?: {
|
|
8
|
+
page?: number;
|
|
9
|
+
limit?: number;
|
|
10
|
+
skip?: number;
|
|
11
|
+
total?: number;
|
|
12
|
+
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const manageResponse = <T>(res: Response, payload: IResponse<T>) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
16
|
+
res.status(payload.statusCode).json({
|
|
17
|
+
success: payload.success,
|
|
18
|
+
message: payload.message,
|
|
19
|
+
data: payload.data ?? null,
|
|
20
|
+
meta: payload.meta ?? null,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
23
|
|
|
24
|
-
export default manageResponse
|
|
24
|
+
export default manageResponse;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createHmac, randomInt, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { configs } from '../configs/index.js';
|
|
3
|
+
|
|
4
|
+
export const OTP_EXPIRY_MS = 5 * 60 * 1000;
|
|
5
|
+
export const OTP_MAX_ATTEMPTS = 5;
|
|
6
|
+
export const OTP_RESEND_COOLDOWN_MS = 60 * 1000;
|
|
7
|
+
|
|
8
|
+
/** A cryptographically random 6-digit code. */
|
|
9
|
+
export const generateOtp = (): string => randomInt(0, 1_000_000).toString().padStart(6, '0');
|
|
10
|
+
|
|
11
|
+
/** OTPs are stored as an HMAC, never in plain text. */
|
|
12
|
+
export const hashOtp = (otp: string): string =>
|
|
13
|
+
createHmac('sha256', configs.jwt.verified_token).update(otp).digest('hex');
|
|
14
|
+
|
|
15
|
+
export const isOtpMatch = (otp: string, storedHash: string | null): boolean => {
|
|
16
|
+
if (!storedHash) return false;
|
|
17
|
+
const a = Buffer.from(hashOtp(otp), 'hex');
|
|
18
|
+
const b = Buffer.from(storedHash, 'hex');
|
|
19
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
20
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CookieOptions, Request, Response } from 'express';
|
|
2
|
+
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
3
|
+
import { configs } from '../configs/index.js';
|
|
4
|
+
import { prisma } from '../lib/prisma.js';
|
|
5
|
+
import { clearAccessToken, issueAccessToken } from './auth_cookie.js';
|
|
6
|
+
import type { TokenUser } from './JWT.js';
|
|
7
|
+
|
|
8
|
+
export const REFRESH_TOKEN_COOKIE = 'refresh_token';
|
|
9
|
+
|
|
10
|
+
const refreshCookieOptions: CookieOptions = {
|
|
11
|
+
httpOnly: true,
|
|
12
|
+
secure: configs.is_production,
|
|
13
|
+
sameSite: 'lax',
|
|
14
|
+
// only sent to the auth routes (refresh, sign out), never to the rest of the API
|
|
15
|
+
path: '/api/auth',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const hashRefreshToken = (token: string) => createHash('sha256').update(token).digest('hex');
|
|
19
|
+
|
|
20
|
+
/** The refresh token from the cookie (browsers) or the body (mobile / other clients). */
|
|
21
|
+
export const readRefreshToken = (req: Request): string | undefined => {
|
|
22
|
+
const token = req.cookies?.[REFRESH_TOKEN_COOKIE] ?? req.body?.refreshToken;
|
|
23
|
+
return typeof token === 'string' && token ? token : undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Signs `user` in: a new access token plus a refresh token, both set as httpOnly
|
|
28
|
+
* cookies and returned. Pass `familyId` when rotating, so the new refresh token
|
|
29
|
+
* belongs to the same session.
|
|
30
|
+
*/
|
|
31
|
+
export const createSession = async (
|
|
32
|
+
res: Response,
|
|
33
|
+
user: TokenUser,
|
|
34
|
+
familyId: string = randomUUID(),
|
|
35
|
+
) => {
|
|
36
|
+
const refreshToken = randomBytes(32).toString('base64url');
|
|
37
|
+
const expiresAt = new Date(Date.now() + configs.jwt.refresh_expires_days * 24 * 60 * 60 * 1000);
|
|
38
|
+
|
|
39
|
+
await prisma.refreshToken.create({
|
|
40
|
+
data: {
|
|
41
|
+
tokenHash: hashRefreshToken(refreshToken),
|
|
42
|
+
accountId: user.accountId,
|
|
43
|
+
familyId,
|
|
44
|
+
tokenVersion: user.tokenVersion,
|
|
45
|
+
expiresAt,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
res.cookie(REFRESH_TOKEN_COOKIE, refreshToken, { ...refreshCookieOptions, expires: expiresAt });
|
|
50
|
+
const accessToken = issueAccessToken(res, user);
|
|
51
|
+
return { accessToken, refreshToken };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const clearSessionCookies = (res: Response) => {
|
|
55
|
+
clearAccessToken(res);
|
|
56
|
+
res.clearCookie(REFRESH_TOKEN_COOKIE, refreshCookieOptions);
|
|
57
|
+
};
|
|
@@ -1,42 +1,73 @@
|
|
|
1
|
-
import cookieParser from 'cookie-parser';
|
|
2
|
-
import cors from 'cors';
|
|
3
|
-
import express, { Request, Response } from 'express';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
app
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
app.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
app.use(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
app.use(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
import cookieParser from 'cookie-parser';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import express, { type Request, type Response } from 'express';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import swaggerJSDoc from 'swagger-jsdoc';
|
|
6
|
+
import swaggerUi from 'swagger-ui-express';
|
|
7
|
+
import { configs } from './app/configs/index.js';
|
|
8
|
+
import globalErrorHandler from './app/middlewares/global_error_handler.js';
|
|
9
|
+
import notFound from './app/middlewares/not_found_api.js';
|
|
10
|
+
import { apiRateLimiter } from './app/middlewares/rate_limiter.js';
|
|
11
|
+
import { setupPassport } from './app/passport-js/index.js';
|
|
12
|
+
import manageResponse from './app/utils/manage_response.js';
|
|
13
|
+
import appRouter from './routes.js';
|
|
14
|
+
import { swaggerOptions } from './swaggerOptions.js';
|
|
15
|
+
|
|
16
|
+
// define app
|
|
17
|
+
const app = express();
|
|
18
|
+
|
|
19
|
+
// number of reverse proxies in front of the app (e.g. 1 behind Caddy/Nginx), so
|
|
20
|
+
// req.ip and rate limiting see the real client IP. Keep 0 when exposed directly.
|
|
21
|
+
app.set('trust proxy', configs.trust_proxy);
|
|
22
|
+
app.disable('x-powered-by');
|
|
23
|
+
|
|
24
|
+
// security headers
|
|
25
|
+
app.use(helmet());
|
|
26
|
+
|
|
27
|
+
// swagger docs (disabled in production unless ENABLE_SWAGGER=true)
|
|
28
|
+
if (configs.enable_swagger) {
|
|
29
|
+
const swaggerSpec = swaggerJSDoc(swaggerOptions);
|
|
30
|
+
app.use(
|
|
31
|
+
'/docs',
|
|
32
|
+
helmet({ contentSecurityPolicy: false }),
|
|
33
|
+
swaggerUi.serve,
|
|
34
|
+
swaggerUi.setup(swaggerSpec),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// cors middleware
|
|
39
|
+
app.use(
|
|
40
|
+
cors({
|
|
41
|
+
origin: configs.cors_origins,
|
|
42
|
+
methods: ['GET', 'POST', 'PATCH', 'DELETE', 'PUT'],
|
|
43
|
+
credentials: true,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// express middleware
|
|
48
|
+
app.use(express.json({ limit: '1mb' }));
|
|
49
|
+
app.use(express.urlencoded({ extended: true, limit: '1mb' }));
|
|
50
|
+
app.use(cookieParser());
|
|
51
|
+
|
|
52
|
+
// passport js (social login)
|
|
53
|
+
setupPassport(app);
|
|
54
|
+
|
|
55
|
+
// Main router (stricter per-route limits live on the auth routes)
|
|
56
|
+
app.use('/api', apiRateLimiter, appRouter);
|
|
57
|
+
|
|
58
|
+
// stating point
|
|
59
|
+
app.get('/', (_req: Request, res: Response) => {
|
|
60
|
+
manageResponse(res, {
|
|
61
|
+
statusCode: 200,
|
|
62
|
+
success: true,
|
|
63
|
+
message: 'Server is running successful !!',
|
|
64
|
+
data: null,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// not found, then global error handler (must be last)
|
|
69
|
+
app.use(notFound);
|
|
70
|
+
app.use(globalErrorHandler);
|
|
71
|
+
|
|
72
|
+
// export app
|
|
73
|
+
export default app;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Router } from
|
|
2
|
-
import accountRouter from
|
|
3
|
-
import profileRoute from
|
|
4
|
-
|
|
5
|
-
const appRouter = Router();
|
|
6
|
-
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import accountRouter from './app/modules/account/account.route.js';
|
|
3
|
+
import profileRoute from './app/modules/profile/profile.route.js';
|
|
4
|
+
|
|
5
|
+
const appRouter = Router();
|
|
6
|
+
|
|
7
7
|
const moduleRoutes = [
|
|
8
|
-
{ path:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
{ path: '/profile', route: profileRoute },
|
|
9
|
+
{ path: '/auth', route: accountRouter },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
moduleRoutes.forEach((route) => appRouter.use(route.path, route.route));
|
|
13
|
+
export default appRouter;
|
|
@@ -1,19 +1,37 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import type { Server } from 'node:http';
|
|
2
|
+
import app from './app.js';
|
|
3
|
+
import { configs } from './app/configs/index.js';
|
|
4
|
+
import { logger } from './app/lib/logger.js';
|
|
5
|
+
import { prisma } from './app/lib/prisma.js';
|
|
6
|
+
import { startQueues, stopQueues } from './app/queues/index.js';
|
|
7
|
+
|
|
8
|
+
let server: Server | undefined;
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
await prisma.$connect();
|
|
12
|
+
await startQueues();
|
|
13
|
+
|
|
14
|
+
server = app.listen(configs.port, '0.0.0.0', () => {
|
|
15
|
+
logger.info(`Server is running on port ${configs.port}`);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function shutdown(signal: string) {
|
|
20
|
+
logger.info(`${signal} received, shutting down...`);
|
|
21
|
+
// stop accepting connections and let in-flight requests finish
|
|
22
|
+
await new Promise<void>((resolve) => (server ? server.close(() => resolve()) : resolve()));
|
|
23
|
+
await stopQueues().catch((err) => logger.error(err, 'Error stopping queues'));
|
|
24
|
+
await prisma.$disconnect();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
29
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
30
|
+
process.on('unhandledRejection', (reason) => {
|
|
31
|
+
logger.error(reason, 'Unhandled promise rejection');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
main().catch((error) => {
|
|
35
|
+
logger.error(error, 'Failed to start server');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
@@ -1,35 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { accountSwaggerDocs } from "./app/modules/account/account.swagger";
|
|
5
|
-
import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger";
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = path.dirname(__filename);
|
|
1
|
+
import { configs } from './app/configs/index.js';
|
|
2
|
+
import { accountSwaggerDocs } from './app/modules/account/account.swagger.js';
|
|
3
|
+
import { profileSwaggerDocs } from './app/modules/profile/profile.swagger.js';
|
|
9
4
|
|
|
10
5
|
export const swaggerOptions = {
|
|
11
6
|
definition: {
|
|
12
|
-
openapi:
|
|
7
|
+
openapi: '3.0.0',
|
|
13
8
|
info: {
|
|
14
|
-
title:
|
|
15
|
-
version:
|
|
16
|
-
description:
|
|
9
|
+
title: 'API Doc - Build with exp-node-server',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
description: 'Express + Prisma API with auto-generated Swagger docs',
|
|
17
12
|
},
|
|
18
13
|
paths: {
|
|
19
14
|
...accountSwaggerDocs,
|
|
20
|
-
|
|
15
|
+
...profileSwaggerDocs,
|
|
21
16
|
},
|
|
22
17
|
servers:
|
|
23
|
-
configs.env ===
|
|
24
|
-
? [
|
|
25
|
-
|
|
18
|
+
configs.env === 'production'
|
|
19
|
+
? [
|
|
20
|
+
{ url: configs.swagger_config.production_base_url },
|
|
21
|
+
{ url: configs.swagger_config.development_base_url },
|
|
22
|
+
]
|
|
23
|
+
: [
|
|
24
|
+
{ url: configs.swagger_config.development_base_url },
|
|
25
|
+
{ url: configs.swagger_config.production_base_url },
|
|
26
|
+
],
|
|
26
27
|
components: {
|
|
27
28
|
securitySchemes: {
|
|
28
29
|
AuthorizationToken: {
|
|
29
|
-
type:
|
|
30
|
-
in:
|
|
31
|
-
name:
|
|
32
|
-
description:
|
|
30
|
+
type: 'apiKey',
|
|
31
|
+
in: 'header',
|
|
32
|
+
name: 'Authorization',
|
|
33
|
+
description: 'Put your accessToken here ',
|
|
33
34
|
},
|
|
34
35
|
},
|
|
35
36
|
},
|
|
@@ -39,10 +40,5 @@ export const swaggerOptions = {
|
|
|
39
40
|
},
|
|
40
41
|
],
|
|
41
42
|
},
|
|
42
|
-
apis: [
|
|
43
|
-
path.join(
|
|
44
|
-
__dirname,
|
|
45
|
-
configs.env === "production" ? "./**/*.js" : "./**/*.ts",
|
|
46
|
-
),
|
|
47
|
-
],
|
|
43
|
+
apis: [],
|
|
48
44
|
};
|