313 lines
9.8 KiB
TypeScript
313 lines
9.8 KiB
TypeScript
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<User[]>(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<User[]>(
|
|
`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<User[]>(
|
|
`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<User[]>(
|
|
"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<User[]>(
|
|
`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<User[]>(
|
|
"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;
|