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:
@@ -0,0 +1,68 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { prisma } from '../db/prisma';
|
||||
import { requireAuth, requireRole } from '../middleware/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use(requireAuth, requireRole(['ADMIN', 'MODERATOR']));
|
||||
|
||||
router.get('/', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const messages = await prisma.boardMessage.findMany({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
res.json(messages);
|
||||
} catch (err) {
|
||||
console.error('Admin list board messages error:', err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/** Toggle active <-> hidden */
|
||||
router.post('/:id/hide', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const rawId = req.params.id;
|
||||
const id = Array.isArray(rawId) ? rawId[0] : rawId;
|
||||
const msg = await prisma.boardMessage.findUnique({ where: { id } });
|
||||
if (!msg) {
|
||||
res.status(404).json({ error: 'Not found' });
|
||||
return;
|
||||
}
|
||||
if (msg.status === 'deleted') {
|
||||
res.status(400).json({ error: 'Message is deleted' });
|
||||
return;
|
||||
}
|
||||
const next = msg.status === 'hidden' ? 'active' : 'hidden';
|
||||
const updated = await prisma.boardMessage.update({
|
||||
where: { id: msg.id },
|
||||
data: { status: next },
|
||||
});
|
||||
res.json(updated);
|
||||
} catch (err) {
|
||||
console.error('Admin hide board message error:', err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
/** Soft-delete */
|
||||
router.delete('/:id', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const rawId = req.params.id;
|
||||
const id = Array.isArray(rawId) ? rawId[0] : rawId;
|
||||
const msg = await prisma.boardMessage.findUnique({ where: { id } });
|
||||
if (!msg) {
|
||||
res.status(404).json({ error: 'Not found' });
|
||||
return;
|
||||
}
|
||||
const updated = await prisma.boardMessage.update({
|
||||
where: { id: msg.id },
|
||||
data: { status: 'deleted' },
|
||||
});
|
||||
res.json(updated);
|
||||
} catch (err) {
|
||||
console.error('Admin delete board message error:', err);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user