mirror of
				https://github.com/9technologygroup/patchmon.net.git
				synced 2025-10-31 03:53:51 +00:00 
			
		
		
		
	Implemented first-time admin registration flow if no admin present
This commit is contained in:
		| @@ -10,6 +10,107 @@ const { v4: uuidv4 } = require('uuid'); | ||||
| const router = express.Router(); | ||||
| const prisma = new PrismaClient(); | ||||
|  | ||||
| // Check if any admin users exist (for first-time setup) | ||||
| router.get('/check-admin-users', async (req, res) => { | ||||
|   try { | ||||
|     const adminCount = await prisma.users.count({ | ||||
|       where: { role: 'admin' } | ||||
|     }); | ||||
|      | ||||
|     res.json({ | ||||
|       hasAdminUsers: adminCount > 0, | ||||
|       adminCount: adminCount | ||||
|     }); | ||||
|   } catch (error) { | ||||
|     console.error('Error checking admin users:', error); | ||||
|     res.status(500).json({  | ||||
|       error: 'Failed to check admin users', | ||||
|       hasAdminUsers: false  | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| // Create first admin user (for first-time setup) | ||||
| router.post('/setup-admin', [ | ||||
|   body('username').isLength({ min: 1 }).withMessage('Username is required'), | ||||
|   body('email').isEmail().withMessage('Valid email is required'), | ||||
|   body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters') | ||||
| ], async (req, res) => { | ||||
|   try { | ||||
|     const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|       return res.status(400).json({  | ||||
|         error: 'Validation failed', | ||||
|         details: errors.array() | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     const { username, email, password } = req.body; | ||||
|  | ||||
|     // Check if any admin users already exist | ||||
|     const adminCount = await prisma.users.count({ | ||||
|       where: { role: 'admin' } | ||||
|     }); | ||||
|  | ||||
|     if (adminCount > 0) { | ||||
|       return res.status(400).json({  | ||||
|         error: 'Admin users already exist. This endpoint is only for first-time setup.' | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // Check if username or email already exists | ||||
|     const existingUser = await prisma.users.findFirst({ | ||||
|       where: { | ||||
|         OR: [ | ||||
|           { username: username.trim() }, | ||||
|           { email: email.trim() } | ||||
|         ] | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     if (existingUser) { | ||||
|       return res.status(400).json({  | ||||
|         error: 'Username or email already exists' | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     // Hash password | ||||
|     const passwordHash = await bcrypt.hash(password, 12); | ||||
|  | ||||
|     // Create admin user | ||||
|     const user = await prisma.users.create({ | ||||
|       data: { | ||||
|         id: uuidv4(), | ||||
|         username: username.trim(), | ||||
|         email: email.trim(), | ||||
|         password_hash: passwordHash, | ||||
|         role: 'admin', | ||||
|         is_active: true, | ||||
|         created_at: new Date(), | ||||
|         updated_at: new Date() | ||||
|       }, | ||||
|       select: { | ||||
|         id: true, | ||||
|         username: true, | ||||
|         email: true, | ||||
|         role: true, | ||||
|         created_at: true | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     res.status(201).json({ | ||||
|       message: 'Admin user created successfully', | ||||
|       user: user | ||||
|     }); | ||||
|  | ||||
|   } catch (error) { | ||||
|     console.error('Error creating admin user:', error); | ||||
|     res.status(500).json({  | ||||
|       error: 'Failed to create admin user' | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| // Generate JWT token | ||||
| const generateToken = (userId) => { | ||||
|   return jwt.sign( | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import React from 'react' | ||||
| import { Routes, Route } from 'react-router-dom' | ||||
| import { AuthProvider } from './contexts/AuthContext' | ||||
| import { AuthProvider, useAuth } from './contexts/AuthContext' | ||||
| import { ThemeProvider } from './contexts/ThemeContext' | ||||
| import { UpdateNotificationProvider } from './contexts/UpdateNotificationContext' | ||||
| import ProtectedRoute from './components/ProtectedRoute' | ||||
| @@ -18,21 +18,38 @@ import Options from './pages/Options' | ||||
| import Profile from './pages/Profile' | ||||
| import HostDetail from './pages/HostDetail' | ||||
| import PackageDetail from './pages/PackageDetail' | ||||
| import FirstTimeAdminSetup from './components/FirstTimeAdminSetup' | ||||
|  | ||||
| function AppRoutes() { | ||||
|   const { needsFirstTimeSetup, checkingSetup, isAuthenticated } = useAuth() | ||||
|  | ||||
|   // Show loading while checking if setup is needed | ||||
|   if (checkingSetup) { | ||||
|     return ( | ||||
|       <div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-secondary-900 dark:to-secondary-800 flex items-center justify-center"> | ||||
|         <div className="text-center"> | ||||
|           <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto mb-4"></div> | ||||
|           <p className="text-secondary-600 dark:text-secondary-300">Checking system status...</p> | ||||
|         </div> | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   // Show first-time setup if no admin users exist | ||||
|   if (needsFirstTimeSetup && !isAuthenticated) { | ||||
|     return <FirstTimeAdminSetup /> | ||||
|   } | ||||
|  | ||||
| function App() { | ||||
|   return ( | ||||
|     <ThemeProvider> | ||||
|       <AuthProvider> | ||||
|         <UpdateNotificationProvider> | ||||
|           <Routes> | ||||
|         <Route path="/login" element={<Login />} /> | ||||
|         <Route path="/" element={ | ||||
|           <ProtectedRoute requirePermission="can_view_dashboard"> | ||||
|             <Layout> | ||||
|               <Dashboard /> | ||||
|             </Layout> | ||||
|           </ProtectedRoute> | ||||
|         } /> | ||||
|     <Routes> | ||||
|       <Route path="/login" element={<Login />} /> | ||||
|       <Route path="/" element={ | ||||
|         <ProtectedRoute requirePermission="can_view_dashboard"> | ||||
|           <Layout> | ||||
|             <Dashboard /> | ||||
|           </Layout> | ||||
|         </ProtectedRoute> | ||||
|       } /> | ||||
|         <Route path="/hosts" element={ | ||||
|           <ProtectedRoute requirePermission="can_view_hosts"> | ||||
|             <Layout> | ||||
| @@ -110,7 +127,16 @@ function App() { | ||||
|             </Layout> | ||||
|           </ProtectedRoute> | ||||
|         } /> | ||||
|           </Routes> | ||||
|     </Routes> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| function App() { | ||||
|   return ( | ||||
|     <ThemeProvider> | ||||
|       <AuthProvider> | ||||
|         <UpdateNotificationProvider> | ||||
|           <AppRoutes /> | ||||
|         </UpdateNotificationProvider> | ||||
|       </AuthProvider> | ||||
|     </ThemeProvider> | ||||
|   | ||||
							
								
								
									
										246
									
								
								frontend/src/components/FirstTimeAdminSetup.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								frontend/src/components/FirstTimeAdminSetup.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,246 @@ | ||||
| import React, { useState } from 'react' | ||||
| import { useAuth } from '../contexts/AuthContext' | ||||
| import { UserPlus, Shield, CheckCircle, AlertCircle } from 'lucide-react' | ||||
|  | ||||
| const FirstTimeAdminSetup = () => { | ||||
|   const { login } = useAuth() | ||||
|   const [formData, setFormData] = useState({ | ||||
|     username: '', | ||||
|     email: '', | ||||
|     password: '', | ||||
|     confirmPassword: '' | ||||
|   }) | ||||
|   const [isLoading, setIsLoading] = useState(false) | ||||
|   const [error, setError] = useState('') | ||||
|   const [success, setSuccess] = useState(false) | ||||
|  | ||||
|   const handleInputChange = (e) => { | ||||
|     const { name, value } = e.target | ||||
|     setFormData(prev => ({ | ||||
|       ...prev, | ||||
|       [name]: value | ||||
|     })) | ||||
|     // Clear error when user starts typing | ||||
|     if (error) setError('') | ||||
|   } | ||||
|  | ||||
|   const validateForm = () => { | ||||
|     if (!formData.username.trim()) { | ||||
|       setError('Username is required') | ||||
|       return false | ||||
|     } | ||||
|     if (!formData.email.trim()) { | ||||
|       setError('Email is required') | ||||
|       return false | ||||
|     } | ||||
|     if (!formData.email.includes('@')) { | ||||
|       setError('Please enter a valid email address') | ||||
|       return false | ||||
|     } | ||||
|     if (formData.password.length < 6) { | ||||
|       setError('Password must be at least 6 characters') | ||||
|       return false | ||||
|     } | ||||
|     if (formData.password !== formData.confirmPassword) { | ||||
|       setError('Passwords do not match') | ||||
|       return false | ||||
|     } | ||||
|     return true | ||||
|   } | ||||
|  | ||||
|   const handleSubmit = async (e) => { | ||||
|     e.preventDefault() | ||||
|      | ||||
|     if (!validateForm()) return | ||||
|  | ||||
|     setIsLoading(true) | ||||
|     setError('') | ||||
|  | ||||
|     try { | ||||
|       const response = await fetch('/api/v1/auth/setup-admin', { | ||||
|         method: 'POST', | ||||
|         headers: { | ||||
|           'Content-Type': 'application/json' | ||||
|         }, | ||||
|         body: JSON.stringify({ | ||||
|           username: formData.username.trim(), | ||||
|           email: formData.email.trim(), | ||||
|           password: formData.password | ||||
|         }) | ||||
|       }) | ||||
|  | ||||
|       const data = await response.json() | ||||
|  | ||||
|       if (response.ok) { | ||||
|         setSuccess(true) | ||||
|         // Auto-login the user after successful setup | ||||
|         setTimeout(() => { | ||||
|           login(formData.username.trim(), formData.password) | ||||
|         }, 2000) | ||||
|       } else { | ||||
|         setError(data.error || 'Failed to create admin user') | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.error('Setup error:', error) | ||||
|       setError('Network error. Please try again.') | ||||
|     } finally { | ||||
|       setIsLoading(false) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (success) { | ||||
|     return ( | ||||
|       <div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-secondary-900 dark:to-secondary-800 flex items-center justify-center p-4"> | ||||
|         <div className="max-w-md w-full"> | ||||
|           <div className="card p-8 text-center"> | ||||
|             <div className="flex justify-center mb-6"> | ||||
|               <div className="bg-green-100 dark:bg-green-900 p-4 rounded-full"> | ||||
|                 <CheckCircle className="h-12 w-12 text-green-600 dark:text-green-400" /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <h1 className="text-2xl font-bold text-secondary-900 dark:text-white mb-4"> | ||||
|               Admin Account Created! | ||||
|             </h1> | ||||
|             <p className="text-secondary-600 dark:text-secondary-300 mb-6"> | ||||
|               Your admin account has been successfully created. You will be automatically logged in shortly. | ||||
|             </p> | ||||
|             <div className="flex justify-center"> | ||||
|               <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary-600"></div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div className="min-h-screen bg-gradient-to-br from-primary-50 to-secondary-50 dark:from-secondary-900 dark:to-secondary-800 flex items-center justify-center p-4"> | ||||
|       <div className="max-w-md w-full"> | ||||
|         <div className="card p-8"> | ||||
|           <div className="text-center mb-8"> | ||||
|             <div className="flex justify-center mb-4"> | ||||
|               <div className="bg-primary-100 dark:bg-primary-900 p-4 rounded-full"> | ||||
|                 <Shield className="h-12 w-12 text-primary-600 dark:text-primary-400" /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <h1 className="text-2xl font-bold text-secondary-900 dark:text-white mb-2"> | ||||
|               Welcome to PatchMon | ||||
|             </h1> | ||||
|             <p className="text-secondary-600 dark:text-secondary-300"> | ||||
|               Let's set up your admin account to get started | ||||
|             </p> | ||||
|           </div> | ||||
|  | ||||
|           {error && ( | ||||
|             <div className="mb-6 p-4 bg-danger-50 dark:bg-danger-900 border border-danger-200 dark:border-danger-700 rounded-lg"> | ||||
|               <div className="flex items-center"> | ||||
|                 <AlertCircle className="h-5 w-5 text-danger-600 dark:text-danger-400 mr-2" /> | ||||
|                 <span className="text-danger-700 dark:text-danger-300 text-sm">{error}</span> | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
|  | ||||
|           <form onSubmit={handleSubmit} className="space-y-6"> | ||||
|             <div> | ||||
|               <label htmlFor="username" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2"> | ||||
|                 Username | ||||
|               </label> | ||||
|               <input | ||||
|                 type="text" | ||||
|                 id="username" | ||||
|                 name="username" | ||||
|                 value={formData.username} | ||||
|                 onChange={handleInputChange} | ||||
|                 className="input w-full" | ||||
|                 placeholder="Enter your username" | ||||
|                 required | ||||
|                 disabled={isLoading} | ||||
|               /> | ||||
|             </div> | ||||
|  | ||||
|             <div> | ||||
|               <label htmlFor="email" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2"> | ||||
|                 Email Address | ||||
|               </label> | ||||
|               <input | ||||
|                 type="email" | ||||
|                 id="email" | ||||
|                 name="email" | ||||
|                 value={formData.email} | ||||
|                 onChange={handleInputChange} | ||||
|                 className="input w-full" | ||||
|                 placeholder="Enter your email" | ||||
|                 required | ||||
|                 disabled={isLoading} | ||||
|               /> | ||||
|             </div> | ||||
|  | ||||
|             <div> | ||||
|               <label htmlFor="password" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2"> | ||||
|                 Password | ||||
|               </label> | ||||
|               <input | ||||
|                 type="password" | ||||
|                 id="password" | ||||
|                 name="password" | ||||
|                 value={formData.password} | ||||
|                 onChange={handleInputChange} | ||||
|                 className="input w-full" | ||||
|                 placeholder="Enter your password (min 6 characters)" | ||||
|                 required | ||||
|                 disabled={isLoading} | ||||
|               /> | ||||
|             </div> | ||||
|  | ||||
|             <div> | ||||
|               <label htmlFor="confirmPassword" className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2"> | ||||
|                 Confirm Password | ||||
|               </label> | ||||
|               <input | ||||
|                 type="password" | ||||
|                 id="confirmPassword" | ||||
|                 name="confirmPassword" | ||||
|                 value={formData.confirmPassword} | ||||
|                 onChange={handleInputChange} | ||||
|                 className="input w-full" | ||||
|                 placeholder="Confirm your password" | ||||
|                 required | ||||
|                 disabled={isLoading} | ||||
|               /> | ||||
|             </div> | ||||
|  | ||||
|             <button | ||||
|               type="submit" | ||||
|               disabled={isLoading} | ||||
|               className="btn-primary w-full flex items-center justify-center gap-2" | ||||
|             > | ||||
|               {isLoading ? ( | ||||
|                 <> | ||||
|                   <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> | ||||
|                   Creating Admin Account... | ||||
|                 </> | ||||
|               ) : ( | ||||
|                 <> | ||||
|                   <UserPlus className="h-4 w-4" /> | ||||
|                   Create Admin Account | ||||
|                 </> | ||||
|               )} | ||||
|             </button> | ||||
|           </form> | ||||
|  | ||||
|           <div className="mt-8 p-4 bg-blue-50 dark:bg-blue-900 border border-blue-200 dark:border-blue-700 rounded-lg"> | ||||
|             <div className="flex items-start"> | ||||
|               <Shield className="h-5 w-5 text-blue-600 dark:text-blue-400 mr-2 mt-0.5 flex-shrink-0" /> | ||||
|               <div className="text-sm text-blue-700 dark:text-blue-300"> | ||||
|                 <p className="font-medium mb-1">Admin Privileges</p> | ||||
|                 <p>This account will have full administrative access to manage users, hosts, packages, and system settings.</p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export default FirstTimeAdminSetup | ||||
| @@ -16,6 +16,8 @@ export const AuthProvider = ({ children }) => { | ||||
|   const [permissions, setPermissions] = useState(null) | ||||
|   const [isLoading, setIsLoading] = useState(true) | ||||
|   const [permissionsLoading, setPermissionsLoading] = useState(false) | ||||
|   const [needsFirstTimeSetup, setNeedsFirstTimeSetup] = useState(false) | ||||
|   const [checkingSetup, setCheckingSetup] = useState(true) | ||||
|  | ||||
|   // Initialize auth state from localStorage | ||||
|   useEffect(() => { | ||||
| @@ -217,11 +219,48 @@ export const AuthProvider = ({ children }) => { | ||||
|   const canExportData = () => hasPermission('can_export_data') | ||||
|   const canManageSettings = () => hasPermission('can_manage_settings') | ||||
|  | ||||
|   // Check if any admin users exist (for first-time setup) | ||||
|   const checkAdminUsersExist = async () => { | ||||
|     try { | ||||
|       const response = await fetch('/api/v1/auth/check-admin-users', { | ||||
|         method: 'GET', | ||||
|         headers: { | ||||
|           'Content-Type': 'application/json' | ||||
|         } | ||||
|       }) | ||||
|        | ||||
|       if (response.ok) { | ||||
|         const data = await response.json() | ||||
|         setNeedsFirstTimeSetup(!data.hasAdminUsers) | ||||
|       } else { | ||||
|         // If endpoint doesn't exist or fails, assume setup is needed | ||||
|         setNeedsFirstTimeSetup(true) | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.error('Error checking admin users:', error) | ||||
|       // If there's an error, assume setup is needed | ||||
|       setNeedsFirstTimeSetup(true) | ||||
|     } finally { | ||||
|       setCheckingSetup(false) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Check for admin users on initial load | ||||
|   useEffect(() => { | ||||
|     if (!token && !user) { | ||||
|       checkAdminUsersExist() | ||||
|     } else { | ||||
|       setCheckingSetup(false) | ||||
|     } | ||||
|   }, [token, user]) | ||||
|  | ||||
|   const value = { | ||||
|     user, | ||||
|     token, | ||||
|     permissions, | ||||
|     isLoading: isLoading || permissionsLoading, | ||||
|     isLoading: isLoading || permissionsLoading || checkingSetup, | ||||
|     needsFirstTimeSetup, | ||||
|     checkingSetup, | ||||
|     login, | ||||
|     logout, | ||||
|     updateProfile, | ||||
|   | ||||
							
								
								
									
										112
									
								
								setup-admin-user-fixed.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								setup-admin-user-fixed.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| const { PrismaClient } = require('@prisma/client'); | ||||
| const bcrypt = require('bcryptjs'); | ||||
| const readline = require('readline'); | ||||
|  | ||||
| const prisma = new PrismaClient(); | ||||
|  | ||||
| const rl = readline.createInterface({ | ||||
|   input: process.stdin, | ||||
|   output: process.stdout | ||||
| }); | ||||
|  | ||||
| const question = (query) => new Promise((resolve) => rl.question(query, resolve)); | ||||
|  | ||||
| async function setupAdminUser() { | ||||
|   try { | ||||
|     console.log('🔐 Setting up PatchMon Admin User'); | ||||
|     console.log('=====================================\n'); | ||||
|  | ||||
|     // Check if any users exist | ||||
|     const existingUsers = await prisma.users.count(); | ||||
|     if (existingUsers > 0) { | ||||
|       console.log('⚠️  Users already exist in the database.'); | ||||
|       const overwrite = await question('Do you want to create another admin user? (y/N): '); | ||||
|       if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') { | ||||
|         console.log('❌ Setup cancelled.'); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Get user input | ||||
|     const username = await question('Enter admin username: '); | ||||
|     if (!username.trim()) { | ||||
|       console.log('❌ Username is required.'); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const email = await question('Enter admin email: '); | ||||
|     if (!email.trim()) { | ||||
|       console.log('❌ Email is required.'); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const password = await question('Enter admin password (min 6 characters): '); | ||||
|     if (password.length < 6) { | ||||
|       console.log('❌ Password must be at least 6 characters.'); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // Check if username or email already exists | ||||
|     const existingUser = await prisma.users.findFirst({ | ||||
|       where: { | ||||
|         OR: [ | ||||
|           { username: username.trim() }, | ||||
|           { email: email.trim() } | ||||
|         ] | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     if (existingUser) { | ||||
|       console.log('❌ Username or email already exists.'); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // Hash password | ||||
|     console.log('\n🔄 Creating admin user...'); | ||||
|     const passwordHash = await bcrypt.hash(password, 12); | ||||
|  | ||||
|     // Create admin user | ||||
|     const user = await prisma.users.create({ | ||||
|       data: { | ||||
|         id: require('crypto').randomUUID(), | ||||
|         username: username.trim(), | ||||
|         email: email.trim(), | ||||
|         password_hash: passwordHash, | ||||
|         role: 'admin', | ||||
|         is_active: true, | ||||
|         created_at: new Date(), | ||||
|         updated_at: new Date() | ||||
|       }, | ||||
|       select: { | ||||
|         id: true, | ||||
|         username: true, | ||||
|         email: true, | ||||
|         role: true, | ||||
|         created_at: true | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     console.log('✅ Admin user created successfully!'); | ||||
|     console.log('\n📋 User Details:'); | ||||
|     console.log(`   Username: ${user.username}`); | ||||
|     console.log(`   Email: ${user.email}`); | ||||
|     console.log(`   Role: ${user.role}`); | ||||
|     console.log(`   Created: ${user.created_at.toISOString()}`); | ||||
|  | ||||
|     console.log('\n🎉 Setup complete!'); | ||||
|     console.log('\nNext steps:'); | ||||
|     console.log('1. The backend server is already running as a systemd service'); | ||||
|     console.log('2. The frontend is already built and served by Nginx'); | ||||
|     console.log('3. Visit https://' + process.env.FQDN + ' and login with your credentials'); | ||||
|     console.log('4. Use the management script: ./manage.sh {status|restart|logs|update|backup|credentials|reset-admin}'); | ||||
|  | ||||
|   } catch (error) { | ||||
|     console.error('❌ Error setting up admin user:', error); | ||||
|   } finally { | ||||
|     rl.close(); | ||||
|     await prisma.$disconnect(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Run the setup | ||||
| setupAdminUser(); | ||||
| @@ -17,7 +17,7 @@ async function setupAdminUser() { | ||||
|     console.log('=====================================\n'); | ||||
|  | ||||
|     // Check if any users exist | ||||
|     const existingUsers = await prisma.user.count(); | ||||
|     const existingUsers = await prisma.users.count(); | ||||
|     if (existingUsers > 0) { | ||||
|       console.log('⚠️  Users already exist in the database.'); | ||||
|       const overwrite = await question('Do you want to create another admin user? (y/N): '); | ||||
| @@ -47,7 +47,7 @@ async function setupAdminUser() { | ||||
|     } | ||||
|  | ||||
|     // Check if username or email already exists | ||||
|     const existingUser = await prisma.user.findFirst({ | ||||
|     const existingUser = await prisma.users.findFirst({ | ||||
|       where: { | ||||
|         OR: [ | ||||
|           { username: username.trim() }, | ||||
| @@ -66,19 +66,23 @@ async function setupAdminUser() { | ||||
|     const passwordHash = await bcrypt.hash(password, 12); | ||||
|  | ||||
|     // Create admin user | ||||
|     const user = await prisma.user.create({ | ||||
|     const user = await prisma.users.create({ | ||||
|       data: { | ||||
|         id: require('crypto').randomUUID(), | ||||
|         username: username.trim(), | ||||
|         email: email.trim(), | ||||
|         passwordHash: passwordHash, | ||||
|         role: 'admin' | ||||
|         password_hash: passwordHash, | ||||
|         role: 'admin', | ||||
|         is_active: true, | ||||
|         created_at: new Date(), | ||||
|         updated_at: new Date() | ||||
|       }, | ||||
|       select: { | ||||
|         id: true, | ||||
|         username: true, | ||||
|         email: true, | ||||
|         role: true, | ||||
|         createdAt: true | ||||
|         created_at: true | ||||
|       } | ||||
|     }); | ||||
|  | ||||
| @@ -87,7 +91,7 @@ async function setupAdminUser() { | ||||
|     console.log(`   Username: ${user.username}`); | ||||
|     console.log(`   Email: ${user.email}`); | ||||
|     console.log(`   Role: ${user.role}`); | ||||
|     console.log(`   Created: ${user.createdAt.toISOString()}`); | ||||
|     console.log(`   Created: ${user.created_at.toISOString()}`); | ||||
|  | ||||
|     console.log('\n🎉 Setup complete!'); | ||||
|     console.log('\nNext steps:'); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user