feat: complete v0.5 operations foundation
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "@/lib/db";
|
||||
import { withinRateLimit } from "@/lib/rate-limit";
|
||||
import { clientIp } from "@/lib/security";
|
||||
import { logEvent } from "@/lib/observability";
|
||||
import { webhookSecretMatches } from "@/lib/webhook";
|
||||
import { queuePull } from "@/lib/sync";
|
||||
|
||||
@@ -9,11 +11,11 @@ export async function POST(request: Request, { params }: { params: Promise<{ sou
|
||||
const id = Number(sourceId);
|
||||
const source = db.prepare("SELECT id, webhook_secret_hash FROM sources WHERE id=? AND is_enabled=1").get(id) as { id: number; webhook_secret_hash: string | null } | undefined;
|
||||
if (!source || !webhookSecretMatches(secret, source.webhook_secret_hash)) return NextResponse.json({ error: "Not found" }, { status: 404 });
|
||||
const forwarded = request.headers.get("x-forwarded-for")?.split(",")[0].trim() || "unknown";
|
||||
if (!withinRateLimit(`webhook:${id}:${forwarded}`)) return NextResponse.json({ error: "Too many requests" }, { status: 429 });
|
||||
if (!withinRateLimit(`webhook:${id}:${clientIp(request)}`, 30, 60_000)) return NextResponse.json({ error: "Too many requests" }, { status: 429 });
|
||||
let payload: unknown = {};
|
||||
try { payload = await request.json(); } catch { /* Memos payload is optional; a pull reconciles source state. */ }
|
||||
db.prepare("UPDATE sources SET last_webhook_at=CURRENT_TIMESTAMP WHERE id=?").run(id);
|
||||
const queued = queuePull(id, "webhook", payload);
|
||||
logEvent("info", "webhook_received", { sourceId: id, queued });
|
||||
return NextResponse.json({ ok: true, queued });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user