- Add findOrCreateUser helper function to handle Clerk ID to database ID mapping - Update all authenticated endpoints to convert Clerk ID to database user ID - Ensures events can be created and managed by authenticated users - Matches pattern used in reportController
347 lines
10 KiB
TypeScript
347 lines
10 KiB
TypeScript
import { Response } from 'express';
|
|
import { AuthRequest } from '../middleware/auth';
|
|
import { eventService } from '../services/eventService';
|
|
import prisma from '../config/database';
|
|
import { clerkClient } from '@clerk/clerk-sdk-node';
|
|
|
|
// Helper to find or create user from Clerk ID
|
|
async function findOrCreateUser(clerkId: string): Promise<string> {
|
|
// Check if user exists
|
|
let user = await prisma.user.findUnique({
|
|
where: { clerkId },
|
|
select: { id: true },
|
|
});
|
|
|
|
if (user) {
|
|
return user.id;
|
|
}
|
|
|
|
// User doesn't exist, fetch from Clerk and create
|
|
try {
|
|
const clerkUser = await clerkClient.users.getUser(clerkId);
|
|
|
|
user = await prisma.user.create({
|
|
data: {
|
|
clerkId,
|
|
email: clerkUser.emailAddresses[0]?.emailAddress || `${clerkId}@temp.com`,
|
|
username: clerkUser.username || `user_${clerkId.substring(0, 8)}`,
|
|
firstName: clerkUser.firstName,
|
|
lastName: clerkUser.lastName,
|
|
avatarUrl: clerkUser.imageUrl,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
|
|
return user.id;
|
|
} catch (error) {
|
|
console.error('Error creating user from Clerk:', error);
|
|
throw new Error('Failed to create user');
|
|
}
|
|
}
|
|
|
|
export const eventController = {
|
|
// Get all events with optional filters
|
|
async getEvents(req: AuthRequest, res: Response) {
|
|
try {
|
|
const { status, startDate, endDate, latitude, longitude, radius } = req.query;
|
|
|
|
const filters: any = {};
|
|
if (status) filters.status = status as string;
|
|
if (startDate) filters.startDate = new Date(startDate as string);
|
|
if (endDate) filters.endDate = new Date(endDate as string);
|
|
if (latitude && longitude) {
|
|
filters.latitude = parseFloat(latitude as string);
|
|
filters.longitude = parseFloat(longitude as string);
|
|
if (radius) filters.radius = parseFloat(radius as string);
|
|
}
|
|
|
|
const events = await eventService.getEvents(filters);
|
|
res.json(events);
|
|
} catch (error: any) {
|
|
console.error('Error fetching events:', error);
|
|
res.status(500).json({
|
|
code: 'FETCH_EVENTS_ERROR',
|
|
message: 'Failed to fetch events',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Get single event by ID
|
|
async getEvent(req: AuthRequest, res: Response) {
|
|
try {
|
|
const { id } = req.params;
|
|
const event = await eventService.getEventById(id);
|
|
|
|
if (!event) {
|
|
return res.status(404).json({
|
|
code: 'EVENT_NOT_FOUND',
|
|
message: 'Event not found',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
res.json(event);
|
|
} catch (error: any) {
|
|
console.error('Error fetching event:', error);
|
|
res.status(500).json({
|
|
code: 'FETCH_EVENT_ERROR',
|
|
message: 'Failed to fetch event',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Create new event
|
|
async createEvent(req: AuthRequest, res: Response) {
|
|
try {
|
|
const clerkId = req.userId;
|
|
if (!clerkId) {
|
|
return res.status(401).json({
|
|
code: 'UNAUTHORIZED',
|
|
message: 'User not authenticated',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
// Find or create user in database
|
|
const userId = await findOrCreateUser(clerkId);
|
|
|
|
const { title, description, latitude, longitude, locationName, scheduledDate, duration } = req.body;
|
|
|
|
// Validation
|
|
if (!title || !description || !latitude || !longitude || !locationName || !scheduledDate || !duration) {
|
|
return res.status(400).json({
|
|
code: 'VALIDATION_ERROR',
|
|
message: 'Missing required fields',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
const eventData = {
|
|
organizerId: userId,
|
|
title,
|
|
description,
|
|
latitude: parseFloat(latitude),
|
|
longitude: parseFloat(longitude),
|
|
locationName,
|
|
scheduledDate: new Date(scheduledDate),
|
|
duration: parseInt(duration),
|
|
};
|
|
|
|
const event = await eventService.createEvent(eventData);
|
|
res.status(201).json(event);
|
|
} catch (error: any) {
|
|
console.error('Error creating event:', error);
|
|
res.status(500).json({
|
|
code: 'CREATE_EVENT_ERROR',
|
|
message: 'Failed to create event',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Update event
|
|
async updateEvent(req: AuthRequest, res: Response) {
|
|
try {
|
|
const clerkId = req.userId;
|
|
if (!clerkId) {
|
|
return res.status(401).json({
|
|
code: 'UNAUTHORIZED',
|
|
message: 'User not authenticated',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
const userId = await findOrCreateUser(clerkId);
|
|
|
|
const { id } = req.params;
|
|
const event = await eventService.getEventById(id);
|
|
|
|
if (!event) {
|
|
return res.status(404).json({
|
|
code: 'EVENT_NOT_FOUND',
|
|
message: 'Event not found',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
// Check if user is the organizer
|
|
if (event.organizerId !== userId) {
|
|
return res.status(403).json({
|
|
code: 'FORBIDDEN',
|
|
message: 'Only the event organizer can update this event',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
const updatedEvent = await eventService.updateEvent(id, req.body);
|
|
res.json(updatedEvent);
|
|
} catch (error: any) {
|
|
console.error('Error updating event:', error);
|
|
res.status(500).json({
|
|
code: 'UPDATE_EVENT_ERROR',
|
|
message: 'Failed to update event',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Delete event
|
|
async deleteEvent(req: AuthRequest, res: Response) {
|
|
try {
|
|
const clerkId = req.userId;
|
|
if (!clerkId) {
|
|
return res.status(401).json({
|
|
code: 'UNAUTHORIZED',
|
|
message: 'User not authenticated',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
const userId = await findOrCreateUser(clerkId);
|
|
|
|
const { id } = req.params;
|
|
const event = await eventService.getEventById(id);
|
|
|
|
if (!event) {
|
|
return res.status(404).json({
|
|
code: 'EVENT_NOT_FOUND',
|
|
message: 'Event not found',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
// Check if user is the organizer
|
|
if (event.organizerId !== userId) {
|
|
return res.status(403).json({
|
|
code: 'FORBIDDEN',
|
|
message: 'Only the event organizer can delete this event',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
await eventService.deleteEvent(id);
|
|
res.status(204).send();
|
|
} catch (error: any) {
|
|
console.error('Error deleting event:', error);
|
|
res.status(500).json({
|
|
code: 'DELETE_EVENT_ERROR',
|
|
message: 'Failed to delete event',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Register for event
|
|
async registerForEvent(req: AuthRequest, res: Response) {
|
|
try {
|
|
const clerkId = req.userId;
|
|
if (!clerkId) {
|
|
return res.status(401).json({
|
|
code: 'UNAUTHORIZED',
|
|
message: 'User not authenticated',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
const userId = await findOrCreateUser(clerkId);
|
|
|
|
const { id } = req.params;
|
|
const registration = await eventService.registerForEvent(id, userId);
|
|
res.status(201).json(registration);
|
|
} catch (error: any) {
|
|
console.error('Error registering for event:', error);
|
|
|
|
if (error.message.includes('already registered')) {
|
|
return res.status(409).json({
|
|
code: 'ALREADY_REGISTERED',
|
|
message: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
res.status(500).json({
|
|
code: 'REGISTER_EVENT_ERROR',
|
|
message: 'Failed to register for event',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Unregister from event
|
|
async unregisterFromEvent(req: AuthRequest, res: Response) {
|
|
try {
|
|
const clerkId = req.userId;
|
|
if (!clerkId) {
|
|
return res.status(401).json({
|
|
code: 'UNAUTHORIZED',
|
|
message: 'User not authenticated',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
const userId = await findOrCreateUser(clerkId);
|
|
|
|
const { id } = req.params;
|
|
await eventService.unregisterFromEvent(id, userId);
|
|
res.status(204).send();
|
|
} catch (error: any) {
|
|
console.error('Error unregistering from event:', error);
|
|
res.status(500).json({
|
|
code: 'UNREGISTER_EVENT_ERROR',
|
|
message: 'Failed to unregister from event',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Get event registrations
|
|
async getEventRegistrations(req: AuthRequest, res: Response) {
|
|
try {
|
|
const { id } = req.params;
|
|
const registrations = await eventService.getEventRegistrations(id);
|
|
res.json(registrations);
|
|
} catch (error: any) {
|
|
console.error('Error fetching event registrations:', error);
|
|
res.status(500).json({
|
|
code: 'FETCH_REGISTRATIONS_ERROR',
|
|
message: 'Failed to fetch event registrations',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
|
|
// Check registration status
|
|
async getRegistrationStatus(req: AuthRequest, res: Response) {
|
|
try {
|
|
const clerkId = req.userId;
|
|
if (!clerkId) {
|
|
return res.json({ registered: false });
|
|
}
|
|
|
|
const userId = await findOrCreateUser(clerkId);
|
|
|
|
const { id } = req.params;
|
|
const isRegistered = await eventService.isUserRegistered(id, userId);
|
|
res.json({ registered: isRegistered });
|
|
} catch (error: any) {
|
|
console.error('Error checking registration status:', error);
|
|
res.status(500).json({
|
|
code: 'CHECK_REGISTRATION_ERROR',
|
|
message: 'Failed to check registration status',
|
|
details: error.message,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
},
|
|
};
|