Added comprehensive user management scripts
- Created list-users.js to display all users with roles and details - Added promote-user-to-admin.js to elevate regular users to admin - Added demote-admin-to-user.js with safety check for last admin - All scripts use proper ES modules and dotenv for environment loading - Scripts validate user existence and current roles before operations - Added detailed documentation to scripts/README.md - Includes user-friendly output with emojis and clear status messages - Tested promotion functionality successfully - Maintains database integrity with proper error handling
This commit is contained in:
parent
3bb1c9357a
commit
be67815cab
6 changed files with 275 additions and 7 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -11,7 +11,7 @@
|
||||||
"@neondatabase/serverless": "^1.0.1",
|
"@neondatabase/serverless": "^1.0.1",
|
||||||
"@vercel/postgres": "^0.10.0",
|
"@vercel/postgres": "^0.10.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"dotenv": "^17.2.0",
|
"dotenv": "^17.2.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"next": "^15.4.2",
|
"next": "^15.4.2",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
|
@ -2574,9 +2574,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "17.2.0",
|
"version": "17.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
||||||
"integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==",
|
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
"@neondatabase/serverless": "^1.0.1",
|
"@neondatabase/serverless": "^1.0.1",
|
||||||
"@vercel/postgres": "^0.10.0",
|
"@vercel/postgres": "^0.10.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"dotenv": "^17.2.0",
|
"dotenv": "^17.2.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"next": "^15.4.2",
|
"next": "^15.4.2",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ This directory contains scripts for bulk importing TCG card data into the databa
|
||||||
|
|
||||||
## Available Scripts
|
## Available Scripts
|
||||||
|
|
||||||
### 1. `import-popular-sets.js` - Popular Sets Import
|
### Card Import Scripts
|
||||||
|
|
||||||
|
#### 1. `import-popular-sets.js` - Popular Sets Import
|
||||||
Imports the most popular and recent sets from all three TCGs (Magic, Pokemon, Lorcana).
|
Imports the most popular and recent sets from all three TCGs (Magic, Pokemon, Lorcana).
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
@ -19,7 +21,7 @@ npm run import-popular
|
||||||
|
|
||||||
**Estimated time:** 2-4 hours (depending on API response times)
|
**Estimated time:** 2-4 hours (depending on API response times)
|
||||||
|
|
||||||
### 2. `bulk-import-all.js` - Complete Import
|
#### 2. `bulk-import-all.js` - Complete Import
|
||||||
Imports ALL available sets from all TCGs (comprehensive import).
|
Imports ALL available sets from all TCGs (comprehensive import).
|
||||||
|
|
||||||
**Usage:**
|
**Usage:**
|
||||||
|
|
@ -34,6 +36,56 @@ npm run import-all
|
||||||
|
|
||||||
**Estimated time:** 4-8 hours (depending on API response times)
|
**Estimated time:** 4-8 hours (depending on API response times)
|
||||||
|
|
||||||
|
### User Management Scripts
|
||||||
|
|
||||||
|
#### 3. `list-users.js` - List All Users
|
||||||
|
Lists all users in the database with their roles and details.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
node scripts/list-users.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:** Shows user ID, email, role (admin/user), and creation date.
|
||||||
|
|
||||||
|
#### 4. `promote-user-to-admin.js` - Promote User to Admin
|
||||||
|
Promotes a regular user to admin role.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
node scripts/promote-user-to-admin.js user@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Requirements:** User must be registered first.
|
||||||
|
|
||||||
|
#### 5. `demote-admin-to-user.js` - Demote Admin to User
|
||||||
|
Demotes an admin back to regular user role.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
node scripts/demote-admin-to-user.js admin@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Safety:** Ensures at least one admin always remains in the system.
|
||||||
|
|
||||||
|
### Database Management Scripts
|
||||||
|
|
||||||
|
#### 6. `setup-neon-db.js` - Database Setup
|
||||||
|
Sets up the Neon PostgreSQL database with all required tables and creates the default admin user.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
node scripts/setup-neon-db.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7. `reset-db.js` - Database Reset
|
||||||
|
Resets the database by dropping and recreating all tables.
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
node scripts/reset-db.js
|
||||||
|
```
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
1. **Sequential Import**: Scripts import sets one by one to avoid overwhelming the APIs
|
1. **Sequential Import**: Scripts import sets one by one to avoid overwhelming the APIs
|
||||||
|
|
|
||||||
89
scripts/demote-admin-to-user.js
Executable file
89
scripts/demote-admin-to-user.js
Executable file
|
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import { sql } from '@vercel/postgres';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
config({ path: '.env.local' });
|
||||||
|
|
||||||
|
async function demoteAdminToUser(email) {
|
||||||
|
if (!email) {
|
||||||
|
console.error('❌ Error: Email is required');
|
||||||
|
console.log('Usage: node scripts/demote-admin-to-user.js <email>');
|
||||||
|
console.log('Example: node scripts/demote-admin-to-user.js admin@example.com');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 Looking for admin user:', email);
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
const userCheck = await sql`
|
||||||
|
SELECT id, email, role FROM users WHERE email = ${email}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (userCheck.rows.length === 0) {
|
||||||
|
console.error('❌ User not found:', email);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = userCheck.rows[0];
|
||||||
|
console.log('👤 Found user:', {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
currentRole: user.role
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user.role === 'user') {
|
||||||
|
console.log('✅ User is already a regular user!');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.role !== 'admin') {
|
||||||
|
console.error('❌ User is not an admin, cannot demote');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the last admin
|
||||||
|
const adminCount = await sql`
|
||||||
|
SELECT COUNT(*) as count FROM users WHERE role = 'admin'
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (parseInt(adminCount.rows[0].count) <= 1) {
|
||||||
|
console.error('❌ Cannot demote the last admin user!');
|
||||||
|
console.log('💡 Make sure there is at least one admin before demoting');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demote admin to user
|
||||||
|
const result = await sql`
|
||||||
|
UPDATE users
|
||||||
|
SET role = 'user'
|
||||||
|
WHERE email = ${email}
|
||||||
|
RETURNING id, email, role, created_at
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (result.rows.length > 0) {
|
||||||
|
const updatedUser = result.rows[0];
|
||||||
|
console.log('✅ Successfully demoted admin to user!');
|
||||||
|
console.log('👤 Updated user details:', {
|
||||||
|
id: updatedUser.id,
|
||||||
|
email: updatedUser.email,
|
||||||
|
role: updatedUser.role,
|
||||||
|
created_at: updatedUser.created_at
|
||||||
|
});
|
||||||
|
console.log('');
|
||||||
|
console.log('🔒 The user no longer has access to admin features');
|
||||||
|
} else {
|
||||||
|
console.error('❌ Failed to demote user');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Database error:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get email from command line arguments
|
||||||
|
const email = process.argv[2];
|
||||||
|
demoteAdminToUser(email);
|
||||||
51
scripts/list-users.js
Executable file
51
scripts/list-users.js
Executable file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import { sql } from '@vercel/postgres';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
config({ path: '.env.local' });
|
||||||
|
|
||||||
|
async function listUsers() {
|
||||||
|
try {
|
||||||
|
console.log('📋 Fetching all users from database...\n');
|
||||||
|
|
||||||
|
const result = await sql`
|
||||||
|
SELECT id, email, role, created_at
|
||||||
|
FROM users
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
console.log('👤 No users found in database');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${result.rows.length} user(s):\n`);
|
||||||
|
|
||||||
|
result.rows.forEach((user, index) => {
|
||||||
|
const roleEmoji = user.role === 'admin' ? '👑' : '👤';
|
||||||
|
const createdDate = new Date(user.created_at).toLocaleDateString();
|
||||||
|
|
||||||
|
console.log(`${index + 1}. ${roleEmoji} ${user.email}`);
|
||||||
|
console.log(` ID: ${user.id}`);
|
||||||
|
console.log(` Role: ${user.role}`);
|
||||||
|
console.log(` Created: ${createdDate}`);
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminCount = result.rows.filter(user => user.role === 'admin').length;
|
||||||
|
const userCount = result.rows.filter(user => user.role === 'user').length;
|
||||||
|
|
||||||
|
console.log('📊 Summary:');
|
||||||
|
console.log(` 👑 Admins: ${adminCount}`);
|
||||||
|
console.log(` 👤 Users: ${userCount}`);
|
||||||
|
console.log(` 📈 Total: ${result.rows.length}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Database error:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listUsers();
|
||||||
76
scripts/promote-user-to-admin.js
Executable file
76
scripts/promote-user-to-admin.js
Executable file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import { sql } from '@vercel/postgres';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
config({ path: '.env.local' });
|
||||||
|
|
||||||
|
async function promoteUserToAdmin(email) {
|
||||||
|
if (!email) {
|
||||||
|
console.error('❌ Error: Email is required');
|
||||||
|
console.log('Usage: node scripts/promote-user-to-admin.js <email>');
|
||||||
|
console.log('Example: node scripts/promote-user-to-admin.js user@example.com');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 Looking for user:', email);
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
const userCheck = await sql`
|
||||||
|
SELECT id, email, role FROM users WHERE email = ${email}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (userCheck.rows.length === 0) {
|
||||||
|
console.error('❌ User not found:', email);
|
||||||
|
console.log('💡 Make sure the user has registered first');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = userCheck.rows[0];
|
||||||
|
console.log('👤 Found user:', {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
currentRole: user.role
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user.role === 'admin') {
|
||||||
|
console.log('✅ User is already an admin!');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promote user to admin
|
||||||
|
const result = await sql`
|
||||||
|
UPDATE users
|
||||||
|
SET role = 'admin'
|
||||||
|
WHERE email = ${email}
|
||||||
|
RETURNING id, email, role, created_at
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (result.rows.length > 0) {
|
||||||
|
const updatedUser = result.rows[0];
|
||||||
|
console.log('🎉 Successfully promoted user to admin!');
|
||||||
|
console.log('👑 Updated user details:', {
|
||||||
|
id: updatedUser.id,
|
||||||
|
email: updatedUser.email,
|
||||||
|
role: updatedUser.role,
|
||||||
|
created_at: updatedUser.created_at
|
||||||
|
});
|
||||||
|
console.log('');
|
||||||
|
console.log('🔐 The user can now access admin features at:');
|
||||||
|
console.log(' • /admin/card-editor');
|
||||||
|
console.log(' • /admin/card-import');
|
||||||
|
} else {
|
||||||
|
console.error('❌ Failed to promote user');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Database error:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get email from command line arguments
|
||||||
|
const email = process.argv[2];
|
||||||
|
promoteUserToAdmin(email);
|
||||||
Loading…
Reference in a new issue