Add missing relation endpoints for content_mentor migration
- GET /api/objects?picture_id=X filter via object_pictures join - GET /api/objects/:id/words — full word details - GET /api/objects/:id/pairs — pairs with nested question + statements - GET /api/words?titel_de=X case-insensitive filter - GET /api/pictures/:id/words — full word details - DELETE /api/pictures/:id/words/:wordId — convenience unlink Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,10 +28,13 @@ async function getWithRelations(id) {
|
|||||||
// GET /api/objects
|
// GET /api/objects
|
||||||
router.get('/', async (req, res, next) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { status, limit = 50, offset = 0 } = req.query;
|
const { status, picture_id, limit = 50, offset = 0 } = req.query;
|
||||||
const params = [Math.min(parseInt(limit), 500), parseInt(offset)];
|
const params = [Math.min(parseInt(limit), 500), parseInt(offset)];
|
||||||
const where = status ? `WHERE o.status = $3` : '';
|
const conditions = [];
|
||||||
if (status) params.push(status);
|
if (status) { conditions.push(`o.status = $${params.length + 1}`); params.push(status); }
|
||||||
|
if (picture_id) { conditions.push(`op2.picture_id = $${params.length + 1}`); params.push(picture_id); }
|
||||||
|
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||||
|
const join2 = picture_id ? `LEFT JOIN object_pictures op2 ON op2.object_id = o.id` : '';
|
||||||
const result = await query(
|
const result = await query(
|
||||||
`SELECT o.*,
|
`SELECT o.*,
|
||||||
COALESCE(json_agg(DISTINCT ow.word_id) FILTER (WHERE ow.word_id IS NOT NULL), '[]') AS word_ids,
|
COALESCE(json_agg(DISTINCT ow.word_id) FILTER (WHERE ow.word_id IS NOT NULL), '[]') AS word_ids,
|
||||||
@@ -41,6 +44,7 @@ router.get('/', async (req, res, next) => {
|
|||||||
LEFT JOIN object_words ow ON ow.object_id = o.id
|
LEFT JOIN object_words ow ON ow.object_id = o.id
|
||||||
LEFT JOIN object_pictures op ON op.object_id = o.id
|
LEFT JOIN object_pictures op ON op.object_id = o.id
|
||||||
LEFT JOIN object_pairs opr ON opr.object_id = o.id
|
LEFT JOIN object_pairs opr ON opr.object_id = o.id
|
||||||
|
${join2}
|
||||||
${where}
|
${where}
|
||||||
GROUP BY o.id
|
GROUP BY o.id
|
||||||
ORDER BY o.created_at DESC
|
ORDER BY o.created_at DESC
|
||||||
@@ -110,6 +114,69 @@ router.delete('/:id', async (req, res, next) => {
|
|||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Relation detail reads ---
|
||||||
|
|
||||||
|
// GET /api/objects/:id/words — full word objects
|
||||||
|
router.get('/:id/words', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const result = await query(
|
||||||
|
`SELECT w.* FROM words w
|
||||||
|
JOIN object_words ow ON ow.word_id = w.id
|
||||||
|
WHERE ow.object_id = $1
|
||||||
|
ORDER BY w.created_at DESC`,
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) { next(err); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /api/objects/:id/pairs — full pair objects with nested question + statements
|
||||||
|
router.get('/:id/pairs', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const pairsResult = await query(
|
||||||
|
`SELECT p.* FROM pairs p
|
||||||
|
JOIN object_pairs op ON op.pair_id = p.id
|
||||||
|
WHERE op.object_id = $1
|
||||||
|
ORDER BY p.created_at DESC`,
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
const pairs = pairsResult.rows;
|
||||||
|
if (!pairs.length) return res.json([]);
|
||||||
|
|
||||||
|
// Fetch linked questions and statements in bulk
|
||||||
|
const qIds = [...new Set(pairs.map(p => p.question_id).filter(Boolean))];
|
||||||
|
const posIds = [...new Set(pairs.map(p => p.positive_statement_id).filter(Boolean))];
|
||||||
|
const negIds = [...new Set(pairs.map(p => p.negative_statement_id).filter(Boolean))];
|
||||||
|
const stmtIds = [...new Set([...posIds, ...negIds])];
|
||||||
|
|
||||||
|
const [qRows, stmtRows] = await Promise.all([
|
||||||
|
qIds.length ? query(`SELECT * FROM questions WHERE id = ANY($1)`, [qIds]) : { rows: [] },
|
||||||
|
stmtIds.length ? query(
|
||||||
|
`SELECT s.*,
|
||||||
|
COALESCE(json_agg(DISTINCT spw.word_id) FILTER (WHERE spw.word_id IS NOT NULL), '[]') AS positive_word_ids,
|
||||||
|
COALESCE(json_agg(DISTINCT snw.word_id) FILTER (WHERE snw.word_id IS NOT NULL), '[]') AS negative_word_ids
|
||||||
|
FROM statements s
|
||||||
|
LEFT JOIN statement_positive_words spw ON spw.statement_id = s.id
|
||||||
|
LEFT JOIN statement_negative_words snw ON snw.statement_id = s.id
|
||||||
|
WHERE s.id = ANY($1)
|
||||||
|
GROUP BY s.id`, [stmtIds]
|
||||||
|
) : { rows: [] },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const qMap = Object.fromEntries(qRows.rows.map(r => [r.id, r]));
|
||||||
|
const stmtMap = Object.fromEntries(stmtRows.rows.map(r => [r.id, r]));
|
||||||
|
|
||||||
|
const enriched = pairs.map(p => ({
|
||||||
|
...p,
|
||||||
|
question: p.question_id ? qMap[p.question_id] || null : null,
|
||||||
|
positive_statement: p.positive_statement_id ? stmtMap[p.positive_statement_id] || null : null,
|
||||||
|
negative_statement: p.negative_statement_id ? stmtMap[p.negative_statement_id] || null : null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.json(enriched);
|
||||||
|
} catch (err) { next(err); }
|
||||||
|
});
|
||||||
|
|
||||||
// --- Relations ---
|
// --- Relations ---
|
||||||
|
|
||||||
// POST /api/objects/:id/words/:wordId
|
// POST /api/objects/:id/words/:wordId
|
||||||
|
|||||||
@@ -33,6 +33,31 @@ router.get('/:id', async (req, res, next) => {
|
|||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/pictures/:id/words — full word objects linked to this picture
|
||||||
|
router.get('/:id/words', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const result = await query(
|
||||||
|
`SELECT w.* FROM words w
|
||||||
|
JOIN word_pictures wp ON wp.word_id = w.id
|
||||||
|
WHERE wp.picture_id = $1
|
||||||
|
ORDER BY w.created_at DESC`,
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) { next(err); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// DELETE /api/pictures/:id/words/:wordId
|
||||||
|
router.delete('/:id/words/:wordId', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
await query(
|
||||||
|
`DELETE FROM word_pictures WHERE picture_id = $1 AND word_id = $2`,
|
||||||
|
[req.params.id, req.params.wordId]
|
||||||
|
);
|
||||||
|
res.status(204).end();
|
||||||
|
} catch (err) { next(err); }
|
||||||
|
});
|
||||||
|
|
||||||
// POST /api/pictures — neuen Eintrag anlegen
|
// POST /api/pictures — neuen Eintrag anlegen
|
||||||
router.post('/', async (req, res, next) => {
|
router.post('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ const STATUS_TIMESTAMP = {
|
|||||||
// GET /api/words
|
// GET /api/words
|
||||||
router.get('/', async (req, res, next) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { status, limit = 50, offset = 0 } = req.query;
|
const { status, titel_de, limit = 50, offset = 0 } = req.query;
|
||||||
const params = [Math.min(parseInt(limit), 500), parseInt(offset)];
|
const params = [Math.min(parseInt(limit), 500), parseInt(offset)];
|
||||||
const where = status ? `WHERE w.status = $3` : '';
|
const conditions = [];
|
||||||
if (status) params.push(status);
|
if (status) { conditions.push(`w.status = $${params.length + 1}`); params.push(status); }
|
||||||
|
if (titel_de) { conditions.push(`lower(w.titel_de) = lower($${params.length + 1})`); params.push(titel_de); }
|
||||||
|
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||||
const result = await query(
|
const result = await query(
|
||||||
`SELECT w.*,
|
`SELECT w.*,
|
||||||
COALESCE(json_agg(DISTINCT p.id) FILTER (WHERE p.id IS NOT NULL), '[]') AS picture_ids,
|
COALESCE(json_agg(DISTINCT p.id) FILTER (WHERE p.id IS NOT NULL), '[]') AS picture_ids,
|
||||||
|
|||||||
Reference in New Issue
Block a user