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
@@ -0,0 +1,185 @@
1
+ export const otpEmailTemplate = (payload: { otp: string; verificationLink: string }) => {
2
+ return `
3
+
4
+ <!DOCTYPE html>
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="UTF-8">
8
+ <title>Verify Your Email</title>
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
+ <style>
11
+ * {
12
+ margin: 0;
13
+ padding: 0;
14
+ box-sizing: border-box;
15
+ }
16
+ body {
17
+ font-family: Arial, Helvetica, sans-serif;
18
+ background-color: #f1f5f9;
19
+ color: #1e2937;
20
+ line-height: 1.6;
21
+ }
22
+ .container {
23
+ max-width: 600px;
24
+ margin: 30px auto;
25
+ background: #ffffff;
26
+ border-radius: 12px;
27
+ overflow: hidden;
28
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08);
29
+ }
30
+
31
+ /* Top Header */
32
+ .header {
33
+ background: linear-gradient(135deg, #ff4d94, #ff6b6b);
34
+ padding: 40px 20px;
35
+ text-align: center;
36
+ color: white;
37
+ }
38
+ .envelope {
39
+ width: 78px;
40
+ height: 78px;
41
+ background: #fff;
42
+ color: #ff4d94;
43
+ border-radius: 50%;
44
+ display: inline-flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ font-size: 36px;
48
+ margin-bottom: 12px;
49
+ }
50
+
51
+ .content {
52
+ padding: 40px 30px;
53
+ text-align: center;
54
+ }
55
+ .title {
56
+ font-size: 22px;
57
+ font-weight: 600;
58
+ margin-bottom: 6px;
59
+ }
60
+ .subtitle {
61
+ font-size: 15px;
62
+ color: #64748b;
63
+ margin-bottom: 30px;
64
+ }
65
+
66
+ /* OTP Table */
67
+ .otp-table {
68
+ margin: 25px auto 30px auto;
69
+ }
70
+ .otp-table td {
71
+ padding: 0 6px;
72
+ }
73
+ .otp-box {
74
+ width: 52px;
75
+ height: 62px;
76
+ background: #f8fafc;
77
+ border: 2px solid #e2e8f0;
78
+ border-radius: 8px;
79
+ font-size: 26px;
80
+ font-weight: 700;
81
+ color: #1e2937;
82
+ text-align: center;
83
+ vertical-align: middle;
84
+ line-height: 60px;
85
+ }
86
+
87
+ .expiry {
88
+ color: #ef4444;
89
+ font-size: 14.5px;
90
+ font-weight: 600;
91
+ margin-bottom: 35px;
92
+ }
93
+
94
+ /* Button */
95
+ .btn {
96
+ display: inline-block;
97
+ background: #4f46e5;
98
+ color: #ffffff !important;
99
+ padding: 16px 40px;
100
+ font-size: 16px;
101
+ font-weight: 600;
102
+ text-decoration: none;
103
+ border-radius: 10px;
104
+ margin: 15px 0;
105
+ }
106
+
107
+ /* Footer - Gradient + Logo Fixed */
108
+ .footer {
109
+ background: linear-gradient(135deg, #ff4d94, #ff6b6b);
110
+ padding: 45px 30px 35px;
111
+ text-align: center;
112
+ color: white;
113
+ }
114
+ </style>
115
+ </head>
116
+ <body>
117
+ <div class="container">
118
+ <!-- Top Header -->
119
+ <div class="header">
120
+ <div class="envelope">✉️</div>
121
+ <h1 style="font-size: 26px; margin: 0;">Email Verification</h1>
122
+ </div>
123
+
124
+ <!-- Content -->
125
+ <div class="content">
126
+ <p class="title">Here is your One Time Password</p>
127
+ <p class="subtitle">to validate your email address</p>
128
+
129
+ <table class="otp-table" cellpadding="0" cellspacing="0" role="presentation">
130
+ <tr>
131
+ <td><div class="otp-box">${payload.otp[0]}</div></td>
132
+ <td><div class="otp-box">${payload.otp[1]}</div></td>
133
+ <td><div class="otp-box">${payload.otp[2]}</div></td>
134
+ <td><div class="otp-box">${payload.otp[3]}</div></td>
135
+ <td><div class="otp-box">${payload.otp[4]}</div></td>
136
+ <td><div class="otp-box">${payload.otp[5]}</div></td>
137
+ </tr>
138
+ </table>
139
+
140
+ <p class="expiry">OTP will expire in <strong>5 minutes</strong></p>
141
+
142
+ <hr style="border: none; border-top: 1px solid #e2e8f0; margin: 35px 0;">
143
+
144
+ <p style="font-size: 15px; color: #475569; margin-bottom: 12px;">
145
+ Or click the button below to verify instantly:
146
+ </p>
147
+
148
+ <a href="${payload.verificationLink}" class="btn">
149
+ Verify My Email
150
+ </a>
151
+ </div>
152
+
153
+ <!-- Footer with Logo -->
154
+ <div class="footer">
155
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
156
+ <tr>
157
+ <td align="center">
158
+ <!-- Logo -->
159
+ <img src="https://i.ibb.co.com/TnKvMyg/democompany.png"
160
+ style="width: 75px; height: 75px; border-radius: 50%; display: block; margin: 0 auto 15px auto;"
161
+ alt="Company Logo">
162
+
163
+ <!-- Company Name -->
164
+ <h3 style="color: #ffffff; font-size: 24px; margin: 0 0 8px 0;">Company Name</h3>
165
+ <p style="color: #ffffff; opacity: 0.95; margin: 0 0 25px 0;">The Support Team</p>
166
+
167
+ <!-- Footer Text -->
168
+ <p style="color: #ffffff; font-size: 13.5px; opacity: 0.92; line-height: 1.5;">
169
+ This is an automated message — please do not reply.<br>
170
+ If you need assistance, contact our support team.
171
+ </p>
172
+
173
+ <p style="color: #ffffff; font-size: 12px; opacity: 0.85; margin-top: 25px;">
174
+ &copy; ${new Date().getFullYear()} Your Company. All rights reserved.
175
+ </p>
176
+ </td>
177
+ </tr>
178
+ </table>
179
+ </div>
180
+ </div>
181
+ </body>
182
+ </html>
183
+
184
+ `;
185
+ };
@@ -0,0 +1,157 @@
1
+ export const resetPasswordEmailTemplate = (resetLink: string, expiresIn: string) => {
2
+ return `
3
+ <!DOCTYPE html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <title>Reset Your Password</title>
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+ <style>
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+ body {
16
+ font-family: Arial, Helvetica, sans-serif;
17
+ background-color: #f1f5f9;
18
+ color: #1e2937;
19
+ line-height: 1.6;
20
+ }
21
+ .container {
22
+ max-width: 600px;
23
+ margin: 30px auto;
24
+ background: #ffffff;
25
+ border-radius: 12px;
26
+ overflow: hidden;
27
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08);
28
+ }
29
+
30
+ /* Header - Pink/Red Gradient */
31
+ .header {
32
+ background: linear-gradient(135deg, #ff4d94, #ff6b6b);
33
+ padding: 45px 30px;
34
+ text-align: center;
35
+ color: white;
36
+ }
37
+ .icon {
38
+ width: 80px;
39
+ height: 80px;
40
+ background: #fff;
41
+ color: #ff4d94;
42
+ border-radius: 50%;
43
+ display: inline-flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ font-size: 42px;
47
+ margin-bottom: 15px;
48
+ box-shadow: 0 8px 25px rgba(0,0,0,0.2);
49
+ }
50
+
51
+ .content {
52
+ padding: 45px 35px;
53
+ text-align: center;
54
+ }
55
+ .title {
56
+ font-size: 24px;
57
+ font-weight: 700;
58
+ margin-bottom: 12px;
59
+ color: #1e2937;
60
+ }
61
+ .message {
62
+ font-size: 16px;
63
+ color: #475569;
64
+ margin-bottom: 35px;
65
+ }
66
+
67
+ /* Button */
68
+ .btn {
69
+ display: inline-block;
70
+ background: #4f46e5;
71
+ color: #ffffff !important;
72
+ padding: 16px 42px;
73
+ font-size: 17px;
74
+ font-weight: 600;
75
+ text-decoration: none;
76
+ border-radius: 10px;
77
+ margin: 15px 0 25px 0;
78
+ box-shadow: 0 6px 20px rgba(79, 70, 229, 0.3);
79
+ }
80
+
81
+ /* Footer with Gradient - Same as OTP */
82
+ .footer {
83
+ background: linear-gradient(135deg, #ff4d94, #ff6b6b);
84
+ padding: 45px 30px;
85
+ text-align: center;
86
+ color: white;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <div class="container">
92
+ <!-- Header -->
93
+ <div class="header">
94
+ <div class="icon">🔑</div>
95
+ <h1 style="font-size: 28px; margin: 0;">Reset Your Password</h1>
96
+ </div>
97
+
98
+ <!-- Content -->
99
+ <div class="content">
100
+ <p class="title">Hello,</p>
101
+ <p class="message">
102
+ You requested to reset your password. Click the button below to create a new password.
103
+ </p>
104
+
105
+ <!-- Reset Button -->
106
+ <a href="${resetLink}" class="btn">
107
+ Reset Password
108
+ </a>
109
+
110
+ <p style="font-size: 15px; color: #64748b; margin: 20px 0 10px 0;">
111
+ Or copy and paste this link in your browser:
112
+ </p>
113
+ <div style="font-size: 14px; color: #64748b; word-break: break-all;">
114
+ <a href="${resetLink}" style="color: #3b82f6; text-decoration: none;">
115
+ ${resetLink}
116
+ </a>
117
+ </div>
118
+
119
+ <!-- Warning -->
120
+ <div style="background: #fef3c7; color: #92400e; padding: 16px 20px; border-radius: 8px; font-size: 14px; margin: 35px 0; text-align: left;">
121
+ <strong>Note:</strong> This password reset link will expire in <strong>${expiresIn}</strong> for your security.
122
+ If you didn't request this, please ignore this email.
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Footer with Gradient + Proper Logo Alignment -->
127
+ <div class="footer">
128
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation">
129
+ <tr>
130
+ <td align="center">
131
+ <!-- Logo -->
132
+ <img src="https://i.ibb.co.com/TnKvMyg/democompany.png"
133
+ style="width: 75px; height: 75px; border-radius: 50%; display: block; margin: 0 auto 15px auto;"
134
+ alt="Company Logo">
135
+
136
+ <!-- Company Info -->
137
+ <h3 style="color: #ffffff; font-size: 24px; margin: 0 0 8px 0;">Company Name</h3>
138
+ <p style="color: #ffffff; margin: 0 0 25px 0; opacity: 0.95;">The Support Team</p>
139
+
140
+ <!-- Footer Text -->
141
+ <p style="color: #ffffff; font-size: 13.5px; line-height: 1.5; opacity: 0.92;">
142
+ This is an automated message — please do not reply to this email.<br>
143
+ If you need help, feel free to contact our support.
144
+ </p>
145
+
146
+ <p style="color: #ffffff; font-size: 12px; margin-top: 25px; opacity: 0.85;">
147
+ &copy; ${new Date().getFullYear()} Your Company. All rights reserved.
148
+ </p>
149
+ </td>
150
+ </tr>
151
+ </table>
152
+ </div>
153
+ </div>
154
+ </body>
155
+ </html>
156
+ `;
157
+ };
@@ -1,10 +1,10 @@
1
1
  export type TErrorSources = {
2
- path: string | number
3
- message: string
4
- }[]
2
+ path: string | number;
3
+ message: string;
4
+ }[];
5
5
 
6
6
  export type TGenericErrorResponse = {
7
- statusCode: number
8
- message: string
9
- errorSources: TErrorSources
10
- }
7
+ statusCode: number;
8
+ message: string;
9
+ errorSources: TErrorSources;
10
+ };
@@ -0,0 +1,11 @@
1
+ import type { AuthUser } from '../utils/JWT.js';
2
+
3
+ declare global {
4
+ namespace Express {
5
+ // `req.user` is set by the auth middleware (from the JWT) and by passport (social login)
6
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
7
+ interface User extends AuthUser {}
8
+ }
9
+ }
10
+
11
+ export {};
@@ -0,0 +1,51 @@
1
+ // passport-oauth1 ships no typings; this covers what providers.ts uses
2
+ declare module 'passport-oauth1' {
3
+ import type { Profile, Strategy as PassportStrategy } from 'passport';
4
+
5
+ type OAuthClient = {
6
+ get(
7
+ url: string,
8
+ token: string,
9
+ tokenSecret: string,
10
+ callback: (
11
+ err: { statusCode: number; data?: unknown } | null,
12
+ body?: string | Buffer,
13
+ ) => void,
14
+ ): void;
15
+ };
16
+
17
+ export interface StrategyOptions {
18
+ requestTokenURL: string;
19
+ accessTokenURL: string;
20
+ userAuthorizationURL: string;
21
+ consumerKey: string;
22
+ consumerSecret: string;
23
+ callbackURL: string;
24
+ requestTokenStore?: object;
25
+ }
26
+
27
+ export class InternalOAuthError extends Error {
28
+ constructor(message: string, err: unknown);
29
+ }
30
+
31
+ export default class OAuth1Strategy extends PassportStrategy {
32
+ constructor(
33
+ options: StrategyOptions,
34
+ verify: (
35
+ token: string,
36
+ tokenSecret: string,
37
+ profile: Profile,
38
+ done: (err: unknown, user?: Express.User | false) => void,
39
+ ) => void,
40
+ );
41
+ name: string;
42
+ protected _oauth: OAuthClient;
43
+ authenticate(req: import('express').Request, options?: object): void;
44
+ userProfile(
45
+ token: string,
46
+ tokenSecret: string,
47
+ params: unknown,
48
+ done: (err?: unknown, profile?: Profile) => void,
49
+ ): void;
50
+ }
51
+ }
@@ -1,26 +1,46 @@
1
- import jwt, { JwtPayload, Secret, SignOptions } from 'jsonwebtoken';
1
+ import type { ROLE } from '../modules/account/account.interface.js';
2
+ import jwt, { type JwtPayload, type Secret, type SignOptions } from 'jsonwebtoken';
3
+ import { AppError } from './app_error.js';
2
4
 
3
- const generateToken = (payload: object, secret: Secret, expiresIn: string) => {
4
- const token = jwt.sign(payload, secret, {
5
- algorithm: 'HS256',
6
- expiresIn: expiresIn,
7
- } as SignOptions);
5
+ const ALGORITHM = 'HS256';
8
6
 
9
- return token;
7
+ /** The authenticated user, as stored in access tokens and on `req.user`. */
8
+ export type AuthUser = {
9
+ accountId: string;
10
+ email: string;
11
+ role: ROLE;
10
12
  };
11
13
 
12
- const verifyToken = (token: string, secret: Secret): JwtPayload => {
13
- return jwt.verify(token, secret) as JwtPayload;
14
+ /** A user plus the account's current token version, enough to sign an access token. */
15
+ export type TokenUser = AuthUser & { tokenVersion: number };
16
+
17
+ /** `tv` must match the account's `tokenVersion`, otherwise the token was revoked. */
18
+ export type JwtPayloadType = JwtPayload & AuthUser & { tv: number };
19
+
20
+ const generateToken = (payload: object, secret: Secret, expiresIn: string) =>
21
+ jwt.sign(payload, secret, {
22
+ algorithm: ALGORITHM,
23
+ expiresIn: expiresIn as SignOptions['expiresIn'],
24
+ });
25
+
26
+ /** Verifies a token, throwing a clean 401 instead of jsonwebtoken's raw errors. */
27
+ const verifyToken = <T extends object = JwtPayload>(
28
+ token: string,
29
+ secret: Secret,
30
+ ): T & JwtPayload => {
31
+ try {
32
+ const decoded = jwt.verify(token, secret, { algorithms: [ALGORITHM] });
33
+ if (typeof decoded === 'string') throw new Error('unexpected payload');
34
+ return decoded as T & JwtPayload;
35
+ } catch (err) {
36
+ if (err instanceof jwt.TokenExpiredError) {
37
+ throw new AppError('Token expired, please try again', 401);
38
+ }
39
+ throw new AppError('Invalid token', 401);
40
+ }
14
41
  };
15
42
 
16
43
  export const jwtHelpers = {
17
- generateToken,
18
- verifyToken,
19
- };
20
- export type JwtPayloadType = JwtPayload & {
21
- email: string;
22
- role: string;
23
- iat: number;
24
- exp: number;
44
+ generateToken,
45
+ verifyToken,
25
46
  };
26
- export type JwtTokenType = string | JwtPayloadType | null;
@@ -1,12 +1,12 @@
1
1
  export class AppError extends Error {
2
- public statusCode: number;
3
- constructor(message: string, statusCode: number, stack = '') {
4
- super(message);
5
- this.statusCode = statusCode;
6
- if (stack) {
7
- this.stack = stack;
8
- } else {
9
- Error.captureStackTrace(this, this.constructor);
10
- }
2
+ public statusCode: number;
3
+ constructor(message: string, statusCode: number, stack = '') {
4
+ super(message);
5
+ this.statusCode = statusCode;
6
+ if (stack) {
7
+ this.stack = stack;
8
+ } else {
9
+ Error.captureStackTrace(this, this.constructor);
11
10
  }
12
- }
11
+ }
12
+ }
@@ -0,0 +1,27 @@
1
+ import type { CookieOptions, Response } from 'express';
2
+ import { configs } from '../configs/index.js';
3
+ import { type TokenUser, jwtHelpers } from './JWT.js';
4
+
5
+ export const ACCESS_TOKEN_COOKIE = 'access_token';
6
+
7
+ export const accessTokenCookieOptions: CookieOptions = {
8
+ httpOnly: true,
9
+ secure: configs.is_production,
10
+ sameSite: 'lax',
11
+ path: '/',
12
+ };
13
+
14
+ /** Signs an access token for `user`, sets it as a cookie and returns it. */
15
+ export const issueAccessToken = (res: Response, user: TokenUser): string => {
16
+ const accessToken = jwtHelpers.generateToken(
17
+ { accountId: user.accountId, email: user.email, role: user.role, tv: user.tokenVersion },
18
+ configs.jwt.access_token,
19
+ configs.jwt.access_expires,
20
+ );
21
+ res.cookie(ACCESS_TOKEN_COOKIE, accessToken, accessTokenCookieOptions);
22
+ return accessToken;
23
+ };
24
+
25
+ export const clearAccessToken = (res: Response) => {
26
+ res.clearCookie(ACCESS_TOKEN_COOKIE, accessTokenCookieOptions);
27
+ };
@@ -1,13 +1,13 @@
1
1
  import { RequestHandler, Request, Response, NextFunction } from 'express';
2
2
 
3
3
  const catchAsync = (fn: RequestHandler): RequestHandler => {
4
- return async (req: Request, res: Response, next: NextFunction) => {
5
- try {
6
- await fn(req, res, next);
7
- } catch (error) {
8
- next(error);
9
- }
10
- };
4
+ return async (req: Request, res: Response, next: NextFunction) => {
5
+ try {
6
+ await fn(req, res, next);
7
+ } catch (error) {
8
+ next(error);
9
+ }
10
+ };
11
11
  };
12
12
 
13
- export default catchAsync;
13
+ export default catchAsync;
@@ -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;