diff --git a/src/routes/objects.js b/src/routes/objects.js index 7ea3426..825cc7f 100644 --- a/src/routes/objects.js +++ b/src/routes/objects.js @@ -55,15 +55,6 @@ router.get('/', async (req, res, next) => { } catch (err) { next(err); } }); -// GET /api/objects/:id -router.get('/:id', async (req, res, next) => { - try { - const row = await getWithRelations(req.params.id); - if (!row) return res.status(404).json({ error: 'Not found' }); - res.json(row); - } catch (err) { next(err); } -}); - // POST /api/objects router.post('/', async (req, res, next) => { try { @@ -177,6 +168,29 @@ router.get('/:id/pairs', async (req, res, next) => { } catch (err) { next(err); } }); +// GET /api/objects/:id/pictures — full picture objects +router.get('/:id/pictures', async (req, res, next) => { + try { + const result = await query( + `SELECT p.* FROM pictures p + JOIN object_pictures op ON op.picture_id = p.id + WHERE op.object_id = $1 + ORDER BY p.created_at DESC`, + [req.params.id] + ); + res.json(result.rows); + } catch (err) { next(err); } +}); + +// GET /api/objects/:id — AFTER sub-routes to avoid shadowing +router.get('/:id', async (req, res, next) => { + try { + const row = await getWithRelations(req.params.id); + if (!row) return res.status(404).json({ error: 'Not found' }); + res.json(row); + } catch (err) { next(err); } +}); + // --- Relations --- // POST /api/objects/:id/words/:wordId diff --git a/src/routes/pictures.js b/src/routes/pictures.js index 5c34710..7eb7ea6 100644 --- a/src/routes/pictures.js +++ b/src/routes/pictures.js @@ -24,16 +24,7 @@ router.get('/', async (req, res, next) => { } catch (err) { next(err); } }); -// GET /api/pictures/:id -router.get('/:id', async (req, res, next) => { - try { - const result = await query('SELECT * FROM pictures WHERE id = $1', [req.params.id]); - if (!result.rows.length) return res.status(404).json({ error: 'Not found' }); - res.json(result.rows[0]); - } catch (err) { next(err); } -}); - -// GET /api/pictures/:id/words — full word objects linked to this picture +// GET /api/pictures/:id/words — BEFORE /:id to avoid shadowing router.get('/:id/words', async (req, res, next) => { try { const result = await query( @@ -47,7 +38,18 @@ router.get('/:id/words', async (req, res, next) => { } catch (err) { next(err); } }); -// DELETE /api/pictures/:id/words/:wordId +// POST /api/pictures/:id/words/:wordId — Wort verknüpfen +router.post('/:id/words/:wordId', async (req, res, next) => { + try { + await query( + `INSERT INTO word_pictures (word_id, picture_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`, + [req.params.wordId, req.params.id] + ); + res.status(204).end(); + } catch (err) { next(err); } +}); + +// DELETE /api/pictures/:id/words/:wordId — Wort-Verknüpfung entfernen router.delete('/:id/words/:wordId', async (req, res, next) => { try { await query( @@ -58,6 +60,15 @@ router.delete('/:id/words/:wordId', async (req, res, next) => { } catch (err) { next(err); } }); +// GET /api/pictures/:id +router.get('/:id', async (req, res, next) => { + try { + const result = await query('SELECT * FROM pictures WHERE id = $1', [req.params.id]); + if (!result.rows.length) return res.status(404).json({ error: 'Not found' }); + res.json(result.rows[0]); + } catch (err) { next(err); } +}); + // POST /api/pictures — neuen Eintrag anlegen router.post('/', async (req, res, next) => { try { diff --git a/src/routes/words.js b/src/routes/words.js index 03160d4..da358b0 100644 --- a/src/routes/words.js +++ b/src/routes/words.js @@ -37,27 +37,6 @@ router.get('/', async (req, res, next) => { } catch (err) { next(err); } }); -// GET /api/words/:id -router.get('/:id', async (req, res, next) => { - try { - const result = await query( - `SELECT w.*, - COALESCE(json_agg(DISTINCT p.id) FILTER (WHERE p.id IS NOT NULL), '[]') AS picture_ids, - COALESCE(json_agg(DISTINCT c.id) FILTER (WHERE c.id IS NOT NULL), '[]') AS category_ids - FROM words w - LEFT JOIN word_pictures wp ON wp.word_id = w.id - LEFT JOIN pictures p ON p.id = wp.picture_id - LEFT JOIN word_categories wc ON wc.word_id = w.id - LEFT JOIN categories c ON c.id = wc.category_id - WHERE w.id = $1 - GROUP BY w.id`, - [req.params.id] - ); - if (!result.rows.length) return res.status(404).json({ error: 'Not found' }); - res.json(result.rows[0]); - } catch (err) { next(err); } -}); - // POST /api/words router.post('/', async (req, res, next) => { try { @@ -109,6 +88,55 @@ router.delete('/:id', async (req, res, next) => { } catch (err) { next(err); } }); +// GET /api/words/:id — AFTER sub-routes to avoid shadowing +router.get('/:id', async (req, res, next) => { + try { + const result = await query( + `SELECT w.*, + COALESCE(json_agg(DISTINCT p.id) FILTER (WHERE p.id IS NOT NULL), '[]') AS picture_ids, + COALESCE(json_agg(DISTINCT c.id) FILTER (WHERE c.id IS NOT NULL), '[]') AS category_ids + FROM words w + LEFT JOIN word_pictures wp ON wp.word_id = w.id + LEFT JOIN pictures p ON p.id = wp.picture_id + LEFT JOIN word_categories wc ON wc.word_id = w.id + LEFT JOIN categories c ON c.id = wc.category_id + WHERE w.id = $1 + GROUP BY w.id`, + [req.params.id] + ); + if (!result.rows.length) return res.status(404).json({ error: 'Not found' }); + res.json(result.rows[0]); + } catch (err) { next(err); } +}); + +// GET /api/words/:id/pictures — verknüpfte Bilder laden +router.get('/:id/pictures', async (req, res, next) => { + try { + const result = await query( + `SELECT p.* FROM pictures p + JOIN word_pictures wp ON wp.picture_id = p.id + WHERE wp.word_id = $1 + ORDER BY p.created_at DESC`, + [req.params.id] + ); + res.json(result.rows); + } catch (err) { next(err); } +}); + +// GET /api/words/:id/categories — verknüpfte Kategorien laden +router.get('/:id/categories', async (req, res, next) => { + try { + const result = await query( + `SELECT c.* FROM categories c + JOIN word_categories wc ON wc.category_id = c.id + WHERE wc.word_id = $1 + ORDER BY c.name_de`, + [req.params.id] + ); + res.json(result.rows); + } catch (err) { next(err); } +}); + // POST /api/words/:id/pictures/:pictureId — Bild verknüpfen router.post('/:id/pictures/:pictureId', async (req, res, next) => { try {