@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.
- package/LICENSE +21 -0
- package/README.md +902 -0
- package/bin/appkit.js +71 -0
- package/bin/commands/generate.js +1050 -0
- package/bin/templates/backend/README.md.template +39 -0
- package/bin/templates/backend/api.http.template +0 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +507 -0
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +61 -0
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +2539 -0
- package/bin/templates/backend/package.json.template +34 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.http.template +29 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.route.ts.template +36 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.service.ts.template +88 -0
- package/bin/templates/backend/src/api/features/welcome/welcome.types.ts.template +18 -0
- package/bin/templates/backend/src/api/lib/api-router.ts.template +84 -0
- package/bin/templates/backend/src/api/server.ts.template +188 -0
- package/bin/templates/backend/tsconfig.api.json.template +24 -0
- package/bin/templates/backend/tsconfig.json.template +40 -0
- package/bin/templates/feature/feature.http.template +63 -0
- package/bin/templates/feature/feature.route.ts.template +36 -0
- package/bin/templates/feature/feature.service.ts.template +81 -0
- package/bin/templates/feature/feature.types.ts.template +23 -0
- package/bin/templates/feature-db/feature.http.template +63 -0
- package/bin/templates/feature-db/feature.model.ts.template +74 -0
- package/bin/templates/feature-db/feature.route.ts.template +58 -0
- package/bin/templates/feature-db/feature.service.ts.template +231 -0
- package/bin/templates/feature-db/feature.types.ts.template +25 -0
- package/bin/templates/feature-db/schema-addition.prisma.template +9 -0
- package/bin/templates/feature-db/seeding/README.md.template +57 -0
- package/bin/templates/feature-db/seeding/feature.seed.js.template +67 -0
- package/bin/templates/feature-user/schema-addition.prisma.template +19 -0
- package/bin/templates/feature-user/user.http.template +157 -0
- package/bin/templates/feature-user/user.model.ts.template +244 -0
- package/bin/templates/feature-user/user.route.ts.template +379 -0
- package/bin/templates/feature-user/user.seed.js.template +182 -0
- package/bin/templates/feature-user/user.service.ts.template +426 -0
- package/bin/templates/feature-user/user.types.ts.template +127 -0
- package/dist/auth/auth.d.ts +182 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +477 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/defaults.d.ts +104 -0
- package/dist/auth/defaults.d.ts.map +1 -0
- package/dist/auth/defaults.js +374 -0
- package/dist/auth/defaults.js.map +1 -0
- package/dist/auth/index.d.ts +70 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +94 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/cache.d.ts +118 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +249 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/cache/defaults.d.ts +63 -0
- package/dist/cache/defaults.d.ts.map +1 -0
- package/dist/cache/defaults.js +193 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +101 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +203 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/strategies/memory.d.ts +138 -0
- package/dist/cache/strategies/memory.d.ts.map +1 -0
- package/dist/cache/strategies/memory.js +348 -0
- package/dist/cache/strategies/memory.js.map +1 -0
- package/dist/cache/strategies/redis.d.ts +105 -0
- package/dist/cache/strategies/redis.d.ts.map +1 -0
- package/dist/cache/strategies/redis.js +318 -0
- package/dist/cache/strategies/redis.js.map +1 -0
- package/dist/config/config.d.ts +62 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +107 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/defaults.d.ts +44 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +217 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +163 -0
- package/dist/config/index.js.map +1 -0
- package/dist/database/adapters/mongoose.d.ts +106 -0
- package/dist/database/adapters/mongoose.d.ts.map +1 -0
- package/dist/database/adapters/mongoose.js +480 -0
- package/dist/database/adapters/mongoose.js.map +1 -0
- package/dist/database/adapters/prisma.d.ts +106 -0
- package/dist/database/adapters/prisma.d.ts.map +1 -0
- package/dist/database/adapters/prisma.js +494 -0
- package/dist/database/adapters/prisma.js.map +1 -0
- package/dist/database/defaults.d.ts +87 -0
- package/dist/database/defaults.d.ts.map +1 -0
- package/dist/database/defaults.js +271 -0
- package/dist/database/defaults.js.map +1 -0
- package/dist/database/index.d.ts +137 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +490 -0
- package/dist/database/index.js.map +1 -0
- package/dist/email/defaults.d.ts +100 -0
- package/dist/email/defaults.d.ts.map +1 -0
- package/dist/email/defaults.js +400 -0
- package/dist/email/defaults.js.map +1 -0
- package/dist/email/email.d.ts +139 -0
- package/dist/email/email.d.ts.map +1 -0
- package/dist/email/email.js +316 -0
- package/dist/email/email.js.map +1 -0
- package/dist/email/index.d.ts +176 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +251 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/strategies/console.d.ts +90 -0
- package/dist/email/strategies/console.d.ts.map +1 -0
- package/dist/email/strategies/console.js +268 -0
- package/dist/email/strategies/console.js.map +1 -0
- package/dist/email/strategies/resend.d.ts +84 -0
- package/dist/email/strategies/resend.d.ts.map +1 -0
- package/dist/email/strategies/resend.js +266 -0
- package/dist/email/strategies/resend.js.map +1 -0
- package/dist/email/strategies/smtp.d.ts +77 -0
- package/dist/email/strategies/smtp.d.ts.map +1 -0
- package/dist/email/strategies/smtp.js +286 -0
- package/dist/email/strategies/smtp.js.map +1 -0
- package/dist/error/defaults.d.ts +40 -0
- package/dist/error/defaults.d.ts.map +1 -0
- package/dist/error/defaults.js +75 -0
- package/dist/error/defaults.js.map +1 -0
- package/dist/error/error.d.ts +140 -0
- package/dist/error/error.d.ts.map +1 -0
- package/dist/error/error.js +200 -0
- package/dist/error/error.js.map +1 -0
- package/dist/error/index.d.ts +145 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +145 -0
- package/dist/error/index.js.map +1 -0
- package/dist/event/defaults.d.ts +111 -0
- package/dist/event/defaults.d.ts.map +1 -0
- package/dist/event/defaults.js +378 -0
- package/dist/event/defaults.js.map +1 -0
- package/dist/event/event.d.ts +171 -0
- package/dist/event/event.d.ts.map +1 -0
- package/dist/event/event.js +391 -0
- package/dist/event/event.js.map +1 -0
- package/dist/event/index.d.ts +173 -0
- package/dist/event/index.d.ts.map +1 -0
- package/dist/event/index.js +302 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/strategies/memory.d.ts +122 -0
- package/dist/event/strategies/memory.d.ts.map +1 -0
- package/dist/event/strategies/memory.js +331 -0
- package/dist/event/strategies/memory.js.map +1 -0
- package/dist/event/strategies/redis.d.ts +115 -0
- package/dist/event/strategies/redis.d.ts.map +1 -0
- package/dist/event/strategies/redis.js +434 -0
- package/dist/event/strategies/redis.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/defaults.d.ts +67 -0
- package/dist/logger/defaults.d.ts.map +1 -0
- package/dist/logger/defaults.js +213 -0
- package/dist/logger/defaults.js.map +1 -0
- package/dist/logger/index.d.ts +84 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +101 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +165 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +843 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/logger/transports/console.d.ts +102 -0
- package/dist/logger/transports/console.d.ts.map +1 -0
- package/dist/logger/transports/console.js +276 -0
- package/dist/logger/transports/console.js.map +1 -0
- package/dist/logger/transports/database.d.ts +153 -0
- package/dist/logger/transports/database.d.ts.map +1 -0
- package/dist/logger/transports/database.js +539 -0
- package/dist/logger/transports/database.js.map +1 -0
- package/dist/logger/transports/file.d.ts +146 -0
- package/dist/logger/transports/file.d.ts.map +1 -0
- package/dist/logger/transports/file.js +464 -0
- package/dist/logger/transports/file.js.map +1 -0
- package/dist/logger/transports/http.d.ts +128 -0
- package/dist/logger/transports/http.d.ts.map +1 -0
- package/dist/logger/transports/http.js +401 -0
- package/dist/logger/transports/http.js.map +1 -0
- package/dist/logger/transports/webhook.d.ts +152 -0
- package/dist/logger/transports/webhook.d.ts.map +1 -0
- package/dist/logger/transports/webhook.js +485 -0
- package/dist/logger/transports/webhook.js.map +1 -0
- package/dist/queue/defaults.d.ts +66 -0
- package/dist/queue/defaults.d.ts.map +1 -0
- package/dist/queue/defaults.js +205 -0
- package/dist/queue/defaults.js.map +1 -0
- package/dist/queue/index.d.ts +124 -0
- package/dist/queue/index.d.ts.map +1 -0
- package/dist/queue/index.js +116 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/queue.d.ts +156 -0
- package/dist/queue/queue.d.ts.map +1 -0
- package/dist/queue/queue.js +387 -0
- package/dist/queue/queue.js.map +1 -0
- package/dist/queue/transports/database.d.ts +165 -0
- package/dist/queue/transports/database.d.ts.map +1 -0
- package/dist/queue/transports/database.js +595 -0
- package/dist/queue/transports/database.js.map +1 -0
- package/dist/queue/transports/memory.d.ts +143 -0
- package/dist/queue/transports/memory.d.ts.map +1 -0
- package/dist/queue/transports/memory.js +415 -0
- package/dist/queue/transports/memory.js.map +1 -0
- package/dist/queue/transports/redis.d.ts +203 -0
- package/dist/queue/transports/redis.d.ts.map +1 -0
- package/dist/queue/transports/redis.js +744 -0
- package/dist/queue/transports/redis.js.map +1 -0
- package/dist/security/defaults.d.ts +64 -0
- package/dist/security/defaults.d.ts.map +1 -0
- package/dist/security/defaults.js +159 -0
- package/dist/security/defaults.js.map +1 -0
- package/dist/security/index.d.ts +110 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +160 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security.d.ts +138 -0
- package/dist/security/security.d.ts.map +1 -0
- package/dist/security/security.js +419 -0
- package/dist/security/security.js.map +1 -0
- package/dist/storage/defaults.d.ts +79 -0
- package/dist/storage/defaults.d.ts.map +1 -0
- package/dist/storage/defaults.js +358 -0
- package/dist/storage/defaults.js.map +1 -0
- package/dist/storage/index.d.ts +153 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +242 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/storage.d.ts +151 -0
- package/dist/storage/storage.d.ts.map +1 -0
- package/dist/storage/storage.js +439 -0
- package/dist/storage/storage.js.map +1 -0
- package/dist/storage/strategies/local.d.ts +117 -0
- package/dist/storage/strategies/local.d.ts.map +1 -0
- package/dist/storage/strategies/local.js +368 -0
- package/dist/storage/strategies/local.js.map +1 -0
- package/dist/storage/strategies/r2.d.ts +130 -0
- package/dist/storage/strategies/r2.d.ts.map +1 -0
- package/dist/storage/strategies/r2.js +470 -0
- package/dist/storage/strategies/r2.js.map +1 -0
- package/dist/storage/strategies/s3.d.ts +121 -0
- package/dist/storage/strategies/s3.d.ts.map +1 -0
- package/dist/storage/strategies/s3.js +461 -0
- package/dist/storage/strategies/s3.js.map +1 -0
- package/dist/util/defaults.d.ts +77 -0
- package/dist/util/defaults.d.ts.map +1 -0
- package/dist/util/defaults.js +193 -0
- package/dist/util/defaults.js.map +1 -0
- package/dist/util/index.d.ts +97 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +165 -0
- package/dist/util/index.js.map +1 -0
- package/dist/util/util.d.ts +145 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +481 -0
- package/dist/util/util.js.map +1 -0
- 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
|
+
}
|