import db from "./db.js" import bcrypt from "bcryptjs" import { randomBytes } from "crypto" // Create a new user export async function createUser({ name, username, password, role = 'user', is_active = true, can_be_assigned = true }) { const existingUser = db.prepare("SELECT id FROM users WHERE username = ?").get(username) if (existingUser) { throw new Error("User with this username already exists") } const passwordHash = await bcrypt.hash(password, 12) const userId = randomBytes(16).toString('hex') const result = db.prepare(` INSERT INTO users (id, name, username, password_hash, role, is_active, can_be_assigned) VALUES (?, ?, ?, ?, ?, ?, ?) `).run(userId, name, username, passwordHash, role, is_active ? 1 : 0, can_be_assigned ? 1 : 0) return db.prepare(` SELECT id, name, username, role, created_at, updated_at, last_login, is_active, failed_login_attempts, locked_until, initial, can_be_assigned FROM users WHERE id = ? `).get(userId) } // Get user by ID export function getUserById(id) { return db.prepare(` SELECT id, name, username, password_hash, role, created_at, updated_at, last_login, is_active, failed_login_attempts, locked_until, initial, can_be_assigned FROM users WHERE id = ? `).get(id) } // Get user by username export function getUserByUsername(username) { return db.prepare(` SELECT id, name, username, role, created_at, last_login, is_active, initial FROM users WHERE username = ? `).get(username) } // Get all users (for admin) export function getAllUsers() { return db.prepare(` SELECT id, name, username, password_hash, role, created_at, updated_at, last_login, is_active, failed_login_attempts, locked_until, initial, can_be_assigned FROM users ORDER BY created_at DESC `).all() } // Update user role export function updateUserRole(userId, role) { const validRoles = ['admin', 'team_lead', 'project_manager', 'user', 'read_only'] if (!validRoles.includes(role)) { throw new Error("Invalid role") } const result = db.prepare(` UPDATE users SET role = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `).run(role, userId) return result.changes > 0 } // Activate/deactivate user export function setUserActive(userId, isActive) { const result = db.prepare(` UPDATE users SET is_active = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `).run(isActive ? 1 : 0, userId) return result.changes > 0 } // Change user password export async function changeUserPassword(userId, newPassword) { const passwordHash = await bcrypt.hash(newPassword, 12) const result = db.prepare(` UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP, failed_login_attempts = 0, locked_until = NULL WHERE id = ? `).run(passwordHash, userId) return result.changes > 0 } // Clean up expired sessions export function cleanupExpiredSessions() { const result = db.prepare(` DELETE FROM sessions WHERE expires < datetime('now') `).run() return result.changes } // Get user sessions export function getUserSessions(userId) { return db.prepare(` SELECT id, session_token, expires, created_at FROM sessions WHERE user_id = ? AND expires > datetime('now') ORDER BY created_at DESC `).all(userId) } // Revoke user session export function revokeSession(sessionToken) { const result = db.prepare(` DELETE FROM sessions WHERE session_token = ? `).run(sessionToken) return result.changes > 0 } // Get audit logs for user export function getUserAuditLogs(userId, limit = 50) { return db.prepare(` SELECT action, resource_type, resource_id, ip_address, timestamp, details FROM audit_logs WHERE user_id = ? ORDER BY timestamp DESC LIMIT ? `).all(userId, limit) } // Update user (comprehensive update function) export async function updateUser(userId, updates) { const user = getUserById(userId); if (!user) { return null; } // Check if username is being changed and if it already exists if (updates.username && updates.username !== user.username) { const existingUser = db.prepare("SELECT id FROM users WHERE username = ? AND id != ?").get(updates.username, userId); if (existingUser) { throw new Error("User with this username already exists"); } } // Prepare update fields const updateFields = []; const updateValues = []; if (updates.name !== undefined) { updateFields.push("name = ?"); updateValues.push(updates.name); } if (updates.username !== undefined) { updateFields.push("username = ?"); updateValues.push(updates.username); } if (updates.role !== undefined) { const validRoles = ['admin', 'team_lead', 'project_manager', 'user', 'read_only']; if (!validRoles.includes(updates.role)) { throw new Error("Invalid role"); } updateFields.push("role = ?"); updateValues.push(updates.role); } if (updates.is_active !== undefined) { updateFields.push("is_active = ?"); updateValues.push(updates.is_active ? 1 : 0); } if (updates.can_be_assigned !== undefined) { updateFields.push("can_be_assigned = ?"); updateValues.push(updates.can_be_assigned ? 1 : 0); } if (updates.initial !== undefined) { updateFields.push("initial = ?"); updateValues.push(updates.initial); } if (updates.password !== undefined) { const passwordHash = await bcrypt.hash(updates.password, 12); updateFields.push("password_hash = ?"); updateValues.push(passwordHash); // Reset failed login attempts when password is changed updateFields.push("failed_login_attempts = 0"); updateFields.push("locked_until = NULL"); } if (updateFields.length === 0) { return getUserById(userId); // Return existing user if no updates } updateFields.push("updated_at = CURRENT_TIMESTAMP"); updateValues.push(userId); const query = ` UPDATE users SET ${updateFields.join(", ")} WHERE id = ? `; const result = db.prepare(query).run(...updateValues); if (result.changes > 0) { return db.prepare(` SELECT id, name, username, role, created_at, updated_at, last_login, is_active, failed_login_attempts, locked_until, initial, can_be_assigned FROM users WHERE id = ? `).get(userId); } return null; } // Delete user export function deleteUser(userId) { // First, delete related data (sessions, audit logs, etc.) db.prepare("DELETE FROM sessions WHERE user_id = ?").run(userId); db.prepare("DELETE FROM audit_logs WHERE user_id = ?").run(userId); // Then delete the user const result = db.prepare("DELETE FROM users WHERE id = ?").run(userId); return result.changes > 0; } // Reset user password (admin function) export async function resetUserPassword(userId, newPassword) { const passwordHash = await bcrypt.hash(newPassword, 12); const result = db.prepare(` UPDATE users SET password_hash = ?, failed_login_attempts = 0, locked_until = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? `).run(passwordHash, userId); return result.changes > 0; } // Unlock user account export function unlockUserAccount(userId) { const result = db.prepare(` UPDATE users SET failed_login_attempts = 0, locked_until = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ? `).run(userId); return result.changes > 0; } // Get user statistics export function getUserStats() { const stats = db.prepare(` SELECT COUNT(*) as total_users, COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_users, COUNT(CASE WHEN is_active = 0 THEN 1 END) as inactive_users, COUNT(CASE WHEN role = 'admin' THEN 1 END) as admin_users, COUNT(CASE WHEN role = 'project_manager' THEN 1 END) as manager_users, COUNT(CASE WHEN role = 'user' THEN 1 END) as regular_users, COUNT(CASE WHEN role = 'read_only' THEN 1 END) as readonly_users, COUNT(CASE WHEN last_login IS NOT NULL THEN 1 END) as users_with_login FROM users `).get(); return stats; }