(Feat-Fix): Lots of fixes done, reporting system fixed, stricter types

This commit is contained in:
2025-12-19 18:48:05 +00:00
parent 01400ad4e1
commit 865e0bf00e
61 changed files with 10072 additions and 6645 deletions

View File

@@ -1,16 +1,20 @@
import { Router } from "@oak/oak";
import { type Context, Router, type RouterContext } from "@oak/oak";
import { db } from "../config/database.ts";
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.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) => {
router.get("/", authenticateToken, async (ctx: Context) => {
try {
const departments = await db.query<Department[]>(
"SELECT * FROM departments ORDER BY name"
"SELECT * FROM departments ORDER BY name",
);
ctx.response.body = departments;
} catch (error) {
@@ -21,21 +25,21 @@ router.get("/", authenticateToken, async (ctx) => {
});
// Get department by ID
router.get("/:id", authenticateToken, async (ctx) => {
router.get("/:id", authenticateToken, async (ctx: RouterContext<"/:id">) => {
try {
const deptId = ctx.params.id;
const departments = await db.query<Department[]>(
"SELECT * FROM departments WHERE id = ?",
[deptId]
[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);
@@ -44,70 +48,101 @@ router.get("/:id", authenticateToken, async (ctx) => {
}
});
// Get all sub-departments (for reporting/filtering)
router.get(
"/sub-departments/all",
authenticateToken,
async (ctx: Context) => {
try {
const subDepartments = await db.query<SubDepartment[]>(
"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) => {
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" };
}
});
router.get(
"/:id/sub-departments",
authenticateToken,
async (ctx: RouterContext<"/:id/sub-departments">) => {
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;
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<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" };
}
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) => {
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 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" };
@@ -115,37 +150,41 @@ router.post("/sub-departments", authenticateToken, async (ctx) => {
}
// Check authorization
if (user.role === 'Supervisor' && user.departmentId !== department_id) {
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" };
ctx.response.body = {
error: "You can only create sub-departments for your own department",
};
return;
}
if (user.role !== 'SuperAdmin' && user.role !== 'Supervisor') {
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]
[department_id, sanitizedName],
);
const newSubDepartment = await db.query<SubDepartment[]>(
"SELECT * FROM sub_departments WHERE id = ?",
[result.lastInsertId]
[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" };
ctx.response.body = {
error: "Sub-department already exists in this department",
};
return;
}
console.error("Create sub-department error:", error);
@@ -155,90 +194,108 @@ router.post("/sub-departments", authenticateToken, async (ctx) => {
});
// 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;
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<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" };
}
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;
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<SubDepartment[]>(
"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" };
}
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;