deafmissoula-website/server/src/routes/minutes.ts
friday-bot a164c1f956 Auth, drag-and-drop, image previews, rate limiting
- 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)
2026-05-27 16:49:10 -06:00

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;