-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathroute.ts
More file actions
165 lines (135 loc) · 3.76 KB
/
route.ts
File metadata and controls
165 lines (135 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@supabase/supabase-js'
import { z } from 'zod'
// Initialize Supabase client
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
// Rate limiting store (in production, use Redis or similar)
const rateLimitStore = new Map<string, { count: number; resetTime: number }>()
// Rate limit configuration
const RATE_LIMIT = {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
}
const waitlistSchema = z.object({
email: z.string().email('Invalid email address'),
})
function getClientIP(request: NextRequest): string {
const forwarded = request.headers.get('x-forwarded-for')
const realIP = request.headers.get('x-real-ip')
if (forwarded) {
return forwarded.split(',')[0].trim()
}
if (realIP) {
return realIP.trim()
}
return 'unknown'
}
function checkRateLimit(ip: string): boolean {
const now = Date.now()
const entry = rateLimitStore.get(ip)
if (!entry) {
rateLimitStore.set(ip, { count: 1, resetTime: now + RATE_LIMIT.windowMs })
return true
}
if (now > entry.resetTime) {
rateLimitStore.set(ip, { count: 1, resetTime: now + RATE_LIMIT.windowMs })
return true
}
if (entry.count >= RATE_LIMIT.max) {
return false
}
entry.count += 1
return true
}
export async function POST(request: NextRequest) {
try {
const ip = getClientIP(request)
// Check rate limit
if (!checkRateLimit(ip)) {
return NextResponse.json(
{ error: 'Too many requests. Please try again later.' },
{ status: 429 }
)
}
const body = await request.json()
const validation = waitlistSchema.safeParse(body)
if (!validation.success) {
return NextResponse.json(
{ error: validation.error.errors[0].message },
{ status: 400 }
)
}
const { email } = validation.data
// Check if email already exists
const { data: existingEmail } = await supabase
.from('waitlist')
.select('email')
.eq('email', email)
.single()
if (existingEmail) {
return NextResponse.json(
{ error: 'This email is already on the waitlist.' },
{ status: 409 }
)
}
// Add to waitlist
const { data, error } = await supabase
.from('waitlist')
.insert([{ email }])
.select()
.single()
if (error) {
console.error('Error adding to waitlist:', error)
return NextResponse.json(
{ error: 'Failed to add to waitlist. Please try again.' },
{ status: 500 }
)
}
return NextResponse.json(
{ message: 'Successfully added to waitlist', data },
{ status: 201 }
)
} catch (error) {
console.error('Waitlist API error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const email = searchParams.get('email')
if (!email) {
return NextResponse.json(
{ error: 'Email parameter is required' },
{ status: 400 }
)
}
if (!z.string().email().safeParse(email).success) {
return NextResponse.json(
{ error: 'Invalid email format' },
{ status: 400 }
)
}
const { data } = await supabase
.from('waitlist')
.select('email')
.eq('email', email)
.single()
return NextResponse.json(
{ exists: !!data },
{ status: 200 }
)
} catch (error) {
console.error('Error checking waitlist:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}