import type { Express } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { setupAuth, isAuthenticated } from "./replitAuth"; 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"; export async function registerRoutes(app: Express): Promise { // Auth middleware await setupAuth(app); // ============= AUTH ROUTES ============= app.get("/api/auth/user", isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; 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" }); } }); // ============= 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" }); } }); // ============= 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 = req.user.claims.sub; const userNotifications = await storage.getNotificationsByUser(userId); res.json(userNotifications); } catch (error) { console.error("Error fetching 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" }); } }); const httpServer = createServer(app); return httpServer; }