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.
Files changed (181) hide show
  1. package/README.md +82 -20
  2. package/dist/cli.js +525 -0
  3. package/package.json +29 -9
  4. package/templates/mongoose/.dockerignore +10 -0
  5. package/templates/mongoose/.env.example +56 -7
  6. package/templates/mongoose/.prettierignore +3 -0
  7. package/templates/mongoose/.prettierrc +7 -0
  8. package/templates/mongoose/Dockerfile +33 -0
  9. package/templates/mongoose/_gitignore +32 -0
  10. package/templates/mongoose/docker-compose.yml +21 -0
  11. package/templates/mongoose/eslint.config.js +35 -0
  12. package/templates/mongoose/package-lock.json +6377 -0
  13. package/templates/mongoose/package.json +49 -10
  14. package/templates/mongoose/readme.md +85 -0
  15. package/templates/mongoose/src/app/configs/index.ts +178 -26
  16. package/templates/mongoose/src/app/errors/zodError.ts +16 -16
  17. package/templates/mongoose/src/app/lib/db.ts +18 -0
  18. package/templates/mongoose/src/app/lib/logger.ts +12 -0
  19. package/templates/mongoose/src/app/middlewares/auth.ts +48 -44
  20. package/templates/mongoose/src/app/middlewares/global_error_handler.ts +59 -65
  21. package/templates/mongoose/src/app/middlewares/not_found_api.ts +7 -7
  22. package/templates/mongoose/src/app/middlewares/rate_limiter.ts +153 -0
  23. package/templates/mongoose/src/app/middlewares/request_validator.ts +11 -10
  24. package/templates/mongoose/src/app/middlewares/uploader.ts +17 -13
  25. package/templates/mongoose/src/app/modules/account/account.controller.ts +156 -0
  26. package/templates/mongoose/src/app/modules/account/account.interface.ts +65 -0
  27. package/templates/mongoose/src/app/modules/account/account.route.ts +101 -0
  28. package/templates/mongoose/src/app/modules/account/account.schema.ts +97 -0
  29. package/templates/mongoose/src/app/modules/account/account.service.ts +422 -0
  30. package/templates/mongoose/src/app/modules/account/account.swagger.ts +278 -0
  31. package/templates/mongoose/src/app/modules/account/account.validation.ts +63 -0
  32. package/templates/mongoose/src/app/modules/profile/profile.controller.ts +17 -0
  33. package/templates/mongoose/src/app/modules/profile/profile.interface.ts +7 -0
  34. package/templates/mongoose/src/app/modules/profile/profile.route.ts +28 -0
  35. package/templates/mongoose/src/app/modules/profile/profile.schema.ts +13 -0
  36. package/templates/mongoose/src/app/modules/profile/profile.service.ts +28 -0
  37. package/templates/mongoose/src/app/modules/profile/profile.swagger.ts +34 -0
  38. package/templates/mongoose/src/app/modules/profile/profile.validation.ts +8 -0
  39. package/templates/mongoose/src/app/passport-js/index.ts +87 -0
  40. package/templates/mongoose/src/app/passport-js/oauth-state.ts +150 -0
  41. package/templates/mongoose/src/app/passport-js/providers.ts +258 -0
  42. package/templates/mongoose/src/app/passport-js/social-account.ts +141 -0
  43. package/templates/mongoose/src/app/queues/agenda.ts +24 -0
  44. package/templates/mongoose/src/app/queues/email.queue.ts +53 -0
  45. package/templates/mongoose/src/app/queues/index.ts +13 -0
  46. package/templates/mongoose/src/app/templates/otpEmailTemplate.ts +185 -0
  47. package/templates/mongoose/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
  48. package/templates/mongoose/src/app/types/error.d.ts +7 -7
  49. package/templates/mongoose/src/app/types/express.d.ts +11 -0
  50. package/templates/mongoose/src/app/types/passport-oauth1.d.ts +51 -0
  51. package/templates/mongoose/src/app/utils/JWT.ts +38 -18
  52. package/templates/mongoose/src/app/utils/app_error.ts +10 -10
  53. package/templates/mongoose/src/app/utils/auth_cookie.ts +27 -0
  54. package/templates/mongoose/src/app/utils/catch_async.ts +8 -8
  55. package/templates/mongoose/src/app/utils/cloudinary.ts +26 -59
  56. package/templates/mongoose/src/app/utils/mail_sender.ts +26 -92
  57. package/templates/mongoose/src/app/utils/manage_response.ts +19 -19
  58. package/templates/mongoose/src/app/utils/otp.ts +20 -0
  59. package/templates/mongoose/src/app/utils/session.ts +55 -0
  60. package/templates/mongoose/src/app.ts +73 -42
  61. package/templates/mongoose/src/routes.ts +13 -13
  62. package/templates/mongoose/src/server.ts +38 -18
  63. package/templates/mongoose/src/swaggerOptions.ts +23 -23
  64. package/templates/mongoose/tests/app.test.ts +97 -0
  65. package/templates/mongoose/tests/global-setup.ts +15 -0
  66. package/templates/mongoose/tests/helpers/fixtures.ts +42 -0
  67. package/templates/mongoose/tests/routes/account.test.ts +614 -0
  68. package/templates/mongoose/tests/routes/profile.test.ts +95 -0
  69. package/templates/mongoose/tests/routes/social.test.ts +310 -0
  70. package/templates/mongoose/tests/setup.ts +25 -0
  71. package/templates/mongoose/tsconfig.json +16 -20
  72. package/templates/mongoose/vitest.config.ts +43 -0
  73. package/templates/prisma/.dockerignore +2 -0
  74. package/templates/prisma/.env.example +62 -6
  75. package/templates/prisma/.prettierignore +3 -0
  76. package/templates/prisma/.prettierrc +7 -0
  77. package/templates/prisma/.projectrc.json +1 -1
  78. package/templates/prisma/Dockerfile +21 -7
  79. package/templates/prisma/_gitignore +35 -0
  80. package/templates/prisma/docker-compose.yml +8 -7
  81. package/templates/prisma/eslint.config.js +35 -0
  82. package/templates/prisma/package-lock.json +7327 -0
  83. package/templates/prisma/package.json +47 -9
  84. package/templates/prisma/prisma/schema/account.schema.prisma +92 -10
  85. package/templates/prisma/prisma/schema/profile.schema.prisma +8 -3
  86. package/templates/prisma/prisma/schema/schema.prisma +1 -2
  87. package/templates/prisma/prisma.config.ts +5 -5
  88. package/templates/prisma/readme.md +81 -0
  89. package/templates/prisma/src/app/configs/index.ts +175 -26
  90. package/templates/prisma/src/app/errors/zodError.ts +16 -16
  91. package/templates/prisma/src/app/lib/logger.ts +12 -0
  92. package/templates/prisma/src/app/lib/prisma.ts +4 -6
  93. package/templates/prisma/src/app/middlewares/auth.ts +37 -16
  94. package/templates/prisma/src/app/middlewares/global_error_handler.ts +42 -35
  95. package/templates/prisma/src/app/middlewares/not_found_api.ts +7 -7
  96. package/templates/prisma/src/app/middlewares/rate_limiter.ts +124 -0
  97. package/templates/prisma/src/app/middlewares/request_validator.ts +4 -3
  98. package/templates/prisma/src/app/middlewares/uploader.ts +17 -13
  99. package/templates/prisma/src/app/modules/account/account.controller.ts +74 -34
  100. package/templates/prisma/src/app/modules/account/account.route.ts +68 -22
  101. package/templates/prisma/src/app/modules/account/account.service.ts +329 -309
  102. package/templates/prisma/src/app/modules/account/account.swagger.ts +167 -76
  103. package/templates/prisma/src/app/modules/account/account.validation.ts +41 -19
  104. package/templates/prisma/src/app/modules/profile/profile.controller.ts +4 -4
  105. package/templates/prisma/src/app/modules/profile/profile.route.ts +18 -12
  106. package/templates/prisma/src/app/modules/profile/profile.service.ts +11 -18
  107. package/templates/prisma/src/app/modules/profile/profile.swagger.ts +11 -11
  108. package/templates/prisma/src/app/modules/profile/profile.validation.ts +2 -2
  109. package/templates/prisma/src/app/passport-js/index.ts +87 -0
  110. package/templates/prisma/src/app/passport-js/oauth-state.ts +150 -0
  111. package/templates/prisma/src/app/passport-js/providers.ts +263 -0
  112. package/templates/prisma/src/app/passport-js/social-account.ts +147 -0
  113. package/templates/prisma/src/app/queues/boss.ts +8 -0
  114. package/templates/prisma/src/app/queues/email.queue.ts +56 -0
  115. package/templates/prisma/src/app/queues/index.ts +14 -0
  116. package/templates/prisma/src/app/queues/maintenance.queue.ts +30 -0
  117. package/templates/prisma/src/app/templates/otpEmailTemplate.ts +185 -0
  118. package/templates/prisma/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
  119. package/templates/prisma/src/app/types/error.d.ts +7 -7
  120. package/templates/prisma/src/app/types/express.d.ts +11 -0
  121. package/templates/prisma/src/app/types/passport-oauth1.d.ts +51 -0
  122. package/templates/prisma/src/app/utils/JWT.ts +36 -17
  123. package/templates/prisma/src/app/utils/app_error.ts +10 -10
  124. package/templates/prisma/src/app/utils/auth_cookie.ts +27 -0
  125. package/templates/prisma/src/app/utils/catch_async.ts +8 -8
  126. package/templates/prisma/src/app/utils/cloudinary.ts +26 -59
  127. package/templates/prisma/src/app/utils/mail_sender.ts +26 -92
  128. package/templates/prisma/src/app/utils/manage_response.ts +19 -19
  129. package/templates/prisma/src/app/utils/otp.ts +20 -0
  130. package/templates/prisma/src/app/utils/session.ts +57 -0
  131. package/templates/prisma/src/app.ts +73 -42
  132. package/templates/prisma/src/routes.ts +12 -10
  133. package/templates/prisma/src/server.ts +37 -19
  134. package/templates/prisma/src/swaggerOptions.ts +22 -26
  135. package/templates/prisma/tests/app.test.ts +86 -0
  136. package/templates/prisma/tests/helpers/fixtures.ts +35 -0
  137. package/templates/prisma/tests/helpers/prisma.ts +32 -0
  138. package/templates/prisma/tests/routes/account.test.ts +627 -0
  139. package/templates/prisma/tests/routes/profile.test.ts +91 -0
  140. package/templates/prisma/tests/routes/social.test.ts +317 -0
  141. package/templates/prisma/tests/setup.ts +10 -0
  142. package/templates/prisma/tsconfig.json +16 -23
  143. package/templates/prisma/vitest.config.ts +38 -0
  144. package/bin/cli.js +0 -41
  145. package/src/createProject.js +0 -61
  146. package/src/generateModule.js +0 -93
  147. package/src/utils/getProjectType.js +0 -13
  148. package/src/utils/mongoose/controllerGenerator.js +0 -70
  149. package/src/utils/mongoose/routeGenerator.js +0 -27
  150. package/src/utils/mongoose/schemaGenerator.js +0 -11
  151. package/src/utils/mongoose/serviceGenerator.js +0 -49
  152. package/src/utils/mongoose/swaggerGenerator.js +0 -112
  153. package/src/utils/mongoose/validationGenerator.js +0 -13
  154. package/src/utils/prisma/prismaSchemaGenerator.js +0 -10
  155. package/src/utils/prisma/prismaServiceGenerator.js +0 -47
  156. package/src/utils/routeInjector.js +0 -40
  157. package/src/utils/swaggerInjector.js +0 -52
  158. package/src/utils/validateModuleName.js +0 -17
  159. package/src/utils/validateProjectName.js +0 -15
  160. package/src/utils.js +0 -26
  161. package/templates/mongoose/src/app/errors/castError.ts +0 -23
  162. package/templates/mongoose/src/app/errors/duplicateError.ts +0 -26
  163. package/templates/mongoose/src/app/errors/validationError.ts +0 -25
  164. package/templates/mongoose/src/app/modules/auth/auth.controller.ts +0 -127
  165. package/templates/mongoose/src/app/modules/auth/auth.interface.ts +0 -24
  166. package/templates/mongoose/src/app/modules/auth/auth.route.ts +0 -46
  167. package/templates/mongoose/src/app/modules/auth/auth.schema.ts +0 -19
  168. package/templates/mongoose/src/app/modules/auth/auth.service.ts +0 -324
  169. package/templates/mongoose/src/app/modules/auth/auth.swagger.ts +0 -214
  170. package/templates/mongoose/src/app/modules/auth/auth.validation.ts +0 -37
  171. package/templates/mongoose/src/app/modules/user/user.controller.ts +0 -17
  172. package/templates/mongoose/src/app/modules/user/user.interface.ts +0 -15
  173. package/templates/mongoose/src/app/modules/user/user.route.ts +0 -20
  174. package/templates/mongoose/src/app/modules/user/user.schema.ts +0 -22
  175. package/templates/mongoose/src/app/modules/user/user.service.ts +0 -22
  176. package/templates/mongoose/src/app/modules/user/user.swagger.ts +0 -75
  177. package/templates/mongoose/src/app/modules/user/user.validation.ts +0 -18
  178. package/templates/mongoose/src/app/types/index.d.ts +0 -12
  179. package/templates/mongoose/src/app/utils/isAccountExist.ts +0 -31
  180. package/templates/prisma/src/app/types/index.d.ts +0 -12
  181. package/templates/prisma/src/app/utils/otpGenerator.ts +0 -29
@@ -1,66 +1,33 @@
1
- import { v2 as cloudinary } from 'cloudinary';
2
- import fs from 'fs';
3
- import { configs } from '../configs';
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
- type ICloudinaryResponse = {
6
- asset_id: string;
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
- cloud_name: configs.cloudinary.cloud_name!,
44
- api_key: configs.cloudinary.cloud_api_key!,
45
- api_secret: configs.cloudinary.cloud_api_secret!,
9
+ cloud_name,
10
+ api_key: cloud_api_key,
11
+ api_secret: cloud_api_secret,
46
12
  });
47
13
 
48
- const uploadCloud = async (
49
- file: IFile
50
- ): Promise<ICloudinaryResponse | undefined> => {
51
- return new Promise((resolve, reject) => {
52
- cloudinary.uploader.upload(
53
- file.path,
54
- (error: Error, result: ICloudinaryResponse) => {
55
- fs.unlinkSync(file.path);
56
- if (error) {
57
- reject(error);
58
- } else {
59
- resolve(result);
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
- to: string,
5
- subject: string,
6
- textBody: string,
7
- htmlBody: string,
8
- name?: string
9
- }
5
+ to: string;
6
+ subject: string;
7
+ textBody: string;
8
+ htmlBody: string;
9
+ };
10
10
 
11
11
  const transporter = nodemailer.createTransport({
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
- },
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
- // ✅ Email Sender Function
21
+ // Email Sender Function
22
22
  const sendMail = async (payload: TMailContent) => {
23
- const info = await transporter.sendMail({
24
- from: 'info@digitalcreditai.com',
25
- to: payload.to,
26
- subject: payload.subject,
27
- text: payload.textBody,
28
- html: `
29
- <!DOCTYPE html>
30
- <html lang="en">
31
-
32
- <head>
33
- <meta charset="UTF-8">
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
- &copy; {{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 "express"
1
+ import { Response } from 'express';
2
2
  interface IResponse<T> {
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
- }
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
- res.status(payload.statusCode).json({
17
- success: payload.success,
18
- message: payload.message,
19
- data: payload.data || undefined || null,
20
- meta: payload.meta || undefined || null
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 swaggerJSDoc from 'swagger-jsdoc';
5
- import swaggerUi from "swagger-ui-express";
6
- import globalErrorHandler from './app/middlewares/global_error_handler';
7
- import notFound from './app/middlewares/not_found_api';
8
- import appRouter from './routes';
9
- import { swaggerOptions } from './swaggerOptions';
10
-
11
- // define app
12
- const app = express()
13
- const swaggerSpec = swaggerJSDoc(swaggerOptions);
14
- app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
15
-
16
- // middleware
17
- app.use(cors({
18
- origin: ["http://localhost:3000"],
19
- methods: ["GET", "POST", "PATCH", "DELETE", "PUT"],
20
- credentials: true
21
- }))
22
- app.use(express.json({ limit: "100mb" }))
23
- app.use(express.raw())
24
- app.use(cookieParser())
25
- app.use(express.urlencoded({ extended: true }));
26
- app.use("/api", appRouter)
27
-
28
- // stating point
29
- app.get('/', (req: Request, res: Response) => {
30
- res.status(200).json({
31
- status: 'success',
32
- message: 'Server is running successful !!',
33
- data: null,
34
- });
35
- });
36
-
37
- // global error handler
38
- app.use(globalErrorHandler);
39
- app.use(notFound);
40
-
41
- // export app
42
- export default app;
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 "express";
2
- import accountRouter from "./app/modules/account/account.route";
3
- import profileRoute from "./app/modules/profile/profile.route";
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: "/profile", route: profileRoute },{ path: "/auth", route: accountRouter }];
9
-
10
- moduleRoutes.forEach((route) => appRouter.use(route.path, route.route));
11
- export default appRouter;
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 app from "./app";
2
- import { configs } from "./app/configs/index";
3
- import { prisma } from "./app/lib/prisma";
4
-
5
- async function main() {
6
- try {
7
- app.listen(configs.port, async () => {
8
- await prisma.$connect();
9
- console.log(`Server is running on port ${configs.port}`);
10
- });
11
- } catch (error) {
12
- console.error("Error starting server:", error);
13
- }
14
- }
15
-
16
- main().catch((error) => {
17
- console.error("Error in main function:", error);
18
- process.exit(1);
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 { fileURLToPath } from "node:url";
2
- import path from "path";
3
- import { configs } from "./app/configs";
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: "3.0.0",
7
+ openapi: '3.0.0',
13
8
  info: {
14
- title: "API Doc - Build with exp-node-server",
15
- version: "1.0.0",
16
- description: "Express + Prisma API with auto-generated Swagger docs",
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
- ...profileSwaggerDocs,
15
+ ...profileSwaggerDocs,
21
16
  },
22
17
  servers:
23
- configs.env === "production"
24
- ? [{ url: "https://live-url.com" }, { url: "http://localhost:5000" }]
25
- : [{ url: "http://localhost:5000" }, { url: "https://live-url.com" }],
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: "apiKey",
30
- in: "header",
31
- name: "Authorization",
32
- description: "Put your accessToken here ",
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
  };