feat(board): Lightning-paid message board with LNbits and admin moderation

Add public /board flow: create invoice, webhook + confirm reconciliation, list
active messages, likes (Nostr), zap fallbacks. Admin table for hide/delete.

Include LNbits webhook body normalization (double-encoded JSON), POST
/api/messages/confirm/:hash, and root npm db:push script. Prisma models for
pending invoices and board messages.

Made-with: Cursor
This commit is contained in:
bbe
2026-04-03 18:37:52 +02:00
parent 7acff1ae38
commit 586b572f73
14 changed files with 1257 additions and 5 deletions

View File

@@ -183,4 +183,51 @@ export const api = {
},
reviewSubmission: (id: string, data: { status: string; reviewNote?: string }) =>
request<any>(`/submissions/${id}`, { method: "PATCH", body: JSON.stringify(data) }),
// Message board (Lightning)
getBoardConfig: () =>
request<{ priceSats: number; bbeZapPubkey: string | null; bbeZapAddress: string | null }>(
"/messages/config"
),
createBoardInvoice: (data: {
content: string;
name?: string;
pubkey?: string;
profilePic?: string;
postAsAnon?: boolean;
}) =>
request<{ payment_request: string; checking_id: string; payment_hash: string }>(
"/messages/invoice",
{ method: "POST", body: JSON.stringify(data) }
),
getBoardPaymentStatus: (paymentHash: string) =>
request<{ paid: boolean; messageCreated: boolean }>(
`/messages/payment/${encodeURIComponent(paymentHash)}/status`
),
confirmBoardPayment: (paymentHash: string) =>
request<{ ok: boolean; duplicate?: boolean; ignored?: string }>(
`/messages/confirm/${encodeURIComponent(paymentHash)}`,
{ method: "POST" }
),
getBoardMessages: () =>
request<
Array<{
id: string;
paymentHash: string;
content: string;
authorName: string;
pubkey: string | null;
profilePic: string | null;
satsPaid: number;
likeCount: number;
createdAt: string;
}>
>("/messages"),
likeBoardMessage: (id: string) =>
request<{ likeCount: number }>(`/messages/${id}/like`, { method: "POST" }),
getAdminBoardMessages: () => request<any[]>("/admin/messages"),
hideBoardMessage: (id: string) =>
request<any>(`/admin/messages/${id}/hide`, { method: "POST" }),
deleteBoardMessage: (id: string) =>
request<any>(`/admin/messages/${id}`, { method: "DELETE" }),
};