(Feat): Initial Commit
This commit is contained in:
312
backend-deno/routes/users.ts
Normal file
312
backend-deno/routes/users.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user