sitepaige-mcp-server 1.0.3 → 1.1.0
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/components/IntegrationComponent.tsx +1 -0
- package/components/admin.tsx +30 -27
- package/components/auth.tsx +9 -9
- package/components/cta.tsx +3 -10
- package/components/headerlogin.tsx +9 -9
- package/components/login.tsx +7 -4
- package/components/logincallback.tsx +1 -0
- package/components/menu.tsx +0 -6
- package/components/profile.tsx +12 -11
- package/defaultapp/api/Auth/route.ts +39 -39
- package/defaultapp/api/Auth/verify-email/route.ts +4 -4
- package/defaultapp/api/admin/users/route.ts +5 -3
- package/defaultapp/auth/auth.ts +9 -9
- package/defaultapp/db-mysql.ts +1 -1
- package/defaultapp/db-postgres.ts +1 -1
- package/defaultapp/db-sqlite.ts +1 -1
- package/defaultapp/db-users.ts +63 -63
- package/dist/components/IntegrationComponent.tsx +1 -0
- package/dist/components/admin.tsx +30 -27
- package/dist/components/auth.tsx +9 -9
- package/dist/components/cta.tsx +3 -10
- package/dist/components/headerlogin.tsx +9 -9
- package/dist/components/login.tsx +7 -4
- package/dist/components/logincallback.tsx +1 -0
- package/dist/components/menu.tsx +0 -6
- package/dist/components/profile.tsx +12 -11
- package/dist/defaultapp/api/Auth/route.ts +39 -39
- package/dist/defaultapp/api/Auth/verify-email/route.ts +4 -4
- package/dist/defaultapp/api/admin/users/route.ts +5 -3
- package/dist/defaultapp/auth/auth.ts +9 -9
- package/dist/defaultapp/db-mysql.ts +1 -1
- package/dist/defaultapp/db-postgres.ts +1 -1
- package/dist/defaultapp/db-sqlite.ts +1 -1
- package/dist/defaultapp/db-users.ts +63 -63
- package/dist/generators/sql.js +11 -3
- package/dist/generators/sql.js.map +1 -1
- package/dist/generators/views.js +2 -2
- package/package.json +1 -1
package/defaultapp/auth/auth.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { cookies } from 'next/headers';
|
|
2
2
|
|
|
3
|
-
export async function check_auth(db: any, db_query: any): Promise<{ userid: string,
|
|
3
|
+
export async function check_auth(db: any, db_query: any): Promise<{ userid: string, userlevel: number, usertier: number, isadmin: boolean }> {
|
|
4
4
|
const cookieStore = await cookies();
|
|
5
5
|
const sessionId = cookieStore.get('session_id')?.value;
|
|
6
6
|
|
|
7
7
|
const sessionInfo = {
|
|
8
8
|
userid: '',
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
userlevel: 0,
|
|
10
|
+
usertier: 0,
|
|
11
|
+
isadmin: false
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
if (!sessionId) {
|
|
@@ -16,16 +16,16 @@ export async function check_auth(db: any, db_query: any): Promise<{ userid: stri
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// SQLite is case-insensitive for identifiers, but use standard column names (no quotes, correct names)
|
|
19
|
-
const session = await db_query(db, 'SELECT userid FROM
|
|
19
|
+
const session = await db_query(db, 'SELECT userid FROM usersession WHERE sessiontoken = ?', [sessionId]);
|
|
20
20
|
if (session.length > 0) {
|
|
21
21
|
sessionInfo.userid = session[0].userid;
|
|
22
22
|
|
|
23
23
|
// In your Users table, the primary key is userid, and there is no IsAdmin column, but UserLevel (2 = admin)
|
|
24
|
-
const user = await db_query(db, 'SELECT
|
|
24
|
+
const user = await db_query(db, 'SELECT userlevel, usertier FROM users WHERE userid = ?', [session[0].userid]);
|
|
25
25
|
if (user.length > 0) {
|
|
26
|
-
sessionInfo.
|
|
27
|
-
sessionInfo.
|
|
28
|
-
sessionInfo.
|
|
26
|
+
sessionInfo.userlevel = user[0].userlevel;
|
|
27
|
+
sessionInfo.isadmin = user[0].userlevel === 2;
|
|
28
|
+
sessionInfo.usertier = user[0].usertier;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
return sessionInfo;
|
package/defaultapp/db-mysql.ts
CHANGED
|
@@ -95,7 +95,7 @@ export async function db_query(
|
|
|
95
95
|
*/
|
|
96
96
|
export function db_migrate(model: Model, dbType: string): string {
|
|
97
97
|
|
|
98
|
-
const sanitizedTableName = model.name;
|
|
98
|
+
const sanitizedTableName = model.name.toLowerCase().replace(/\s+/g, '_');
|
|
99
99
|
|
|
100
100
|
// Start with the model's fields
|
|
101
101
|
let fields = [...model.fields];
|
|
@@ -101,7 +101,7 @@ export async function db_query(
|
|
|
101
101
|
*/
|
|
102
102
|
export function db_migrate(model: Model, dbType: string): string {
|
|
103
103
|
|
|
104
|
-
const sanitizedTableName = model.name;
|
|
104
|
+
const sanitizedTableName = model.name.toLowerCase().replace(/\s+/g, '_');
|
|
105
105
|
|
|
106
106
|
// Start with the model's fields
|
|
107
107
|
let fields = [...model.fields];
|
package/defaultapp/db-sqlite.ts
CHANGED
|
@@ -175,7 +175,7 @@ export async function db_query(
|
|
|
175
175
|
export function db_migrate(model: Model, dbType: string): string {
|
|
176
176
|
// Special handling for auth tables - create them first
|
|
177
177
|
|
|
178
|
-
const sanitizedTableName = model.name;
|
|
178
|
+
const sanitizedTableName = model.name.toLowerCase().replace(/\s+/g, '_');
|
|
179
179
|
|
|
180
180
|
// Start with the model's fields
|
|
181
181
|
let fields = [...model.fields];
|
package/defaultapp/db-users.ts
CHANGED
|
@@ -9,33 +9,33 @@ import * as crypto from 'node:crypto';
|
|
|
9
9
|
|
|
10
10
|
export interface User {
|
|
11
11
|
userid: string;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
oauthid: string;
|
|
13
|
+
source: 'google' | 'facebook' | 'apple' | 'github' | 'userpass';
|
|
14
|
+
username: string;
|
|
15
|
+
email?: string;
|
|
16
|
+
avatarurl?: string;
|
|
17
|
+
userlevel: number; // 0: everyone, 1: registered user, 2: admin
|
|
18
|
+
lastlogindate: string;
|
|
19
|
+
createddate: string;
|
|
20
|
+
isactive: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface UserSession {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
id: string;
|
|
25
|
+
sessiontoken: string;
|
|
26
26
|
userid: string;
|
|
27
|
-
|
|
27
|
+
expirationdate: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface OAuthToken {
|
|
31
|
-
|
|
31
|
+
id: string;
|
|
32
32
|
userid: string;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
provider: 'google' | 'facebook' | 'apple' | 'github';
|
|
34
|
+
accesstoken: string;
|
|
35
|
+
refreshtoken?: string;
|
|
36
|
+
expiresat?: string;
|
|
37
|
+
createdat: string;
|
|
38
|
+
updatedat: string;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -46,8 +46,8 @@ export async function getAllUsers(): Promise<User[]> {
|
|
|
46
46
|
|
|
47
47
|
const users = await db_query(client,
|
|
48
48
|
`SELECT * FROM users
|
|
49
|
-
WHERE
|
|
50
|
-
ORDER BY
|
|
49
|
+
WHERE isactive = ?
|
|
50
|
+
ORDER BY userlevel DESC, username ASC`,
|
|
51
51
|
[1] // Use 1 instead of true for PostgreSQL compatibility
|
|
52
52
|
);
|
|
53
53
|
|
|
@@ -61,7 +61,7 @@ export async function getUserByOAuthID(oauthId: string): Promise<User | null> {
|
|
|
61
61
|
const client = await db_init();
|
|
62
62
|
|
|
63
63
|
const users = await db_query(client,
|
|
64
|
-
"SELECT * FROM users WHERE
|
|
64
|
+
"SELECT * FROM users WHERE oauthid = ? AND isactive = ?",
|
|
65
65
|
[oauthId, 1] // Use 1 instead of true for PostgreSQL compatibility
|
|
66
66
|
);
|
|
67
67
|
|
|
@@ -75,7 +75,7 @@ export async function getUserByID(userId: string): Promise<User | null> {
|
|
|
75
75
|
const client = await db_init();
|
|
76
76
|
|
|
77
77
|
const users = await db_query(client,
|
|
78
|
-
"SELECT * FROM users WHERE userid = ? AND
|
|
78
|
+
"SELECT * FROM users WHERE userid = ? AND isactive = ?",
|
|
79
79
|
[userId, 1] // Use 1 instead of true for PostgreSQL compatibility
|
|
80
80
|
);
|
|
81
81
|
|
|
@@ -101,9 +101,9 @@ export async function upsertUser(
|
|
|
101
101
|
// Update existing user
|
|
102
102
|
await db_query(client,
|
|
103
103
|
`UPDATE users
|
|
104
|
-
SET
|
|
105
|
-
|
|
106
|
-
WHERE
|
|
104
|
+
SET username = ?, email = COALESCE(?, email), avatarurl = ?,
|
|
105
|
+
lastlogindate = CURRENT_TIMESTAMP, source = ?
|
|
106
|
+
WHERE oauthid = ?`,
|
|
107
107
|
[userName, email, avatarUrl || '', source, oauthId]
|
|
108
108
|
);
|
|
109
109
|
|
|
@@ -119,8 +119,8 @@ export async function upsertUser(
|
|
|
119
119
|
|
|
120
120
|
await db_query(client,
|
|
121
121
|
`INSERT INTO users
|
|
122
|
-
(userid,
|
|
123
|
-
|
|
122
|
+
(userid, oauthid, source, username, email, avatarurl, userlevel, usertier,
|
|
123
|
+
lastlogindate, createddate, isactive)
|
|
124
124
|
VALUES (?, ?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?)`,
|
|
125
125
|
[userId, oauthId, source, userName, email || null, avatarUrl || '', permissionLevel, 1] // Use 1 instead of true for PostgreSQL compatibility
|
|
126
126
|
);
|
|
@@ -141,7 +141,7 @@ export async function updateUserPermission(
|
|
|
141
141
|
// Ensure there's always at least one admin
|
|
142
142
|
if (permissionLevel < 2) {
|
|
143
143
|
const admins = await db_query(client,
|
|
144
|
-
"SELECT COUNT(*) as count FROM users WHERE
|
|
144
|
+
"SELECT COUNT(*) as count FROM users WHERE userlevel = ? AND userid != ? AND isactive = ?",
|
|
145
145
|
[2, userId, 1] // Use 1 instead of true for PostgreSQL compatibility
|
|
146
146
|
);
|
|
147
147
|
|
|
@@ -151,7 +151,7 @@ export async function updateUserPermission(
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
const result = await db_query(client,
|
|
154
|
-
"UPDATE users SET
|
|
154
|
+
"UPDATE users SET userlevel = ? WHERE userid = ?",
|
|
155
155
|
[permissionLevel, userId]
|
|
156
156
|
);
|
|
157
157
|
|
|
@@ -166,9 +166,9 @@ export async function deleteUser(userId: string): Promise<boolean> {
|
|
|
166
166
|
|
|
167
167
|
// Ensure there's always at least one admin
|
|
168
168
|
const user = await getUserByID(userId);
|
|
169
|
-
if (user && user.
|
|
169
|
+
if (user && user.userlevel === 2) {
|
|
170
170
|
const admins = await db_query(client,
|
|
171
|
-
"SELECT COUNT(*) as count FROM users WHERE
|
|
171
|
+
"SELECT COUNT(*) as count FROM users WHERE userlevel = ? AND userid != ? AND isactive = ?",
|
|
172
172
|
[2, userId, 1] // Use 1 instead of true for PostgreSQL compatibility
|
|
173
173
|
);
|
|
174
174
|
|
|
@@ -179,7 +179,7 @@ export async function deleteUser(userId: string): Promise<boolean> {
|
|
|
179
179
|
|
|
180
180
|
// Soft delete the user
|
|
181
181
|
const result = await db_query(client,
|
|
182
|
-
"UPDATE users SET
|
|
182
|
+
"UPDATE users SET isactive = ? WHERE userid = ?",
|
|
183
183
|
[0, userId] // Use 0 instead of false for PostgreSQL compatibility
|
|
184
184
|
);
|
|
185
185
|
|
|
@@ -200,11 +200,11 @@ export async function getUserStats(): Promise<{
|
|
|
200
200
|
const stats = await db_query(client, `
|
|
201
201
|
SELECT
|
|
202
202
|
COUNT(*) as totalUsers,
|
|
203
|
-
SUM(CASE WHEN
|
|
204
|
-
SUM(CASE WHEN
|
|
205
|
-
SUM(CASE WHEN
|
|
203
|
+
SUM(CASE WHEN userlevel = 2 THEN 1 ELSE 0 END) as admins,
|
|
204
|
+
SUM(CASE WHEN userlevel = 1 THEN 1 ELSE 0 END) as registeredUsers,
|
|
205
|
+
SUM(CASE WHEN userlevel = 0 THEN 1 ELSE 0 END) as guestUsers
|
|
206
206
|
FROM users
|
|
207
|
-
WHERE
|
|
207
|
+
WHERE isactive = ?
|
|
208
208
|
`, [1]); // Use 1 instead of true for PostgreSQL compatibility
|
|
209
209
|
|
|
210
210
|
return stats[0];
|
|
@@ -217,7 +217,7 @@ export async function cleanupExpiredSessions(): Promise<number> {
|
|
|
217
217
|
const client = await db_init();
|
|
218
218
|
|
|
219
219
|
const result = await db_query(client,
|
|
220
|
-
"DELETE FROM usersession WHERE
|
|
220
|
+
"DELETE FROM usersession WHERE expirationdate::TIMESTAMP < CURRENT_TIMESTAMP"
|
|
221
221
|
);
|
|
222
222
|
|
|
223
223
|
return result[0].changes;
|
|
@@ -241,14 +241,14 @@ export async function storeOAuthToken(
|
|
|
241
241
|
|
|
242
242
|
// Delete existing tokens for this user/provider combo
|
|
243
243
|
await db_query(client,
|
|
244
|
-
"DELETE FROM oauthtokens WHERE userid = ? AND
|
|
244
|
+
"DELETE FROM oauthtokens WHERE userid = ? AND provider = ?",
|
|
245
245
|
[userId, provider]
|
|
246
246
|
);
|
|
247
247
|
|
|
248
248
|
// Insert new token
|
|
249
249
|
await db_query(client,
|
|
250
250
|
`INSERT INTO oauthtokens
|
|
251
|
-
(
|
|
251
|
+
(id, userid, provider, accesstoken, refreshtoken, expiresat, createdat, updatedat)
|
|
252
252
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
253
253
|
[tokenId, userId, provider, accessToken, refreshToken || null, expiresAt, now, now]
|
|
254
254
|
);
|
|
@@ -264,7 +264,7 @@ export async function getOAuthToken(
|
|
|
264
264
|
const client = await db_init();
|
|
265
265
|
|
|
266
266
|
const tokens = await db_query(client,
|
|
267
|
-
"SELECT * FROM oauthtokens WHERE userid = ? AND
|
|
267
|
+
"SELECT * FROM oauthtokens WHERE userid = ? AND provider = ?",
|
|
268
268
|
[userId, provider]
|
|
269
269
|
);
|
|
270
270
|
|
|
@@ -285,7 +285,7 @@ export async function validateSession(sessionToken: string): Promise<{
|
|
|
285
285
|
const sessions = await db_query(client,
|
|
286
286
|
`SELECT s.*, u.* FROM usersession s
|
|
287
287
|
JOIN users u ON s.userid = u.userid
|
|
288
|
-
WHERE s.
|
|
288
|
+
WHERE s.sessiontoken = ? AND s.expirationdate::TIMESTAMP > CURRENT_TIMESTAMP AND u.isactive = ?`,
|
|
289
289
|
[sessionToken, 1] // Use 1 instead of true for PostgreSQL compatibility
|
|
290
290
|
);
|
|
291
291
|
|
|
@@ -296,22 +296,22 @@ export async function validateSession(sessionToken: string): Promise<{
|
|
|
296
296
|
const session = sessions[0];
|
|
297
297
|
|
|
298
298
|
// Check if session needs rotation (older than 24 hours)
|
|
299
|
-
const sessionAge = Date.now() - new Date(session.
|
|
299
|
+
const sessionAge = Date.now() - new Date(session.id).getTime();
|
|
300
300
|
const needsRotation = sessionAge > 24 * 60 * 60 * 1000; // 24 hours
|
|
301
301
|
|
|
302
302
|
return {
|
|
303
303
|
valid: true,
|
|
304
304
|
user: {
|
|
305
305
|
userid: session.userid,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
306
|
+
oauthid: session.oauthid,
|
|
307
|
+
source: session.source,
|
|
308
|
+
username: session.username,
|
|
309
|
+
email: session.email,
|
|
310
|
+
avatarurl: session.avatarurl,
|
|
311
|
+
userlevel: session.userlevel,
|
|
312
|
+
lastlogindate: session.lastlogindate,
|
|
313
|
+
createddate: session.createddate,
|
|
314
|
+
isactive: session.isactive
|
|
315
315
|
} as User,
|
|
316
316
|
needsRotation
|
|
317
317
|
};
|
|
@@ -325,7 +325,7 @@ export async function rotateSession(oldSessionToken: string): Promise<string | n
|
|
|
325
325
|
|
|
326
326
|
// Get existing session
|
|
327
327
|
const sessions = await db_query(client,
|
|
328
|
-
"SELECT * FROM usersession WHERE
|
|
328
|
+
"SELECT * FROM usersession WHERE sessiontoken = ? AND expirationdate::TIMESTAMP > CURRENT_TIMESTAMP",
|
|
329
329
|
[oldSessionToken]
|
|
330
330
|
);
|
|
331
331
|
|
|
@@ -338,8 +338,8 @@ export async function rotateSession(oldSessionToken: string): Promise<string | n
|
|
|
338
338
|
|
|
339
339
|
// Update session with new token
|
|
340
340
|
await db_query(client,
|
|
341
|
-
"UPDATE usersession SET
|
|
342
|
-
[newSessionToken, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), session.
|
|
341
|
+
"UPDATE usersession SET sessiontoken = ?, expirationdate = ? WHERE id = ?",
|
|
342
|
+
[newSessionToken, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), session.id]
|
|
343
343
|
);
|
|
344
344
|
|
|
345
345
|
return newSessionToken;
|
|
@@ -371,9 +371,9 @@ export async function validateOAuthToken(
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
// Check if token is expired based on stored expiry
|
|
374
|
-
if (token.
|
|
374
|
+
if (token.expiresat && new Date(token.expiresat) < new Date()) {
|
|
375
375
|
// Try to refresh the token
|
|
376
|
-
if (token.
|
|
376
|
+
if (token.refreshtoken) {
|
|
377
377
|
return await refreshOAuthToken(userId, provider);
|
|
378
378
|
}
|
|
379
379
|
return false;
|
|
@@ -384,10 +384,10 @@ export async function validateOAuthToken(
|
|
|
384
384
|
let validationUrl: string;
|
|
385
385
|
switch (provider) {
|
|
386
386
|
case 'google':
|
|
387
|
-
validationUrl = `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${token.
|
|
387
|
+
validationUrl = `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${token.accesstoken}`;
|
|
388
388
|
break;
|
|
389
389
|
case 'facebook':
|
|
390
|
-
validationUrl = `https://graph.facebook.com/me?access_token=${token.
|
|
390
|
+
validationUrl = `https://graph.facebook.com/me?access_token=${token.accesstoken}`;
|
|
391
391
|
break;
|
|
392
392
|
case 'github':
|
|
393
393
|
validationUrl = 'https://api.github.com/user';
|
|
@@ -398,7 +398,7 @@ export async function validateOAuthToken(
|
|
|
398
398
|
|
|
399
399
|
const response = await fetch(validationUrl, {
|
|
400
400
|
headers: provider === 'github' ? {
|
|
401
|
-
Authorization: `Bearer ${token.
|
|
401
|
+
Authorization: `Bearer ${token.accesstoken}`
|
|
402
402
|
} : {}
|
|
403
403
|
});
|
|
404
404
|
|
|
@@ -419,7 +419,7 @@ export async function refreshOAuthToken(
|
|
|
419
419
|
|
|
420
420
|
// Get stored OAuth token with refresh token
|
|
421
421
|
const token = await getOAuthToken(userId, provider);
|
|
422
|
-
if (!token || !token.
|
|
422
|
+
if (!token || !token.refreshtoken) {
|
|
423
423
|
return false;
|
|
424
424
|
}
|
|
425
425
|
|
|
@@ -428,7 +428,7 @@ export async function refreshOAuthToken(
|
|
|
428
428
|
|
|
429
429
|
const params: Record<string, string> = {
|
|
430
430
|
grant_type: 'refresh_token',
|
|
431
|
-
refresh_token: token.
|
|
431
|
+
refresh_token: token.refreshtoken,
|
|
432
432
|
client_id: process.env[`NEXT_PUBLIC_${provider.toUpperCase()}_CLIENT_ID`]!,
|
|
433
433
|
client_secret: process.env[`${provider.toUpperCase()}_CLIENT_SECRET`]!
|
|
434
434
|
};
|
|
@@ -453,7 +453,7 @@ export async function refreshOAuthToken(
|
|
|
453
453
|
userId,
|
|
454
454
|
provider,
|
|
455
455
|
data.access_token,
|
|
456
|
-
data.refresh_token || token.
|
|
456
|
+
data.refresh_token || token.refreshtoken, // Some providers don't return new refresh token
|
|
457
457
|
data.expires_in
|
|
458
458
|
);
|
|
459
459
|
|
|
@@ -5,15 +5,15 @@ import { useRouter } from 'next/navigation';
|
|
|
5
5
|
|
|
6
6
|
interface User {
|
|
7
7
|
userid: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
oauthid: string;
|
|
9
|
+
source: string;
|
|
10
|
+
username: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
avatarurl?: string;
|
|
13
|
+
userlevel: number;
|
|
14
|
+
lastlogindate: string;
|
|
15
|
+
createddate: string;
|
|
16
|
+
isactive: boolean;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
interface UserStats {
|
|
@@ -46,7 +46,9 @@ export default function AdminPanel() {
|
|
|
46
46
|
setError(null);
|
|
47
47
|
|
|
48
48
|
// Fetch users - authorization is handled server-side via session cookie
|
|
49
|
-
const response = await fetch('/api/admin/users'
|
|
49
|
+
const response = await fetch('/api/admin/users', {
|
|
50
|
+
credentials: 'include'
|
|
51
|
+
});
|
|
50
52
|
if (!response.ok) {
|
|
51
53
|
if (response.status === 401) {
|
|
52
54
|
setError('Access denied. Admin privileges required.');
|
|
@@ -80,6 +82,7 @@ export default function AdminPanel() {
|
|
|
80
82
|
headers: {
|
|
81
83
|
'Content-Type': 'application/json',
|
|
82
84
|
},
|
|
85
|
+
credentials: 'include',
|
|
83
86
|
body: JSON.stringify({
|
|
84
87
|
userId,
|
|
85
88
|
permissionLevel: newLevel,
|
|
@@ -169,13 +172,13 @@ export default function AdminPanel() {
|
|
|
169
172
|
|
|
170
173
|
// Filter users based on search and permission level
|
|
171
174
|
const filteredUsers = users.filter(user => {
|
|
172
|
-
const matchesSearch = user.
|
|
173
|
-
(user.
|
|
175
|
+
const matchesSearch = user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
176
|
+
(user.email && user.email.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
174
177
|
|
|
175
178
|
const matchesFilter = filterLevel === 'all' ||
|
|
176
|
-
(filterLevel === 'admin' && user.
|
|
177
|
-
(filterLevel === 'registered' && user.
|
|
178
|
-
(filterLevel === 'guest' && user.
|
|
179
|
+
(filterLevel === 'admin' && user.userlevel === 2) ||
|
|
180
|
+
(filterLevel === 'registered' && user.userlevel === 1) ||
|
|
181
|
+
(filterLevel === 'guest' && user.userlevel === 0);
|
|
179
182
|
|
|
180
183
|
return matchesSearch && matchesFilter;
|
|
181
184
|
});
|
|
@@ -298,26 +301,26 @@ export default function AdminPanel() {
|
|
|
298
301
|
<tr key={user.userid} className="hover:bg-gray-50">
|
|
299
302
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
300
303
|
<div className="flex items-center">
|
|
301
|
-
{user.
|
|
304
|
+
{user.avatarurl ? (
|
|
302
305
|
<img
|
|
303
306
|
className="h-10 w-10 rounded-full"
|
|
304
|
-
src={user.
|
|
305
|
-
alt={user.
|
|
307
|
+
src={user.avatarurl}
|
|
308
|
+
alt={user.username}
|
|
306
309
|
/>
|
|
307
310
|
) : (
|
|
308
311
|
<div className="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
|
|
309
312
|
<span className="text-gray-600 font-medium">
|
|
310
|
-
{user.
|
|
313
|
+
{user.username.charAt(0).toUpperCase()}
|
|
311
314
|
</span>
|
|
312
315
|
</div>
|
|
313
316
|
)}
|
|
314
317
|
<div className="ml-4">
|
|
315
318
|
<div className="text-sm font-medium text-gray-900">
|
|
316
|
-
{user.
|
|
319
|
+
{user.username}
|
|
317
320
|
</div>
|
|
318
|
-
{user.
|
|
321
|
+
{user.email && (
|
|
319
322
|
<div className="text-sm text-gray-500">
|
|
320
|
-
{user.
|
|
323
|
+
{user.email}
|
|
321
324
|
</div>
|
|
322
325
|
)}
|
|
323
326
|
</div>
|
|
@@ -325,15 +328,15 @@ export default function AdminPanel() {
|
|
|
325
328
|
</td>
|
|
326
329
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
327
330
|
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">
|
|
328
|
-
{user.
|
|
331
|
+
{user.source}
|
|
329
332
|
</span>
|
|
330
333
|
</td>
|
|
331
334
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
332
335
|
<select
|
|
333
|
-
value={user.
|
|
336
|
+
value={user.userlevel}
|
|
334
337
|
onChange={(e) => handlePermissionChange(user.userid, parseInt(e.target.value))}
|
|
335
338
|
disabled={isUpdating === user.userid}
|
|
336
|
-
className={`px-2 py-1 text-xs leading-5 font-semibold rounded-full ${getPermissionLevelColor(user.
|
|
339
|
+
className={`px-2 py-1 text-xs leading-5 font-semibold rounded-full ${getPermissionLevelColor(user.userlevel)} cursor-pointer hover:opacity-80 disabled:opacity-50 disabled:cursor-not-allowed`}
|
|
337
340
|
>
|
|
338
341
|
<option value={0}>Guest</option>
|
|
339
342
|
<option value={1}>Registered User</option>
|
|
@@ -341,11 +344,11 @@ export default function AdminPanel() {
|
|
|
341
344
|
</select>
|
|
342
345
|
</td>
|
|
343
346
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
344
|
-
{formatDate(user.
|
|
347
|
+
{formatDate(user.lastlogindate)}
|
|
345
348
|
</td>
|
|
346
349
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
347
350
|
<button
|
|
348
|
-
onClick={() => handleDeleteUser(user.userid, user.
|
|
351
|
+
onClick={() => handleDeleteUser(user.userid, user.username)}
|
|
349
352
|
disabled={isDeleting === user.userid}
|
|
350
353
|
className="text-red-600 hover:text-red-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
351
354
|
>
|
package/dist/components/auth.tsx
CHANGED
|
@@ -11,11 +11,11 @@ import { useUserStore } from '@/store/user';
|
|
|
11
11
|
|
|
12
12
|
interface AuthProps {
|
|
13
13
|
auth: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
id: string;
|
|
15
|
+
username: string;
|
|
16
|
+
avatarurl: string;
|
|
17
|
+
userlevel: string;
|
|
18
|
+
isadmin: boolean;
|
|
19
19
|
} | null;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -25,10 +25,10 @@ export default function Auth({ auth }: AuthProps) {
|
|
|
25
25
|
useEffect(() => {
|
|
26
26
|
if (auth) {
|
|
27
27
|
setIsAuthenticated(true);
|
|
28
|
-
setUserLevel(auth.
|
|
29
|
-
setIsAdmin(auth.
|
|
30
|
-
setUserName(auth.
|
|
31
|
-
setAvatarURL(auth.
|
|
28
|
+
setUserLevel(auth.userlevel);
|
|
29
|
+
setIsAdmin(auth.isadmin);
|
|
30
|
+
setUserName(auth.username);
|
|
31
|
+
setAvatarURL(auth.avatarurl);
|
|
32
32
|
} else {
|
|
33
33
|
setIsAuthenticated(false);
|
|
34
34
|
setUserLevel('0');
|
package/dist/components/cta.tsx
CHANGED
|
@@ -28,10 +28,9 @@ interface CTAData {
|
|
|
28
28
|
interface RCTAProps {
|
|
29
29
|
custom_view_description: string;
|
|
30
30
|
onNavigate: (pageId: string) => void;
|
|
31
|
-
isPaigeLoading?: boolean;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate
|
|
33
|
+
const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate }) => {
|
|
35
34
|
let ctaData: CTAData;
|
|
36
35
|
|
|
37
36
|
try {
|
|
@@ -101,10 +100,6 @@ const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate, isPaig
|
|
|
101
100
|
};
|
|
102
101
|
|
|
103
102
|
const handleButtonClick = (pageId: string) => {
|
|
104
|
-
if (isPaigeLoading) {
|
|
105
|
-
console.log('Navigation blocked: Paige is currently processing a request');
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
103
|
if (pageId) {
|
|
109
104
|
onNavigate(pageId);
|
|
110
105
|
}
|
|
@@ -175,11 +170,9 @@ const RCTA: React.FC<RCTAProps> = ({ custom_view_description, onNavigate, isPaig
|
|
|
175
170
|
<button
|
|
176
171
|
key={index}
|
|
177
172
|
onClick={() => handleButtonClick(button.page)}
|
|
178
|
-
className={`px-6 py-3 rounded-lg transition-all ${hoverClass} ${
|
|
179
|
-
isPaigeLoading ? 'opacity-50 cursor-not-allowed' : ''
|
|
180
|
-
} ${!button.page ? 'opacity-75 cursor-not-allowed' : ''}`}
|
|
173
|
+
className={`px-6 py-3 rounded-lg transition-all ${hoverClass} ${!button.page ? 'opacity-75 cursor-not-allowed' : ''}`}
|
|
181
174
|
style={buttonStyles}
|
|
182
|
-
disabled={
|
|
175
|
+
disabled={!button.page}
|
|
183
176
|
>
|
|
184
177
|
{button.buttonTitle || 'Button'}
|
|
185
178
|
</button>
|
|
@@ -17,11 +17,11 @@ interface MenuItem {
|
|
|
17
17
|
|
|
18
18
|
interface UserData {
|
|
19
19
|
userid: string;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
username: string;
|
|
21
|
+
avatarurl: string;
|
|
22
|
+
email: string;
|
|
23
|
+
userlevel: number;
|
|
24
|
+
isadmin: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const getLocalizedText = (text: string, language: string = 'English'): string => {
|
|
@@ -210,11 +210,11 @@ const LoginSection: React.FC<LoginSectionProps> = ({ websiteLanguage = 'English'
|
|
|
210
210
|
className="flex items-center focus:outline-none relative group cursor-pointer"
|
|
211
211
|
style={{ color: textColor }}
|
|
212
212
|
>
|
|
213
|
-
<span className="mr-2" style={{ color: textColor }}>{userData.
|
|
213
|
+
<span className="mr-2" style={{ color: textColor }}>{userData.username || getLocalizedText('Sample User', websiteLanguage)}</span>
|
|
214
214
|
<div className="relative">
|
|
215
|
-
{userData.
|
|
215
|
+
{userData.avatarurl ? (
|
|
216
216
|
<img
|
|
217
|
-
src={userData.
|
|
217
|
+
src={userData.avatarurl}
|
|
218
218
|
alt="User avatar"
|
|
219
219
|
className="w-10 h-10 rounded-full"
|
|
220
220
|
referrerPolicy="no-referrer"
|
|
@@ -222,7 +222,7 @@ const LoginSection: React.FC<LoginSectionProps> = ({ websiteLanguage = 'English'
|
|
|
222
222
|
/>
|
|
223
223
|
) : (
|
|
224
224
|
<div className="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white font-semibold">
|
|
225
|
-
{userData.
|
|
225
|
+
{userData.username ? userData.username.charAt(0).toUpperCase() : 'U'}
|
|
226
226
|
</div>
|
|
227
227
|
)}
|
|
228
228
|
</div>
|