245 lines
7.6 KiB
TypeScript
245 lines
7.6 KiB
TypeScript
import { Router } 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) => {
|
|
try {
|
|
const departments = await db.query<Department[]>(
|
|
"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) => {
|
|
try {
|
|
const deptId = ctx.params.id;
|
|
|
|
const departments = await db.query<Department[]>(
|
|
"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 sub-departments by department ID
|
|
router.get("/:id/sub-departments", authenticateToken, async (ctx) => {
|
|
try {
|
|
const deptId = ctx.params.id;
|
|
|
|
const subDepartments = await db.query<SubDepartment[]>(
|
|
"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) => {
|
|
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<Department[]>(
|
|
"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) => {
|
|
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<SubDepartment[]>(
|
|
"SELECT * FROM sub_departments WHERE id = ?",
|
|
[result.lastInsertId]
|
|
);
|
|
|
|
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) => {
|
|
try {
|
|
const user = getCurrentUser(ctx);
|
|
const subDeptId = ctx.params.id;
|
|
|
|
// Get the sub-department to check department ownership
|
|
const subDepts = await db.query<SubDepartment[]>(
|
|
"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) => {
|
|
try {
|
|
const deptId = 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<SubDepartment[]>(
|
|
"SELECT * FROM sub_departments WHERE id = ?",
|
|
[result.lastInsertId]
|
|
);
|
|
|
|
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;
|