@bloomneo/appkit 1.2.9

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +902 -0
  3. package/bin/appkit.js +71 -0
  4. package/bin/commands/generate.js +1050 -0
  5. package/bin/templates/backend/README.md.template +39 -0
  6. package/bin/templates/backend/api.http.template +0 -0
  7. package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
  8. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
  9. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
  10. package/bin/templates/backend/package.json.template +34 -0
  11. package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
  12. package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
  13. package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
  14. package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
  15. package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
  16. package/bin/templates/backend/src/api/server.ts.template +188 -0
  17. package/bin/templates/backend/tsconfig.api.json.template +24 -0
  18. package/bin/templates/backend/tsconfig.json.template +40 -0
  19. package/bin/templates/feature/feature.http.template +63 -0
  20. package/bin/templates/feature/feature.route.ts.template +36 -0
  21. package/bin/templates/feature/feature.service.ts.template +81 -0
  22. package/bin/templates/feature/feature.types.ts.template +23 -0
  23. package/bin/templates/feature-db/feature.http.template +63 -0
  24. package/bin/templates/feature-db/feature.model.ts.template +74 -0
  25. package/bin/templates/feature-db/feature.route.ts.template +58 -0
  26. package/bin/templates/feature-db/feature.service.ts.template +231 -0
  27. package/bin/templates/feature-db/feature.types.ts.template +25 -0
  28. package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
  29. package/bin/templates/feature-db/seeding/README.md.template +57 -0
  30. package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
  31. package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
  32. package/bin/templates/feature-user/user.http.template +157 -0
  33. package/bin/templates/feature-user/user.model.ts.template +244 -0
  34. package/bin/templates/feature-user/user.route.ts.template +379 -0
  35. package/bin/templates/feature-user/user.seed.js.template +182 -0
  36. package/bin/templates/feature-user/user.service.ts.template +426 -0
  37. package/bin/templates/feature-user/user.types.ts.template +127 -0
  38. package/dist/auth/auth.d.ts +182 -0
  39. package/dist/auth/auth.d.ts.map +1 -0
  40. package/dist/auth/auth.js +477 -0
  41. package/dist/auth/auth.js.map +1 -0
  42. package/dist/auth/defaults.d.ts +104 -0
  43. package/dist/auth/defaults.d.ts.map +1 -0
  44. package/dist/auth/defaults.js +374 -0
  45. package/dist/auth/defaults.js.map +1 -0
  46. package/dist/auth/index.d.ts +70 -0
  47. package/dist/auth/index.d.ts.map +1 -0
  48. package/dist/auth/index.js +94 -0
  49. package/dist/auth/index.js.map +1 -0
  50. package/dist/cache/cache.d.ts +118 -0
  51. package/dist/cache/cache.d.ts.map +1 -0
  52. package/dist/cache/cache.js +249 -0
  53. package/dist/cache/cache.js.map +1 -0
  54. package/dist/cache/defaults.d.ts +63 -0
  55. package/dist/cache/defaults.d.ts.map +1 -0
  56. package/dist/cache/defaults.js +193 -0
  57. package/dist/cache/defaults.js.map +1 -0
  58. package/dist/cache/index.d.ts +101 -0
  59. package/dist/cache/index.d.ts.map +1 -0
  60. package/dist/cache/index.js +203 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/cache/strategies/memory.d.ts +138 -0
  63. package/dist/cache/strategies/memory.d.ts.map +1 -0
  64. package/dist/cache/strategies/memory.js +348 -0
  65. package/dist/cache/strategies/memory.js.map +1 -0
  66. package/dist/cache/strategies/redis.d.ts +105 -0
  67. package/dist/cache/strategies/redis.d.ts.map +1 -0
  68. package/dist/cache/strategies/redis.js +318 -0
  69. package/dist/cache/strategies/redis.js.map +1 -0
  70. package/dist/config/config.d.ts +62 -0
  71. package/dist/config/config.d.ts.map +1 -0
  72. package/dist/config/config.js +107 -0
  73. package/dist/config/config.js.map +1 -0
  74. package/dist/config/defaults.d.ts +44 -0
  75. package/dist/config/defaults.d.ts.map +1 -0
  76. package/dist/config/defaults.js +217 -0
  77. package/dist/config/defaults.js.map +1 -0
  78. package/dist/config/index.d.ts +105 -0
  79. package/dist/config/index.d.ts.map +1 -0
  80. package/dist/config/index.js +163 -0
  81. package/dist/config/index.js.map +1 -0
  82. package/dist/database/adapters/mongoose.d.ts +106 -0
  83. package/dist/database/adapters/mongoose.d.ts.map +1 -0
  84. package/dist/database/adapters/mongoose.js +480 -0
  85. package/dist/database/adapters/mongoose.js.map +1 -0
  86. package/dist/database/adapters/prisma.d.ts +106 -0
  87. package/dist/database/adapters/prisma.d.ts.map +1 -0
  88. package/dist/database/adapters/prisma.js +494 -0
  89. package/dist/database/adapters/prisma.js.map +1 -0
  90. package/dist/database/defaults.d.ts +87 -0
  91. package/dist/database/defaults.d.ts.map +1 -0
  92. package/dist/database/defaults.js +271 -0
  93. package/dist/database/defaults.js.map +1 -0
  94. package/dist/database/index.d.ts +137 -0
  95. package/dist/database/index.d.ts.map +1 -0
  96. package/dist/database/index.js +490 -0
  97. package/dist/database/index.js.map +1 -0
  98. package/dist/email/defaults.d.ts +100 -0
  99. package/dist/email/defaults.d.ts.map +1 -0
  100. package/dist/email/defaults.js +400 -0
  101. package/dist/email/defaults.js.map +1 -0
  102. package/dist/email/email.d.ts +139 -0
  103. package/dist/email/email.d.ts.map +1 -0
  104. package/dist/email/email.js +316 -0
  105. package/dist/email/email.js.map +1 -0
  106. package/dist/email/index.d.ts +176 -0
  107. package/dist/email/index.d.ts.map +1 -0
  108. package/dist/email/index.js +251 -0
  109. package/dist/email/index.js.map +1 -0
  110. package/dist/email/strategies/console.d.ts +90 -0
  111. package/dist/email/strategies/console.d.ts.map +1 -0
  112. package/dist/email/strategies/console.js +268 -0
  113. package/dist/email/strategies/console.js.map +1 -0
  114. package/dist/email/strategies/resend.d.ts +84 -0
  115. package/dist/email/strategies/resend.d.ts.map +1 -0
  116. package/dist/email/strategies/resend.js +266 -0
  117. package/dist/email/strategies/resend.js.map +1 -0
  118. package/dist/email/strategies/smtp.d.ts +77 -0
  119. package/dist/email/strategies/smtp.d.ts.map +1 -0
  120. package/dist/email/strategies/smtp.js +286 -0
  121. package/dist/email/strategies/smtp.js.map +1 -0
  122. package/dist/error/defaults.d.ts +40 -0
  123. package/dist/error/defaults.d.ts.map +1 -0
  124. package/dist/error/defaults.js +75 -0
  125. package/dist/error/defaults.js.map +1 -0
  126. package/dist/error/error.d.ts +140 -0
  127. package/dist/error/error.d.ts.map +1 -0
  128. package/dist/error/error.js +200 -0
  129. package/dist/error/error.js.map +1 -0
  130. package/dist/error/index.d.ts +145 -0
  131. package/dist/error/index.d.ts.map +1 -0
  132. package/dist/error/index.js +145 -0
  133. package/dist/error/index.js.map +1 -0
  134. package/dist/event/defaults.d.ts +111 -0
  135. package/dist/event/defaults.d.ts.map +1 -0
  136. package/dist/event/defaults.js +378 -0
  137. package/dist/event/defaults.js.map +1 -0
  138. package/dist/event/event.d.ts +171 -0
  139. package/dist/event/event.d.ts.map +1 -0
  140. package/dist/event/event.js +391 -0
  141. package/dist/event/event.js.map +1 -0
  142. package/dist/event/index.d.ts +173 -0
  143. package/dist/event/index.d.ts.map +1 -0
  144. package/dist/event/index.js +302 -0
  145. package/dist/event/index.js.map +1 -0
  146. package/dist/event/strategies/memory.d.ts +122 -0
  147. package/dist/event/strategies/memory.d.ts.map +1 -0
  148. package/dist/event/strategies/memory.js +331 -0
  149. package/dist/event/strategies/memory.js.map +1 -0
  150. package/dist/event/strategies/redis.d.ts +115 -0
  151. package/dist/event/strategies/redis.d.ts.map +1 -0
  152. package/dist/event/strategies/redis.js +434 -0
  153. package/dist/event/strategies/redis.js.map +1 -0
  154. package/dist/index.d.ts +58 -0
  155. package/dist/index.d.ts.map +1 -0
  156. package/dist/index.js +72 -0
  157. package/dist/index.js.map +1 -0
  158. package/dist/logger/defaults.d.ts +67 -0
  159. package/dist/logger/defaults.d.ts.map +1 -0
  160. package/dist/logger/defaults.js +213 -0
  161. package/dist/logger/defaults.js.map +1 -0
  162. package/dist/logger/index.d.ts +84 -0
  163. package/dist/logger/index.d.ts.map +1 -0
  164. package/dist/logger/index.js +101 -0
  165. package/dist/logger/index.js.map +1 -0
  166. package/dist/logger/logger.d.ts +165 -0
  167. package/dist/logger/logger.d.ts.map +1 -0
  168. package/dist/logger/logger.js +843 -0
  169. package/dist/logger/logger.js.map +1 -0
  170. package/dist/logger/transports/console.d.ts +102 -0
  171. package/dist/logger/transports/console.d.ts.map +1 -0
  172. package/dist/logger/transports/console.js +276 -0
  173. package/dist/logger/transports/console.js.map +1 -0
  174. package/dist/logger/transports/database.d.ts +153 -0
  175. package/dist/logger/transports/database.d.ts.map +1 -0
  176. package/dist/logger/transports/database.js +539 -0
  177. package/dist/logger/transports/database.js.map +1 -0
  178. package/dist/logger/transports/file.d.ts +146 -0
  179. package/dist/logger/transports/file.d.ts.map +1 -0
  180. package/dist/logger/transports/file.js +464 -0
  181. package/dist/logger/transports/file.js.map +1 -0
  182. package/dist/logger/transports/http.d.ts +128 -0
  183. package/dist/logger/transports/http.d.ts.map +1 -0
  184. package/dist/logger/transports/http.js +401 -0
  185. package/dist/logger/transports/http.js.map +1 -0
  186. package/dist/logger/transports/webhook.d.ts +152 -0
  187. package/dist/logger/transports/webhook.d.ts.map +1 -0
  188. package/dist/logger/transports/webhook.js +485 -0
  189. package/dist/logger/transports/webhook.js.map +1 -0
  190. package/dist/queue/defaults.d.ts +66 -0
  191. package/dist/queue/defaults.d.ts.map +1 -0
  192. package/dist/queue/defaults.js +205 -0
  193. package/dist/queue/defaults.js.map +1 -0
  194. package/dist/queue/index.d.ts +124 -0
  195. package/dist/queue/index.d.ts.map +1 -0
  196. package/dist/queue/index.js +116 -0
  197. package/dist/queue/index.js.map +1 -0
  198. package/dist/queue/queue.d.ts +156 -0
  199. package/dist/queue/queue.d.ts.map +1 -0
  200. package/dist/queue/queue.js +387 -0
  201. package/dist/queue/queue.js.map +1 -0
  202. package/dist/queue/transports/database.d.ts +165 -0
  203. package/dist/queue/transports/database.d.ts.map +1 -0
  204. package/dist/queue/transports/database.js +595 -0
  205. package/dist/queue/transports/database.js.map +1 -0
  206. package/dist/queue/transports/memory.d.ts +143 -0
  207. package/dist/queue/transports/memory.d.ts.map +1 -0
  208. package/dist/queue/transports/memory.js +415 -0
  209. package/dist/queue/transports/memory.js.map +1 -0
  210. package/dist/queue/transports/redis.d.ts +203 -0
  211. package/dist/queue/transports/redis.d.ts.map +1 -0
  212. package/dist/queue/transports/redis.js +744 -0
  213. package/dist/queue/transports/redis.js.map +1 -0
  214. package/dist/security/defaults.d.ts +64 -0
  215. package/dist/security/defaults.d.ts.map +1 -0
  216. package/dist/security/defaults.js +159 -0
  217. package/dist/security/defaults.js.map +1 -0
  218. package/dist/security/index.d.ts +110 -0
  219. package/dist/security/index.d.ts.map +1 -0
  220. package/dist/security/index.js +160 -0
  221. package/dist/security/index.js.map +1 -0
  222. package/dist/security/security.d.ts +138 -0
  223. package/dist/security/security.d.ts.map +1 -0
  224. package/dist/security/security.js +419 -0
  225. package/dist/security/security.js.map +1 -0
  226. package/dist/storage/defaults.d.ts +79 -0
  227. package/dist/storage/defaults.d.ts.map +1 -0
  228. package/dist/storage/defaults.js +358 -0
  229. package/dist/storage/defaults.js.map +1 -0
  230. package/dist/storage/index.d.ts +153 -0
  231. package/dist/storage/index.d.ts.map +1 -0
  232. package/dist/storage/index.js +242 -0
  233. package/dist/storage/index.js.map +1 -0
  234. package/dist/storage/storage.d.ts +151 -0
  235. package/dist/storage/storage.d.ts.map +1 -0
  236. package/dist/storage/storage.js +439 -0
  237. package/dist/storage/storage.js.map +1 -0
  238. package/dist/storage/strategies/local.d.ts +117 -0
  239. package/dist/storage/strategies/local.d.ts.map +1 -0
  240. package/dist/storage/strategies/local.js +368 -0
  241. package/dist/storage/strategies/local.js.map +1 -0
  242. package/dist/storage/strategies/r2.d.ts +130 -0
  243. package/dist/storage/strategies/r2.d.ts.map +1 -0
  244. package/dist/storage/strategies/r2.js +470 -0
  245. package/dist/storage/strategies/r2.js.map +1 -0
  246. package/dist/storage/strategies/s3.d.ts +121 -0
  247. package/dist/storage/strategies/s3.d.ts.map +1 -0
  248. package/dist/storage/strategies/s3.js +461 -0
  249. package/dist/storage/strategies/s3.js.map +1 -0
  250. package/dist/util/defaults.d.ts +77 -0
  251. package/dist/util/defaults.d.ts.map +1 -0
  252. package/dist/util/defaults.js +193 -0
  253. package/dist/util/defaults.js.map +1 -0
  254. package/dist/util/index.d.ts +97 -0
  255. package/dist/util/index.d.ts.map +1 -0
  256. package/dist/util/index.js +165 -0
  257. package/dist/util/index.js.map +1 -0
  258. package/dist/util/util.d.ts +145 -0
  259. package/dist/util/util.d.ts.map +1 -0
  260. package/dist/util/util.js +481 -0
  261. package/dist/util/util.js.map +1 -0
  262. package/package.json +234 -0
@@ -0,0 +1,379 @@
1
+ /**
2
+ * User Feature Routes - Authentication endpoints with AppKit integration
3
+ * @module {{projectName}}/user-routes
4
+ * @file src/api/features/user/user.route.ts
5
+ */
6
+
7
+ import express from 'express';
8
+ import { errorClass } from '@bloomneo/appkit/error';
9
+ import { authClass } from '@bloomneo/appkit/auth';
10
+ import { userService } from './user.service.js';
11
+
12
+ // Initialize AppKit modules
13
+ const router = express.Router();
14
+ const error = errorClass.get();
15
+ const auth = authClass.get();
16
+
17
+ /**
18
+ * Register a new user
19
+ */
20
+ router.post('/register', error.asyncRoute(async (req, res) => {
21
+ const requestId = req.requestMetadata?.requestId || 'unknown';
22
+
23
+ try {
24
+ const user = await userService.register(req.body);
25
+
26
+ res.status(201).json({
27
+ message: 'User registered successfully',
28
+ user,
29
+ requestId
30
+ });
31
+
32
+ } catch (err: any) {
33
+ if (err.statusCode) {
34
+ throw err;
35
+ }
36
+ res.status(err.statusCode || 500).json({
37
+ error: err.message || 'Registration failed',
38
+ requestId
39
+ });
40
+ }
41
+ }));
42
+
43
+ /**
44
+ * Login user and generate JWT token
45
+ */
46
+ router.post('/login', error.asyncRoute(async (req, res) => {
47
+ const requestId = req.requestMetadata?.requestId || 'unknown';
48
+ const { email, password } = req.body;
49
+
50
+ try {
51
+ const result = await userService.login(email, password);
52
+
53
+ res.json({
54
+ message: 'Login successful',
55
+ ...result,
56
+ requestId
57
+ });
58
+
59
+ } catch (err: any) {
60
+ if (err.statusCode) {
61
+ throw err;
62
+ }
63
+ res.status(err.statusCode || 500).json({
64
+ error: err.message || 'Login failed',
65
+ requestId
66
+ });
67
+ }
68
+ }));
69
+
70
+ /**
71
+ * Request password reset
72
+ */
73
+ router.post('/forgot-password', error.asyncRoute(async (req, res) => {
74
+ const requestId = req.requestMetadata?.requestId || 'unknown';
75
+ const { email } = req.body;
76
+
77
+ try {
78
+ await userService.forgotPassword(email);
79
+
80
+ // Always return success to prevent email enumeration
81
+ res.json({
82
+ message: 'If an account with that email exists, a password reset link has been sent',
83
+ requestId
84
+ });
85
+
86
+ } catch (err: any) {
87
+ // Still return success to prevent email enumeration
88
+ res.json({
89
+ message: 'If an account with that email exists, a password reset link has been sent',
90
+ requestId
91
+ });
92
+ }
93
+ }));
94
+
95
+ /**
96
+ * Reset password with token
97
+ */
98
+ router.post('/reset-password', error.asyncRoute(async (req, res) => {
99
+ const requestId = req.requestMetadata?.requestId || 'unknown';
100
+ const { token, newPassword } = req.body;
101
+
102
+ try {
103
+ await userService.resetPassword(token, newPassword);
104
+
105
+ res.json({
106
+ message: 'Password reset successfully',
107
+ requestId
108
+ });
109
+
110
+ } catch (err: any) {
111
+ if (err.statusCode) {
112
+ throw err;
113
+ }
114
+ res.status(err.statusCode || 500).json({
115
+ error: err.message || 'Password reset failed',
116
+ requestId
117
+ });
118
+ }
119
+ }));
120
+
121
+ /**
122
+ * Get user profile by ID
123
+ */
124
+ router.get('/profile',
125
+ auth.requireLoginToken(),
126
+ error.asyncRoute(async (req, res) => {
127
+ const requestId = req.requestMetadata?.requestId || 'unknown';
128
+
129
+ try {
130
+ const authenticatedUser = auth.user(req as any);
131
+
132
+ if (!authenticatedUser) {
133
+ throw error.serverError('Authentication failed - user not found in request');
134
+ }
135
+
136
+ const user = await userService.getProfile(authenticatedUser.userId as number);
137
+
138
+ res.json({
139
+ message: 'Profile retrieved successfully',
140
+ user,
141
+ authenticatedAs: {
142
+ userId: authenticatedUser.userId,
143
+ role: authenticatedUser.role,
144
+ level: authenticatedUser.level
145
+ },
146
+ requestId
147
+ });
148
+
149
+ } catch (err: any) {
150
+ if (err.statusCode) {
151
+ throw err;
152
+ }
153
+ res.status(err.statusCode || 500).json({
154
+ error: err.message || 'Failed to get profile',
155
+ requestId
156
+ });
157
+ }
158
+ })
159
+ );
160
+
161
+ /**
162
+ * Update user profile
163
+ */
164
+ router.put('/profile',
165
+ auth.requireLoginToken(),
166
+ error.asyncRoute(async (req, res) => {
167
+ const requestId = req.requestMetadata?.requestId || 'unknown';
168
+ const { name, phone } = req.body;
169
+
170
+ try {
171
+ const authenticatedUser = auth.user(req as any);
172
+
173
+ if (!authenticatedUser) {
174
+ throw error.serverError('Authentication failed - user not found in request');
175
+ }
176
+
177
+ const user = await userService.updateProfile(authenticatedUser.userId as number, { name, phone });
178
+
179
+ res.json({
180
+ message: 'Profile updated successfully',
181
+ user,
182
+ requestId
183
+ });
184
+
185
+ } catch (err: any) {
186
+ if (err.statusCode) {
187
+ throw err;
188
+ }
189
+ res.status(err.statusCode || 500).json({
190
+ error: err.message || 'Failed to update profile',
191
+ requestId
192
+ });
193
+ }
194
+ })
195
+ );
196
+
197
+ /**
198
+ * Change user password
199
+ */
200
+ router.post('/change-password',
201
+ auth.requireLoginToken(),
202
+ error.asyncRoute(async (req, res) => {
203
+ const requestId = req.requestMetadata?.requestId || 'unknown';
204
+ const { currentPassword, newPassword } = req.body;
205
+
206
+ try {
207
+ const authenticatedUser = auth.user(req as any);
208
+
209
+ if (!authenticatedUser) {
210
+ throw error.serverError('Authentication failed - user not found in request');
211
+ }
212
+
213
+ await userService.changePassword(authenticatedUser.userId as number, currentPassword, newPassword);
214
+
215
+ res.json({
216
+ message: 'Password changed successfully',
217
+ requestId
218
+ });
219
+
220
+ } catch (err: any) {
221
+ if (err.statusCode) {
222
+ throw err;
223
+ }
224
+ res.status(err.statusCode || 500).json({
225
+ error: err.message || 'Password change failed',
226
+ requestId
227
+ });
228
+ }
229
+ })
230
+ );
231
+
232
+ /**
233
+ * Get all users (admin/moderator only)
234
+ */
235
+ router.get('/all',
236
+ auth.requireLoginToken(),
237
+ auth.requireUserRoles(['admin.tenant', 'admin.org', 'admin.system']),
238
+ error.asyncRoute(async (req, res) => {
239
+ const requestId = req.requestMetadata?.requestId || 'unknown';
240
+ const { tenantId } = req.query;
241
+
242
+ try {
243
+ const users = await userService.getAllUsers(tenantId as string);
244
+
245
+ res.json({
246
+ message: 'Users retrieved successfully',
247
+ users,
248
+ count: users.length,
249
+ requestId
250
+ });
251
+
252
+ } catch (err: any) {
253
+ if (err.statusCode) {
254
+ throw err;
255
+ }
256
+ res.status(err.statusCode || 500).json({
257
+ error: err.message || 'Failed to get users',
258
+ requestId
259
+ });
260
+ }
261
+ })
262
+ );
263
+
264
+ /**
265
+ * Get users list (moderator+ access)
266
+ */
267
+ router.get('/list',
268
+ auth.requireLoginToken(),
269
+ auth.requireUserRoles(['moderator.review', 'moderator.approve', 'moderator.manage', 'admin.tenant', 'admin.org', 'admin.system']),
270
+ error.asyncRoute(async (req, res) => {
271
+ const requestId = req.requestMetadata?.requestId || 'unknown';
272
+ const { tenantId } = req.query;
273
+
274
+ try {
275
+ const users = await userService.getAllUsers(tenantId as string);
276
+
277
+ res.json({
278
+ message: 'Users retrieved successfully',
279
+ users,
280
+ count: users.length,
281
+ requestId
282
+ });
283
+
284
+ } catch (err: any) {
285
+ if (err.statusCode) {
286
+ throw err;
287
+ }
288
+ res.status(err.statusCode || 500).json({
289
+ error: err.message || 'Failed to get users',
290
+ requestId
291
+ });
292
+ }
293
+ })
294
+ );
295
+
296
+ /**
297
+ * Update user by admin (admin only)
298
+ */
299
+ router.put('/list/:id',
300
+ auth.requireLoginToken(),
301
+ auth.requireUserRoles(['admin.tenant', 'admin.org', 'admin.system']),
302
+ error.asyncRoute(async (req, res) => {
303
+ const requestId = req.requestMetadata?.requestId || 'unknown';
304
+ const userId = parseInt(req.params.id);
305
+
306
+ try {
307
+ const user = await userService.updateUser(userId, req.body);
308
+
309
+ res.json({
310
+ message: 'User updated successfully',
311
+ user,
312
+ requestId
313
+ });
314
+
315
+ } catch (err: any) {
316
+ if (err.statusCode) {
317
+ throw err;
318
+ }
319
+ res.status(err.statusCode || 500).json({
320
+ error: err.message || 'Failed to update user',
321
+ requestId
322
+ });
323
+ }
324
+ })
325
+ );
326
+
327
+ /**
328
+ * Delete user (admin only)
329
+ */
330
+ router.delete('/list/:id',
331
+ auth.requireLoginToken(),
332
+ auth.requireUserRoles(['admin.tenant', 'admin.org', 'admin.system']),
333
+ error.asyncRoute(async (req, res) => {
334
+ const requestId = req.requestMetadata?.requestId || 'unknown';
335
+ const userId = parseInt(req.params.id);
336
+
337
+ try {
338
+ const authenticatedUser = auth.user(req as any);
339
+
340
+ // Prevent self-deletion
341
+ if (authenticatedUser?.userId === userId) {
342
+ return res.status(400).json({
343
+ error: 'Operation not allowed',
344
+ message: 'Cannot delete your own account',
345
+ requestId
346
+ });
347
+ }
348
+
349
+ await userService.deleteUser(userId);
350
+
351
+ res.json({
352
+ message: 'User deleted successfully',
353
+ requestId
354
+ });
355
+
356
+ } catch (err: any) {
357
+ if (err.statusCode) {
358
+ throw err;
359
+ }
360
+ res.status(err.statusCode || 500).json({
361
+ error: err.message || 'Failed to delete user',
362
+ requestId
363
+ });
364
+ }
365
+ })
366
+ );
367
+
368
+ /**
369
+ * Test route to verify discovery and functionality
370
+ */
371
+ router.get('/test', (_req, res) => {
372
+ res.json({
373
+ message: 'User routes are working!',
374
+ timestamp: new Date().toISOString(),
375
+ status: 'success'
376
+ });
377
+ });
378
+
379
+ export default router;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * User Feature Seed Data - Creates all 9 role accounts for testing
3
+ * @file prisma/seeding/user.seed.js
4
+ *
5
+ * Requires DEFAULT_USER_PASSWORD environment variable to be set.
6
+ * This ensures no hardcoded passwords in the repository.
7
+ *
8
+ * Run this seed file individually:
9
+ * node prisma/seeding/user.seed.js
10
+ *
11
+ * Or include in your main seed file:
12
+ * require('./seeding/user.seed.js');
13
+ */
14
+
15
+ import { PrismaClient } from '@prisma/client';
16
+ import bcrypt from 'bcrypt';
17
+
18
+ const prisma = new PrismaClient();
19
+
20
+ export async function seedUsers() {
21
+ console.log('🌱 Seeding user data with all 9 role accounts...');
22
+
23
+ try {
24
+ // Check if user data already exists
25
+ const existingCount = await prisma.user.count();
26
+
27
+ if (existingCount > 0) {
28
+ console.log(`⚠️ User table already has ${existingCount} records. Skipping seed...`);
29
+ return { skipped: true, count: existingCount };
30
+ }
31
+
32
+ // Hash default password for all accounts (from environment)
33
+ const defaultPassword = process.env.DEFAULT_USER_PASSWORD;
34
+ if (!defaultPassword) {
35
+ throw new Error('DEFAULT_USER_PASSWORD environment variable is required for seeding');
36
+ }
37
+ const hashedPassword = await bcrypt.hash(defaultPassword, 10);
38
+
39
+ // Create all 9 role accounts based on AppKit's role hierarchy
40
+ const userData = [
41
+ // User roles (3 levels)
42
+ {
43
+ email: 'user.basic@{{projectName}}.com',
44
+ password: hashedPassword,
45
+ name: 'Basic User',
46
+ phone: '+1-555-0001',
47
+ role: 'user',
48
+ level: 'basic',
49
+ isVerified: true,
50
+ isActive: true,
51
+ },
52
+ {
53
+ email: 'user.pro@{{projectName}}.com',
54
+ password: hashedPassword,
55
+ name: 'Pro User',
56
+ phone: '+1-555-0002',
57
+ role: 'user',
58
+ level: 'pro',
59
+ isVerified: true,
60
+ isActive: true,
61
+ },
62
+ {
63
+ email: 'user.max@{{projectName}}.com',
64
+ password: hashedPassword,
65
+ name: 'Max User',
66
+ phone: '+1-555-0003',
67
+ role: 'user',
68
+ level: 'max',
69
+ isVerified: true,
70
+ isActive: true,
71
+ },
72
+
73
+ // Moderator roles (3 levels - matching AppKit hierarchy)
74
+ {
75
+ email: 'moderator.review@{{projectName}}.com',
76
+ password: hashedPassword,
77
+ name: 'Review Moderator',
78
+ phone: '+1-555-0004',
79
+ role: 'moderator',
80
+ level: 'review',
81
+ isVerified: true,
82
+ isActive: true,
83
+ },
84
+ {
85
+ email: 'moderator.approve@{{projectName}}.com',
86
+ password: hashedPassword,
87
+ name: 'Approve Moderator',
88
+ phone: '+1-555-0005',
89
+ role: 'moderator',
90
+ level: 'approve',
91
+ isVerified: true,
92
+ isActive: true,
93
+ },
94
+ {
95
+ email: 'moderator.manage@{{projectName}}.com',
96
+ password: hashedPassword,
97
+ name: 'Manage Moderator',
98
+ phone: '+1-555-0006',
99
+ role: 'moderator',
100
+ level: 'manage',
101
+ isVerified: true,
102
+ isActive: true,
103
+ },
104
+
105
+ // Admin roles (3 levels)
106
+ {
107
+ email: 'admin.tenant@{{projectName}}.com',
108
+ password: hashedPassword,
109
+ name: 'Tenant Admin',
110
+ phone: '+1-555-0007',
111
+ role: 'admin',
112
+ level: 'tenant',
113
+ isVerified: true,
114
+ isActive: true,
115
+ },
116
+ {
117
+ email: 'admin.org@{{projectName}}.com',
118
+ password: hashedPassword,
119
+ name: 'Organization Admin',
120
+ phone: '+1-555-0008',
121
+ role: 'admin',
122
+ level: 'org',
123
+ isVerified: true,
124
+ isActive: true,
125
+ },
126
+ {
127
+ email: 'admin.system@{{projectName}}.com',
128
+ password: hashedPassword,
129
+ name: 'System Admin',
130
+ phone: '+1-555-0009',
131
+ role: 'admin',
132
+ level: 'system',
133
+ isVerified: true,
134
+ isActive: true,
135
+ },
136
+ ];
137
+
138
+ const result = await prisma.user.createMany({
139
+ data: userData
140
+ });
141
+
142
+ console.log(`✅ Successfully seeded ${result.count} user accounts with all 9 roles`);
143
+ console.log('📋 Test accounts created:');
144
+ console.log(` Default password for all accounts: ${defaultPassword}`);
145
+ console.log(`
146
+ 👤 USER ACCOUNTS:
147
+ • user.basic@{{projectName}}.com (role: user.basic)
148
+ • user.pro@{{projectName}}.com (role: user.pro)
149
+ • user.max@{{projectName}}.com (role: user.max)
150
+
151
+ 🛡️ MODERATOR ACCOUNTS:
152
+ • moderator.review@{{projectName}}.com (role: moderator.review)
153
+ • moderator.approve@{{projectName}}.com (role: moderator.approve)
154
+ • moderator.manage@{{projectName}}.com (role: moderator.manage)
155
+
156
+ 🔑 ADMIN ACCOUNTS:
157
+ • admin.tenant@{{projectName}}.com (role: admin.tenant)
158
+ • admin.org@{{projectName}}.com (role: admin.org)
159
+ • admin.system@{{projectName}}.com (role: admin.system)`);
160
+
161
+ return { seeded: true, count: result.count };
162
+
163
+ } catch (error) {
164
+ console.error(`❌ Error seeding user data:`, error);
165
+ throw error;
166
+ }
167
+ }
168
+
169
+ // Run directly if this file is executed
170
+ if (import.meta.url === `file://${process.argv[1]}`) {
171
+ seedUsers()
172
+ .then((result) => {
173
+ console.log('User seeding completed:', result);
174
+ })
175
+ .catch((error) => {
176
+ console.error('User seeding failed:', error);
177
+ process.exit(1);
178
+ })
179
+ .finally(async () => {
180
+ await prisma.$disconnect();
181
+ });
182
+ }