From 4d15e6119c64b18f4d4ba14a422f2adc9aa578bc Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 12 Jul 2025 13:47:30 +0530 Subject: [PATCH] Refactor EventsPage and EventDetailPage to utilize custom hooks for data fetching, removing mock data and loading states. Update event filtering logic and improve performance by optimizing state management. --- app/api/events/[slug]/route.ts | 62 +++++++ app/api/events/featured/route.ts | 20 ++ app/api/events/route.ts | 58 ++++++ app/events/[id]/page.tsx | 34 +--- app/events/page.tsx | 48 +++-- hooks/useEvents.ts | 254 +++++++++++++++++++++++++ lib/services/events.ts | 309 +++++++++++++++++++++++++++++++ 7 files changed, 729 insertions(+), 56 deletions(-) create mode 100644 app/api/events/[slug]/route.ts create mode 100644 app/api/events/featured/route.ts create mode 100644 app/api/events/route.ts create mode 100644 hooks/useEvents.ts create mode 100644 lib/services/events.ts diff --git a/app/api/events/[slug]/route.ts b/app/api/events/[slug]/route.ts new file mode 100644 index 00000000..cad55fb9 --- /dev/null +++ b/app/api/events/[slug]/route.ts @@ -0,0 +1,62 @@ +import { NextRequest, NextResponse } from 'next/server' +import { eventsService } from '@/lib/services/events' + +// GET: Fetch a single event by slug +export async function GET(request: NextRequest) { + try { + const { pathname } = request.nextUrl + const slug = pathname.split('/').pop() || '' + const event = await eventsService.getEventBySlug(slug) + + if (!event) { + return NextResponse.json( + { error: 'Event not found' }, + { status: 404 } + ) + } + + return NextResponse.json(event) + } catch (error) { + console.error('Error in GET /api/events/[slug]:', error) + return NextResponse.json( + { error: 'Failed to fetch event' }, + { status: 500 } + ) + } +} + +// PUT: Update an event +export async function PUT(request: NextRequest) { + try { + const { pathname } = request.nextUrl + const slug = pathname.split('/').pop() || '' + const eventData = await request.json() + + const event = await eventsService.updateEvent(slug, eventData) + + return NextResponse.json(event) + } catch (error) { + console.error('Error in PUT /api/events/[slug]:', error) + return NextResponse.json( + { error: 'Failed to update event' }, + { status: 500 } + ) + } +} + +// DELETE: Delete an event +export async function DELETE(request: NextRequest) { + try { + const { pathname } = request.nextUrl + const slug = pathname.split('/').pop() || '' + await eventsService.deleteEvent(slug) + + return NextResponse.json({ message: 'Event deleted successfully' }) + } catch (error) { + console.error('Error in DELETE /api/events/[slug]:', error) + return NextResponse.json( + { error: 'Failed to delete event' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/app/api/events/featured/route.ts b/app/api/events/featured/route.ts new file mode 100644 index 00000000..ac48f536 --- /dev/null +++ b/app/api/events/featured/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from 'next/server' +import { eventsService } from '@/lib/services/events' + +// GET: Fetch featured events +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const limit = searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : 5 + + const events = await eventsService.getFeaturedEvents(limit) + + return NextResponse.json({ events }) + } catch (error) { + console.error('Error in GET /api/events/featured:', error) + return NextResponse.json( + { error: 'Failed to fetch featured events' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/app/api/events/route.ts b/app/api/events/route.ts new file mode 100644 index 00000000..d7e8249f --- /dev/null +++ b/app/api/events/route.ts @@ -0,0 +1,58 @@ +import { NextRequest, NextResponse } from 'next/server' +import { eventsService, EventsFilters } from '@/lib/services/events' + +// GET: Fetch events with optional filters +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + + const filters: EventsFilters = { + search: searchParams.get('search') || undefined, + category: searchParams.get('category') || undefined, + status: searchParams.get('status') || undefined, + featured: searchParams.get('featured') === 'true' ? true : + searchParams.get('featured') === 'false' ? false : undefined, + dateFilter: searchParams.get('dateFilter') as EventsFilters['dateFilter'] || undefined, + limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : undefined, + offset: searchParams.get('offset') ? parseInt(searchParams.get('offset')!) : undefined, + } + + const result = await eventsService.getEvents(filters) + + return NextResponse.json(result) + } catch (error) { + console.error('Error in GET /api/events:', error) + return NextResponse.json( + { error: 'Failed to fetch events' }, + { status: 500 } + ) + } +} + +// POST: Create a new event +export async function POST(request: NextRequest) { + try { + const eventData = await request.json() + + // Validate required fields + const requiredFields = ['slug', 'title', 'excerpt', 'description', 'organizer', 'date', 'time', 'duration', 'category', 'location', 'capacity', 'price', 'payment'] + for (const field of requiredFields) { + if (!eventData[field]) { + return NextResponse.json( + { error: `Missing required field: ${field}` }, + { status: 400 } + ) + } + } + + const event = await eventsService.createEvent(eventData) + + return NextResponse.json(event, { status: 201 }) + } catch (error) { + console.error('Error in POST /api/events:', error) + return NextResponse.json( + { error: 'Failed to create event' }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/app/events/[id]/page.tsx b/app/events/[id]/page.tsx index 65895daa..2e00c83c 100644 --- a/app/events/[id]/page.tsx +++ b/app/events/[id]/page.tsx @@ -8,10 +8,10 @@ import { Badge } from "@/components/ui/badge" import { ArrowLeft, Clock, Calendar, Users, DollarSign, Star, Sparkles} from "lucide-react" import Link from "next/link" import { motion } from "framer-motion" -import { Event, mockEvents } from "@/components/data/events" import Image from "next/image"; import { Tabs as AnimatedTabs } from "@/components/ui/tabs"; import React from "react"; +import { useEvent } from "@/hooks/useEvents" // import Header from "@/components/header"; import Footer from "@/components/footer"; @@ -67,13 +67,13 @@ const RotatingSponsorsGrid = ({ sponsors }: { sponsors?: Sponsor[] }) => { export default function EventDetailPage() { const [isAuthenticated, setIsAuthenticated] = useState(false) - const [isLoading, setIsLoading] = useState(true) - const [event, setEvent] = useState(null) - const [fetchError, setFetchError] = useState(null) const params = useParams() const slug = params?.id as string + // Use custom hook for fetching event + const { event, loading: isLoading, error: fetchError } = useEvent(slug) + useEffect(() => { const checkAuth = async () => { const supabase = createClient() @@ -83,32 +83,6 @@ export default function EventDetailPage() { checkAuth() }, []) - useEffect(() => { - const fetchEvent = async () => { - setIsLoading(true) - setFetchError(null) - - // For now, using mock data since events table doesn't exist yet - // In a real app, this would fetch from Supabase - try { - // Simulate API delay - await new Promise(resolve => setTimeout(resolve, 1000)) - const foundEvent = mockEvents.find(e => e.slug === slug) - if (foundEvent) { - setEvent(foundEvent) - } else { - setFetchError('Event not found.') - setEvent(null) - } - } catch { - setFetchError('Failed to fetch event.') - setEvent(null) - } - setIsLoading(false) - } - if (slug) fetchEvent() - }, [slug]) - const getCategoryColor = (category: string) => { switch (category) { case "Hackathons": diff --git a/app/events/page.tsx b/app/events/page.tsx index 10048625..91daff2b 100644 --- a/app/events/page.tsx +++ b/app/events/page.tsx @@ -9,7 +9,7 @@ import { Input } from "@/components/ui/input" import { Search, Clock, ArrowRight, Calendar, Star, Users, MapPin, DollarSign, Filter, Link as LinkIcon, Sparkles } from "lucide-react" import Link from "next/link" import { motion } from "framer-motion" -import { mockEvents, Event } from "@/components/data/events" +import { Event } from "@/components/data/events" import Header from "@/components/header"; import Footer from "@/components/footer"; import Image from "next/image"; @@ -18,6 +18,7 @@ import { Checkbox } from "@/components/ui/checkbox" import "keen-slider/keen-slider.min.css" import { useKeenSlider } from "keen-slider/react" import { cn } from "@/lib/utils"; +import { useEvents, useFeaturedEvents } from "@/hooks/useEvents" // Event categories for dropdown const eventCategories = [ @@ -35,8 +36,6 @@ const eventCategories = [ export default function EventsPage() { const [searchTerm, setSearchTerm] = useState("") - const [isLoading, setIsLoading] = useState(true) - const [events, setEvents] = useState([]) const [dateFilter] = useState("Upcoming") const [filterOpen, setFilterOpen] = useState(false) const [selectedStatuses, setSelectedStatuses] = useState([]) @@ -49,6 +48,19 @@ export default function EventsPage() { const [selectedCategory, setSelectedCategory] = useState("All") const [copiedEventId, setCopiedEventId] = useState(null) + // Use custom hooks for data fetching + const { data: eventsData, loading: eventsLoading } = useEvents({ + search: searchTerm, + category: selectedCategory, + dateFilter: dateFilter === "Upcoming" ? "upcoming" : "all" + }) + + const { loading: featuredLoading } = useFeaturedEvents(5) + + // Extract events from the response + const events = eventsData?.events || [] + const isLoading = eventsLoading || featuredLoading + // Unique values for filters const allStatuses = ["Live", "Expired", "Closed", "Recent"] const allLocations = Array.from(new Set(events.flatMap(e => e.locations))).sort() @@ -85,7 +97,7 @@ export default function EventsPage() { return matchesSearch && matchesStatus && matchesLocation && matchesEventType && matchesTeamSize && matchesPayment && matchesUserType && matchesCategory && matchesDate && matchesDropdownCategory }) - const featuredEvents = filteredEvents.filter((event) => event.featured) + const filteredFeaturedEvents = filteredEvents.filter((event) => event.featured) const regularEvents = filteredEvents.filter((event) => !event.featured) // Add keen-slider hook for featured events with autoplay and navigation @@ -97,13 +109,13 @@ export default function EventsPage() { "(min-width: 640px)": { slides: { perView: 1.5, spacing: 24 } }, "(min-width: 1024px)": { slides: { perView: 2.2, spacing: 32 } }, }, - loop: featuredEvents.length > 1, + loop: filteredFeaturedEvents.length > 1, }) // Autoplay effect useEffect(() => { if (!slider) return - if (featuredEvents.length > 1) { + if (filteredFeaturedEvents.length > 1) { const autoplay = () => { slider.current?.next() } @@ -127,25 +139,9 @@ export default function EventsPage() { // If less than 2, clear any existing interval if (sliderTimer.current) clearInterval(sliderTimer.current) } - }, [slider, featuredEvents.length]) + }, [slider, filteredFeaturedEvents.length]) + - useEffect(() => { - const fetchEvents = async () => { - setIsLoading(true) - - // For now, using mock data since events table doesn't exist yet - // In a real app, this would fetch from Supabase - try { - // Simulate API delay - await new Promise(resolve => setTimeout(resolve, 1000)) - setEvents(mockEvents) - } catch { - setEvents([]) - } - setIsLoading(false) - } - fetchEvents() - }, []) // Date filter logic function isDateMatch(event: Event) { @@ -525,7 +521,7 @@ export default function EventsPage() { {/* Featured Events - Redesigned */} - {featuredEvents.length > 0 && ( + {filteredFeaturedEvents.length > 0 && (
- {(featuredEvents.length > 2 ? [...featuredEvents, ...featuredEvents] : featuredEvents).map((event, index) => ( + {(filteredFeaturedEvents.length > 2 ? [...filteredFeaturedEvents, ...filteredFeaturedEvents] : filteredFeaturedEvents).map((event, index) => ( (null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const fetchEvents = useCallback(async () => { + try { + setLoading(true) + setError(null) + + const queryParams = new URLSearchParams() + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + queryParams.append(key, String(value)) + } + }) + + const response = await fetch(`/api/events?${queryParams.toString()}`) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const result = await response.json() + setData(result) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch events') + } finally { + setLoading(false) + } + }, [JSON.stringify(filters)]) + + useEffect(() => { + fetchEvents() + // Only re-run when filters actually change + }, [fetchEvents]) + + return { + data, + loading, + error, + refetch: fetchEvents + } +} + +// Hook for fetching a single event by slug +export function useEvent(slug: string) { + const [event, setEvent] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const fetchEvent = useCallback(async () => { + if (!slug) { + setLoading(false) + return + } + + try { + setLoading(true) + setError(null) + + const response = await fetch(`/api/events/${slug}`) + + if (!response.ok) { + if (response.status === 404) { + setEvent(null) + return + } + throw new Error(`HTTP error! status: ${response.status}`) + } + + const result = await response.json() + setEvent(result) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch event') + } finally { + setLoading(false) + } + }, [slug]) + + useEffect(() => { + fetchEvent() + }, [fetchEvent]) + + return { + event, + loading, + error, + refetch: fetchEvent + } +} + +// Hook for fetching featured events +export function useFeaturedEvents(limit: number = 5) { + const [events, setEvents] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const fetchFeaturedEvents = useCallback(async () => { + try { + setLoading(true) + setError(null) + + const response = await fetch(`/api/events/featured?limit=${limit}`) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const result = await response.json() + setEvents(result.events) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch featured events') + } finally { + setLoading(false) + } + }, [limit]) + + useEffect(() => { + fetchFeaturedEvents() + }, [fetchFeaturedEvents]) + + return { + events, + loading, + error, + refetch: fetchFeaturedEvents + } +} + +// Hook for creating events +export function useCreateEvent() { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const createEvent = useCallback(async (eventData: Omit) => { + try { + setLoading(true) + setError(null) + + const response = await fetch('/api/events', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(eventData), + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.error || `HTTP error! status: ${response.status}`) + } + + const result = await response.json() + return result + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to create event' + setError(errorMessage) + throw err + } finally { + setLoading(false) + } + }, []) + + return { + createEvent, + loading, + error + } +} + +// Hook for updating events +export function useUpdateEvent() { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const updateEvent = useCallback(async (slug: string, eventData: Partial) => { + try { + setLoading(true) + setError(null) + + const response = await fetch(`/api/events/${slug}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(eventData), + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.error || `HTTP error! status: ${response.status}`) + } + + const result = await response.json() + return result + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to update event' + setError(errorMessage) + throw err + } finally { + setLoading(false) + } + }, []) + + return { + updateEvent, + loading, + error + } +} + +// Hook for deleting events +export function useDeleteEvent() { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const deleteEvent = useCallback(async (slug: string) => { + try { + setLoading(true) + setError(null) + + const response = await fetch(`/api/events/${slug}`, { + method: 'DELETE', + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.error || `HTTP error! status: ${response.status}`) + } + + return await response.json() + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to delete event' + setError(errorMessage) + throw err + } finally { + setLoading(false) + } + }, []) + + return { + deleteEvent, + loading, + error + } +} \ No newline at end of file diff --git a/lib/services/events.ts b/lib/services/events.ts new file mode 100644 index 00000000..1702d55e --- /dev/null +++ b/lib/services/events.ts @@ -0,0 +1,309 @@ +import { createClient } from '@/lib/supabase/client' +import { Event } from '@/components/data/events' + +export interface EventsFilters { + search?: string + category?: string + status?: string + featured?: boolean + dateFilter?: 'all' | 'upcoming' | 'today' | 'this-week' + limit?: number + offset?: number +} + +export interface EventsResponse { + events: Event[] + total: number + hasMore: boolean +} + +export class EventsService { + private supabase = createClient() + + // Get all events with optional filters + async getEvents(filters: EventsFilters = {}): Promise { + let query = this.supabase + .from('events') + .select('*', { count: 'exact' }) + + // Apply filters + if (filters.search) { + query = query.or(`title.ilike.%${filters.search}%,excerpt.ilike.%${filters.search}%,description.ilike.%${filters.search}%`) + } + + if (filters.category && filters.category !== 'All') { + query = query.eq('category', filters.category) + } + + if (filters.status) { + query = query.eq('status', filters.status) + } + + if (filters.featured !== undefined) { + query = query.eq('featured', filters.featured) + } + + // Date filtering + if (filters.dateFilter) { + const today = new Date() + today.setHours(0, 0, 0, 0) + + switch (filters.dateFilter) { + case 'today': + query = query.eq('date', today.toISOString().split('T')[0]) + break + case 'this-week': + const weekStart = new Date(today) + weekStart.setDate(today.getDate() - today.getDay()) + const weekEnd = new Date(weekStart) + weekEnd.setDate(weekStart.getDate() + 6) + query = query.gte('date', weekStart.toISOString().split('T')[0]) + .lte('date', weekEnd.toISOString().split('T')[0]) + break + case 'upcoming': + query = query.gte('date', today.toISOString().split('T')[0]) + break + } + } + + // Order by featured first, then by date + query = query.order('featured', { ascending: false }) + .order('date', { ascending: true }) + + // Apply pagination + if (filters.limit) { + query = query.limit(filters.limit) + } + if (filters.offset) { + query = query.range(filters.offset, (filters.offset + (filters.limit || 10)) - 1) + } + + const { data, error, count } = await query + + if (error) { + console.error('Error fetching events:', error) + throw new Error(`Failed to fetch events: ${error.message}`) + } + + // Transform data to match Event interface + const events = data?.map(this.transformEvent) || [] + + return { + events, + total: count || 0, + hasMore: filters.limit ? (count || 0) > (filters.offset || 0) + (filters.limit || 10) : false + } + } + + // Get a single event by slug + async getEventBySlug(slug: string): Promise { + const { data, error } = await this.supabase + .from('events') + .select('*') + .eq('slug', slug) + .single() + + if (error) { + if (error.code === 'PGRST116') { + // No rows returned + return null + } + console.error('Error fetching event:', error) + throw new Error(`Failed to fetch event: ${error.message}`) + } + + return data ? this.transformEvent(data) : null + } + + // Get featured events + async getFeaturedEvents(limit: number = 5): Promise { + const { data, error } = await this.supabase + .from('events') + .select('*') + .eq('featured', true) + .order('date', { ascending: true }) + .limit(limit) + + if (error) { + console.error('Error fetching featured events:', error) + throw new Error(`Failed to fetch featured events: ${error.message}`) + } + + return data?.map(this.transformEvent) || [] + } + + // Create a new event + async createEvent(eventData: Omit): Promise { + const { data, error } = await this.supabase + .from('events') + .insert([this.transformToDatabase(eventData)]) + .select() + .single() + + if (error) { + console.error('Error creating event:', error) + throw new Error(`Failed to create event: ${error.message}`) + } + + return this.transformEvent(data) + } + + // Update an event + async updateEvent(slug: string, eventData: Partial): Promise { + const { data, error } = await this.supabase + .from('events') + .update(this.transformToDatabase(eventData)) + .eq('slug', slug) + .select() + .single() + + if (error) { + console.error('Error updating event:', error) + throw new Error(`Failed to update event: ${error.message}`) + } + + return this.transformEvent(data) + } + + // Delete an event + async deleteEvent(slug: string): Promise { + const { error } = await this.supabase + .from('events') + .delete() + .eq('slug', slug) + + if (error) { + console.error('Error deleting event:', error) + throw new Error(`Failed to delete event: ${error.message}`) + } + } + + // Increment registered count + async incrementRegistered(slug: string): Promise { + // First get the current registered count + const { data: currentEvent, error: fetchError } = await this.supabase + .from('events') + .select('registered') + .eq('slug', slug) + .single() + + if (fetchError) { + console.error('Error fetching current registered count:', fetchError) + throw new Error(`Failed to fetch current registered count: ${fetchError.message}`) + } + + // Then update with incremented value + const { error } = await this.supabase + .from('events') + .update({ registered: (currentEvent.registered || 0) + 1 }) + .eq('slug', slug) + + if (error) { + console.error('Error incrementing registered count:', error) + throw new Error(`Failed to increment registered count: ${error.message}`) + } + } + + // Get event categories + async getCategories(): Promise { + const { data, error } = await this.supabase + .from('events') + .select('category') + .not('category', 'is', null) + + if (error) { + console.error('Error fetching categories:', error) + throw new Error(`Failed to fetch categories: ${error.message}`) + } + + const categories = [...new Set(data?.map(item => item.category) || [])] + return categories.sort() + } + + // Transform database record to Event interface + private transformEvent(dbEvent: Record): Event { + return { + id: dbEvent.id as number, + slug: dbEvent.slug as string, + title: dbEvent.title as string, + excerpt: dbEvent.excerpt as string, + description: dbEvent.description as string, + organizer: dbEvent.organizer as string, + organizer_contact: dbEvent.organizer_contact as Event['organizer_contact'], + date: dbEvent.date as string, + time: dbEvent.time as string, + duration: dbEvent.duration as string, + category: dbEvent.category as string, + categories: (dbEvent.categories as string[]) || [], + tags: (dbEvent.tags as string[]) || [], + featured: dbEvent.featured as boolean, + image: dbEvent.image as string, + location: dbEvent.location as string, + locations: (dbEvent.locations as string[]) || [], + capacity: dbEvent.capacity as number, + registered: dbEvent.registered as number, + price: dbEvent.price as string, + payment: dbEvent.payment as 'Paid' | 'Free', + status: dbEvent.status as 'live' | 'expired' | 'closed' | 'recent', + eventType: (dbEvent.event_type as ('Online' | 'Offline' | 'Hybrid')[]) || [], + teamSize: dbEvent.team_size as number | [number, number], + userTypes: (dbEvent.user_types as string[]) || [], + registration_required: dbEvent.registration_required as boolean, + registration_deadline: dbEvent.registration_deadline as string, + rules: (dbEvent.rules as string[]) || [], + schedule: (dbEvent.schedule as { date: string; label: string }[]) || [], + prize: dbEvent.prize as string, + prize_details: dbEvent.prize_details as string, + faq: (dbEvent.faq as { question: string; answer: string }[]) || [], + socials: dbEvent.socials as Event['socials'], + sponsors: (dbEvent.sponsors as { name: string; logo: string; type: string }[]) || [], + marking_scheme: dbEvent.marking_scheme as Event['marking_scheme'], + } + } + + // Transform Event interface to database format + private transformToDatabase(event: Partial): Record { + const dbEvent: Record = { ...event } + + // Transform field names to match database schema + if (event.eventType !== undefined) { + dbEvent.event_type = event.eventType + delete dbEvent.eventType + } + + if (event.teamSize !== undefined) { + dbEvent.team_size = event.teamSize + delete dbEvent.teamSize + } + + if (event.userTypes !== undefined) { + dbEvent.user_types = event.userTypes + delete dbEvent.userTypes + } + + if (event.registration_required !== undefined) { + dbEvent.registration_required = event.registration_required + delete dbEvent.registration_required + } + + if (event.registration_deadline !== undefined) { + dbEvent.registration_deadline = event.registration_deadline + delete dbEvent.registration_deadline + } + + if (event.prize_details !== undefined) { + dbEvent.prize_details = event.prize_details + delete dbEvent.prize_details + } + + if (event.marking_scheme !== undefined) { + dbEvent.marking_scheme = event.marking_scheme + delete dbEvent.marking_scheme + } + + return dbEvent + } +} + +// Export a singleton instance +export const eventsService = new EventsService() \ No newline at end of file