452 lines
13 KiB
TypeScript
452 lines
13 KiB
TypeScript
import { type Context, Router } from "@oak/oak";
|
|
import { genSalt, hash } from "bcrypt";
|
|
import { db } from "../config/database.ts";
|
|
import { config } from "../config/env.ts";
|
|
|
|
// Helper function to hash password with proper salt generation
|
|
async function hashPassword(password: string): Promise<string> {
|
|
const salt = await genSalt(config.BCRYPT_ROUNDS);
|
|
return await hash(password, salt);
|
|
}
|
|
import {
|
|
authenticateToken,
|
|
authorize,
|
|
getCurrentUser,
|
|
} from "../middleware/auth.ts";
|
|
import { isValidEmail, sanitizeInput } from "../middleware/security.ts";
|
|
import type {
|
|
CreateUserRequest,
|
|
UpdateUserRequest,
|
|
User,
|
|
} from "../types/index.ts";
|
|
|
|
const router = new Router();
|
|
|
|
// Get all users (with filters)
|
|
router.get("/", authenticateToken, async (ctx: Context) => {
|
|
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,
|
|
u.phone_number, u.aadhar_number, u.bank_account_number,
|
|
u.bank_name, u.bank_ifsc,
|
|
u.contractor_agreement_number, u.pf_number, u.esic_number,
|
|
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: Context) => {
|
|
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,
|
|
u.phone_number, u.aadhar_number, u.bank_account_number,
|
|
u.bank_name, u.bank_ifsc,
|
|
u.contractor_agreement_number, u.pf_number, u.esic_number,
|
|
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: Context) => {
|
|
try {
|
|
const currentUser = getCurrentUser(ctx);
|
|
const body = await ctx.request.body.json() as CreateUserRequest;
|
|
const {
|
|
username,
|
|
name,
|
|
email,
|
|
password,
|
|
role,
|
|
departmentId,
|
|
contractorId,
|
|
phoneNumber,
|
|
aadharNumber,
|
|
bankAccountNumber,
|
|
bankName,
|
|
bankIfsc,
|
|
contractorAgreementNumber,
|
|
pfNumber,
|
|
esicNumber,
|
|
} = 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 hashPassword(password);
|
|
|
|
const result = await db.execute(
|
|
`INSERT INTO users (username, name, email, password, role, department_id, contractor_id,
|
|
phone_number, aadhar_number, bank_account_number, bank_name, bank_ifsc,
|
|
contractor_agreement_number, pf_number, esic_number)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
sanitizedUsername,
|
|
sanitizedName,
|
|
sanitizedEmail,
|
|
hashedPassword,
|
|
role,
|
|
departmentId || null,
|
|
contractorId || null,
|
|
phoneNumber || null,
|
|
aadharNumber || null,
|
|
bankAccountNumber || null,
|
|
bankName || null,
|
|
bankIfsc || null,
|
|
contractorAgreementNumber || null,
|
|
pfNumber || null,
|
|
esicNumber || 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,
|
|
u.phone_number, u.aadhar_number, u.bank_account_number,
|
|
u.bank_name, u.bank_ifsc,
|
|
u.contractor_agreement_number, u.pf_number, u.esic_number,
|
|
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: Context) => {
|
|
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,
|
|
phoneNumber,
|
|
aadharNumber,
|
|
bankAccountNumber,
|
|
bankName,
|
|
bankIfsc,
|
|
contractorAgreementNumber,
|
|
pfNumber,
|
|
esicNumber,
|
|
} = 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);
|
|
}
|
|
// New fields
|
|
if (phoneNumber !== undefined) {
|
|
updates.push("phone_number = ?");
|
|
params.push(phoneNumber);
|
|
}
|
|
if (aadharNumber !== undefined) {
|
|
updates.push("aadhar_number = ?");
|
|
params.push(aadharNumber);
|
|
}
|
|
if (bankAccountNumber !== undefined) {
|
|
updates.push("bank_account_number = ?");
|
|
params.push(bankAccountNumber);
|
|
}
|
|
if (bankName !== undefined) {
|
|
updates.push("bank_name = ?");
|
|
params.push(bankName);
|
|
}
|
|
if (bankIfsc !== undefined) {
|
|
updates.push("bank_ifsc = ?");
|
|
params.push(bankIfsc);
|
|
}
|
|
if (contractorAgreementNumber !== undefined) {
|
|
updates.push("contractor_agreement_number = ?");
|
|
params.push(contractorAgreementNumber);
|
|
}
|
|
if (pfNumber !== undefined) {
|
|
updates.push("pf_number = ?");
|
|
params.push(pfNumber);
|
|
}
|
|
if (esicNumber !== undefined) {
|
|
updates.push("esic_number = ?");
|
|
params.push(esicNumber);
|
|
}
|
|
|
|
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,
|
|
u.phone_number, u.aadhar_number, u.bank_account_number,
|
|
u.bank_name, u.bank_ifsc,
|
|
u.contractor_agreement_number, u.pf_number, u.esic_number,
|
|
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;
|