- Login by email; board members are dashboard users (full access) - Forced password change on first login; forgot/reset password via email - Auto-send welcome email with set-password link when board member created - 5 attempts / 5 min rate limit on login endpoint - Board + Minutes tabs: drag-and-drop card reordering, persisted to DB - PATCH /api/board/reorder and /api/minutes/reorder endpoints - New minutes auto-insert at top (order 0) - ImagePicker: inline preview thumbnail before upload (board, gallery, sponsors) - Fix Minutes public sort: proper date parsing instead of broken string compare - Remove server/dist/ from git tracking (.gitignore)
43 lines
1.4 KiB
TypeScript
43 lines
1.4 KiB
TypeScript
import { Router } from 'express';
|
|
import { z } from 'zod';
|
|
import { Minute } from '../models/Minute';
|
|
import { requireAuth } from '../middleware/auth';
|
|
import { uploadMinutes } from '../middleware/upload';
|
|
|
|
const router = Router();
|
|
|
|
router.get('/', async (_req, res) => {
|
|
const minutes = await Minute.find().sort({ order: 1, createdAt: -1 });
|
|
res.json(minutes);
|
|
});
|
|
|
|
router.post('/', requireAuth, uploadMinutes.single('file'), async (req, res) => {
|
|
const schema = z.object({
|
|
date: z.string().min(1),
|
|
meetingType: z.string().min(1),
|
|
location: z.string().min(1),
|
|
});
|
|
const data = schema.parse(req.body);
|
|
const fileUrl = req.file
|
|
? `/uploads/minutes/${req.file.filename}`
|
|
: z.string().min(1).parse(req.body.fileUrl);
|
|
|
|
// Insert at top (order 0), shift everything else down
|
|
await Minute.updateMany({}, { $inc: { order: 1 } });
|
|
const minute = await Minute.create({ ...data, fileUrl, order: 0 });
|
|
res.status(201).json(minute);
|
|
});
|
|
|
|
// PATCH /api/minutes/reorder — body: { ids: [id1, id2, ...] } in desired order
|
|
router.patch('/reorder', requireAuth, async (req, res) => {
|
|
const { ids } = z.object({ ids: z.array(z.string()) }).parse(req.body);
|
|
await Promise.all(ids.map((id, index) => Minute.findByIdAndUpdate(id, { order: index })));
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
router.delete('/:id', requireAuth, async (req, res) => {
|
|
await Minute.findByIdAndDelete(req.params.id);
|
|
res.status(204).send();
|
|
});
|
|
|
|
export default router;
|