From be67815cabe1ffb9fb3979fe70afceb89c248a40 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Thu, 24 Jul 2025 20:03:31 -0500 Subject: [PATCH] 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 --- package-lock.json | 8 +-- package.json | 2 +- scripts/README.md | 56 +++++++++++++++++++- scripts/demote-admin-to-user.js | 89 ++++++++++++++++++++++++++++++++ scripts/list-users.js | 51 ++++++++++++++++++ scripts/promote-user-to-admin.js | 76 +++++++++++++++++++++++++++ 6 files changed, 275 insertions(+), 7 deletions(-) create mode 100755 scripts/demote-admin-to-user.js create mode 100755 scripts/list-users.js create mode 100755 scripts/promote-user-to-admin.js diff --git a/package-lock.json b/package-lock.json index c432869..5350538 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@neondatabase/serverless": "^1.0.1", "@vercel/postgres": "^0.10.0", "bcryptjs": "^3.0.2", - "dotenv": "^17.2.0", + "dotenv": "^17.2.1", "jsonwebtoken": "^9.0.2", "next": "^15.4.2", "node-fetch": "^3.3.2", @@ -2574,9 +2574,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", - "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 208578c..112b9bb 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@neondatabase/serverless": "^1.0.1", "@vercel/postgres": "^0.10.0", "bcryptjs": "^3.0.2", - "dotenv": "^17.2.0", + "dotenv": "^17.2.1", "jsonwebtoken": "^9.0.2", "next": "^15.4.2", "node-fetch": "^3.3.2", diff --git a/scripts/README.md b/scripts/README.md index 7c55b9c..ebf3414 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -4,7 +4,9 @@ This directory contains scripts for bulk importing TCG card data into the databa ## 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). **Usage:** @@ -19,7 +21,7 @@ npm run import-popular **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). **Usage:** @@ -34,6 +36,56 @@ npm run import-all **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 1. **Sequential Import**: Scripts import sets one by one to avoid overwhelming the APIs diff --git a/scripts/demote-admin-to-user.js b/scripts/demote-admin-to-user.js new file mode 100755 index 0000000..6afbd1e --- /dev/null +++ b/scripts/demote-admin-to-user.js @@ -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 '); + 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); \ No newline at end of file diff --git a/scripts/list-users.js b/scripts/list-users.js new file mode 100755 index 0000000..81ba26e --- /dev/null +++ b/scripts/list-users.js @@ -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(); \ No newline at end of file diff --git a/scripts/promote-user-to-admin.js b/scripts/promote-user-to-admin.js new file mode 100755 index 0000000..235401d --- /dev/null +++ b/scripts/promote-user-to-admin.js @@ -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 '); + 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); \ No newline at end of file