create-nara 1.0.20 → 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,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nara",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "CLI to scaffold NARA projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,6 @@
1
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';
3
4
  import bcrypt from 'bcrypt';
4
5
  import jwt from 'jsonwebtoken';
5
6
 
@@ -22,15 +23,15 @@ export class AuthController extends BaseController {
22
23
  throw new ValidationError({ email: ['Email and password are required'] });
23
24
  }
24
25
 
25
- // TODO: Replace with your actual user lookup
26
- // const user = await UserModel.findByEmail(email);
27
- // if (!user || !await bcrypt.compare(password, user.password)) {
28
- // throw new ValidationError({ email: ['Invalid credentials'] });
29
- // }
26
+ // Find user by email
27
+ const user = await UserModel.findByEmail(email);
28
+ if (!user || !await bcrypt.compare(password, user.password)) {
29
+ throw new ValidationError({ email: ['Invalid credentials'] });
30
+ }
30
31
 
31
- // Example: Generate JWT token with user info
32
+ // Generate JWT token with user info
32
33
  const token = jwt.sign(
33
- { userId: 1, email, name: 'Demo User' },
34
+ { userId: user.id, email: user.email, name: user.name },
34
35
  JWT_SECRET,
35
36
  { expiresIn: JWT_EXPIRES_SECONDS }
36
37
  );
@@ -39,7 +40,7 @@ export class AuthController extends BaseController {
39
40
  res.cookie('auth_token', token, JWT_EXPIRES_SECONDS * 1000, COOKIE_OPTIONS);
40
41
 
41
42
  return jsonSuccess(res, {
42
- user: { id: 1, email, name: 'Demo User' },
43
+ user: { id: user.id, email: user.email, name: user.name },
43
44
  redirect: '/dashboard'
44
45
  }, 'Login successful');
45
46
  }
@@ -55,15 +56,21 @@ export class AuthController extends BaseController {
55
56
  });
56
57
  }
57
58
 
59
+ // Check if email already exists
60
+ const existing = await UserModel.findByEmail(email);
61
+ if (existing) {
62
+ throw new ValidationError({ email: ['Email already registered'] });
63
+ }
64
+
58
65
  // Hash password
59
66
  const hashedPassword = await bcrypt.hash(password, 10);
60
67
 
61
- // TODO: Replace with your actual user creation
62
- // const user = await UserModel.create({ name, email, password: hashedPassword });
68
+ // Create user in database
69
+ const [userId] = await UserModel.create({ name, email, password: hashedPassword });
63
70
 
64
71
  // Generate JWT token
65
72
  const token = jwt.sign(
66
- { userId: 1, email, name },
73
+ { userId, email, name },
67
74
  JWT_SECRET,
68
75
  { expiresIn: JWT_EXPIRES_SECONDS }
69
76
  );
@@ -72,7 +79,7 @@ export class AuthController extends BaseController {
72
79
  res.cookie('auth_token', token, JWT_EXPIRES_SECONDS * 1000, COOKIE_OPTIONS);
73
80
 
74
81
  return jsonSuccess(res, {
75
- user: { id: 1, email, name },
82
+ user: { id: userId, email, name },
76
83
  redirect: '/dashboard'
77
84
  }, 'Registration successful');
78
85
  }
@@ -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
- // TODO: Implement pagination and filtering
8
- // const { page = 1, limit = 10, search, filter } = req.query;
9
- // const users = await UserModel.paginate({ page, limit, search, filter });
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: 0,
14
- page: 1,
15
- limit: 10,
16
- totalPages: 0,
17
- hasNext: false,
18
- hasPrev: false,
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
- // TODO: Create user in database
33
- // const hashedPassword = password ? await bcrypt.hash(password, 10) : null;
34
- // const user = await UserModel.create({ name, email, password: hashedPassword, phone, is_admin, is_verified });
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: { id: '1', name, email, phone, is_admin, is_verified }
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
- // TODO: Update user in database
53
- // const updateData: any = { name, email, phone, is_admin, is_verified };
54
- // if (password) {
55
- // updateData.password = await bcrypt.hash(password, 10);
56
- // }
57
- // await UserModel.update(id, updateData);
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: { id, name, email, phone, is_admin, is_verified }
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
- // TODO: Delete users from database
74
- // await UserModel.deleteMany(ids);
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, {}, `${ids.length} user(s) deleted successfully`);
194
+ return jsonSuccess(res, { deleted }, `${deleted} user(s) deleted successfully`);
77
195
  }
78
196
  }