Introduce a local authentication system using Passport.js for deployments outside of Replit, including session management and a default admin user setup. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 42d8028a-fa71-4ec2-938c-e43eedf7df01 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/42d8028a-fa71-4ec2-938c-e43eedf7df01/EAVbbe1
632 lines
22 KiB
TypeScript
632 lines
22 KiB
TypeScript
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<Server> {
|
|
// 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.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" });
|
|
}
|
|
});
|
|
|
|
// ============= 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 = 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;
|
|
}
|