VigilanzaTurni/server/routes.ts
marco370 06c03be97c Add and update guard, site, and shift management functionality
Implement CRUD operations for guards, sites, and shifts, including PATCH and DELETE endpoints, and enhance shift assignment logic with skill validation and real-time synchronization.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 99f0fce6-9386-489a-9632-1d81223cab44
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/99f0fce6-9386-489a-9632-1d81223cab44/9lvKVew
2025-10-11 11:09:25 +00:00

382 lines
13 KiB
TypeScript

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<Server> {
// 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;
}