Template
Modelo do encurtador em Next.js
Gerado a partir de deploy-templates/nextjs (dado.cloud 4c2a882).
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { criarLink, enderecoBase, ErroDeValidacao, listarLinks, paraJson } from '@/lib/links';
|
||||
|
||||
// CORS aberto só na API: qualquer site (ou outro app seu) pode chamar /api/*.
|
||||
const CORS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
};
|
||||
|
||||
// Freio simples contra abuso: no máximo 30 links novos a cada 10 minutos por IP.
|
||||
const LIMITE_POR_IP = 30;
|
||||
const JANELA_MS = 10 * 60 * 1000;
|
||||
const tentativas = new Map<string, { total: number; expiraEm: number }>();
|
||||
|
||||
function passouDoLimite(ip: string): boolean {
|
||||
const agora = Date.now();
|
||||
if (tentativas.size > 10_000) tentativas.clear();
|
||||
const atual = tentativas.get(ip);
|
||||
if (!atual || atual.expiraEm <= agora) {
|
||||
tentativas.set(ip, { total: 1, expiraEm: agora + JANELA_MS });
|
||||
return false;
|
||||
}
|
||||
atual.total += 1;
|
||||
return atual.total > LIMITE_POR_IP;
|
||||
}
|
||||
|
||||
const erro = (status: number, mensagem: string) => Response.json({ erro: mensagem }, { status, headers: CORS });
|
||||
|
||||
export function OPTIONS() {
|
||||
return new Response(null, { status: 204, headers: CORS });
|
||||
}
|
||||
|
||||
export function GET(request: Request) {
|
||||
const base = enderecoBase(request.headers);
|
||||
return Response.json(
|
||||
{ links: listarLinks().map((link) => paraJson(link, base)) },
|
||||
{ headers: { ...CORS, 'Cache-Control': 'no-store' } },
|
||||
);
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const ip = request.headers.get('cf-connecting-ip') ?? request.headers.get('x-forwarded-for')?.split(',')[0].trim() ?? '?';
|
||||
if (passouDoLimite(ip)) return erro(429, 'Muitos links em pouco tempo. Espere alguns minutos e tente de novo.');
|
||||
|
||||
let corpo: { url?: unknown } | null;
|
||||
try {
|
||||
corpo = await request.json();
|
||||
} catch {
|
||||
return erro(400, 'Corpo inválido. Envie JSON no formato {"url": "https://..."}.');
|
||||
}
|
||||
try {
|
||||
const link = criarLink(corpo?.url);
|
||||
return Response.json(paraJson(link, enderecoBase(request.headers)), { status: 201, headers: CORS });
|
||||
} catch (e) {
|
||||
if (e instanceof ErroDeValidacao) return erro(400, e.message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user