import { type Context, Router, type RouterContext } from "@oak/oak"; import { db } from "../config/database.ts"; import { authenticateToken, authorize, getCurrentUser, } from "../middleware/auth.ts"; import { sanitizeInput } from "../middleware/security.ts"; import type { Department, SubDepartment } from "../types/index.ts"; const router = new Router(); // Get all departments router.get("/", authenticateToken, async (ctx: Context) => { try { const departments = await db.query( "SELECT * FROM departments ORDER BY name", ); ctx.response.body = departments; } catch (error) { console.error("Get departments error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Get department by ID router.get("/:id", authenticateToken, async (ctx: RouterContext<"/:id">) => { try { const deptId = ctx.params.id; const departments = await db.query( "SELECT * FROM departments WHERE id = ?", [deptId], ); if (departments.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "Department not found" }; return; } ctx.response.body = departments[0]; } catch (error) { console.error("Get department error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Get all sub-departments (for reporting/filtering) router.get( "/sub-departments/all", authenticateToken, async (ctx: Context) => { try { const subDepartments = await db.query( "SELECT sd.*, d.name as department_name FROM sub_departments sd LEFT JOIN departments d ON sd.department_id = d.id ORDER BY d.name, sd.name", ); ctx.response.body = subDepartments; } catch (error) { console.error("Get all sub-departments error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); // Get sub-departments by department ID router.get( "/:id/sub-departments", authenticateToken, async (ctx: RouterContext<"/:id/sub-departments">) => { try { const deptId = ctx.params.id; const subDepartments = await db.query( "SELECT * FROM sub_departments WHERE department_id = ? ORDER BY name", [deptId], ); ctx.response.body = subDepartments; } catch (error) { console.error("Get sub-departments error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); // Create department (SuperAdmin only) router.post( "/", authenticateToken, authorize("SuperAdmin"), async (ctx: Context) => { try { const body = await ctx.request.body.json() as { name: string }; const { name } = body; if (!name) { ctx.response.status = 400; ctx.response.body = { error: "Department name required" }; return; } const sanitizedName = sanitizeInput(name); const result = await db.execute( "INSERT INTO departments (name) VALUES (?)", [sanitizedName], ); const newDepartment = await db.query( "SELECT * FROM departments WHERE id = ?", [result.insertId], ); ctx.response.status = 201; ctx.response.body = newDepartment[0]; } catch (error) { const err = error as { code?: string }; if (err.code === "ER_DUP_ENTRY") { ctx.response.status = 400; ctx.response.body = { error: "Department already exists" }; return; } console.error("Create department error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); // Create sub-department (SuperAdmin or Supervisor for their own department) router.post("/sub-departments", authenticateToken, async (ctx: Context) => { try { const user = getCurrentUser(ctx); const body = await ctx.request.body.json() as { department_id: number; name: string; }; const { department_id, name } = body; if (!name || !department_id) { ctx.response.status = 400; ctx.response.body = { error: "Department ID and name are required" }; return; } // Check authorization if (user.role === "Supervisor" && user.departmentId !== department_id) { ctx.response.status = 403; ctx.response.body = { error: "You can only create sub-departments for your own department", }; return; } if (user.role !== "SuperAdmin" && user.role !== "Supervisor") { ctx.response.status = 403; ctx.response.body = { error: "Unauthorized" }; return; } const sanitizedName = sanitizeInput(name); const result = await db.execute( "INSERT INTO sub_departments (department_id, name) VALUES (?, ?)", [department_id, sanitizedName], ); const newSubDepartment = await db.query( "SELECT * FROM sub_departments WHERE id = ?", [result.insertId], ); ctx.response.status = 201; ctx.response.body = newSubDepartment[0]; } catch (error) { const err = error as { code?: string }; if (err.code === "ER_DUP_ENTRY") { ctx.response.status = 400; ctx.response.body = { error: "Sub-department already exists in this department", }; return; } console.error("Create sub-department error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Delete sub-department (SuperAdmin or Supervisor for their own department) router.delete( "/sub-departments/:id", authenticateToken, async (ctx: RouterContext<"/sub-departments/:id">) => { try { const user = getCurrentUser(ctx); const subDeptId = ctx.params.id; // Get the sub-department to check department ownership const subDepts = await db.query( "SELECT * FROM sub_departments WHERE id = ?", [subDeptId], ); if (subDepts.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "Sub-department not found" }; return; } const subDept = subDepts[0]; // Check authorization if ( user.role === "Supervisor" && user.departmentId !== subDept.department_id ) { ctx.response.status = 403; ctx.response.body = { error: "You can only delete sub-departments from your own department", }; return; } if (user.role !== "SuperAdmin" && user.role !== "Supervisor") { ctx.response.status = 403; ctx.response.body = { error: "Unauthorized" }; return; } // Delete associated activities first (cascade should handle this, but being explicit) await db.execute("DELETE FROM activities WHERE sub_department_id = ?", [ subDeptId, ]); // Delete the sub-department await db.execute("DELETE FROM sub_departments WHERE id = ?", [subDeptId]); ctx.response.body = { message: "Sub-department deleted successfully" }; } catch (error) { console.error("Delete sub-department error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); // Legacy route for creating sub-department under specific department (SuperAdmin only) router.post( "/:id/sub-departments", authenticateToken, authorize("SuperAdmin"), async (ctx: RouterContext<"/:id/sub-departments">) => { try { const deptId: string | number = ctx.params.id; const body = await ctx.request.body.json() as { name: string }; const { name } = body; if (!name) { ctx.response.status = 400; ctx.response.body = { error: "Name is required" }; return; } const sanitizedName = sanitizeInput(name); const result = await db.execute( "INSERT INTO sub_departments (department_id, name) VALUES (?, ?)", [deptId, sanitizedName], ); const newSubDepartment = await db.query( "SELECT * FROM sub_departments WHERE id = ?", [result.insertId], ); ctx.response.status = 201; ctx.response.body = newSubDepartment[0]; } catch (error) { const err = error as { code?: string }; if (err.code === "ER_DUP_ENTRY") { ctx.response.status = 400; ctx.response.body = { error: "Sub-department already exists in this department", }; return; } console.error("Create sub-department error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); export default router;