Skip to content

Commit 91a5f3b

Browse files
authored
Merge pull request #486 from YOGESH-08/staging
feat: rate-limiting endpoints and migration to Upstash
2 parents fd7f3e9 + 623f1c6 commit 91a5f3b

File tree

7 files changed

+70
-43
lines changed

7 files changed

+70
-43
lines changed

.env.example

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ GOOGLE_CLOUD_BUCKET="" # Your Google Cloud Storage Bucket n
2323
GOOGLE_APPLICATION_CREDENTIALS_JSON="" # The content of the JSON file you download when creating a service account key
2424

2525
# Vercel KV
26-
KV_URL="" # The URL of your Vercel KV instance
27-
KV_REST_API_URL="" # The REST API URL of your Vercel KV instance
28-
KV_REST_API_TOKEN="" # The REST API token for your Vercel KV instance
29-
KV_REST_API_READ_ONLY_TOKEN="" # The read-only REST API token for your Vercel KV instance
26+
# KV_URL="" # The URL of your Vercel KV instance
27+
# KV_REST_API_URL="" # The REST API URL of your Vercel KV instance
28+
# KV_REST_API_TOKEN="" # The REST API token for your Vercel KV instance
29+
# KV_REST_API_READ_ONLY_TOKEN="" # The read-only REST API token for your Vercel KV instance
3030

3131
# Upstash_Redis
3232
UPSTASH_REDIS_REST_URL="" # REST URL of your Upstash Redis database

next.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ await import("./src/env.js");
88
const config = {
99
swcMinify: false,
1010
images: {
11-
domains: ["storage.googleapis.com"],
11+
remotePatterns: [
12+
{
13+
protocol: "https",
14+
hostname: "storage.googleapis.com",
15+
},
16+
],
1217
},
1318
async headers() {
1419
return [

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"@ungap/with-resolvers": "^0.1.0",
3232
"@upstash/ratelimit": "^2.0.5",
3333
"@upstash/redis": "^1.33.0",
34-
"@vercel/kv": "^3.0.0",
3534
"axios": "^1.8.4",
3635
"canvas": "^3.2.0",
3736
"class-variance-authority": "^0.7.1",

pnpm-lock.yaml

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Card.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
43
import { type IPaper } from "@/interface";
54
import Image from "next/image";
65
import { Eye, Download, Check } from "lucide-react";
@@ -24,22 +23,12 @@ interface CardProps {
2423
}
2524

2625
const Card = ({ paper, onSelect, isSelected }: CardProps) => {
27-
const [checked, setChecked] = useState<boolean>(isSelected);
28-
29-
useEffect(() => {
30-
setChecked(isSelected);
31-
}, [isSelected]);
32-
3326
const handleDownload = async (paper: IPaper) => {
3427
await downloadFile(getSecureUrl(paper.file_url), generateFileName(paper));
3528
};
3629

3730
const handleCheckboxChange = () => {
38-
setChecked((prev) => {
39-
const newChecked = !prev;
40-
onSelect(paper, newChecked);
41-
return newChecked;
42-
});
31+
onSelect(paper, !isSelected);
4332
};
4433

4534
const paperLink = `/paper/${paper._id}`;
@@ -48,7 +37,7 @@ const Card = ({ paper, onSelect, isSelected }: CardProps) => {
4837
<div
4938
className={cn(
5039
"overflow-hidden rounded-sm border-2 border-[#734DFF] bg-[#FFFFFF] font-play transition-all duration-150 hover:bg-[#EFEAFF] dark:border-[#36266D] dark:bg-[#171720] hover:dark:bg-[#262635]",
51-
checked && "bg-white",
40+
isSelected && "bg-white",
5241
)}
5342
>
5443
<Link href={paperLink} target="_blank" rel="noopener noreferrer">
@@ -100,7 +89,7 @@ const Card = ({ paper, onSelect, isSelected }: CardProps) => {
10089
<div className="flex items-center justify-between gap-2 px-4 pb-4 font-play">
10190
<div className="flex items-center gap-2">
10291
<input
103-
checked={checked}
92+
checked={isSelected}
10493
onChange={handleCheckboxChange}
10594
className="h-5 w-5 accent-[#7480FF]"
10695
type="checkbox"

src/lib/utils/ratelimit.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Ratelimit } from "@upstash/ratelimit";
2+
import { redis } from "./redis";
3+
4+
// Rate limiter for AI Upload (5 requests per 15 minutes)
5+
export const aiUploadRatelimit = new Ratelimit({
6+
redis: redis,
7+
limiter: Ratelimit.slidingWindow(5, "900 s"),
8+
});
9+
10+
// Rate limiter for Paper Requests (5 requests per 15 minutes)
11+
export const paperRequestRatelimit = new Ratelimit({
12+
redis: redis,
13+
limiter: Ratelimit.slidingWindow(5, "900 s"),
14+
});
15+
16+
// Rate limiter for Subscriptions (3 requests per hour)
17+
export const subscribeRatelimit = new Ratelimit({
18+
redis: redis,
19+
limiter: Ratelimit.slidingWindow(3, "1 h"),
20+
});

src/middleware.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,48 @@
11
import { type NextRequest, NextResponse } from "next/server";
2-
import { Ratelimit } from "@upstash/ratelimit";
3-
import { kv } from "@vercel/kv";
4-
5-
const ratelimit = new Ratelimit({
6-
redis: kv,
7-
limiter: Ratelimit.slidingWindow(100, "900 s"),
8-
});
2+
import {
3+
aiUploadRatelimit,
4+
paperRequestRatelimit,
5+
subscribeRatelimit,
6+
} from "./lib/utils/ratelimit";
97

108
export const config = {
11-
matcher: "/api/upload",
9+
matcher: ["/api/upload", "/api/request", "/api/subscribe"],
1210
};
1311

1412
export default async function middleware(request: NextRequest) {
1513
const ip = request.ip ?? "127.0.0.1";
16-
const { success } = await ratelimit.limit(ip);
17-
return success
18-
? NextResponse.next()
19-
: NextResponse.json(
14+
const { pathname } = request.nextUrl;
15+
16+
if (pathname === "/api/upload") {
17+
const { success } = await aiUploadRatelimit.limit(ip);
18+
if (!success) {
19+
return NextResponse.json(
2020
{ message: "You can upload a maximum of 5 papers every 15 minutes" },
2121
{ status: 429 },
2222
);
23+
}
24+
}
25+
26+
if (pathname === "/api/request") {
27+
const { success } = await paperRequestRatelimit.limit(ip);
28+
if (!success) {
29+
return NextResponse.json(
30+
{ message: "You can submit a maximum of 5 requests every 15 minutes" },
31+
{ status: 429 },
32+
);
33+
}
34+
}
35+
36+
if (pathname === "/api/subscribe") {
37+
const { success } = await subscribeRatelimit.limit(ip);
38+
if (!success) {
39+
return NextResponse.json(
40+
{ message: "Maximum of 3 newsletter subscriptions per hour" },
41+
{ status: 429 },
42+
);
43+
}
44+
}
45+
46+
return NextResponse.next();
2347
}
48+

0 commit comments

Comments
 (0)