Initial Mebbling hub implementation

This commit is contained in:
2026-07-19 00:29:30 +08:00
commit e6ebdb0576
41 changed files with 2571 additions and 0 deletions
@@ -0,0 +1,18 @@
import { NextResponse } from "next/server";
import { db } from "@/lib/db";
import { withinRateLimit } from "@/lib/rate-limit";
import { webhookSecretMatches } from "@/lib/webhook";
export async function POST(request: Request, { params }: { params: Promise<{ sourceId: string; secret: string }> }) {
const { sourceId, secret } = await params;
const id = Number(sourceId);
const source = db.prepare("SELECT id, webhook_secret_hash FROM sources WHERE id=?").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 });
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);
db.prepare("INSERT INTO sync_jobs(source_id,kind,payload_json) VALUES(?, 'pull', ?)").run(id, JSON.stringify(payload));
return NextResponse.json({ ok: true });
}