- Add StringParams = Record<string, string> to types.ts - Use Request<StringParams> in all route handlers across 14 files - Clean up earlier as-cast workarounds in places.ts and admin.ts - tsconfig.json: keep original (removed bad 'types:node' addition) - package.json: restore @types/express back to ^5.0.6
114 lines
4.2 KiB
TypeScript
114 lines
4.2 KiB
TypeScript
import express, { Request, Response } from 'express';
|
|
import { db, canAccessTrip } from '../db/database';
|
|
import { authenticate } from '../middleware/auth';
|
|
import { broadcast } from '../websocket';
|
|
import { StringParams, AuthRequest } from '../types';
|
|
|
|
const router = express.Router({ mergeParams: true });
|
|
|
|
function verifyTripOwnership(tripId: string | number, userId: number) {
|
|
return canAccessTrip(tripId, userId);
|
|
}
|
|
|
|
router.get('/', authenticate, (req: Request<StringParams>, res: Response) => {
|
|
const authReq = req as AuthRequest;
|
|
const { tripId } = req.params;
|
|
|
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
|
|
|
const items = db.prepare(
|
|
'SELECT * FROM packing_items WHERE trip_id = ? ORDER BY sort_order ASC, created_at ASC'
|
|
).all(tripId);
|
|
|
|
res.json({ items });
|
|
});
|
|
|
|
router.post('/', authenticate, (req: Request<StringParams>, res: Response) => {
|
|
const authReq = req as AuthRequest;
|
|
const { tripId } = req.params;
|
|
const { name, category, checked } = req.body;
|
|
|
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
|
|
|
if (!name) return res.status(400).json({ error: 'Item name is required' });
|
|
|
|
const maxOrder = db.prepare('SELECT MAX(sort_order) as max FROM packing_items WHERE trip_id = ?').get(tripId) as { max: number | null };
|
|
const sortOrder = (maxOrder.max !== null ? maxOrder.max : -1) + 1;
|
|
|
|
const result = db.prepare(
|
|
'INSERT INTO packing_items (trip_id, name, checked, category, sort_order) VALUES (?, ?, ?, ?, ?)'
|
|
).run(tripId, name, checked ? 1 : 0, category || 'Allgemein', sortOrder);
|
|
|
|
const item = db.prepare('SELECT * FROM packing_items WHERE id = ?').get(result.lastInsertRowid);
|
|
res.status(201).json({ item });
|
|
broadcast(tripId, 'packing:created', { item }, req.headers['x-socket-id'] as string);
|
|
});
|
|
|
|
router.put('/:id', authenticate, (req: Request<StringParams>, res: Response) => {
|
|
const authReq = req as AuthRequest;
|
|
const { tripId, id } = req.params;
|
|
const { name, checked, category } = req.body;
|
|
|
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
|
|
|
const item = db.prepare('SELECT * FROM packing_items WHERE id = ? AND trip_id = ?').get(id, tripId);
|
|
if (!item) return res.status(404).json({ error: 'Item not found' });
|
|
|
|
db.prepare(`
|
|
UPDATE packing_items SET
|
|
name = COALESCE(?, name),
|
|
checked = CASE WHEN ? IS NOT NULL THEN ? ELSE checked END,
|
|
category = COALESCE(?, category)
|
|
WHERE id = ?
|
|
`).run(
|
|
name || null,
|
|
checked !== undefined ? 1 : null,
|
|
checked ? 1 : 0,
|
|
category || null,
|
|
id
|
|
);
|
|
|
|
const updated = db.prepare('SELECT * FROM packing_items WHERE id = ?').get(id);
|
|
res.json({ item: updated });
|
|
broadcast(tripId, 'packing:updated', { item: updated }, req.headers['x-socket-id'] as string);
|
|
});
|
|
|
|
router.delete('/:id', authenticate, (req: Request<StringParams>, res: Response) => {
|
|
const authReq = req as AuthRequest;
|
|
const { tripId, id } = req.params;
|
|
|
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
|
|
|
const item = db.prepare('SELECT id FROM packing_items WHERE id = ? AND trip_id = ?').get(id, tripId);
|
|
if (!item) return res.status(404).json({ error: 'Item not found' });
|
|
|
|
db.prepare('DELETE FROM packing_items WHERE id = ?').run(id);
|
|
res.json({ success: true });
|
|
broadcast(tripId, 'packing:deleted', { itemId: Number(id) }, req.headers['x-socket-id'] as string);
|
|
});
|
|
|
|
router.put('/reorder', authenticate, (req: Request<StringParams>, res: Response) => {
|
|
const authReq = req as AuthRequest;
|
|
const { tripId } = req.params;
|
|
const { orderedIds } = req.body;
|
|
|
|
const trip = verifyTripOwnership(tripId, authReq.user.id);
|
|
if (!trip) return res.status(404).json({ error: 'Trip not found' });
|
|
|
|
const update = db.prepare('UPDATE packing_items SET sort_order = ? WHERE id = ? AND trip_id = ?');
|
|
const updateMany = db.transaction((ids: number[]) => {
|
|
ids.forEach((id, index) => {
|
|
update.run(index, id, tripId);
|
|
});
|
|
});
|
|
|
|
updateMany(orderedIds);
|
|
res.json({ success: true });
|
|
});
|
|
|
|
export default router;
|