create-nara 1.0.21 → 1.0.22
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.
Potentially problematic release.
This version of create-nara might be problematic. Click here for more details.
package/package.json
CHANGED
|
@@ -1,21 +1,71 @@
|
|
|
1
|
-
import { BaseController, jsonSuccess, ValidationError } from '@nara-web/core';
|
|
1
|
+
import { BaseController, jsonSuccess, jsonError, ValidationError } from '@nara-web/core';
|
|
2
2
|
import type { NaraRequest, NaraResponse } from '@nara-web/core';
|
|
3
|
+
import { UserModel } from '../models/User.js';
|
|
4
|
+
import { db } from '../config/database.js';
|
|
3
5
|
import bcrypt from 'bcrypt';
|
|
4
6
|
|
|
5
7
|
export class UserController extends BaseController {
|
|
6
8
|
async index(req: NaraRequest, res: NaraResponse) {
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
// Get query parameters
|
|
10
|
+
const page = parseInt(req.query?.page as string) || 1;
|
|
11
|
+
const limit = parseInt(req.query?.limit as string) || 10;
|
|
12
|
+
const search = (req.query?.search as string) || '';
|
|
13
|
+
const filter = (req.query?.filter as string) || 'all';
|
|
14
|
+
const offset = (page - 1) * limit;
|
|
15
|
+
|
|
16
|
+
// Build query
|
|
17
|
+
let query = db('users').select(
|
|
18
|
+
'id', 'name', 'email', 'phone', 'avatar', 'role',
|
|
19
|
+
'email_verified_at', 'created_at', 'updated_at'
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Apply search filter
|
|
23
|
+
if (search) {
|
|
24
|
+
query = query.where(function() {
|
|
25
|
+
this.where('name', 'like', `%${search}%`)
|
|
26
|
+
.orWhere('email', 'like', `%${search}%`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Apply role filter
|
|
31
|
+
if (filter === 'admin') {
|
|
32
|
+
query = query.where('role', 'admin');
|
|
33
|
+
} else if (filter === 'user') {
|
|
34
|
+
query = query.where('role', 'user');
|
|
35
|
+
} else if (filter === 'verified') {
|
|
36
|
+
query = query.whereNotNull('email_verified_at');
|
|
37
|
+
} else if (filter === 'unverified') {
|
|
38
|
+
query = query.whereNull('email_verified_at');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get total count for pagination
|
|
42
|
+
const countQuery = query.clone();
|
|
43
|
+
const [{ count: totalCount }] = await countQuery.count('* as count');
|
|
44
|
+
const total = Number(totalCount);
|
|
45
|
+
|
|
46
|
+
// Apply pagination and ordering
|
|
47
|
+
const users = await query
|
|
48
|
+
.orderBy('created_at', 'desc')
|
|
49
|
+
.limit(limit)
|
|
50
|
+
.offset(offset);
|
|
51
|
+
|
|
52
|
+
// Transform users to include is_admin and is_verified flags
|
|
53
|
+
const transformedUsers = users.map(user => ({
|
|
54
|
+
...user,
|
|
55
|
+
is_admin: user.role === 'admin',
|
|
56
|
+
is_verified: !!user.email_verified_at
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const totalPages = Math.ceil(total / limit);
|
|
10
60
|
|
|
11
61
|
return jsonSuccess(res, {
|
|
12
|
-
users:
|
|
13
|
-
total
|
|
14
|
-
page
|
|
15
|
-
limit
|
|
16
|
-
totalPages
|
|
17
|
-
hasNext:
|
|
18
|
-
hasPrev:
|
|
62
|
+
users: transformedUsers,
|
|
63
|
+
total,
|
|
64
|
+
page,
|
|
65
|
+
limit,
|
|
66
|
+
totalPages,
|
|
67
|
+
hasNext: page < totalPages,
|
|
68
|
+
hasPrev: page > 1,
|
|
19
69
|
});
|
|
20
70
|
}
|
|
21
71
|
|
|
@@ -29,12 +79,40 @@ export class UserController extends BaseController {
|
|
|
29
79
|
});
|
|
30
80
|
}
|
|
31
81
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
82
|
+
// Check if email already exists
|
|
83
|
+
const existing = await UserModel.findByEmail(email);
|
|
84
|
+
if (existing) {
|
|
85
|
+
throw new ValidationError({ email: ['Email already registered'] });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Hash password if provided, otherwise generate random
|
|
89
|
+
const hashedPassword = password
|
|
90
|
+
? await bcrypt.hash(password, 10)
|
|
91
|
+
: await bcrypt.hash(Math.random().toString(36).slice(-8), 10);
|
|
92
|
+
|
|
93
|
+
// Create user in database
|
|
94
|
+
const [userId] = await UserModel.create({
|
|
95
|
+
name,
|
|
96
|
+
email,
|
|
97
|
+
password: hashedPassword,
|
|
98
|
+
phone: phone || null,
|
|
99
|
+
role: is_admin ? 'admin' : 'user',
|
|
100
|
+
email_verified_at: is_verified ? new Date().toISOString() : null
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Fetch created user
|
|
104
|
+
const user = await UserModel.findById(userId);
|
|
35
105
|
|
|
36
106
|
return jsonSuccess(res, {
|
|
37
|
-
user: {
|
|
107
|
+
user: {
|
|
108
|
+
id: user?.id,
|
|
109
|
+
name: user?.name,
|
|
110
|
+
email: user?.email,
|
|
111
|
+
phone: user?.phone,
|
|
112
|
+
avatar: user?.avatar,
|
|
113
|
+
is_admin: user?.role === 'admin',
|
|
114
|
+
is_verified: !!user?.email_verified_at
|
|
115
|
+
}
|
|
38
116
|
}, 'User created successfully');
|
|
39
117
|
}
|
|
40
118
|
|
|
@@ -49,15 +127,49 @@ export class UserController extends BaseController {
|
|
|
49
127
|
});
|
|
50
128
|
}
|
|
51
129
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
130
|
+
// Check if user exists
|
|
131
|
+
const existingUser = await UserModel.findById(Number(id));
|
|
132
|
+
if (!existingUser) {
|
|
133
|
+
return jsonError(res, 'User not found', 404);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check if email is taken by another user
|
|
137
|
+
const emailUser = await UserModel.findByEmail(email);
|
|
138
|
+
if (emailUser && emailUser.id !== Number(id)) {
|
|
139
|
+
throw new ValidationError({ email: ['Email already registered'] });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Build update data
|
|
143
|
+
const updateData: Record<string, any> = {
|
|
144
|
+
name,
|
|
145
|
+
email,
|
|
146
|
+
phone: phone || null,
|
|
147
|
+
role: is_admin ? 'admin' : 'user',
|
|
148
|
+
email_verified_at: is_verified ? (existingUser.email_verified_at || new Date().toISOString()) : null,
|
|
149
|
+
updated_at: new Date().toISOString()
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Hash new password if provided
|
|
153
|
+
if (password) {
|
|
154
|
+
updateData.password = await bcrypt.hash(password, 10);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Update user in database
|
|
158
|
+
await UserModel.update(Number(id), updateData);
|
|
159
|
+
|
|
160
|
+
// Fetch updated user
|
|
161
|
+
const user = await UserModel.findById(Number(id));
|
|
58
162
|
|
|
59
163
|
return jsonSuccess(res, {
|
|
60
|
-
user: {
|
|
164
|
+
user: {
|
|
165
|
+
id: user?.id,
|
|
166
|
+
name: user?.name,
|
|
167
|
+
email: user?.email,
|
|
168
|
+
phone: user?.phone,
|
|
169
|
+
avatar: user?.avatar,
|
|
170
|
+
is_admin: user?.role === 'admin',
|
|
171
|
+
is_verified: !!user?.email_verified_at
|
|
172
|
+
}
|
|
61
173
|
}, 'User updated successfully');
|
|
62
174
|
}
|
|
63
175
|
|
|
@@ -70,9 +182,15 @@ export class UserController extends BaseController {
|
|
|
70
182
|
});
|
|
71
183
|
}
|
|
72
184
|
|
|
73
|
-
//
|
|
74
|
-
|
|
185
|
+
// Prevent deleting current user
|
|
186
|
+
const currentUserId = req.user?.id;
|
|
187
|
+
if (currentUserId && ids.includes(currentUserId)) {
|
|
188
|
+
return jsonError(res, 'Cannot delete your own account', 400);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Delete users from database
|
|
192
|
+
const deleted = await db('users').whereIn('id', ids).delete();
|
|
75
193
|
|
|
76
|
-
return jsonSuccess(res, {}, `${
|
|
194
|
+
return jsonSuccess(res, { deleted }, `${deleted} user(s) deleted successfully`);
|
|
77
195
|
}
|
|
78
196
|
}
|