Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/2026-06-12.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/api/admin/attendees/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function GET(req: NextRequest) {
}

const { searchParams } = new URL(req.url)
const search = searchParams.get('search') || ''
const search = (searchParams.get('search') || '').slice(0, 100)
const crewId = searchParams.get('crew_id')
const checkedIn = searchParams.get('checked_in')
const page = parseInt(searchParams.get('page') || '1', 10)
Expand Down
40 changes: 39 additions & 1 deletion src/app/api/redeem/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ const ory = new FrontendApi(
new Configuration({ basePath: process.env.NEXT_PUBLIC_ORY_URL })
)

// In-memory rate limiter: max 5 redemption attempts per identity per hour
const RATE_LIMIT = 5
const RATE_WINDOW_MS = 60 * 60 * 1000
const redeemAttempts = new Map<string, { count: number; resetAt: number }>()

function checkRateLimit(identityId: string): boolean {
const now = Date.now()
const record = redeemAttempts.get(identityId)
if (!record || now > record.resetAt) {
redeemAttempts.set(identityId, { count: 1, resetAt: now + RATE_WINDOW_MS })
return true
}
if (record.count >= RATE_LIMIT) return false
record.count++
return true
}

// Safe error codes the RPC may return that can be forwarded to the client
const SAFE_RPC_ERRORS = new Set([
'ticket_not_found',
'ticket_already_redeemed',
'already_registered',
'crew_not_found',
'crew_inactive',
])

function safeRedeemError(raw: string): string {
return SAFE_RPC_ERRORS.has(raw) ? raw : 'redemption_failed'
}

export async function POST(req: NextRequest) {
const cookie = req.headers.get('cookie') || ''

Expand Down Expand Up @@ -46,6 +76,14 @@ export async function POST(req: NextRequest) {
const identityId = session.identity?.id
const email = session.identity?.traits?.email

if (!identityId) {
return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
}

if (!checkRateLimit(identityId)) {
return NextResponse.json({ error: 'too_many_attempts' }, { status: 429 })
}

// Call Supabase function
const { data, error } = await supabase.rpc('redeem_ticket', {
p_uuid: uuid,
Expand All @@ -63,7 +101,7 @@ export async function POST(req: NextRequest) {

// data is the JSONB result from the function
if (data.error) {
return NextResponse.json({ error: data.error }, { status: 400 })
return NextResponse.json({ error: safeRedeemError(data.error) }, { status: 400 })
}

// Resolve crew name for the confirmation email
Expand Down
54 changes: 54 additions & 0 deletions src/content/events/2026-06-12.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
date: 2026-06-12
cover: '/2026-06-12.jpeg'
mdxMeta:
title: 'Eisbach Callin Sommerfest'
description: 'Sunny Red, Feierwerk, Hansastraße 39, Munich'
---

Eisbach Callin' is celebrating summer in the city with an Open Decks party for new faces in Munich's bass music scene!

## PLAY AT EISBACH CALLIN'

If you love spinning Drum & Bass, Dubstep, or similar bass music, get in touch with us via [Instagram](https://www.instagram.com/eisbachcallin/)/[Facebook](https://www.facebook.com/eisbachcallin/) (@eisbachcallin) or send an email to mail@eisbachcallin.com

Let us know what type of music you play, what equipment you need, and preferably include a link to one of your sets.

We're looking forward to welcoming many exciting newcomers! And remember, don't act like a clown, dance like one!

## Time

<RenderDoors timeDoors='22' price='10' />

## Location

<RenderLocation
venueName='Sunny Red'
address='Feierwerk Hansastr. 39'
city='Munich'
mapUrl='https://goo.gl/maps/mA8iv2XEHf7XfRLv7'
/>

## Lineup

<RenderArtists
artists={[
{
name: 'Open Decks Lineup TBA',
},
{ name: 'Noise Colors' },
{ name: 'Miso' },
{ name: 'Dubz.you' },

]}
/>

### VJ

<RenderArtists artists={[{ name: 'Visionaer' }]} />

### MC

<RenderArtists artists={[{ name: 'MC Treasure Irie' }]} />

## Support underground music