11import { NextResponse } from "next/server" ;
22import supabase from "@/lib/supabase" ;
33import { generateUUID } from "@/lib/utils-uuid" ;
4- import { rateLimit } from "@/lib/rate-limiter" ;
4+ import { rateLimit , createRateLimitHeaders } from "@/lib/rate-limiter" ;
55
66// Input validation and sanitization
77const sanitizeInput = ( input : string ) : string => {
@@ -30,12 +30,23 @@ const validateUserId = (userId: string): boolean => {
3030// GET: Fetch comments
3131export async function GET ( request : Request ) {
3232 try {
33- // Apply rate limiting
33+ // Apply rate limiting with better limits
3434 const rateLimitResult = await rateLimit ( request , 'comments' ) ;
35+ const rateLimitHeaders = createRateLimitHeaders ( rateLimitResult ) ;
36+
3537 if ( ! rateLimitResult . success ) {
3638 return NextResponse . json (
37- { error : "Too many requests" } ,
38- { status : 429 }
39+ {
40+ error : "Too many requests" ,
41+ retryAfter : rateLimitResult . reset
42+ } ,
43+ {
44+ status : 429 ,
45+ headers : {
46+ ...rateLimitHeaders ,
47+ 'Retry-After' : rateLimitResult . reset ?. toString ( ) || '300'
48+ }
49+ }
3950 ) ;
4051 }
4152
@@ -50,14 +61,14 @@ export async function GET(request: Request) {
5061 if ( sanitizedProjectName && ! validateProjectName ( sanitizedProjectName ) ) {
5162 return NextResponse . json (
5263 { error : "Invalid project name" } ,
53- { status : 400 }
64+ { status : 400 , headers : rateLimitHeaders }
5465 ) ;
5566 }
5667
5768 if ( sanitizedUserId && ! validateUserId ( sanitizedUserId ) ) {
5869 return NextResponse . json (
5970 { error : "Invalid user ID" } ,
60- { status : 400 }
71+ { status : 400 , headers : rateLimitHeaders }
6172 ) ;
6273 }
6374
@@ -80,11 +91,11 @@ export async function GET(request: Request) {
8091 console . error ( "Error getting comments:" , error ) ;
8192 return NextResponse . json (
8293 { error : "Failed to get comments" } ,
83- { status : 500 }
94+ { status : 500 , headers : rateLimitHeaders }
8495 ) ;
8596 }
8697
87- return NextResponse . json ( comments || [ ] ) ;
98+ return NextResponse . json ( comments || [ ] , { headers : rateLimitHeaders } ) ;
8899 } catch ( error ) {
89100 console . error ( "Error getting comments:" , error ) ;
90101 return NextResponse . json (
@@ -99,10 +110,21 @@ export async function POST(request: Request) {
99110 try {
100111 // Apply rate limiting
101112 const rateLimitResult = await rateLimit ( request , 'comments' ) ;
113+ const rateLimitHeaders = createRateLimitHeaders ( rateLimitResult ) ;
114+
102115 if ( ! rateLimitResult . success ) {
103116 return NextResponse . json (
104- { error : "Too many comments. Please wait before posting again." } ,
105- { status : 429 }
117+ {
118+ error : "Too many comments. Please wait before posting again." ,
119+ retryAfter : rateLimitResult . reset
120+ } ,
121+ {
122+ status : 429 ,
123+ headers : {
124+ ...rateLimitHeaders ,
125+ 'Retry-After' : rateLimitResult . reset ?. toString ( ) || '300'
126+ }
127+ }
106128 ) ;
107129 }
108130
@@ -113,7 +135,7 @@ export async function POST(request: Request) {
113135 if ( ! projectName || ! userId || ! text ) {
114136 return NextResponse . json (
115137 { error : "Project name, user ID, and comment text are required" } ,
116- { status : 400 }
138+ { status : 400 , headers : rateLimitHeaders }
117139 ) ;
118140 }
119141
@@ -127,21 +149,21 @@ export async function POST(request: Request) {
127149 if ( ! validateProjectName ( sanitizedProjectName ) ) {
128150 return NextResponse . json (
129151 { error : "Invalid project name" } ,
130- { status : 400 }
152+ { status : 400 , headers : rateLimitHeaders }
131153 ) ;
132154 }
133155
134156 if ( ! validateUserId ( sanitizedUserId ) ) {
135157 return NextResponse . json (
136158 { error : "Invalid user ID" } ,
137- { status : 400 }
159+ { status : 400 , headers : rateLimitHeaders }
138160 ) ;
139161 }
140162
141163 if ( ! validateComment ( sanitizedText ) ) {
142164 return NextResponse . json (
143165 { error : "Comment must be between 1 and 2000 characters" } ,
144- { status : 400 }
166+ { status : 400 , headers : rateLimitHeaders }
145167 ) ;
146168 }
147169
@@ -155,7 +177,7 @@ export async function POST(request: Request) {
155177 if ( userError || ! user ) {
156178 return NextResponse . json (
157179 { error : "User authentication failed" } ,
158- { status : 401 }
180+ { status : 401 , headers : rateLimitHeaders }
159181 ) ;
160182 }
161183
@@ -170,23 +192,23 @@ export async function POST(request: Request) {
170192 if ( parentError || ! parentComment ) {
171193 return NextResponse . json (
172194 { error : "Parent comment not found" } ,
173- { status : 404 }
195+ { status : 404 , headers : rateLimitHeaders }
174196 ) ;
175197 }
176198 }
177199
178- // Check for recent comments to prevent spam
200+ // Check for recent comments to prevent spam (more lenient)
179201 const fiveMinutesAgo = new Date ( Date . now ( ) - 5 * 60 * 1000 ) ;
180202 const { data : recentComments , error : recentError } = await supabase
181203 . from ( 'comments' )
182204 . select ( 'id' )
183205 . eq ( 'user_id' , sanitizedUserId )
184206 . gte ( 'created_at' , fiveMinutesAgo . toISOString ( ) ) ;
185207
186- if ( recentComments && recentComments . length >= 5 ) {
208+ if ( recentComments && recentComments . length >= 10 ) { // Increased from 5 to 10
187209 return NextResponse . json (
188210 { error : "Too many recent comments. Please wait before posting again." } ,
189- { status : 429 }
211+ { status : 429 , headers : rateLimitHeaders }
190212 ) ;
191213 }
192214
@@ -202,7 +224,7 @@ export async function POST(request: Request) {
202224 if ( duplicateComment && duplicateComment . length > 0 ) {
203225 return NextResponse . json (
204226 { error : "Duplicate comment detected" } ,
205- { status : 400 }
227+ { status : 400 , headers : rateLimitHeaders }
206228 ) ;
207229 }
208230
@@ -225,7 +247,7 @@ export async function POST(request: Request) {
225247 console . error ( "Error inserting comment:" , insertError ) ;
226248 return NextResponse . json (
227249 { error : "Failed to create comment" } ,
228- { status : 500 }
250+ { status : 500 , headers : rateLimitHeaders }
229251 ) ;
230252 }
231253
@@ -248,7 +270,7 @@ export async function POST(request: Request) {
248270 }
249271
250272 // Check for badges (simplified)
251- await fetch ( "/ api/users?action=check_badges" , {
273+ await fetch ( ` ${ process . env . NEXT_PUBLIC_SITE_URL || 'http://localhost:3000' } / api/users?action=check_badges` , {
252274 method : "POST" ,
253275 headers : {
254276 "Content-Type" : "application/json" ,
@@ -262,7 +284,7 @@ export async function POST(request: Request) {
262284 // Don't fail the comment creation if points award fails
263285 }
264286
265- return NextResponse . json ( newComment ) ;
287+ return NextResponse . json ( newComment , { headers : rateLimitHeaders } ) ;
266288 } catch ( error ) {
267289 console . error ( "Error creating comment:" , error ) ;
268290 return NextResponse . json (
@@ -275,14 +297,30 @@ export async function POST(request: Request) {
275297// PUT: Update a comment (like/unlike/edit)
276298export async function PUT ( request : Request ) {
277299 try {
300+ const rateLimitResult = await rateLimit ( request , 'comments' ) ;
301+ const rateLimitHeaders = createRateLimitHeaders ( rateLimitResult ) ;
302+
303+ if ( ! rateLimitResult . success ) {
304+ return NextResponse . json (
305+ { error : "Too many requests" } ,
306+ {
307+ status : 429 ,
308+ headers : {
309+ ...rateLimitHeaders ,
310+ 'Retry-After' : rateLimitResult . reset ?. toString ( ) || '60'
311+ }
312+ }
313+ ) ;
314+ }
315+
278316 const body = await request . json ( ) ;
279317 const { commentId, userId, action, text } = body ;
280318
281319 // Validation
282320 if ( ! commentId || ! userId || ! action ) {
283321 return NextResponse . json (
284322 { error : "Comment ID, user ID, and action are required" } ,
285- { status : 400 }
323+ { status : 400 , headers : rateLimitHeaders }
286324 ) ;
287325 }
288326
@@ -295,14 +333,14 @@ export async function PUT(request: Request) {
295333 if ( ! [ "like" , "unlike" , "edit" ] . includes ( sanitizedAction ) ) {
296334 return NextResponse . json (
297335 { error : "Invalid action" } ,
298- { status : 400 }
336+ { status : 400 , headers : rateLimitHeaders }
299337 ) ;
300338 }
301339
302340 if ( sanitizedAction === "edit" && ( ! sanitizedText || ! validateComment ( sanitizedText ) ) ) {
303341 return NextResponse . json (
304342 { error : "Valid comment text is required for edit action" } ,
305- { status : 400 }
343+ { status : 400 , headers : rateLimitHeaders }
306344 ) ;
307345 }
308346
@@ -316,7 +354,7 @@ export async function PUT(request: Request) {
316354 if ( getCommentError || ! comment ) {
317355 return NextResponse . json (
318356 { error : "Comment not found" } ,
319- { status : 404 }
357+ { status : 404 , headers : rateLimitHeaders }
320358 ) ;
321359 }
322360
@@ -336,7 +374,7 @@ export async function PUT(request: Request) {
336374 if ( comment . user_id !== sanitizedUserId ) {
337375 return NextResponse . json (
338376 { error : "Not authorized to edit this comment" } ,
339- { status : 403 }
377+ { status : 403 , headers : rateLimitHeaders }
340378 ) ;
341379 }
342380
@@ -358,11 +396,11 @@ export async function PUT(request: Request) {
358396 console . error ( "Error updating comment:" , updateError ) ;
359397 return NextResponse . json (
360398 { error : "Failed to update comment" } ,
361- { status : 500 }
399+ { status : 500 , headers : rateLimitHeaders }
362400 ) ;
363401 }
364402
365- return NextResponse . json ( updatedComment ) ;
403+ return NextResponse . json ( updatedComment , { headers : rateLimitHeaders } ) ;
366404 } catch ( error ) {
367405 console . error ( "Error updating comment:" , error ) ;
368406 return NextResponse . json (
@@ -375,14 +413,30 @@ export async function PUT(request: Request) {
375413// DELETE: Delete a comment
376414export async function DELETE ( request : Request ) {
377415 try {
416+ const rateLimitResult = await rateLimit ( request , 'comments' ) ;
417+ const rateLimitHeaders = createRateLimitHeaders ( rateLimitResult ) ;
418+
419+ if ( ! rateLimitResult . success ) {
420+ return NextResponse . json (
421+ { error : "Too many requests" } ,
422+ {
423+ status : 429 ,
424+ headers : {
425+ ...rateLimitHeaders ,
426+ 'Retry-After' : rateLimitResult . reset ?. toString ( ) || '60'
427+ }
428+ }
429+ ) ;
430+ }
431+
378432 const { searchParams } = new URL ( request . url ) ;
379433 const commentId = searchParams . get ( "id" ) ;
380434 const userId = searchParams . get ( "userId" ) ;
381435
382436 if ( ! commentId || ! userId ) {
383437 return NextResponse . json (
384438 { error : "Comment ID and User ID are required" } ,
385- { status : 400 }
439+ { status : 400 , headers : rateLimitHeaders }
386440 ) ;
387441 }
388442
@@ -400,14 +454,14 @@ export async function DELETE(request: Request) {
400454 if ( getCommentError || ! comment ) {
401455 return NextResponse . json (
402456 { error : "Comment not found" } ,
403- { status : 404 }
457+ { status : 404 , headers : rateLimitHeaders }
404458 ) ;
405459 }
406460
407461 if ( comment . user_id !== sanitizedUserId ) {
408462 return NextResponse . json (
409463 { error : "Not authorized to delete this comment" } ,
410- { status : 403 }
464+ { status : 403 , headers : rateLimitHeaders }
411465 ) ;
412466 }
413467
@@ -421,7 +475,7 @@ export async function DELETE(request: Request) {
421475 console . error ( "Error deleting comment:" , deleteError ) ;
422476 return NextResponse . json (
423477 { error : "Failed to delete comment" } ,
424- { status : 500 }
478+ { status : 500 , headers : rateLimitHeaders }
425479 ) ;
426480 }
427481
@@ -436,7 +490,7 @@ export async function DELETE(request: Request) {
436490 // Continue execution
437491 }
438492
439- return NextResponse . json ( { success : true } ) ;
493+ return NextResponse . json ( { success : true } , { headers : rateLimitHeaders } ) ;
440494 } catch ( error ) {
441495 console . error ( "Error deleting comment:" , error ) ;
442496 return NextResponse . json (
0 commit comments