35ed5223a0
- pnpm monorepo (apps/client + apps/server) - Server: Express + node:sqlite with numbered migration runner, REST API for all 9 features (members, events, chores, shopping, meals, messages, countdowns, photos, settings) - Client: React 18 + Vite + TypeScript + Tailwind + Framer Motion + Zustand - Theme system: dark/light + 5 accent colors, CSS custom properties, anti-FOUC script, ThemeToggle on every surface - AppShell: collapsible sidebar, animated route transitions, mobile drawer - Phase 2 features: Calendar (custom month grid, event chips, add/edit modal), Chores (card grid, complete/reset, member filter, streaks), Shopping (multi-list tabs, animated check-off, quick-add bar, member assign) - Family member CRUD with avatar, color picker - Settings page: theme/accent, photo folder, slideshow, weather, date/time - Docker: multi-stage Dockerfile, docker-compose.yml, entrypoint with PUID/PGID - Unraid: CA XML template, CLI install script, UNRAID.md guide - .gitignore covering node_modules, dist, db files, secrets, build artifacts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
68 lines
3.4 KiB
TypeScript
68 lines
3.4 KiB
TypeScript
import { Router } from 'express';
|
|
import db from '../db/db';
|
|
|
|
const router = Router();
|
|
|
|
// ── Lists ──────────────────────────────────────────────────────────────────
|
|
router.get('/lists', (_req, res) => {
|
|
res.json(db.prepare('SELECT * FROM shopping_lists ORDER BY name ASC').all());
|
|
});
|
|
|
|
router.post('/lists', (req, res) => {
|
|
const { name } = req.body as { name: string };
|
|
if (!name?.trim()) return res.status(400).json({ error: 'Name is required' });
|
|
const result = db.prepare('INSERT INTO shopping_lists (name) VALUES (?)').run(name.trim());
|
|
res.status(201).json(db.prepare('SELECT * FROM shopping_lists WHERE id = ?').get(result.lastInsertRowid));
|
|
});
|
|
|
|
router.delete('/lists/:id', (req, res) => {
|
|
const result = db.prepare('DELETE FROM shopping_lists WHERE id = ?').run(req.params.id);
|
|
if (result.changes === 0) return res.status(404).json({ error: 'List not found' });
|
|
res.status(204).end();
|
|
});
|
|
|
|
// ── Items ──────────────────────────────────────────────────────────────────
|
|
router.get('/lists/:listId/items', (req, res) => {
|
|
res.json(
|
|
db.prepare('SELECT * FROM shopping_items WHERE list_id = ? ORDER BY sort_order ASC, id ASC').all(req.params.listId)
|
|
);
|
|
});
|
|
|
|
router.post('/lists/:listId/items', (req, res) => {
|
|
const { name, quantity, member_id } = req.body as { name: string; quantity?: string; member_id?: number };
|
|
if (!name?.trim()) return res.status(400).json({ error: 'Name is required' });
|
|
const maxOrder = (db.prepare('SELECT MAX(sort_order) as m FROM shopping_items WHERE list_id = ?').get(req.params.listId) as any)?.m ?? 0;
|
|
const result = db
|
|
.prepare('INSERT INTO shopping_items (list_id, name, quantity, member_id, sort_order) VALUES (?, ?, ?, ?, ?)')
|
|
.run(req.params.listId, name.trim(), quantity ?? null, member_id ?? null, maxOrder + 1);
|
|
res.status(201).json(db.prepare('SELECT * FROM shopping_items WHERE id = ?').get(result.lastInsertRowid));
|
|
});
|
|
|
|
router.patch('/items/:id', (req, res) => {
|
|
const existing = db.prepare('SELECT * FROM shopping_items WHERE id = ?').get(req.params.id) as any;
|
|
if (!existing) return res.status(404).json({ error: 'Item not found' });
|
|
const { name, quantity, checked, member_id, sort_order } = req.body;
|
|
db.prepare('UPDATE shopping_items SET name=?, quantity=?, checked=?, member_id=?, sort_order=? WHERE id=?').run(
|
|
name?.trim() ?? existing.name,
|
|
quantity !== undefined ? quantity : existing.quantity,
|
|
checked !== undefined ? (checked ? 1 : 0) : existing.checked,
|
|
member_id !== undefined ? member_id : existing.member_id,
|
|
sort_order !== undefined ? sort_order : existing.sort_order,
|
|
req.params.id
|
|
);
|
|
res.json(db.prepare('SELECT * FROM shopping_items WHERE id = ?').get(req.params.id));
|
|
});
|
|
|
|
router.delete('/items/:id', (req, res) => {
|
|
const result = db.prepare('DELETE FROM shopping_items WHERE id = ?').run(req.params.id);
|
|
if (result.changes === 0) return res.status(404).json({ error: 'Item not found' });
|
|
res.status(204).end();
|
|
});
|
|
|
|
router.delete('/lists/:listId/items/checked', (req, res) => {
|
|
db.prepare('DELETE FROM shopping_items WHERE list_id = ? AND checked = 1').run(req.params.listId);
|
|
res.status(204).end();
|
|
});
|
|
|
|
export default router;
|