60b5735588
Introduce API endpoints and storage methods for setting user passwords, soft deactivation (hiding availability), and hard deletion (removing all associated future data). Replit-Commit-Author: Agent Replit-Commit-Session-Id: 3a22ac80-cd1d-4441-9e36-f24fc2f4c3de Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3478f7c3-db8c-4fca-9165-3adbdf1b5829/3a22ac80-cd1d-4441-9e36-f24fc2f4c3de/gBqmpbl
697 lines
25 KiB
TypeScript
697 lines
25 KiB
TypeScript
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<Server> {
|
|
// 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" });
|
|
}
|
|
});
|
|
|
|
// Resend invitation
|
|
app.post('/api/invitations/:id/resend', isAuthenticated, isAdmin, async (req, res) => {
|
|
try {
|
|
const invitationId = parseInt(req.params.id);
|
|
const invitation = await storage.getInvitation(invitationId);
|
|
|
|
if (!invitation) {
|
|
return res.status(404).json({ message: "Invitation not found" });
|
|
}
|
|
|
|
// Generate new token and extend expiration
|
|
const newToken = crypto.randomBytes(32).toString('hex');
|
|
const newExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
|
|
|
|
const updatedInvitation = await storage.updateInvitation(invitationId, {
|
|
token: newToken,
|
|
expiresAt: newExpiresAt,
|
|
isUsed: false
|
|
});
|
|
|
|
res.json({
|
|
message: "Invitation resent successfully",
|
|
invitation: updatedInvitation
|
|
});
|
|
} catch (error) {
|
|
console.error("Error resending invitation:", error);
|
|
res.status(500).json({ message: "Failed to resend invitation" });
|
|
}
|
|
});
|
|
|
|
// Delete invitation
|
|
app.delete('/api/invitations/:id', isAuthenticated, isAdmin, async (req, res) => {
|
|
try {
|
|
const invitationId = parseInt(req.params.id);
|
|
await storage.deleteInvitation(invitationId);
|
|
res.json({ message: "Invitation deleted successfully" });
|
|
} catch (error) {
|
|
console.error("Error deleting invitation:", error);
|
|
res.status(500).json({ message: "Failed to delete invitation" });
|
|
}
|
|
});
|
|
|
|
// Manual activation - create user directly
|
|
app.post('/api/users/create-manual', isAuthenticated, isAdmin, async (req, res) => {
|
|
try {
|
|
const { email, firstName, lastName, displayName, tempPassword } = req.body;
|
|
|
|
if (!email || !tempPassword) {
|
|
return res.status(400).json({ message: "Email and temporary password are required" });
|
|
}
|
|
|
|
// Check if user already exists
|
|
const existingUser = await storage.getUserByEmail(email);
|
|
if (existingUser) {
|
|
return res.status(409).json({ message: "User with this email already exists" });
|
|
}
|
|
|
|
// Create user with temporary ID (will be replaced when they log in via Replit Auth)
|
|
const tempUserId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
const userData = {
|
|
id: tempUserId,
|
|
email,
|
|
firstName: firstName || '',
|
|
lastName: lastName || '',
|
|
displayName: displayName || `${firstName || ''} ${lastName || ''}`.trim(),
|
|
password: tempPassword,
|
|
isTemporary: true
|
|
};
|
|
|
|
const user = await storage.createUserWithPassword(userData);
|
|
|
|
res.json({
|
|
message: "User created successfully",
|
|
user: { ...user, tempPassword: undefined } // Don't send password back
|
|
});
|
|
} catch (error) {
|
|
console.error("Error creating user manually:", error);
|
|
res.status(500).json({ message: "Failed to create user manually" });
|
|
}
|
|
});
|
|
|
|
// Set/change user password
|
|
app.post('/api/users/:id/set-password', isAuthenticated, isAdmin, async (req, res) => {
|
|
try {
|
|
const { password, isTemporary } = req.body;
|
|
if (!password) {
|
|
return res.status(400).json({ message: "Password is required" });
|
|
}
|
|
await storage.setUserPassword(req.params.id, password, isTemporary || false);
|
|
res.json({ message: "Password updated successfully" });
|
|
} catch (error) {
|
|
console.error("Error setting user password:", error);
|
|
res.status(500).json({ message: "Failed to set user password" });
|
|
}
|
|
});
|
|
|
|
// Soft deactivate user (hide from scheduling but keep data)
|
|
app.post('/api/users/:id/deactivate-soft', isAuthenticated, isAdmin, async (req, res) => {
|
|
try {
|
|
await storage.deactivateUserSoft(req.params.id);
|
|
res.json({ message: "User deactivated successfully (data preserved)" });
|
|
} catch (error) {
|
|
console.error("Error deactivating user:", error);
|
|
res.status(500).json({ message: "Failed to deactivate user" });
|
|
}
|
|
});
|
|
|
|
// Hard delete user (remove user and future data)
|
|
app.delete('/api/users/:id/delete-hard', isAuthenticated, isAdmin, async (req, res) => {
|
|
try {
|
|
await storage.deleteUserHard(req.params.id);
|
|
res.json({ message: "User and associated future data deleted successfully" });
|
|
} catch (error) {
|
|
console.error("Error deleting user:", error);
|
|
res.status(500).json({ message: "Failed to delete user" });
|
|
}
|
|
});
|
|
|
|
// Get user active status
|
|
app.get('/api/users/:id/active-status', isAuthenticated, isAdmin, async (req, res) => {
|
|
try {
|
|
const isActive = await storage.getUserActiveStatus(req.params.id);
|
|
res.json({ isActive });
|
|
} catch (error) {
|
|
console.error("Error checking user active status:", error);
|
|
res.status(500).json({ message: "Failed to check user status" });
|
|
}
|
|
});
|
|
|
|
// 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;
|
|
}
|