Compare commits

..

1 Commits

Author SHA1 Message Date
fahed af91dba268 feat: admin migration endpoint — Translations with post_id → Artefacts
Deploy / deploy (push) Successful in 12s
Converts old caption/body copy Translations to Artefacts (type=copy)
with ArtefactVersions + ArtefactVersionTexts. Idempotent: skips posts
that already have copy Artefacts. Dry-run by default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:55:14 +03:00
+105
View File
@@ -673,6 +673,111 @@ app.get('/api/health', async (req, res) => {
});
});
// ─── MIGRATIONS ─────────────────────────────────────────────────
// One-time migration: convert Translations with post_id → Artefacts (copy type)
// Safe to run multiple times — skips posts that already have copy Artefacts.
app.post('/api/admin/migrate-translations-to-artefacts', requireAuth, async (req, res) => {
if (req.session.userRole !== 'superadmin') return res.status(403).json({ error: 'Superadmin only' });
const dryRun = req.body.dry_run !== false; // default true for safety
const report = { dry_run: dryRun, skipped: [], migrated: [], errors: [] };
try {
// All translations linked to a post (caption or body copy)
const translations = await nocodb.list('Translations', {
where: `(post_id,gt,0)`, limit: 2000,
});
for (const t of translations) {
if (!t.post_id) continue;
const copyType = t.copy_type || 'body';
// Skip if an Artefact for this post+copy_type already exists
const existing = await nocodb.list('Artefacts', {
where: `(post_id,eq,${t.post_id})~and(type,eq,copy)~and(copy_type,eq,${copyType})`,
limit: 1,
});
if (existing.length > 0) {
report.skipped.push({ translation_id: t.Id, post_id: t.post_id, copy_type: copyType, reason: 'artefact already exists' });
continue;
}
if (dryRun) {
report.migrated.push({ translation_id: t.Id, post_id: t.post_id, copy_type: copyType, title: t.title, dry_run: true });
continue;
}
try {
// 1. Create Artefact
const artefact = await nocodb.create('Artefacts', {
title: t.title || (copyType === 'caption' ? 'Caption' : 'Body Copy'),
type: 'copy',
copy_type: copyType,
status: t.status || 'draft',
post_id: t.post_id,
brand_id: t.brand_id || null,
approver_ids: t.approver_ids || null,
approval_token: t.approval_token || null,
approved_by_name: t.approved_by_name || null,
approved_at: t.approved_at || null,
feedback: t.feedback || null,
created_by_user_id: t.created_by_user_id || null,
});
// 2. Create first ArtefactVersion
const version = await nocodb.create('ArtefactVersions', {
artefact_id: artefact.Id,
version_number: 1,
status: t.status || 'draft',
created_by_user_id: t.created_by_user_id || null,
});
// 3. Migrate source content as a version text entry
const textsToCreate = [];
if (t.source_content && t.source_language) {
textsToCreate.push({
version_id: version.Id,
language_code: t.source_language,
language_label: t.source_language,
content: t.source_content,
status: 'draft',
});
}
// 4. Migrate TranslationTexts as additional language entries
const translationTexts = await nocodb.list('TranslationTexts', {
where: `(translation_id,eq,${t.Id})`, limit: 50,
});
for (const tt of translationTexts) {
if (tt.language_code === t.source_language) continue; // already added above
textsToCreate.push({
version_id: version.Id,
language_code: tt.language_code,
language_label: tt.language_label || tt.language_code,
content: tt.content || '',
status: tt.status || 'draft',
});
}
if (textsToCreate.length > 0) {
await nocodb.bulkCreate('ArtefactVersionTexts', textsToCreate);
}
// 5. Set current_version on Artefact
await nocodb.update('Artefacts', artefact.Id, { current_version: 1 });
report.migrated.push({ translation_id: t.Id, artefact_id: artefact.Id, post_id: t.post_id, copy_type: copyType, title: artefact.title, texts: textsToCreate.length });
} catch (err) {
report.errors.push({ translation_id: t.Id, post_id: t.post_id, error: err.message });
}
}
res.json({ ...report, summary: { total: translations.length, migrated: report.migrated.length, skipped: report.skipped.length, errors: report.errors.length } });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// ─── EMAIL TEST ─────────────────────────────────────────────────
app.post('/api/admin/test-email', requireAuth, async (req, res) => {