Skip to content

Commit 84e9288

Browse files
committed
fix(hackathons): Improve registration RLS handling and add service role bypass
- Use service role client to bypass RLS when updating hackathon registered count - Change `.single()` to `.maybeSingle()` for safer query handling in registration checks - Add comprehensive logging for registration/unregistration operations with count tracking - Implement fallback to direct delete if master registrations service fails - Add `updated_at` timestamp when incrementing/decrementing registered count - Fix registration status check to properly handle missing registrations - Improve error handling to not fail registration if count update fails - Add error code checking (PGRST116) to distinguish "not found" from actual errors - Update registration UI state immediately after successful registration - Ensure `setIsRegistered(false)` is called when user is not authenticated
1 parent 97302a0 commit 84e9288

File tree

2 files changed

+127
-33
lines changed

2 files changed

+127
-33
lines changed

app/api/hackathons/[id]/register/route.ts

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextRequest, NextResponse } from 'next/server'
22
import { createClient } from '@/lib/supabase/server'
3+
import { createClient as createSupabaseClient } from '@supabase/supabase-js'
34

45
export async function POST(
56
request: NextRequest,
@@ -62,7 +63,7 @@ export async function POST(
6263
.eq('user_id', user.id)
6364
.eq('activity_type', 'hackathon')
6465
.eq('activity_id', hackathon.id.toString())
65-
.single()
66+
.maybeSingle()
6667

6768
if (existingRegistration) {
6869
return NextResponse.json(
@@ -101,14 +102,36 @@ export async function POST(
101102
)
102103
}
103104

104-
// Increment registered count
105-
const { error: updateError } = await supabase
105+
// Increment registered count using service role client to bypass RLS
106+
console.log('Attempting to increment registered count:', {
107+
hackathonId: hackathon.id,
108+
currentCount: hackathon.registered,
109+
newCount: (hackathon.registered || 0) + 1
110+
})
111+
112+
// Create admin client with service role key to bypass RLS
113+
const supabaseAdmin = createSupabaseClient(
114+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
115+
process.env.SUPABASE_SERVICE_ROLE_KEY!
116+
)
117+
118+
const { data: updatedHackathon, error: updateError } = await supabaseAdmin
106119
.from('hackathons')
107-
.update({ registered: (hackathon.registered || 0) + 1 })
120+
.update({
121+
registered: (hackathon.registered || 0) + 1,
122+
updated_at: new Date().toISOString()
123+
})
108124
.eq('id', hackathon.id)
125+
.select('registered')
126+
.single()
109127

110128
if (updateError) {
111129
console.error('Error updating registered count:', updateError)
130+
// Don't fail the registration if count update fails
131+
} else if (updatedHackathon) {
132+
console.log('Successfully updated registered count to:', updatedHackathon.registered)
133+
} else {
134+
console.error('No hackathon returned from update')
112135
}
113136

114137
// Track registration in analytics
@@ -164,30 +187,86 @@ export async function DELETE(
164187
)
165188
}
166189

167-
// Delete registration from master_registrations
168-
const { error: deleteError } = await supabase
169-
.from('master_registrations')
170-
.delete()
171-
.eq('user_id', user.id)
172-
.eq('activity_type', 'hackathon')
173-
.eq('activity_id', hackathon.id.toString())
190+
console.log('Attempting to unregister:', {
191+
userId: user.id,
192+
hackathonId: hackathon.id,
193+
activityType: 'hackathon',
194+
activityId: hackathon.id.toString()
195+
})
174196

175-
if (deleteError) {
176-
console.error('Error deleting registration:', deleteError)
177-
return NextResponse.json(
178-
{ error: 'Failed to unregister from hackathon' },
179-
{ status: 500 }
197+
// Use the master registrations service which should handle RLS properly
198+
try {
199+
const { masterRegistrationsService } = await import('@/lib/services/master-registrations')
200+
await masterRegistrationsService.unregister(
201+
user.id,
202+
'hackathon',
203+
hackathon.id.toString()
180204
)
205+
console.log('Successfully unregistered using service')
206+
} catch (serviceError) {
207+
console.error('Error using service, trying direct delete:', serviceError)
208+
209+
// Fallback to direct delete if service fails
210+
const { data: deletedData, error: deleteError } = await supabase
211+
.from('master_registrations')
212+
.delete()
213+
.eq('user_id', user.id)
214+
.eq('activity_type', 'hackathon')
215+
.eq('activity_id', hackathon.id.toString())
216+
.select()
217+
218+
if (deleteError) {
219+
console.error('Error deleting registration:', deleteError)
220+
return NextResponse.json(
221+
{ error: 'Failed to unregister from hackathon' },
222+
{ status: 500 }
223+
)
224+
}
225+
226+
console.log('Direct delete result:', {
227+
deletedCount: deletedData?.length || 0,
228+
deletedData: deletedData
229+
})
230+
231+
if (!deletedData || deletedData.length === 0) {
232+
console.log('No registration found to delete')
233+
return NextResponse.json({
234+
success: true,
235+
message: 'No active registration found',
236+
})
237+
}
181238
}
182239

183-
// Decrement registered count
184-
const { error: updateError } = await supabase
240+
// Decrement registered count using service role client to bypass RLS
241+
console.log('Attempting to decrement registered count:', {
242+
hackathonId: hackathon.id,
243+
currentCount: hackathon.registered,
244+
newCount: Math.max(0, (hackathon.registered || 0) - 1)
245+
})
246+
247+
// Create admin client with service role key to bypass RLS
248+
const supabaseAdmin = createSupabaseClient(
249+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
250+
process.env.SUPABASE_SERVICE_ROLE_KEY!
251+
)
252+
253+
const { data: updatedHackathon, error: updateError } = await supabaseAdmin
185254
.from('hackathons')
186-
.update({ registered: Math.max(0, (hackathon.registered || 0) - 1) })
255+
.update({
256+
registered: Math.max(0, (hackathon.registered || 0) - 1),
257+
updated_at: new Date().toISOString()
258+
})
187259
.eq('id', hackathon.id)
260+
.select('registered')
261+
.single()
188262

189263
if (updateError) {
190264
console.error('Error updating registered count:', updateError)
265+
// Don't fail the unregistration if count update fails
266+
} else if (updatedHackathon) {
267+
console.log('Successfully updated registered count to:', updatedHackathon.registered)
268+
} else {
269+
console.log('No hackathon returned from update')
191270
}
192271

193272
return NextResponse.json({

app/hackathons/[id]/page.tsx

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export default function HackathonDetailPage() {
141141
const checkRegistrationStatus = async () => {
142142
if (!isAuthenticated || !hackathon?.id) {
143143
setCheckingRegistration(false)
144+
setIsRegistered(false)
144145
return
145146
}
146147

@@ -150,20 +151,30 @@ export default function HackathonDetailPage() {
150151

151152
if (!user) {
152153
setCheckingRegistration(false)
154+
setIsRegistered(false)
153155
return
154156
}
155157

156-
const { data } = await supabase
158+
// Check for this specific hackathon registration
159+
const { data, error } = await supabase
157160
.from('master_registrations')
158161
.select('id')
159162
.eq('user_id', user.id)
160163
.eq('activity_type', 'hackathon')
161164
.eq('activity_id', hackathon.id.toString())
162-
.single()
165+
.maybeSingle()
163166

167+
// If there's an error (other than not found), log it
168+
if (error && error.code !== 'PGRST116') {
169+
console.error('Error checking registration:', error)
170+
}
171+
172+
// Set registration status based on whether data exists
164173
setIsRegistered(!!data)
165174
} catch (error) {
166175
console.error('Error checking registration:', error)
176+
// On error, assume not registered
177+
setIsRegistered(false)
167178
} finally {
168179
setCheckingRegistration(false)
169180
}
@@ -187,19 +198,21 @@ export default function HackathonDetailPage() {
187198
},
188199
})
189200

201+
const result = await response.json()
202+
190203
if (!response.ok) {
191-
const errorData = await response.json()
192-
throw new Error(errorData.error || 'Failed to register')
204+
throw new Error(result.error || 'Failed to register')
193205
}
194206

195-
toast.success('Successfully registered for the hackathon!')
207+
// Update state immediately
196208
setIsRegistered(true)
209+
setCheckingRegistration(false)
210+
toast.success('Successfully registered for the hackathon!')
197211

198-
// Refresh hackathon data to update registered count
199-
window.location.reload()
212+
// Force a hard reload to clear any cached state
213+
window.location.href = window.location.href
200214
} catch (err) {
201215
toast.error(err instanceof Error ? err.message : 'Failed to register')
202-
} finally {
203216
setRegistering(false)
204217
}
205218
}
@@ -213,19 +226,21 @@ export default function HackathonDetailPage() {
213226
method: 'DELETE',
214227
})
215228

229+
const result = await response.json()
230+
216231
if (!response.ok) {
217-
const errorData = await response.json()
218-
throw new Error(errorData.error || 'Failed to unregister')
232+
throw new Error(result.error || 'Failed to unregister')
219233
}
220234

221-
toast.success('Successfully unregistered from the hackathon')
235+
// Update state immediately
222236
setIsRegistered(false)
237+
setCheckingRegistration(false)
238+
toast.success('Successfully unregistered from the hackathon')
223239

224-
// Refresh hackathon data to update registered count
225-
window.location.reload()
240+
// Force a hard reload to clear any cached state
241+
window.location.href = window.location.href
226242
} catch (err) {
227243
toast.error(err instanceof Error ? err.message : 'Failed to unregister')
228-
} finally {
229244
setRegistering(false)
230245
}
231246
}

0 commit comments

Comments
 (0)