Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
129 lines
5.4 KiB
JavaScript
129 lines
5.4 KiB
JavaScript
// Documents v4: Language management — add-language, migrate-to-content-en
|
|
import express from 'express';
|
|
import {
|
|
dbAll, dbGet, dbRun, safeJsonParse, apiLogger,
|
|
success, error, badRequest,
|
|
requireEditorAccess,
|
|
} from './_helpers.js';
|
|
|
|
const router = express.Router();
|
|
|
|
/** POST /api/v3/projects/:projectId/documents/add-language */
|
|
router.post('/projects/:projectId/documents/add-language', async (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { language_code, language_name, folder_path = 'databases/documents/' } = req.body;
|
|
if (!(await requireEditorAccess(req, res, projectId))) return;
|
|
if (!language_code) return badRequest(res, 'language_code is required (e.g., "de", "fr", "es")');
|
|
|
|
const docTables = await dbAll(
|
|
`SELECT id, name FROM universal_tables WHERE project_id = ? AND folder_path LIKE ? AND name LIKE 'doc_%'`,
|
|
[projectId, `%${folder_path}%`]
|
|
);
|
|
|
|
let updatedTables = 0;
|
|
for (const table of docTables) {
|
|
const existingCols = await dbAll(
|
|
`SELECT column_name FROM table_columns WHERE table_id = ? AND column_name IN (?, ?)`,
|
|
[table.id, `title_${language_code}`, `content_${language_code}`]
|
|
);
|
|
if (existingCols.length < 2) {
|
|
const maxOrder = await dbGet(
|
|
`SELECT MAX(order_index) as max_order FROM table_columns WHERE table_id = ?`,
|
|
[table.id]
|
|
);
|
|
let nextOrder = (maxOrder?.max_order || 0) + 1;
|
|
|
|
if (!existingCols.find(c => c.column_name === `title_${language_code}`)) {
|
|
await dbRun(
|
|
`INSERT INTO table_columns (table_id, column_name, display_name, type, order_index, is_visible, config)
|
|
VALUES (?, ?, ?, 'text', ?, 1, ?)`,
|
|
[table.id, `title_${language_code}`, `Заголовок (${language_name || language_code})`, nextOrder++, JSON.stringify({ translation: true })]
|
|
);
|
|
}
|
|
if (!existingCols.find(c => c.column_name === `content_${language_code}`)) {
|
|
await dbRun(
|
|
`INSERT INTO table_columns (table_id, column_name, display_name, type, order_index, is_visible, config)
|
|
VALUES (?, ?, ?, 'text', ?, 1, ?)`,
|
|
[table.id, `content_${language_code}`, `Контент (${language_name || language_code})`, nextOrder, JSON.stringify({ translation: true })]
|
|
);
|
|
}
|
|
updatedTables++;
|
|
}
|
|
}
|
|
|
|
apiLogger.info(`[Documents v4] Added language ${language_code} to ${updatedTables} tables`);
|
|
success(res, { language_code, updated_tables: updatedTables, total_tables: docTables.length });
|
|
} catch (err) {
|
|
apiLogger.error({ err }, 'POST /projects/:projectId/documents/add-language error:', err);
|
|
error(res, 'ADD_LANGUAGE_ERROR', err.message, 500);
|
|
}
|
|
});
|
|
|
|
/** POST /api/v3/projects/:projectId/documents/migrate-to-content-en */
|
|
router.post('/projects/:projectId/documents/migrate-to-content-en', async (req, res) => {
|
|
try {
|
|
const { projectId } = req.params;
|
|
const { folder_path = 'databases/documents/' } = req.body;
|
|
if (!(await requireEditorAccess(req, res, projectId))) return;
|
|
|
|
const docTables = await dbAll(
|
|
`SELECT id, name FROM universal_tables WHERE project_id = ? AND folder_path LIKE ? AND name LIKE 'doc_%'`,
|
|
[projectId, `%${folder_path}%`]
|
|
);
|
|
|
|
let migratedTables = 0;
|
|
let migratedRows = 0;
|
|
|
|
for (const table of docTables) {
|
|
const contentCol = await dbGet(
|
|
`SELECT id FROM table_columns WHERE table_id = ? AND column_name = 'content'`, [table.id]
|
|
);
|
|
const contentEnCol = await dbGet(
|
|
`SELECT id FROM table_columns WHERE table_id = ? AND column_name = 'content_en'`, [table.id]
|
|
);
|
|
|
|
if (contentCol && !contentEnCol) {
|
|
await dbRun(
|
|
`UPDATE table_columns SET column_name = 'content_en', display_name = 'Content (EN)',
|
|
config = ? WHERE table_id = ? AND column_name = 'content'`,
|
|
[JSON.stringify({ is_default_language: true }), table.id]
|
|
);
|
|
migratedTables++;
|
|
} else if (!contentEnCol) {
|
|
const maxOrder = await dbGet(
|
|
`SELECT MAX(order_index) as max_order FROM table_columns WHERE table_id = ?`, [table.id]
|
|
);
|
|
await dbRun(
|
|
`INSERT INTO table_columns (table_id, column_name, display_name, type, order_index, is_visible, config)
|
|
VALUES (?, 'content_en', 'Content (EN)', 'text', ?, 1, ?)`,
|
|
[table.id, (maxOrder?.max_order || 0) + 1, JSON.stringify({ is_default_language: true })]
|
|
);
|
|
migratedTables++;
|
|
}
|
|
|
|
const rows = await dbAll(`SELECT id, data FROM table_rows WHERE table_id = ?`, [table.id]);
|
|
for (const row of rows) {
|
|
try {
|
|
const data = safeJsonParse(row.data, {});
|
|
if (data.content && !data.content_en) {
|
|
data.content_en = data.content;
|
|
delete data.content;
|
|
await dbRun(`UPDATE table_rows SET data = ? WHERE id = ?`, [JSON.stringify(data), row.id]);
|
|
migratedRows++;
|
|
}
|
|
} catch (e) {
|
|
apiLogger.warn(`Failed to migrate row ${row.id}:`, e.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
apiLogger.info(`[Documents v4] Migrated ${migratedTables} tables, ${migratedRows} rows to content_en`);
|
|
success(res, { migrated_tables: migratedTables, migrated_rows: migratedRows, total_tables: docTables.length });
|
|
} catch (err) {
|
|
apiLogger.error({ err }, 'POST /projects/:projectId/documents/migrate-to-content-en error:', err);
|
|
error(res, 'MIGRATION_ERROR', err.message, 500);
|
|
}
|
|
});
|
|
|
|
export default router;
|