From 9f738312e72600149edc74f92dab822fd5c8500b Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 25 May 2026 21:24:27 +0200 Subject: [PATCH] Add bbox coordinates to object_pictures for chip highlight feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add bbox_x/y/w/h FLOAT columns to object_pictures (0–1 percentage range) - Include type ('word'|'object') and bbox in feed placeholder response - Fix picture query to use DISTINCT ON instead of LIMIT 1 Co-Authored-By: Claude Sonnet 4.6 --- src/db-migrate.js | 6 ++++++ src/routes/feed.js | 25 ++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/db-migrate.js b/src/db-migrate.js index 5e9d207..b3381e0 100644 --- a/src/db-migrate.js +++ b/src/db-migrate.js @@ -310,6 +310,12 @@ async function migrate() { ) `); + // Bounding-box columns for object highlights (percentage 0–1 of image size) + await query(`ALTER TABLE object_pictures ADD COLUMN IF NOT EXISTS bbox_x FLOAT`); + await query(`ALTER TABLE object_pictures ADD COLUMN IF NOT EXISTS bbox_y FLOAT`); + await query(`ALTER TABLE object_pictures ADD COLUMN IF NOT EXISTS bbox_w FLOAT`); + await query(`ALTER TABLE object_pictures ADD COLUMN IF NOT EXISTS bbox_h FLOAT`); + // M2M: objects <-> pairs (Platzhalter) await query(` CREATE TABLE IF NOT EXISTS object_pairs ( diff --git a/src/routes/feed.js b/src/routes/feed.js index d3ab3c2..e1a5f28 100644 --- a/src/routes/feed.js +++ b/src/routes/feed.js @@ -94,7 +94,9 @@ router.get('/', requireJwt, async (req, res, next) => { `SELECT id, titel_de AS de, titel_en AS en, titel_sv AS sv FROM words WHERE id = ANY($1)`, [uuidArr] ); - wordRes.rows.forEach(w => { placeholderMap[w.id] = { de: w.de, en: w.en, sv: w.sv }; }); + wordRes.rows.forEach(w => { + placeholderMap[w.id] = { de: w.de, en: w.en, sv: w.sv, type: 'word', bbox: null }; + }); // Object lookup → get first linked word as label const objUuids = uuidArr.filter(u => !placeholderMap[u]); @@ -109,7 +111,10 @@ router.get('/', requireJwt, async (req, res, next) => { // First word per object const seen = new Set(); objRes.rows.forEach(r => { - if (!seen.has(r.id)) { placeholderMap[r.id] = { de: r.de, en: r.en, sv: r.sv }; seen.add(r.id); } + if (!seen.has(r.id)) { + placeholderMap[r.id] = { de: r.de, en: r.en, sv: r.sv, type: 'object', bbox: null }; + seen.add(r.id); + } }); } } @@ -156,14 +161,24 @@ router.get('/', requireJwt, async (req, res, next) => { const pictureMap = {}; // objectId → { url, blurhash } if (resolvedObjectIds.size) { const picRes = await query( - `SELECT op.object_id, p.picture_link AS url, p.blurhash + `SELECT DISTINCT ON (op.object_id) + op.object_id, p.picture_link AS url, p.blurhash, + op.bbox_x, op.bbox_y, op.bbox_w, op.bbox_h FROM object_pictures op JOIN pictures p ON p.id = op.picture_id WHERE op.object_id = ANY($1) - LIMIT 1`, + ORDER BY op.object_id, p.created_at`, [[...resolvedObjectIds]] ); - picRes.rows.forEach(r => { pictureMap[r.object_id] = { url: r.url, blurhash: r.blurhash }; }); + picRes.rows.forEach(r => { + pictureMap[r.object_id] = { url: r.url, blurhash: r.blurhash }; + // Attach bbox to placeholder if all four values are present + if (placeholderMap[r.object_id] && r.bbox_x != null) { + placeholderMap[r.object_id].bbox = { + x: r.bbox_x, y: r.bbox_y, w: r.bbox_w, h: r.bbox_h, + }; + } + }); } // Pick first available picture across all placeholders in the pair