migrate: backfill old {{uuid}} placeholders to new {{label.w/o:uuid}} format
Runs at startup (idempotent) — only touches rows that still contain bare
{{uuid}} placeholders. Looks up each UUID in words first, then objects,
and rewrites to {{label.w:uuid}} or {{label.o:uuid}} accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -494,7 +494,93 @@ async function migrate() {
|
|||||||
AND bbox_x IS NULL
|
AND bbox_x IS NULL
|
||||||
`).catch(() => {});
|
`).catch(() => {});
|
||||||
|
|
||||||
|
// ── Migrate old {{uuid}} placeholders → new {{label.w:uuid}} / {{label.o:uuid}} ──
|
||||||
|
await migratePlaceholders();
|
||||||
|
|
||||||
console.log('Migration complete');
|
console.log('Migration complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UUID regex — matches bare {{uuid}} but NOT already-migrated {{label.w:uuid}}
|
||||||
|
const UUID_RE = /\{\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}\}/gi;
|
||||||
|
|
||||||
|
async function migratePlaceholders() {
|
||||||
|
const textCols = {
|
||||||
|
questions: ['sentence_de', 'sentence_en', 'sentence_sv'],
|
||||||
|
statements: [
|
||||||
|
'positive_sentence_de', 'positive_sentence_en', 'positive_sentence_sv',
|
||||||
|
'negative_sentence_de', 'negative_sentence_en', 'negative_sentence_sv',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const uuidSet = new Set();
|
||||||
|
const affected = {};
|
||||||
|
|
||||||
|
for (const [table, cols] of Object.entries(textCols)) {
|
||||||
|
const whereClause = cols
|
||||||
|
.map(c => `${c} ~ '\\{\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}\\}'`)
|
||||||
|
.join(' OR ');
|
||||||
|
const { rows } = await query(`SELECT id, ${cols.join(', ')} FROM ${table} WHERE ${whereClause}`);
|
||||||
|
if (rows.length) {
|
||||||
|
affected[table] = rows;
|
||||||
|
rows.forEach(row => cols.forEach(col => {
|
||||||
|
for (const m of (row[col] || '').matchAll(UUID_RE)) uuidSet.add(m[1]);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uuidSet.size === 0) return;
|
||||||
|
|
||||||
|
const uuids = [...uuidSet];
|
||||||
|
const labelMap = {};
|
||||||
|
|
||||||
|
// Words first
|
||||||
|
const { rows: wordRows } = await query(
|
||||||
|
`SELECT id, titel_de, titel_en FROM words WHERE id = ANY($1::uuid[])`, [uuids]
|
||||||
|
);
|
||||||
|
wordRows.forEach(w => { labelMap[w.id] = { label: w.titel_de || w.titel_en || 'Wort', type: 'w' }; });
|
||||||
|
|
||||||
|
// Remaining → objects
|
||||||
|
const missing = uuids.filter(id => !labelMap[id]);
|
||||||
|
if (missing.length) {
|
||||||
|
const { rows: objRows } = await query(
|
||||||
|
`SELECT o.id, w.titel_de, w.titel_en
|
||||||
|
FROM objects o
|
||||||
|
LEFT JOIN object_words ow ON ow.object_id = o.id
|
||||||
|
LEFT JOIN words w ON w.id = ow.word_id
|
||||||
|
WHERE o.id = ANY($1::uuid[])`, [missing]
|
||||||
|
);
|
||||||
|
const seen = new Set();
|
||||||
|
objRows.forEach(r => {
|
||||||
|
if (!seen.has(r.id)) {
|
||||||
|
seen.add(r.id);
|
||||||
|
labelMap[r.id] = { label: r.titel_de || r.titel_en || 'Objekt', type: 'o' };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE affected rows
|
||||||
|
for (const [table, rows] of Object.entries(affected)) {
|
||||||
|
const cols = textCols[table];
|
||||||
|
for (const row of rows) {
|
||||||
|
const updates = {};
|
||||||
|
for (const col of cols) {
|
||||||
|
const text = row[col];
|
||||||
|
if (!text) continue;
|
||||||
|
const replaced = text.replace(UUID_RE, (_, uuid) => {
|
||||||
|
const info = labelMap[uuid];
|
||||||
|
return info ? `{{${info.label}.${info.type}:${uuid}}}` : `{{${uuid}}}`;
|
||||||
|
});
|
||||||
|
if (replaced !== text) updates[col] = replaced;
|
||||||
|
}
|
||||||
|
if (Object.keys(updates).length) {
|
||||||
|
const setClauses = Object.keys(updates).map((k, i) => `${k} = $${i + 2}`).join(', ');
|
||||||
|
await query(`UPDATE ${table} SET ${setClauses} WHERE id = $1`, [row.id, ...Object.values(updates)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = Object.values(affected).reduce((s, r) => s + r.length, 0);
|
||||||
|
if (count > 0) console.log(`Placeholder migration: updated ${count} rows`);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = migrate;
|
module.exports = migrate;
|
||||||
|
|||||||
Reference in New Issue
Block a user