import type { Express } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { setupAuth as setupReplitAuth, isAuthenticated as isAuthenticatedReplit } from "./replitAuth"; import { setupLocalAuth, isAuthenticated as isAuthenticatedLocal } from "./localAuth"; import { db } from "./db"; import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema } from "@shared/schema"; import { eq } from "drizzle-orm"; import { differenceInDays } from "date-fns"; // Determina quale sistema auth usare basandosi sull'ambiente const USE_LOCAL_AUTH = process.env.DOMAIN === "vt.alfacom.it" || !process.env.REPLIT_DOMAINS; // Helper per estrarre user ID in modo compatibile function getUserId(req: any): string { if (USE_LOCAL_AUTH) { return req.user?.id || ""; } else { return req.user?.claims?.sub || ""; } } // Usa il middleware auth appropriato const isAuthenticated = USE_LOCAL_AUTH ? isAuthenticatedLocal : isAuthenticatedReplit; export async function registerRoutes(app: Express): Promise { // Setup auth system appropriato if (USE_LOCAL_AUTH) { console.log("šŸ” Usando Local Auth (vt.alfacom.it)"); await setupLocalAuth(app); } else { console.log("šŸ” Usando Replit OIDC Auth"); await setupReplitAuth(app); } // ============= AUTH ROUTES ============= app.get("/api/auth/user", isAuthenticated, async (req: any, res) => { try { const userId = getUserId(req); const user = await storage.getUser(userId); res.json(user); } catch (error) { console.error("Error fetching user:", error); res.status(500).json({ message: "Failed to fetch user" }); } }); // ============= USER MANAGEMENT ROUTES ============= app.get("/api/users", isAuthenticated, async (req: any, res) => { try { const currentUserId = getUserId(req); const currentUser = await storage.getUser(currentUserId); // Only admins can view all users if (currentUser?.role !== "admin") { return res.status(403).json({ message: "Forbidden: Admin access required" }); } const allUsers = await storage.getAllUsers(); res.json(allUsers); } catch (error) { console.error("Error fetching users:", error); res.status(500).json({ message: "Failed to fetch users" }); } }); app.post("/api/users", isAuthenticated, async (req: any, res) => { try { const currentUserId = getUserId(req); const currentUser = await storage.getUser(currentUserId); // Only admins can create users if (currentUser?.role !== "admin") { return res.status(403).json({ message: "Forbidden: Admin access required" }); } const { email, firstName, lastName, password, role } = req.body; if (!email || !firstName || !lastName || !password) { return res.status(400).json({ message: "Missing required fields" }); } // Hash password const bcrypt = await import("bcrypt"); const passwordHash = await bcrypt.hash(password, 10); // Generate UUID const crypto = await import("crypto"); const userId = crypto.randomUUID(); const newUser = await storage.upsertUser({ id: userId, email, firstName, lastName, profileImageUrl: null, passwordHash, }); // Set role if provided if (role) { await storage.updateUserRole(newUser.id, role); } res.json(newUser); } catch (error: any) { console.error("Error creating user:", error); if (error.code === '23505') { // Unique violation return res.status(409).json({ message: "Email giĆ  esistente" }); } res.status(500).json({ message: "Failed to create user" }); } }); app.patch("/api/users/:id", isAuthenticated, async (req: any, res) => { try { const currentUserId = getUserId(req); const currentUser = await storage.getUser(currentUserId); // Only admins can update user roles if (currentUser?.role !== "admin") { return res.status(403).json({ message: "Forbidden: Admin access required" }); } // Prevent admins from changing their own role if (req.params.id === currentUserId) { return res.status(403).json({ message: "Cannot change your own role" }); } const { role } = req.body; if (!role || !["admin", "coordinator", "guard", "client"].includes(role)) { return res.status(400).json({ message: "Invalid role" }); } const updated = await storage.updateUserRole(req.params.id, role); if (!updated) { return res.status(404).json({ message: "User not found" }); } res.json(updated); } catch (error) { console.error("Error updating user role:", error); res.status(500).json({ message: "Failed to update user role" }); } }); app.put("/api/users/:id", isAuthenticated, async (req: any, res) => { try { const currentUserId = getUserId(req); const currentUser = await storage.getUser(currentUserId); // Only admins can update users if (currentUser?.role !== "admin") { return res.status(403).json({ message: "Forbidden: Admin access required" }); } const { email, firstName, lastName, password, role } = req.body; const existingUser = await storage.getUser(req.params.id); if (!existingUser) { return res.status(404).json({ message: "User not found" }); } // Prepare update data const updateData: any = { id: req.params.id, email: email || existingUser.email, firstName: firstName || existingUser.firstName, lastName: lastName || existingUser.lastName, profileImageUrl: existingUser.profileImageUrl, }; // Hash new password if provided if (password) { const bcrypt = await import("bcrypt"); updateData.passwordHash = await bcrypt.hash(password, 10); } const updated = await storage.upsertUser(updateData); // Update role if provided and not changing own role if (role && req.params.id !== currentUserId) { await storage.updateUserRole(req.params.id, role); } res.json(updated); } catch (error: any) { console.error("Error updating user:", error); if (error.code === '23505') { return res.status(409).json({ message: "Email giĆ  esistente" }); } res.status(500).json({ message: "Failed to update user" }); } }); app.delete("/api/users/:id", isAuthenticated, async (req: any, res) => { try { const currentUserId = getUserId(req); const currentUser = await storage.getUser(currentUserId); // Only admins can delete users if (currentUser?.role !== "admin") { return res.status(403).json({ message: "Forbidden: Admin access required" }); } // Prevent admins from deleting themselves if (req.params.id === currentUserId) { return res.status(403).json({ message: "Cannot delete your own account" }); } await storage.deleteUser(req.params.id); res.json({ success: true }); } catch (error) { console.error("Error deleting user:", error); res.status(500).json({ message: "Failed to delete user" }); } }); // ============= GUARD ROUTES ============= app.get("/api/guards", isAuthenticated, async (req, res) => { try { const allGuards = await storage.getAllGuards(); // Fetch related data for each guard const guardsWithDetails = await Promise.all( allGuards.map(async (guard) => { const certs = await storage.getCertificationsByGuard(guard.id); const user = guard.userId ? await storage.getUser(guard.userId) : undefined; // Update certification status based on expiry date for (const cert of certs) { const daysUntilExpiry = differenceInDays(new Date(cert.expiryDate), new Date()); let newStatus: "valid" | "expiring_soon" | "expired" = "valid"; if (daysUntilExpiry < 0) { newStatus = "expired"; } else if (daysUntilExpiry <= 30) { newStatus = "expiring_soon"; } if (cert.status !== newStatus) { await storage.updateCertificationStatus(cert.id, newStatus); cert.status = newStatus; } } return { ...guard, certifications: certs, user, }; }) ); res.json(guardsWithDetails); } catch (error) { console.error("Error fetching guards:", error); res.status(500).json({ message: "Failed to fetch guards" }); } }); app.post("/api/guards", isAuthenticated, async (req, res) => { try { const guard = await storage.createGuard(req.body); res.json(guard); } catch (error) { console.error("Error creating guard:", error); res.status(500).json({ message: "Failed to create guard" }); } }); app.patch("/api/guards/:id", isAuthenticated, async (req, res) => { try { const updated = await storage.updateGuard(req.params.id, req.body); if (!updated) { return res.status(404).json({ message: "Guard not found" }); } res.json(updated); } catch (error) { console.error("Error updating guard:", error); res.status(500).json({ message: "Failed to update guard" }); } }); app.delete("/api/guards/:id", isAuthenticated, async (req, res) => { try { const deleted = await storage.deleteGuard(req.params.id); if (!deleted) { return res.status(404).json({ message: "Guard not found" }); } res.json({ success: true }); } catch (error) { console.error("Error deleting guard:", error); res.status(500).json({ message: "Failed to delete guard" }); } }); // ============= VEHICLE ROUTES ============= app.get("/api/vehicles", isAuthenticated, async (req, res) => { try { const vehicles = await storage.getAllVehicles(); res.json(vehicles); } catch (error) { console.error("Error fetching vehicles:", error); res.status(500).json({ message: "Failed to fetch vehicles" }); } }); app.post("/api/vehicles", isAuthenticated, async (req, res) => { try { const vehicle = await storage.createVehicle(req.body); res.json(vehicle); } catch (error: any) { console.error("Error creating vehicle:", error); if (error.code === '23505') { return res.status(409).json({ message: "Targa giĆ  esistente" }); } res.status(500).json({ message: "Failed to create vehicle" }); } }); app.patch("/api/vehicles/:id", isAuthenticated, async (req, res) => { try { const updated = await storage.updateVehicle(req.params.id, req.body); if (!updated) { return res.status(404).json({ message: "Vehicle not found" }); } res.json(updated); } catch (error: any) { console.error("Error updating vehicle:", error); if (error.code === '23505') { return res.status(409).json({ message: "Targa giĆ  esistente" }); } res.status(500).json({ message: "Failed to update vehicle" }); } }); app.delete("/api/vehicles/:id", isAuthenticated, async (req, res) => { try { const deleted = await storage.deleteVehicle(req.params.id); if (!deleted) { return res.status(404).json({ message: "Vehicle not found" }); } res.json({ success: true }); } catch (error) { console.error("Error deleting vehicle:", error); res.status(500).json({ message: "Failed to delete vehicle" }); } }); // ============= CERTIFICATION ROUTES ============= app.post("/api/certifications", isAuthenticated, async (req, res) => { try { const cert = await storage.createCertification(req.body); res.json(cert); } catch (error) { console.error("Error creating certification:", error); res.status(500).json({ message: "Failed to create certification" }); } }); // ============= SITE ROUTES ============= app.get("/api/sites", isAuthenticated, async (req, res) => { try { const allSites = await storage.getAllSites(); res.json(allSites); } catch (error) { console.error("Error fetching sites:", error); res.status(500).json({ message: "Failed to fetch sites" }); } }); app.post("/api/sites", isAuthenticated, async (req, res) => { try { const site = await storage.createSite(req.body); res.json(site); } catch (error) { console.error("Error creating site:", error); res.status(500).json({ message: "Failed to create site" }); } }); app.patch("/api/sites/:id", isAuthenticated, async (req, res) => { try { const updated = await storage.updateSite(req.params.id, req.body); if (!updated) { return res.status(404).json({ message: "Site not found" }); } res.json(updated); } catch (error) { console.error("Error updating site:", error); res.status(500).json({ message: "Failed to update site" }); } }); app.delete("/api/sites/:id", isAuthenticated, async (req, res) => { try { const deleted = await storage.deleteSite(req.params.id); if (!deleted) { return res.status(404).json({ message: "Site not found" }); } res.json({ success: true }); } catch (error) { console.error("Error deleting site:", error); res.status(500).json({ message: "Failed to delete site" }); } }); // ============= SHIFT ROUTES ============= app.get("/api/shifts", isAuthenticated, async (req, res) => { try { const allShifts = await storage.getAllShifts(); // Fetch related data for each shift const shiftsWithDetails = await Promise.all( allShifts.map(async (shift) => { const site = await storage.getSite(shift.siteId); const assignments = await storage.getShiftAssignments(shift.id); // Fetch guard details for each assignment const assignmentsWithGuards = await Promise.all( assignments.map(async (assignment) => { const guard = await storage.getGuard(assignment.guardId); const certs = guard ? await storage.getCertificationsByGuard(guard.id) : []; const user = guard?.userId ? await storage.getUser(guard.userId) : undefined; return { ...assignment, guard: guard ? { ...guard, certifications: certs, user, } : null, }; }) ); return { ...shift, site: site!, assignments: assignmentsWithGuards.filter(a => a.guard !== null), }; }) ); res.json(shiftsWithDetails); } catch (error) { console.error("Error fetching shifts:", error); res.status(500).json({ message: "Failed to fetch shifts" }); } }); app.get("/api/shifts/active", isAuthenticated, async (req, res) => { try { const activeShifts = await storage.getActiveShifts(); // Fetch related data for each shift const shiftsWithDetails = await Promise.all( activeShifts.map(async (shift) => { const site = await storage.getSite(shift.siteId); const assignments = await storage.getShiftAssignments(shift.id); // Fetch guard details for each assignment const assignmentsWithGuards = await Promise.all( assignments.map(async (assignment) => { const guard = await storage.getGuard(assignment.guardId); const certs = guard ? await storage.getCertificationsByGuard(guard.id) : []; const user = guard?.userId ? await storage.getUser(guard.userId) : undefined; return { ...assignment, guard: guard ? { ...guard, certifications: certs, user, } : null, }; }) ); return { ...shift, site: site!, assignments: assignmentsWithGuards.filter(a => a.guard !== null), }; }) ); res.json(shiftsWithDetails); } catch (error) { console.error("Error fetching active shifts:", error); res.status(500).json({ message: "Failed to fetch active shifts" }); } }); app.post("/api/shifts", isAuthenticated, async (req, res) => { try { // Validate that required fields are present and dates are valid strings if (!req.body.siteId || !req.body.startTime || !req.body.endTime) { return res.status(400).json({ message: "Missing required fields" }); } // Convert and validate dates const startTime = new Date(req.body.startTime); const endTime = new Date(req.body.endTime); if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) { return res.status(400).json({ message: "Invalid date format" }); } // Validate and transform the request body const validatedData = insertShiftSchema.parse({ siteId: req.body.siteId, startTime, endTime, status: req.body.status || "planned", }); const shift = await storage.createShift(validatedData); res.json(shift); } catch (error) { console.error("Error creating shift:", error); res.status(500).json({ message: "Failed to create shift" }); } }); app.patch("/api/shifts/:id/status", isAuthenticated, async (req, res) => { try { await storage.updateShiftStatus(req.params.id, req.body.status); res.json({ success: true }); } catch (error) { console.error("Error updating shift status:", error); res.status(500).json({ message: "Failed to update shift status" }); } }); app.patch("/api/shifts/:id", isAuthenticated, async (req, res) => { try { const { startTime: startTimeStr, endTime: endTimeStr, ...rest } = req.body; const updateData: any = { ...rest }; if (startTimeStr) { const startTime = new Date(startTimeStr); if (isNaN(startTime.getTime())) { return res.status(400).json({ message: "Invalid start time format" }); } updateData.startTime = startTime; } if (endTimeStr) { const endTime = new Date(endTimeStr); if (isNaN(endTime.getTime())) { return res.status(400).json({ message: "Invalid end time format" }); } updateData.endTime = endTime; } const updated = await storage.updateShift(req.params.id, updateData); if (!updated) { return res.status(404).json({ message: "Shift not found" }); } res.json(updated); } catch (error) { console.error("Error updating shift:", error); res.status(500).json({ message: "Failed to update shift" }); } }); app.delete("/api/shifts/:id", isAuthenticated, async (req, res) => { try { const deleted = await storage.deleteShift(req.params.id); if (!deleted) { return res.status(404).json({ message: "Shift not found" }); } res.json({ success: true }); } catch (error) { console.error("Error deleting shift:", error); res.status(500).json({ message: "Failed to delete shift" }); } }); // ============= SHIFT ASSIGNMENT ROUTES ============= app.post("/api/shift-assignments", isAuthenticated, async (req, res) => { try { const assignment = await storage.createShiftAssignment(req.body); res.json(assignment); } catch (error) { console.error("Error creating shift assignment:", error); res.status(500).json({ message: "Failed to create shift assignment" }); } }); app.delete("/api/shift-assignments/:id", isAuthenticated, async (req, res) => { try { await storage.deleteShiftAssignment(req.params.id); res.json({ success: true }); } catch (error) { console.error("Error deleting shift assignment:", error); res.status(500).json({ message: "Failed to delete shift assignment" }); } }); // ============= NOTIFICATION ROUTES ============= app.get("/api/notifications", isAuthenticated, async (req: any, res) => { try { const userId = getUserId(req); const userNotifications = await storage.getNotificationsByUser(userId); res.json(userNotifications); } catch (error) { console.error("Error fetching user notifications:", error); res.status(500).json({ message: "Failed to fetch notifications" }); } }); app.patch("/api/notifications/:id/read", isAuthenticated, async (req, res) => { try { await storage.markNotificationAsRead(req.params.id); res.json({ success: true }); } catch (error) { console.error("Error marking notification as read:", error); res.status(500).json({ message: "Failed to mark notification as read" }); } }); // ============= GUARD CONSTRAINTS ROUTES ============= app.get("/api/guard-constraints/:guardId", isAuthenticated, async (req, res) => { try { const constraints = await storage.getGuardConstraints(req.params.guardId); res.json(constraints || null); } catch (error) { console.error("Error fetching guard constraints:", error); res.status(500).json({ message: "Failed to fetch guard constraints" }); } }); app.post("/api/guard-constraints", isAuthenticated, async (req, res) => { try { const constraints = await storage.upsertGuardConstraints(req.body); res.json(constraints); } catch (error) { console.error("Error upserting guard constraints:", error); res.status(500).json({ message: "Failed to save guard constraints" }); } }); // ============= SITE PREFERENCES ROUTES ============= app.get("/api/site-preferences/:siteId", isAuthenticated, async (req, res) => { try { const preferences = await storage.getSitePreferences(req.params.siteId); res.json(preferences); } catch (error) { console.error("Error fetching site preferences:", error); res.status(500).json({ message: "Failed to fetch site preferences" }); } }); app.post("/api/site-preferences", isAuthenticated, async (req, res) => { try { const preference = await storage.createSitePreference(req.body); res.json(preference); } catch (error) { console.error("Error creating site preference:", error); res.status(500).json({ message: "Failed to create site preference" }); } }); app.delete("/api/site-preferences/:id", isAuthenticated, async (req, res) => { try { await storage.deleteSitePreference(req.params.id); res.json({ success: true }); } catch (error) { console.error("Error deleting site preference:", error); res.status(500).json({ message: "Failed to delete site preference" }); } }); // ============= TRAINING COURSES ROUTES ============= app.get("/api/training-courses", isAuthenticated, async (req, res) => { try { const guardId = req.query.guardId as string | undefined; const courses = guardId ? await storage.getTrainingCoursesByGuard(guardId) : await storage.getAllTrainingCourses(); res.json(courses); } catch (error) { console.error("Error fetching training courses:", error); res.status(500).json({ message: "Failed to fetch training courses" }); } }); app.post("/api/training-courses", isAuthenticated, async (req, res) => { try { const course = await storage.createTrainingCourse(req.body); res.json(course); } catch (error) { console.error("Error creating training course:", error); res.status(500).json({ message: "Failed to create training course" }); } }); app.patch("/api/training-courses/:id", isAuthenticated, async (req, res) => { try { const updated = await storage.updateTrainingCourse(req.params.id, req.body); if (!updated) { return res.status(404).json({ message: "Training course not found" }); } res.json(updated); } catch (error) { console.error("Error updating training course:", error); res.status(500).json({ message: "Failed to update training course" }); } }); app.delete("/api/training-courses/:id", isAuthenticated, async (req, res) => { try { await storage.deleteTrainingCourse(req.params.id); res.json({ success: true }); } catch (error) { console.error("Error deleting training course:", error); res.status(500).json({ message: "Failed to delete training course" }); } }); // ============= HOLIDAYS ROUTES ============= app.get("/api/holidays", isAuthenticated, async (req, res) => { try { const year = req.query.year ? parseInt(req.query.year as string) : undefined; const holidays = await storage.getAllHolidays(year); res.json(holidays); } catch (error) { console.error("Error fetching holidays:", error); res.status(500).json({ message: "Failed to fetch holidays" }); } }); app.post("/api/holidays", isAuthenticated, async (req, res) => { try { const holiday = await storage.createHoliday(req.body); res.json(holiday); } catch (error) { console.error("Error creating holiday:", error); res.status(500).json({ message: "Failed to create holiday" }); } }); app.delete("/api/holidays/:id", isAuthenticated, async (req, res) => { try { await storage.deleteHoliday(req.params.id); res.json({ success: true }); } catch (error) { console.error("Error deleting holiday:", error); res.status(500).json({ message: "Failed to delete holiday" }); } }); // ============= ABSENCES ROUTES ============= app.get("/api/absences", isAuthenticated, async (req, res) => { try { const guardId = req.query.guardId as string | undefined; const absences = guardId ? await storage.getAbsencesByGuard(guardId) : await storage.getAllAbsences(); res.json(absences); } catch (error) { console.error("Error fetching absences:", error); res.status(500).json({ message: "Failed to fetch absences" }); } }); app.post("/api/absences", isAuthenticated, async (req, res) => { try { const absence = await storage.createAbsence(req.body); res.json(absence); } catch (error) { console.error("Error creating absence:", error); res.status(500).json({ message: "Failed to create absence" }); } }); app.patch("/api/absences/:id", isAuthenticated, async (req, res) => { try { const updated = await storage.updateAbsence(req.params.id, req.body); if (!updated) { return res.status(404).json({ message: "Absence not found" }); } res.json(updated); } catch (error) { console.error("Error updating absence:", error); res.status(500).json({ message: "Failed to update absence" }); } }); app.delete("/api/absences/:id", isAuthenticated, async (req, res) => { try { await storage.deleteAbsence(req.params.id); res.json({ success: true }); } catch (error) { console.error("Error deleting absence:", error); res.status(500).json({ message: "Failed to delete absence" }); } }); const httpServer = createServer(app); return httpServer; }