import type { Express } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { setupAuth, isAuthenticated } from "./replitAuth"; import { insertEventSchema, insertEventTypeSchema, insertScheduleTemplateSchema, insertScheduleTemplateSlotSchema, insertSocialLinkSchema, insertAvailabilitySchema, insertRemovalRequestSchema, insertInvitationSchema, updateUserSchema, updateEventSchema, updateEventTypeSchema, updateScheduleTemplateSchema, updateRemovalRequestSchema, } from "@shared/schema"; import { z } from "zod"; import { fromZodError } from "zod-validation-error"; import crypto from "crypto"; export async function registerRoutes(app: Express): Promise { // Auth middleware await setupAuth(app); // Helper function to check if user is admin const isAdmin = async (req: any, res: any, next: any) => { try { const userId = req.user.claims.sub; const user = await storage.getUser(userId); if (!user || user.role !== "admin") { return res.status(403).json({ message: "Admin access required" }); } next(); } catch (error) { res.status(500).json({ message: "Error checking admin status" }); } }; // 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" }); } }); // User management routes app.get('/api/users', isAuthenticated, isAdmin, async (req, res) => { try { const users = await storage.getAllDJs(); res.json(users); } 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 userId = req.user.claims.sub; const targetUserId = req.params.id; const user = await storage.getUser(userId); // Allow users to update their own profile or admins to update any profile if (userId !== targetUserId && user?.role !== "admin") { return res.status(403).json({ message: "Permission denied" }); } const updates = updateUserSchema.parse(req.body); const updatedUser = await storage.updateUser(targetUserId, updates); res.json(updatedUser); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error updating user:", error); res.status(500).json({ message: "Failed to update user" }); } }); app.post('/api/users/:id/deactivate', isAuthenticated, isAdmin, async (req, res) => { try { await storage.deactivateUser(req.params.id); res.json({ message: "User deactivated successfully" }); } catch (error) { console.error("Error deactivating user:", error); res.status(500).json({ message: "Failed to deactivate user" }); } }); app.post('/api/users/:id/reactivate', isAuthenticated, isAdmin, async (req, res) => { try { await storage.reactivateUser(req.params.id); res.json({ message: "User reactivated successfully" }); } catch (error) { console.error("Error reactivating user:", error); res.status(500).json({ message: "Failed to reactivate user" }); } }); app.post('/api/users/:id/make-admin', isAuthenticated, isAdmin, async (req, res) => { try { await storage.makeAdmin(req.params.id); res.json({ message: "User promoted to admin successfully" }); } catch (error) { console.error("Error promoting user to admin:", error); res.status(500).json({ message: "Failed to promote user to admin" }); } }); app.post('/api/users/:id/remove-admin', isAuthenticated, isAdmin, async (req, res) => { try { await storage.removeAdmin(req.params.id); res.json({ message: "Admin privileges removed successfully" }); } catch (error) { console.error("Error removing admin privileges:", error); res.status(500).json({ message: "Failed to remove admin privileges" }); } }); // Event routes app.get('/api/events', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const user = await storage.getUser(userId); if (user?.role === "admin") { const events = await storage.getAllEvents(); res.json(events); } else { const events = await storage.getEventsByDJ(userId); res.json(events); } } catch (error) { console.error("Error fetching events:", error); res.status(500).json({ message: "Failed to fetch events" }); } }); app.get('/api/events/upcoming', isAuthenticated, async (req, res) => { try { const limit = req.query.limit ? parseInt(req.query.limit as string) : 50; const events = await storage.getUpcomingEvents(limit); res.json(events); } catch (error) { console.error("Error fetching upcoming events:", error); res.status(500).json({ message: "Failed to fetch upcoming events" }); } }); app.post('/api/events', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const eventData = insertEventSchema.parse({ ...req.body, djId: userId }); const event = await storage.createEvent(eventData); res.json(event); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating event:", error); res.status(500).json({ message: "Failed to create event" }); } }); app.get('/api/events/:id', isAuthenticated, async (req: any, res) => { try { const event = await storage.getEvent(parseInt(req.params.id)); if (!event) { return res.status(404).json({ message: "Event not found" }); } const userId = req.user.claims.sub; const user = await storage.getUser(userId); // Allow access to event if user is admin or the DJ assigned to the event if (user?.role !== "admin" && event.djId !== userId) { return res.status(403).json({ message: "Permission denied" }); } res.json(event); } catch (error) { console.error("Error fetching event:", error); res.status(500).json({ message: "Failed to fetch event" }); } }); app.patch('/api/events/:id', isAuthenticated, async (req: any, res) => { try { const eventId = parseInt(req.params.id); const event = await storage.getEvent(eventId); if (!event) { return res.status(404).json({ message: "Event not found" }); } const userId = req.user.claims.sub; const user = await storage.getUser(userId); // Only allow DJ to edit their own self-added events or admin to edit any event if (user?.role !== "admin" && (event.djId !== userId || event.isAssignedByAdmin)) { return res.status(403).json({ message: "Permission denied" }); } const updates = updateEventSchema.parse(req.body); const updatedEvent = await storage.updateEvent(eventId, updates); res.json(updatedEvent); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error updating event:", error); res.status(500).json({ message: "Failed to update event" }); } }); app.delete('/api/events/:id', isAuthenticated, async (req: any, res) => { try { const eventId = parseInt(req.params.id); const event = await storage.getEvent(eventId); if (!event) { return res.status(404).json({ message: "Event not found" }); } const userId = req.user.claims.sub; const user = await storage.getUser(userId); // Only allow DJ to delete their own self-added events or admin to delete any event if (user?.role !== "admin" && (event.djId !== userId || event.isAssignedByAdmin)) { return res.status(403).json({ message: "Permission denied" }); } await storage.deleteEvent(eventId); res.json({ message: "Event deleted successfully" }); } catch (error) { console.error("Error deleting event:", error); res.status(500).json({ message: "Failed to delete event" }); } }); // Public API for WordPress integration app.get('/api/public/upcoming-events', async (req, res) => { try { const limit = req.query.limit ? parseInt(req.query.limit as string) : 10; const events = await storage.getUpcomingEvents(limit); // Format events for WordPress widget const formattedEvents = events.map(event => ({ id: event.id, name: event.name, date: event.date, startTime: event.startTime, endTime: event.endTime, locationName: event.locationName, locationAddress: event.locationAddress, djId: event.djId, eventTypeId: event.eventTypeId, description: event.description, })); res.json(formattedEvents); } catch (error) { console.error("Error fetching public events:", error); res.status(500).json({ message: "Failed to fetch events" }); } }); // Event type routes app.get('/api/event-types', isAuthenticated, async (req, res) => { try { const eventTypes = await storage.getAllEventTypes(); res.json(eventTypes); } catch (error) { console.error("Error fetching event types:", error); res.status(500).json({ message: "Failed to fetch event types" }); } }); app.post('/api/event-types', isAuthenticated, isAdmin, async (req, res) => { try { const eventTypeData = insertEventTypeSchema.parse(req.body); const eventType = await storage.createEventType(eventTypeData); res.json(eventType); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating event type:", error); res.status(500).json({ message: "Failed to create event type" }); } }); app.patch('/api/event-types/:id', isAuthenticated, isAdmin, async (req, res) => { try { const eventTypeId = parseInt(req.params.id); const updates = updateEventTypeSchema.parse(req.body); const eventType = await storage.updateEventType(eventTypeId, updates); res.json(eventType); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error updating event type:", error); res.status(500).json({ message: "Failed to update event type" }); } }); app.delete('/api/event-types/:id', isAuthenticated, isAdmin, async (req, res) => { try { const eventTypeId = parseInt(req.params.id); await storage.deleteEventType(eventTypeId); res.json({ message: "Event type deleted successfully" }); } catch (error) { console.error("Error deleting event type:", error); res.status(500).json({ message: "Failed to delete event type" }); } }); // Schedule template routes app.get('/api/schedule-templates', isAuthenticated, isAdmin, async (req, res) => { try { const templates = await storage.getAllScheduleTemplates(); res.json(templates); } catch (error) { console.error("Error fetching schedule templates:", error); res.status(500).json({ message: "Failed to fetch schedule templates" }); } }); app.post('/api/schedule-templates', isAuthenticated, isAdmin, async (req, res) => { try { const templateData = insertScheduleTemplateSchema.parse(req.body); const template = await storage.createScheduleTemplate(templateData); res.json(template); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating schedule template:", error); res.status(500).json({ message: "Failed to create schedule template" }); } }); app.get('/api/schedule-templates/:id/slots', isAuthenticated, isAdmin, async (req, res) => { try { const templateId = parseInt(req.params.id); const slots = await storage.getScheduleTemplateSlots(templateId); res.json(slots); } catch (error) { console.error("Error fetching template slots:", error); res.status(500).json({ message: "Failed to fetch template slots" }); } }); app.post('/api/schedule-templates/:id/slots', isAuthenticated, isAdmin, async (req, res) => { try { const templateId = parseInt(req.params.id); const slotData = insertScheduleTemplateSlotSchema.parse({ ...req.body, templateId }); const slot = await storage.createScheduleTemplateSlot(slotData); res.json(slot); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating template slot:", error); res.status(500).json({ message: "Failed to create template slot" }); } }); // Social links routes app.get('/api/social-links', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const links = await storage.getSocialLinksByDJ(userId); res.json(links); } catch (error) { console.error("Error fetching social links:", error); res.status(500).json({ message: "Failed to fetch social links" }); } }); app.post('/api/social-links', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const linkData = insertSocialLinkSchema.parse({ ...req.body, djId: userId }); const link = await storage.createSocialLink(linkData); res.json(link); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating social link:", error); res.status(500).json({ message: "Failed to create social link" }); } }); app.delete('/api/social-links/:id', isAuthenticated, async (req: any, res) => { try { await storage.deleteSocialLink(parseInt(req.params.id)); res.json({ message: "Social link deleted successfully" }); } catch (error) { console.error("Error deleting social link:", error); res.status(500).json({ message: "Failed to delete social link" }); } }); // Availability routes app.get('/api/availability', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const availability = await storage.getAvailabilityByDJ(userId); res.json(availability); } catch (error) { console.error("Error fetching availability:", error); res.status(500).json({ message: "Failed to fetch availability" }); } }); app.post('/api/availability', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const availabilityData = insertAvailabilitySchema.parse({ ...req.body, djId: userId }); const availability = await storage.createAvailability(availabilityData); res.json(availability); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating availability:", error); res.status(500).json({ message: "Failed to create availability" }); } }); app.delete('/api/availability/:id', isAuthenticated, async (req: any, res) => { try { await storage.deleteAvailability(parseInt(req.params.id)); res.json({ message: "Availability deleted successfully" }); } catch (error) { console.error("Error deleting availability:", error); res.status(500).json({ message: "Failed to delete availability" }); } }); // Removal request routes app.get('/api/removal-requests', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const user = await storage.getUser(userId); if (user?.role === "admin") { const requests = await storage.getPendingRemovalRequests(); res.json(requests); } else { return res.status(403).json({ message: "Admin access required" }); } } catch (error) { console.error("Error fetching removal requests:", error); res.status(500).json({ message: "Failed to fetch removal requests" }); } }); app.post('/api/removal-requests', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const requestData = insertRemovalRequestSchema.parse({ ...req.body, djId: userId }); const request = await storage.createRemovalRequest(requestData); res.json(request); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating removal request:", error); res.status(500).json({ message: "Failed to create removal request" }); } }); app.post('/api/removal-requests/:id/approve', isAuthenticated, isAdmin, async (req: any, res) => { try { const requestId = parseInt(req.params.id); const reviewedBy = req.user.claims.sub; await storage.approveRemovalRequest(requestId, reviewedBy); res.json({ message: "Removal request approved successfully" }); } catch (error) { console.error("Error approving removal request:", error); res.status(500).json({ message: "Failed to approve removal request" }); } }); app.post('/api/removal-requests/:id/deny', isAuthenticated, isAdmin, async (req: any, res) => { try { const requestId = parseInt(req.params.id); const reviewedBy = req.user.claims.sub; await storage.denyRemovalRequest(requestId, reviewedBy); res.json({ message: "Removal request denied successfully" }); } catch (error) { console.error("Error denying removal request:", error); res.status(500).json({ message: "Failed to deny removal request" }); } }); // Invitation routes app.post('/api/invitations', isAuthenticated, isAdmin, async (req: any, res) => { try { const createdBy = req.user.claims.sub; const token = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days const invitationData = insertInvitationSchema.parse({ ...req.body, token, createdBy, expiresAt }); const invitation = await storage.createInvitation(invitationData); res.json(invitation); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: fromZodError(error).toString() }); } console.error("Error creating invitation:", error); res.status(500).json({ message: "Failed to create invitation" }); } }); app.get('/api/invitations', isAuthenticated, isAdmin, async (req, res) => { try { const invitations = await storage.getActiveInvitations(); res.json(invitations); } catch (error) { console.error("Error fetching invitations:", error); res.status(500).json({ message: "Failed to fetch invitations" }); } }); // Statistics routes app.get('/api/stats/dashboard', isAuthenticated, async (req: any, res) => { try { const userId = req.user.claims.sub; const stats = await storage.getDashboardStats(userId); res.json(stats); } catch (error) { console.error("Error fetching dashboard stats:", error); res.status(500).json({ message: "Failed to fetch dashboard stats" }); } }); app.get('/api/stats/admin', isAuthenticated, isAdmin, async (req, res) => { try { const stats = await storage.getAdminStats(); res.json(stats); } catch (error) { console.error("Error fetching admin stats:", error); res.status(500).json({ message: "Failed to fetch admin stats" }); } }); const httpServer = createServer(app); return httpServer; }