import { Router } from "@oak/oak"; import { hash } from "bcrypt"; import { db } from "../config/database.ts"; import { config } from "../config/env.ts"; import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts"; import { sanitizeInput, isValidEmail } from "../middleware/security.ts"; import type { User, CreateUserRequest, UpdateUserRequest } from "../types/index.ts"; const router = new Router(); // Get all users (with filters) router.get("/", authenticateToken, async (ctx) => { try { const currentUser = getCurrentUser(ctx); const params = ctx.request.url.searchParams; const role = params.get("role"); const departmentId = params.get("departmentId"); let query = ` SELECT u.id, u.username, u.name, u.email, u.role, u.department_id, u.contractor_id, u.is_active, u.created_at, d.name as department_name, c.name as contractor_name FROM users u LEFT JOIN departments d ON u.department_id = d.id LEFT JOIN users c ON u.contractor_id = c.id WHERE 1=1 `; const queryParams: unknown[] = []; // Supervisors can only see users in their department if (currentUser.role === "Supervisor") { query += " AND u.department_id = ?"; queryParams.push(currentUser.departmentId); } if (role) { query += " AND u.role = ?"; queryParams.push(role); } if (departmentId) { query += " AND u.department_id = ?"; queryParams.push(departmentId); } query += " ORDER BY u.created_at DESC"; const users = await db.query(query, queryParams); ctx.response.body = users; } catch (error) { console.error("Get users error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Get user by ID router.get("/:id", authenticateToken, async (ctx) => { try { const currentUser = getCurrentUser(ctx); const userId = ctx.params.id; const users = await db.query( `SELECT u.id, u.username, u.name, u.email, u.role, u.department_id, u.contractor_id, u.is_active, u.created_at, d.name as department_name, c.name as contractor_name FROM users u LEFT JOIN departments d ON u.department_id = d.id LEFT JOIN users c ON u.contractor_id = c.id WHERE u.id = ?`, [userId] ); if (users.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "User not found" }; return; } // Supervisors can only view users in their department if (currentUser.role === "Supervisor" && users[0].department_id !== currentUser.departmentId) { ctx.response.status = 403; ctx.response.body = { error: "Access denied" }; return; } ctx.response.body = users[0]; } catch (error) { console.error("Get user error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Create user router.post("/", authenticateToken, authorize("SuperAdmin", "Supervisor"), async (ctx) => { try { const currentUser = getCurrentUser(ctx); const body = await ctx.request.body.json() as CreateUserRequest; const { username, name, email, password, role, departmentId, contractorId } = body; // Input validation if (!username || !name || !email || !password || !role) { ctx.response.status = 400; ctx.response.body = { error: "Missing required fields" }; return; } // Sanitize inputs const sanitizedUsername = sanitizeInput(username); const sanitizedName = sanitizeInput(name); const sanitizedEmail = sanitizeInput(email); // Validate email if (!isValidEmail(sanitizedEmail)) { ctx.response.status = 400; ctx.response.body = { error: "Invalid email format" }; return; } // Supervisors can only create users in their department if (currentUser.role === "Supervisor") { if (departmentId !== currentUser.departmentId) { ctx.response.status = 403; ctx.response.body = { error: "Can only create users in your department" }; return; } if (role === "SuperAdmin" || role === "Supervisor") { ctx.response.status = 403; ctx.response.body = { error: "Cannot create admin or supervisor users" }; return; } } // Hash password const hashedPassword = await hash(password, config.BCRYPT_ROUNDS); const result = await db.execute( "INSERT INTO users (username, name, email, password, role, department_id, contractor_id) VALUES (?, ?, ?, ?, ?, ?, ?)", [sanitizedUsername, sanitizedName, sanitizedEmail, hashedPassword, role, departmentId || null, contractorId || null] ); const newUser = await db.query( `SELECT u.id, u.username, u.name, u.email, u.role, u.department_id, u.contractor_id, u.is_active, u.created_at, d.name as department_name, c.name as contractor_name FROM users u LEFT JOIN departments d ON u.department_id = d.id LEFT JOIN users c ON u.contractor_id = c.id WHERE u.id = ?`, [result.insertId] ); ctx.response.status = 201; ctx.response.body = newUser[0]; } catch (error) { const err = error as { code?: string }; if (err.code === "ER_DUP_ENTRY") { ctx.response.status = 400; ctx.response.body = { error: "Username or email already exists" }; return; } console.error("Create user error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Update user router.put("/:id", authenticateToken, authorize("SuperAdmin", "Supervisor"), async (ctx) => { try { const currentUser = getCurrentUser(ctx); const userId = ctx.params.id; const body = await ctx.request.body.json() as UpdateUserRequest; const { name, email, role, departmentId, contractorId, isActive } = body; // Check if user exists const existingUsers = await db.query( "SELECT * FROM users WHERE id = ?", [userId] ); if (existingUsers.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "User not found" }; return; } // Supervisors can only update users in their department if (currentUser.role === "Supervisor") { if (existingUsers[0].department_id !== currentUser.departmentId) { ctx.response.status = 403; ctx.response.body = { error: "Can only update users in your department" }; return; } if (role === "SuperAdmin" || role === "Supervisor") { ctx.response.status = 403; ctx.response.body = { error: "Cannot modify admin or supervisor roles" }; return; } } const updates: string[] = []; const params: unknown[] = []; if (name !== undefined) { updates.push("name = ?"); params.push(sanitizeInput(name)); } if (email !== undefined) { if (!isValidEmail(email)) { ctx.response.status = 400; ctx.response.body = { error: "Invalid email format" }; return; } updates.push("email = ?"); params.push(sanitizeInput(email)); } if (role !== undefined) { updates.push("role = ?"); params.push(role); } if (departmentId !== undefined) { updates.push("department_id = ?"); params.push(departmentId); } if (contractorId !== undefined) { updates.push("contractor_id = ?"); params.push(contractorId); } if (isActive !== undefined) { updates.push("is_active = ?"); params.push(isActive); } if (updates.length === 0) { ctx.response.status = 400; ctx.response.body = { error: "No fields to update" }; return; } params.push(userId); await db.execute( `UPDATE users SET ${updates.join(", ")} WHERE id = ?`, params ); const updatedUser = await db.query( `SELECT u.id, u.username, u.name, u.email, u.role, u.department_id, u.contractor_id, u.is_active, u.created_at, d.name as department_name, c.name as contractor_name FROM users u LEFT JOIN departments d ON u.department_id = d.id LEFT JOIN users c ON u.contractor_id = c.id WHERE u.id = ?`, [userId] ); ctx.response.body = updatedUser[0]; } catch (error) { console.error("Update user error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Delete user router.delete("/:id", authenticateToken, authorize("SuperAdmin", "Supervisor"), async (ctx) => { try { const currentUser = getCurrentUser(ctx); const userId = ctx.params.id; const users = await db.query( "SELECT * FROM users WHERE id = ?", [userId] ); if (users.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "User not found" }; return; } // Supervisors can only delete users in their department if (currentUser.role === "Supervisor") { if (users[0].department_id !== currentUser.departmentId) { ctx.response.status = 403; ctx.response.body = { error: "Can only delete users in your department" }; return; } if (users[0].role === "SuperAdmin" || users[0].role === "Supervisor") { ctx.response.status = 403; ctx.response.body = { error: "Cannot delete admin or supervisor users" }; return; } } await db.execute("DELETE FROM users WHERE id = ?", [userId]); ctx.response.body = { message: "User deleted successfully" }; } catch (error) { console.error("Delete user error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); export default router;