@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,426 @@
1
+ /**
2
+ * User Feature Service - Authentication business logic with AppKit integration
3
+ * @module {{projectName}}/user-service
4
+ * @file src/api/features/user/user.service.ts
5
+ *
6
+ * @llm-rule WHEN: Need authentication business logic with JWT, validation, and database access
7
+ * @llm-rule AVOID: Direct database calls from routes - always use service layer
8
+ * @llm-rule NOTE: Implements authentication flow with AppKit auth, database, logger, and error patterns
9
+ */
10
+
11
+ import { loggerClass } from '@bloomneo/appkit/logger';
12
+ import { errorClass } from '@bloomneo/appkit/error';
13
+ import { authClass } from '@bloomneo/appkit/auth';
14
+ import { model } from './user.model.js';
15
+ import type {
16
+ UserRegisterRequest,
17
+ UserLoginResponse,
18
+ UserResponse,
19
+ UserProfileUpdateRequest,
20
+ UserUpdateRequest
21
+ } from './user.types.js';
22
+
23
+ // Initialize AppKit modules following the pattern
24
+ const logger = loggerClass.get('user-service');
25
+ const error = errorClass.get();
26
+ const auth = authClass.get();
27
+
28
+ export const userService = {
29
+ /**
30
+ * Validate email format
31
+ */
32
+ validateEmail(email: string): boolean {
33
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
34
+ },
35
+
36
+ /**
37
+ * Validate password strength
38
+ */
39
+ validatePassword(password: string): boolean {
40
+ return !!(password && password.length >= 6);
41
+ },
42
+
43
+ /**
44
+ * Register a new user
45
+ */
46
+ async register(data: UserRegisterRequest): Promise<UserResponse> {
47
+ try {
48
+ logger.info('Processing user registration', { email: data.email, role: data.role, level: data.level });
49
+
50
+ // Validate input
51
+ if (!data.email || !this.validateEmail(data.email)) {
52
+ throw error.badRequest('Valid email is required');
53
+ }
54
+
55
+ if (!data.password || !this.validatePassword(data.password)) {
56
+ throw error.badRequest('Password must be at least 6 characters long');
57
+ }
58
+
59
+ // Check if user already exists
60
+ const existingUser = await model.findByEmail(data.email);
61
+ if (existingUser) {
62
+ logger.warn('User already exists', { email: data.email });
63
+ throw error.badRequest('User already exists with this email');
64
+ }
65
+
66
+ // Create user
67
+ const user = await model.create(data);
68
+
69
+ logger.info('User registration completed', { userId: user.id, email: user.email });
70
+ return user;
71
+
72
+ } catch (err: any) {
73
+ if (err.statusCode) {
74
+ throw err;
75
+ }
76
+ logger.error('Failed to register user', { data, error: err });
77
+ throw error.serverError('Failed to register user');
78
+ }
79
+ },
80
+
81
+ /**
82
+ * Login user and generate JWT token
83
+ */
84
+ async login(email: string, password: string): Promise<UserLoginResponse> {
85
+ try {
86
+ logger.info('Processing user login', { email });
87
+
88
+ // Validate input
89
+ if (!email || !this.validateEmail(email)) {
90
+ throw error.badRequest('Valid email is required');
91
+ }
92
+
93
+ if (!password) {
94
+ throw error.badRequest('Password is required');
95
+ }
96
+
97
+ // Find user by email
98
+ const user = await model.findByEmail(email);
99
+ if (!user) {
100
+ logger.warn('User not found for login', { email });
101
+ throw error.badRequest('Invalid email or password');
102
+ }
103
+
104
+ logger.info('User found, verifying password', { userId: user.id, email });
105
+
106
+ // Verify password
107
+ const isPasswordValid = await model.verifyPassword(password, user.password);
108
+ if (!isPasswordValid) {
109
+ logger.warn('Invalid password for login', { email, userId: user.id });
110
+ throw error.badRequest('Invalid email or password');
111
+ }
112
+
113
+ logger.info('Password verified, checking user status', { userId: user.id, isActive: user.isActive });
114
+
115
+ // Check if user is active
116
+ if (!user.isActive) {
117
+ logger.warn('Inactive user login attempt', { email, userId: user.id });
118
+ throw error.badRequest('Account is deactivated');
119
+ }
120
+
121
+ logger.info('User is active, generating JWT token', {
122
+ userId: user.id,
123
+ role: user.role,
124
+ level: user.level,
125
+ roleLevel: `${user.role}.${user.level}`
126
+ });
127
+
128
+ // Generate JWT token with detailed error handling
129
+ let token;
130
+ try {
131
+ token = auth.generateLoginToken({
132
+ userId: user.id,
133
+ role: user.role,
134
+ level: user.level
135
+ });
136
+ logger.info('JWT token generated successfully', { userId: user.id });
137
+ } catch (tokenErr: any) {
138
+ logger.error('JWT token generation failed', {
139
+ userId: user.id,
140
+ role: user.role,
141
+ level: user.level,
142
+ roleLevel: `${user.role}.${user.level}`,
143
+ tokenError: tokenErr.message
144
+ });
145
+
146
+ // Check if it's a role validation error
147
+ if (tokenErr.message?.includes('Invalid role.level')) {
148
+ throw error.badRequest(`Invalid user role configuration: ${user.role}.${user.level}. Please contact administrator.`);
149
+ }
150
+
151
+ throw error.serverError(`Failed to generate authentication token: ${tokenErr.message}`);
152
+ }
153
+
154
+ // Update last login
155
+ await model.updateLastLogin(user.id);
156
+
157
+ // Return user data with token (exclude password)
158
+ const { password: _, ...userWithoutPassword } = user;
159
+
160
+ logger.info('User login completed', { userId: user.id, email });
161
+ return {
162
+ user: userWithoutPassword,
163
+ token
164
+ };
165
+
166
+ } catch (err: any) {
167
+ if (err.statusCode) {
168
+ throw err;
169
+ }
170
+
171
+ // More specific error messages based on error type
172
+ if (err.message?.includes('User not found')) {
173
+ logger.error('Database error during user lookup', { email, error: err.message });
174
+ throw error.badRequest('Invalid email or password');
175
+ }
176
+
177
+ if (err.message?.includes('password')) {
178
+ logger.error('Password verification error', { email, error: err.message });
179
+ throw error.badRequest('Invalid email or password');
180
+ }
181
+
182
+ if (err.message?.includes('role') || err.message?.includes('token')) {
183
+ logger.error('Authentication system error', { email, error: err.message });
184
+ throw error.serverError('Authentication system error. Please contact administrator.');
185
+ }
186
+
187
+ logger.error('Unexpected login error', { email, error: err });
188
+ throw error.serverError('Login failed due to unexpected error. Please try again.');
189
+ }
190
+ },
191
+
192
+ /**
193
+ * Get user profile by ID
194
+ */
195
+ async getProfile(userId: number): Promise<UserResponse> {
196
+ try {
197
+ logger.info('Processing get profile request', { userId });
198
+
199
+ const user = await model.findById(userId);
200
+ if (!user) {
201
+ throw error.notFound('User not found');
202
+ }
203
+
204
+ logger.info('Get profile completed', { userId });
205
+ return user;
206
+
207
+ } catch (err: any) {
208
+ if (err.statusCode) {
209
+ throw err;
210
+ }
211
+ logger.error('Failed to get user profile', { userId, error: err });
212
+ throw error.serverError('Failed to get user profile');
213
+ }
214
+ },
215
+
216
+ /**
217
+ * Update user profile
218
+ */
219
+ async updateProfile(userId: number, data: UserProfileUpdateRequest): Promise<UserResponse> {
220
+ try {
221
+ logger.info('Processing update profile request', { userId, data });
222
+
223
+ const user = await model.findById(userId);
224
+ if (!user) {
225
+ throw error.notFound('User not found');
226
+ }
227
+
228
+ const updatedUser = await model.update(userId, data);
229
+
230
+ logger.info('Update profile completed', { userId });
231
+ return updatedUser;
232
+
233
+ } catch (err: any) {
234
+ if (err.statusCode) {
235
+ throw err;
236
+ }
237
+ logger.error('Failed to update user profile', { userId, data, error: err });
238
+ throw error.serverError('Failed to update user profile');
239
+ }
240
+ },
241
+
242
+ /**
243
+ * Change user password
244
+ */
245
+ async changePassword(userId: number, currentPassword: string, newPassword: string): Promise<void> {
246
+ try {
247
+ logger.info('Processing change password request', { userId });
248
+
249
+ // Validate input
250
+ if (!currentPassword) {
251
+ throw error.badRequest('Current password is required');
252
+ }
253
+
254
+ if (!newPassword || !this.validatePassword(newPassword)) {
255
+ throw error.badRequest('New password must be at least 6 characters long');
256
+ }
257
+
258
+ const fullUser = await model.findById(userId);
259
+ if (!fullUser) {
260
+ throw error.notFound('User not found');
261
+ }
262
+
263
+ // Need to get user with password - model.findById excludes password
264
+ const userWithPassword = await model.findByEmail(fullUser.email);
265
+ if (!userWithPassword) {
266
+ throw error.notFound('User not found');
267
+ }
268
+
269
+ // Verify current password
270
+ const isCurrentPasswordValid = await model.verifyPassword(currentPassword, userWithPassword.password);
271
+ if (!isCurrentPasswordValid) {
272
+ throw error.badRequest('Current password is incorrect');
273
+ }
274
+
275
+ // Update password
276
+ await model.updatePassword(userId, newPassword);
277
+
278
+ logger.info('Change password completed', { userId });
279
+
280
+ } catch (err: any) {
281
+ if (err.statusCode) {
282
+ throw err;
283
+ }
284
+ logger.error('Failed to change password', { userId, error: err });
285
+ throw error.serverError('Failed to change password');
286
+ }
287
+ },
288
+
289
+ /**
290
+ * Request password reset
291
+ */
292
+ async forgotPassword(email: string): Promise<void> {
293
+ try {
294
+ logger.info('Processing forgot password request', { email });
295
+
296
+ // Validate input
297
+ if (!email || !this.validateEmail(email)) {
298
+ throw error.badRequest('Valid email is required');
299
+ }
300
+
301
+ const user = await model.findByEmail(email);
302
+ if (!user) {
303
+ // Don't reveal if user exists
304
+ logger.info('Password reset requested for non-existent email', { email });
305
+ return;
306
+ }
307
+
308
+ // Generate reset token
309
+ const token = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
310
+ const expiry = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
311
+
312
+ await model.setResetToken(email, token, expiry);
313
+
314
+ // TODO: Send email with reset token
315
+ logger.info('Password reset token generated', { email, token });
316
+
317
+ } catch (err: any) {
318
+ logger.error('Failed to process forgot password', { email, error: err });
319
+ throw error.serverError('Failed to process password reset request');
320
+ }
321
+ },
322
+
323
+ /**
324
+ * Reset password with token
325
+ */
326
+ async resetPassword(token: string, newPassword: string): Promise<void> {
327
+ try {
328
+ logger.info('Processing reset password request');
329
+
330
+ // Validate input
331
+ if (!token) {
332
+ throw error.badRequest('Reset token is required');
333
+ }
334
+
335
+ if (!newPassword || !this.validatePassword(newPassword)) {
336
+ throw error.badRequest('New password must be at least 6 characters long');
337
+ }
338
+
339
+ const user = await model.findByResetToken(token);
340
+ if (!user) {
341
+ throw error.badRequest('Invalid or expired reset token');
342
+ }
343
+
344
+ await model.updatePassword(user.id, newPassword);
345
+ await model.clearResetToken(user.id);
346
+
347
+ logger.info('Password reset completed', { userId: user.id });
348
+
349
+ } catch (err: any) {
350
+ if (err.statusCode) {
351
+ throw err;
352
+ }
353
+ logger.error('Failed to reset password', { error: err });
354
+ throw error.serverError('Failed to reset password');
355
+ }
356
+ },
357
+
358
+ /**
359
+ * Get all users (admin/moderator only)
360
+ */
361
+ async getAllUsers(tenantId?: string): Promise<UserResponse[]> {
362
+ try {
363
+ logger.info('Processing get all users request', { tenantId });
364
+
365
+ const users = await model.findAll(tenantId);
366
+
367
+ logger.info('Get all users completed', { count: users.length });
368
+ return users;
369
+
370
+ } catch (err: any) {
371
+ logger.error('Failed to get all users', { error: err });
372
+ throw error.serverError('Failed to retrieve users');
373
+ }
374
+ },
375
+
376
+ /**
377
+ * Update user by admin (admin only)
378
+ */
379
+ async updateUser(id: number, data: UserUpdateRequest): Promise<UserResponse> {
380
+ try {
381
+ logger.info('Processing admin update user request', { id, data });
382
+
383
+ const user = await model.findById(id);
384
+ if (!user) {
385
+ throw error.notFound('User not found');
386
+ }
387
+
388
+ const updatedUser = await model.update(id, data);
389
+
390
+ logger.info('Admin update user completed', { id });
391
+ return updatedUser;
392
+
393
+ } catch (err: any) {
394
+ if (err.statusCode) {
395
+ throw err;
396
+ }
397
+ logger.error('Failed to update user by admin', { id, data, error: err });
398
+ throw error.serverError('Failed to update user');
399
+ }
400
+ },
401
+
402
+ /**
403
+ * Delete user (admin only)
404
+ */
405
+ async deleteUser(id: number): Promise<void> {
406
+ try {
407
+ logger.info('Processing delete user request', { id });
408
+
409
+ const user = await model.findById(id);
410
+ if (!user) {
411
+ throw error.notFound('User not found');
412
+ }
413
+
414
+ await model.delete(id);
415
+
416
+ logger.info('Delete user completed', { id });
417
+
418
+ } catch (err: any) {
419
+ if (err.statusCode) {
420
+ throw err;
421
+ }
422
+ logger.error('Failed to delete user', { id, error: err });
423
+ throw error.serverError('Failed to delete user');
424
+ }
425
+ }
426
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * User Feature Types - TypeScript interfaces for User feature
3
+ * @module {{projectName}}/user-types
4
+ * @file src/api/features/user/user.types.ts
5
+ *
6
+ * @llm-rule WHEN: Need type safety for User feature request/response data
7
+ * @llm-rule AVOID: Using 'any' type - always define proper interfaces
8
+ * @llm-rule NOTE: Supports User authentication with multi-tenant architecture
9
+ */
10
+
11
+ // User registration request
12
+ export interface UserRegisterRequest {
13
+ email: string;
14
+ password: string;
15
+ name?: string;
16
+ phone?: string;
17
+ role?: string;
18
+ level?: string;
19
+ tenantId?: string;
20
+ }
21
+
22
+ // User login request
23
+ export interface UserLoginRequest {
24
+ email: string;
25
+ password: string;
26
+ }
27
+
28
+ // User login response
29
+ export interface UserLoginResponse {
30
+ user: UserResponse;
31
+ token: string;
32
+ }
33
+
34
+ // User profile update request
35
+ export interface UserProfileUpdateRequest {
36
+ name?: string;
37
+ phone?: string;
38
+ }
39
+
40
+ // Admin user update request
41
+ export interface UserUpdateRequest {
42
+ name?: string;
43
+ phone?: string;
44
+ role?: string;
45
+ level?: string;
46
+ tenantId?: string;
47
+ isVerified?: boolean;
48
+ isActive?: boolean;
49
+ }
50
+
51
+ // Change password request
52
+ export interface ChangePasswordRequest {
53
+ currentPassword: string;
54
+ newPassword: string;
55
+ }
56
+
57
+ // Forgot password request
58
+ export interface ForgotPasswordRequest {
59
+ email: string;
60
+ }
61
+
62
+ // Reset password request
63
+ export interface ResetPasswordRequest {
64
+ token: string;
65
+ newPassword: string;
66
+ }
67
+
68
+ // User response (excludes sensitive data)
69
+ export interface UserResponse {
70
+ id: number;
71
+ email: string;
72
+ name: string | null;
73
+ phone: string | null;
74
+ role: string;
75
+ level: string;
76
+ tenantId: string | null;
77
+ isVerified: boolean;
78
+ isActive: boolean;
79
+ lastLogin: Date | null;
80
+ createdAt: Date;
81
+ updatedAt: Date;
82
+ }
83
+
84
+ // User list response
85
+ export interface UserListResponse {
86
+ users: UserResponse[];
87
+ total: number;
88
+ }
89
+
90
+ // Generic API response
91
+ export interface ApiResponse<T = any> {
92
+ success: boolean;
93
+ data?: T;
94
+ message?: string;
95
+ error?: string;
96
+ }
97
+
98
+ // JWT payload for authentication
99
+ export interface AuthTokenPayload {
100
+ userId: number;
101
+ role: string;
102
+ level: string;
103
+ type: 'login';
104
+ iat: number;
105
+ exp: number;
106
+ }
107
+
108
+ // Role levels for authorization
109
+ export type UserRole = 'user' | 'moderator' | 'admin';
110
+ export type UserLevel = 'basic' | 'pro' | 'max' | 'review' | 'approve' | 'manage' | 'tenant' | 'org' | 'system';
111
+
112
+ // Complete role.level combinations
113
+ export type RoleLevel =
114
+ | 'user.basic'
115
+ | 'user.pro'
116
+ | 'user.max'
117
+ | 'moderator.review'
118
+ | 'moderator.approve'
119
+ | 'moderator.manage'
120
+ | 'admin.tenant'
121
+ | 'admin.org'
122
+ | 'admin.system';
123
+
124
+ // Express request with authenticated user
125
+ export interface AuthenticatedRequest {
126
+ user: AuthTokenPayload;
127
+ }