Skip to content
Open
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
7 changes: 6 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ CRON_SECRET=
# mainnet or sepolia
# Note: Not everything is supported on sepolia
# Default: mainnet
NEXT_PUBLIC_NETWORK=mainnet
NEXT_PUBLIC_NETWORK=mainnet
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
# Rate limiting configuration
RATE_LIMIT_REQUESTS=50
RATE_LIMIT_WINDOW="60 s"
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"format:check": "prettier --check \"**/*.{ts,tsx,json}\"",
"format:fix": "prettier --write \"**/*.{ts,tsx,json}\"",
"prepare": "husky",
"postinstall": "prisma generate"
"postinstall": "prisma generate",
"test:ratelimit": "ts-node --project tsconfig.server.json src/scripts/test-rate-limit.ts"
},
"files": [
"CHANGELOG.md",
Expand All @@ -34,6 +35,8 @@
"@tanstack/query-core": "5.28.0",
"@types/mixpanel-browser": "2.49.0",
"@types/mustache": "4.2.5",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.34.3",
"@vercel/analytics": "1.2.2",
"@vercel/speed-insights": "1.0.12",
"axios": "1.6.7",
Expand Down Expand Up @@ -86,6 +89,7 @@
"prettier": "3.3.3",
"prisma": "5.18.0",
"tailwindcss": "3.3.0",
"ts-node": "^10.9.2",
"typescript": "5"
},
"engines": {
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/price/[name]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';

export const revalidate = 300; // 5 mins
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

// only meant for backend calls
async function initRedis() {
Expand Down
1 change: 1 addition & 0 deletions src/app/api/raffle/luckyWinner/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Raffle } from '@prisma/client';
import { NextResponse } from 'next/server';

export const dynamic = 'force-dynamic'; // static by default, unless reading the request
export const runtime = 'nodejs';

export async function GET(request: Request) {
const authHeader = request.headers.get('authorization');
Expand Down
3 changes: 3 additions & 0 deletions src/app/api/raffle/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { db } from '@/db';
import { getStrategies } from '@/store/strategies.atoms';
import { standariseAddress } from '@/utils';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function POST(req: Request) {
const { address, type } = await req.json();

Expand Down
3 changes: 3 additions & 0 deletions src/app/api/referral/createUser/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { NextResponse } from 'next/server';
import { db } from '@/db';
import { standariseAddress } from '@/utils';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

function isSixDigitAlphanumeric(str: string) {
const regex = /^[a-zA-Z0-9]{6}$/;
return regex.test(str);
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/stats/[address]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { standariseAddress } from '@/utils';
import { NextResponse } from 'next/server';

export const revalidate = 0;
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function GET(_req: Request, context: any) {
const { params } = context;
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/stats/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { getStrategies } from '@/store/strategies.atoms';
import { NextResponse } from 'next/server';

export const revalidate = 1800;
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function GET(_req: Request) {
const strategies = getStrategies();
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/strategies/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { IStrategy, NFTInfo, TokenInfo } from '@/strategies/IStrategy';
import { STRKFarmStrategyAPIResult } from '@/store/strkfarm.atoms';

export const revalidate = 3600; // 1 hr
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

const allPoolsAtom = atom<PoolInfo[]>((get) => {
const pools: PoolInfo[] = [];
Expand Down
2 changes: 2 additions & 0 deletions src/app/api/tnc/getUser/[address]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { NextResponse } from 'next/server';

import { db } from '@/db';
import { standariseAddress } from '@/utils';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function GET(req: Request, context: any) {
const { params } = context;
Expand Down
3 changes: 3 additions & 0 deletions src/app/api/tnc/signUser/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { toBigInt } from 'ethers';
import Mixpanel from 'mixpanel';
const mixpanel = Mixpanel.init('118f29da6a372f0ccb6f541079cad56b');

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function POST(req: Request) {
const { address, signature } = await req.json();

Expand Down
2 changes: 2 additions & 0 deletions src/app/api/users/ognft/[address]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { standariseAddress } from '../../../../../utils';
import OGNFTUsersJson from '../../../../../../public/og_nft_eligible_users.json';

export const revalidate = 3600;
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function GET(req: Request, context: any) {
try {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Redis } from '@upstash/redis';

const redis = Redis.fromEnv();

export default redis;
56 changes: 56 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { NextRequest, NextResponse } from 'next/server';
import { Ratelimit } from '@upstash/ratelimit';
import redis from './lib/redis';

export const config = {
matcher: ['/api/:path*'],
};

const RATE_LIMIT_REQUESTS = parseInt(
process.env.RATE_LIMIT_REQUESTS || '20',
10,
);
const RATE_LIMIT_WINDOW = process.env.RATE_LIMIT_WINDOW || '10 s';

const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(
RATE_LIMIT_REQUESTS,
RATE_LIMIT_WINDOW as `${number} s`,
),
analytics: true,
prefix: '@upstash/ratelimit',
});

export async function middleware(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') || '127.0.0.1';
const identifier = ip;

let success, limit, remaining, reset;
try {
const result = await ratelimit.limit(identifier);
success = result.success;
limit = result.limit;
remaining = result.remaining;
reset = result.reset;
} catch (error) {
console.log(error);
return NextResponse.json(
{ message: 'Internal Server Error' },
{ status: 500 },
);
}

const response = success
? NextResponse.next()
: NextResponse.json(
{ message: 'Rate limit exceeded', limit, remaining, reset },
{ status: 429 },
);

response.headers.set('X-RateLimit-Limit', limit.toString());
response.headers.set('X-RateLimit-Remaining', remaining.toString());
response.headers.set('X-RateLimit-Reset', reset.toString());

return response;
}
30 changes: 30 additions & 0 deletions src/scripts/test-rate-limit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
async function testRateLimit(url: string, attempts: number) {
console.log(`Testing rate limit for ${url}`);
for (let i = 0; i < attempts; i++) {
const response = await fetch(url);
const remaining = response.headers.get('X-RateLimit-Remaining');
console.log(
`Attempt ${i + 1}: Status ${response.status}, Remaining: ${remaining}`,
);
await new Promise((resolve) => setTimeout(resolve, 100)); // Wait 100ms between requests
}
console.log('\n');
}

async function runTests() {
const baseUrl = 'http://localhost:3000/api';
await testRateLimit(`${baseUrl}/price`, 25);
await testRateLimit(`${baseUrl}/raffle`, 25);
await testRateLimit(`${baseUrl}/raffle/luckyWinner`, 25);
await testRateLimit(`${baseUrl}/referral/createUser`, 25);
await testRateLimit(`${baseUrl}/stats`, 25);
await testRateLimit(`${baseUrl}/stats/[address]`, 25);
await testRateLimit(`${baseUrl}/strategies`, 25);
await testRateLimit(`${baseUrl}/tnc/getUser`, 25);
await testRateLimit(`${baseUrl}/tnc/getUser/[address]`, 25);
await testRateLimit(`${baseUrl}/tnc/signUser`, 25);
await testRateLimit(`${baseUrl}/users/ognft`, 25);
await testRateLimit(`${baseUrl}/users/ognft/[address]`, 25);
}

runTests().catch(console.error);
5 changes: 5 additions & 0 deletions src/types/redis.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module 'lib/redis' {
import { Redis } from '@upstash/redis';
const redis: Redis;
export default redis;
}
11 changes: 11 additions & 0 deletions tsconfig.server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["scripts/**/*.ts"]
}
Loading