Skip to content

Commit 638191a

Browse files
committed
sachen
1 parent f73e3be commit 638191a

File tree

4 files changed

+209
-89
lines changed

4 files changed

+209
-89
lines changed

app/api/comments/route.tsx

Lines changed: 89 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextResponse } from "next/server";
22
import supabase from "@/lib/supabase";
33
import { 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
77
const sanitizeInput = (input: string): string => {
@@ -30,12 +30,23 @@ const validateUserId = (userId: string): boolean => {
3030
// GET: Fetch comments
3131
export 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)
276298
export 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
376414
export 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

Comments
 (0)