Skip to content

Commit 2a2cf2c

Browse files
authored
Merge pull request #230 from codeunia-dev/feature/comprehensive-updates-and-fixes
🚀 Comprehensive Updates: Events System, Database Fixes & Admin Improvements
2 parents a087695 + 886c320 commit 2a2cf2c

File tree

25 files changed

+5405
-91
lines changed

25 files changed

+5405
-91
lines changed

app/admin/events/page.tsx

Lines changed: 413 additions & 3 deletions
Large diffs are not rendered by default.

app/api/admin/events/route.ts

Lines changed: 157 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NextRequest, NextResponse } from 'next/server'
2-
import { createClient } from '@/lib/supabase/server'
2+
import { createClient, createServiceClient } from '@/lib/supabase/server'
33

44
// GET - Fetch all events (admin only)
55
export async function GET(request: NextRequest) {
@@ -23,59 +23,38 @@ export async function GET(request: NextRequest) {
2323
}
2424

2525
const { searchParams } = new URL(request.url)
26-
const limit = parseInt(searchParams.get('limit') || '50')
26+
const limit = parseInt(searchParams.get('limit') || '100')
2727
const offset = parseInt(searchParams.get('offset') || '0')
28-
const search = searchParams.get('search') || undefined
29-
const status = searchParams.get('status') || undefined
30-
const featured = searchParams.get('featured') === 'true' ? true : undefined
3128

32-
let query = supabase
33-
.from('events')
34-
.select('*', { count: 'exact' })
35-
36-
// Apply filters
37-
if (search) {
38-
query = query.or(`title.ilike.%${search}%,excerpt.ilike.%${search}%,tags.cs.{${search}}`)
39-
}
40-
41-
if (status) {
42-
query = query.eq('status', status)
43-
}
29+
// Use service client for admin operations to bypass RLS
30+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
31+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
4432

45-
if (featured !== undefined) {
46-
query = query.eq('featured', featured)
33+
if (!supabaseUrl || !supabaseServiceKey) {
34+
return NextResponse.json(
35+
{ error: 'Server configuration error' },
36+
{ status: 500 }
37+
)
4738
}
4839

49-
// Order by date (upcoming first, then by date)
50-
query = query.order('date', { ascending: true })
40+
const serviceSupabase = createServiceClient(supabaseUrl, supabaseServiceKey)
5141

52-
// Apply pagination
53-
query = query.range(offset, offset + limit - 1)
54-
55-
const { data: events, error, count } = await query
42+
const { data: events, error } = await serviceSupabase
43+
.from('events')
44+
.select('*')
45+
.order('created_at', { ascending: false })
46+
.range(offset, offset + limit - 1)
5647

5748
if (error) {
5849
console.error('Error fetching events:', error)
59-
return NextResponse.json(
60-
{ error: 'Failed to fetch events' },
61-
{ status: 500 }
62-
)
50+
return NextResponse.json({ error: 'Failed to fetch events' }, { status: 500 })
6351
}
6452

65-
const total = count || 0
66-
const hasMore = offset + limit < total
53+
return NextResponse.json(events || [])
6754

68-
return NextResponse.json({
69-
events: events || [],
70-
total,
71-
hasMore
72-
})
7355
} catch (error) {
74-
console.error('Error fetching events:', error)
75-
return NextResponse.json(
76-
{ error: 'Internal server error' },
77-
{ status: 500 }
78-
)
56+
console.error('Error in GET /api/admin/events:', error)
57+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
7958
}
8059
}
8160

@@ -89,7 +68,7 @@ export async function POST(request: NextRequest) {
8968
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
9069
}
9170

92-
// Check if user is admin (using profiles table)
71+
// Check if user is admin
9372
const { data: profile, error: profileError } = await supabase
9473
.from('profiles')
9574
.select('is_admin')
@@ -100,32 +79,49 @@ export async function POST(request: NextRequest) {
10079
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
10180
}
10281

103-
const eventData = await request.json()
104-
const { data: event, error } = await supabase
82+
const formData = await request.json()
83+
84+
// Transform payment field for database constraint
85+
const transformedData = { ...formData }
86+
if (transformedData.payment === 'Required') {
87+
transformedData.payment = 'Paid' // Transform 'Required' to 'Paid' for database constraint
88+
} else if (transformedData.payment === 'Not Required') {
89+
transformedData.payment = 'Free' // Transform 'Not Required' to 'Free' for database constraint
90+
}
91+
92+
// Use service client for database operations
93+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
94+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
95+
96+
if (!supabaseUrl || !supabaseServiceKey) {
97+
return NextResponse.json(
98+
{ error: 'Server configuration error' },
99+
{ status: 500 }
100+
)
101+
}
102+
103+
const serviceSupabase = createServiceClient(supabaseUrl, supabaseServiceKey)
104+
105+
const { data: newEvent, error } = await serviceSupabase
105106
.from('events')
106-
.insert([eventData])
107+
.insert(transformedData)
107108
.select()
108109
.single()
109110

110111
if (error) {
111112
console.error('Error creating event:', error)
112-
return NextResponse.json(
113-
{ error: 'Failed to create event' },
114-
{ status: 500 }
115-
)
113+
return NextResponse.json({ error: 'Failed to create event' }, { status: 500 })
116114
}
117115

118-
return NextResponse.json(event, { status: 201 })
116+
return NextResponse.json(newEvent)
117+
119118
} catch (error) {
120-
console.error('Error creating event:', error)
121-
return NextResponse.json(
122-
{ error: 'Internal server error' },
123-
{ status: 500 }
124-
)
119+
console.error('Error in POST /api/admin/events:', error)
120+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
125121
}
126122
}
127123

128-
// PUT - Update event (admin only)
124+
// PUT - Update existing event (admin only)
129125
export async function PUT(request: NextRequest) {
130126
try {
131127
const supabase = await createClient()
@@ -135,7 +131,7 @@ export async function PUT(request: NextRequest) {
135131
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
136132
}
137133

138-
// Check if user is admin (using profiles table)
134+
// Check if user is admin
139135
const { data: profile, error: profileError } = await supabase
140136
.from('profiles')
141137
.select('is_admin')
@@ -146,38 +142,106 @@ export async function PUT(request: NextRequest) {
146142
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
147143
}
148144

149-
const { slug, data } = await request.json()
145+
const requestData = await request.json()
146+
147+
// Handle nested data format from frontend
148+
const { slug, data: eventData } = requestData as { slug: string; data?: Record<string, unknown> }
149+
const formData = eventData || requestData // Use nested data if available, otherwise use direct data
150150

151151
if (!slug) {
152-
return NextResponse.json({ error: 'Slug is required' }, { status: 400 })
152+
return NextResponse.json({ error: 'Event slug is required' }, { status: 400 })
153+
}
154+
155+
// Transform payment field for database constraint
156+
const transformedData = { ...formData }
157+
if (transformedData.payment === 'Required') {
158+
transformedData.payment = 'Paid' // Transform 'Required' to 'Paid' for database constraint
159+
} else if (transformedData.payment === 'Not Required') {
160+
transformedData.payment = 'Free' // Transform 'Not Required' to 'Free' for database constraint
153161
}
154162

155-
if (!data) {
156-
return NextResponse.json({ error: 'Event data is required' }, { status: 400 })
163+
// Use service client for database operations
164+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
165+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
166+
167+
if (!supabaseUrl || !supabaseServiceKey) {
168+
return NextResponse.json(
169+
{ error: 'Server configuration error' },
170+
{ status: 500 }
171+
)
157172
}
158173

159-
const { data: event, error } = await supabase
174+
const serviceSupabase = createServiceClient(supabaseUrl, supabaseServiceKey)
175+
176+
// Check for event existence with the original slug
177+
const { data: existingEvents, error: checkError } = await serviceSupabase
178+
.from('events')
179+
.select('id, slug, title')
180+
.eq('slug', slug)
181+
182+
if (checkError) {
183+
return NextResponse.json(
184+
{ error: 'Failed to check event existence' },
185+
{ status: 500 }
186+
)
187+
}
188+
189+
if (!existingEvents || existingEvents.length === 0) {
190+
return NextResponse.json(
191+
{ error: `Event with slug '${slug}' not found` },
192+
{ status: 404 }
193+
)
194+
}
195+
196+
197+
// Check if slug is being changed
198+
if (transformedData.slug && transformedData.slug !== slug) {
199+
// Check if new slug already exists
200+
const { data: existingWithNewSlug, error: slugCheckError } = await serviceSupabase
201+
.from('events')
202+
.select('id')
203+
.eq('slug', transformedData.slug)
204+
205+
if (slugCheckError) {
206+
return NextResponse.json(
207+
{ error: 'Failed to check new slug existence' },
208+
{ status: 500 }
209+
)
210+
}
211+
212+
if (existingWithNewSlug && existingWithNewSlug.length > 0) {
213+
return NextResponse.json(
214+
{ error: `Event with slug '${transformedData.slug}' already exists` },
215+
{ status: 409 }
216+
)
217+
}
218+
}
219+
220+
const { data: events, error } = await serviceSupabase
160221
.from('events')
161-
.update(data)
222+
.update(transformedData)
162223
.eq('slug', slug)
163224
.select()
164-
.single()
165225

166226
if (error) {
167-
console.error('Error updating event:', error)
168227
return NextResponse.json(
169228
{ error: 'Failed to update event' },
170229
{ status: 500 }
171230
)
172231
}
173232

174-
return NextResponse.json(event)
233+
if (!events || events.length === 0) {
234+
return NextResponse.json(
235+
{ error: `Event with slug '${slug}' not found` },
236+
{ status: 404 }
237+
)
238+
}
239+
240+
return NextResponse.json(events[0])
241+
175242
} catch (error) {
176-
console.error('Error updating event:', error)
177-
return NextResponse.json(
178-
{ error: 'Internal server error' },
179-
{ status: 500 }
180-
)
243+
console.error('Error in PUT /api/admin/events:', error)
244+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
181245
}
182246
}
183247

@@ -191,7 +255,7 @@ export async function DELETE(request: NextRequest) {
191255
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
192256
}
193257

194-
// Check if user is admin (using profiles table)
258+
// Check if user is admin
195259
const { data: profile, error: profileError } = await supabase
196260
.from('profiles')
197261
.select('is_admin')
@@ -204,30 +268,38 @@ export async function DELETE(request: NextRequest) {
204268

205269
const { searchParams } = new URL(request.url)
206270
const slug = searchParams.get('slug')
207-
271+
208272
if (!slug) {
209273
return NextResponse.json({ error: 'Slug is required' }, { status: 400 })
210274
}
211275

212-
const { error } = await supabase
276+
// Use service client for database operations
277+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
278+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
279+
280+
if (!supabaseUrl || !supabaseServiceKey) {
281+
return NextResponse.json(
282+
{ error: 'Server configuration error' },
283+
{ status: 500 }
284+
)
285+
}
286+
287+
const serviceSupabase = createServiceClient(supabaseUrl, supabaseServiceKey)
288+
289+
const { error } = await serviceSupabase
213290
.from('events')
214291
.delete()
215292
.eq('slug', slug)
216293

217294
if (error) {
218295
console.error('Error deleting event:', error)
219-
return NextResponse.json(
220-
{ error: 'Failed to delete event' },
221-
{ status: 500 }
222-
)
296+
return NextResponse.json({ error: 'Failed to delete event' }, { status: 500 })
223297
}
224298

225-
return NextResponse.json({ success: true })
299+
return NextResponse.json({ message: 'Event deleted successfully' })
300+
226301
} catch (error) {
227-
console.error('Error deleting event:', error)
228-
return NextResponse.json(
229-
{ error: 'Internal server error' },
230-
{ status: 500 }
231-
)
302+
console.error('Error in DELETE /api/admin/events:', error)
303+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
232304
}
233-
}
305+
}

0 commit comments

Comments
 (0)