Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
158 lines
7.8 KiB
JavaScript
158 lines
7.8 KiB
JavaScript
/**
|
|
* Setup Tables Controller
|
|
* POST /setup-tables — Create AI tables in System Data project for a space
|
|
*/
|
|
|
|
import { Router } from 'express';
|
|
import { dbGet, dbRun } from '../../../database/connection.js';
|
|
import { apiLogger } from '../../../utils/logger.js';
|
|
import { success, badRequest, error } from '../../../utils/response.js';
|
|
import { generateBaseId } from './shared.js';
|
|
import { getTableDefinitions, getDefaultAgents } from './setupTablesData.js';
|
|
|
|
const router = Router();
|
|
|
|
/**
|
|
* POST /api/v3/ai/setup-tables
|
|
*/
|
|
router.post('/setup-tables', async (req, res) => {
|
|
try {
|
|
const userId = req.user?.id;
|
|
const { spaceId } = req.body;
|
|
|
|
if (!spaceId) return badRequest(res, 'spaceId is required');
|
|
|
|
apiLogger.debug({ context: 'AI Setup' }, `Creating AI tables for space ${spaceId} by user ${userId}`);
|
|
|
|
// Step 1: Find or create System Data project
|
|
let projectId = null;
|
|
const existingProject = await dbGet(`
|
|
SELECT id FROM projects
|
|
WHERE space_id = ? AND (type = 'system_data' OR name = 'System Data')
|
|
LIMIT 1
|
|
`, [spaceId]);
|
|
|
|
if (existingProject) {
|
|
projectId = existingProject.id;
|
|
} else {
|
|
const result = await dbRun(`
|
|
INSERT INTO projects (name, description, icon, space_id, type, owner_id, created_at, updated_at)
|
|
VALUES ('System Data', 'System configuration and logs', '⚙️', ?, 'system_data', ?, datetime('now'), datetime('now'))
|
|
`, [spaceId, userId]);
|
|
projectId = result.lastInsertRowid;
|
|
}
|
|
|
|
// Step 2: Get table definitions
|
|
const aiTables = getTableDefinitions();
|
|
|
|
const createdTables = {};
|
|
|
|
// Step 3: Create tables that don't exist
|
|
for (const tableDef of aiTables) {
|
|
const existing = await dbGet(`SELECT id FROM universal_tables WHERE project_id = ? AND name = ?`, [projectId, tableDef.name]);
|
|
|
|
if (existing) {
|
|
createdTables[tableDef.name] = { id: existing.id, existed: true };
|
|
continue;
|
|
}
|
|
|
|
// ADR-164 Phase 3a: stamp the stable agent-table marker at birth so the
|
|
// AI Agents table is marker-identifiable without the name convention.
|
|
const tableResult = await dbRun(`
|
|
INSERT INTO universal_tables (name, display_name, icon, project_id, table_type, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
|
`, [tableDef.name, tableDef.name, tableDef.icon, projectId,
|
|
tableDef.name === 'AI Agents' ? 'ai_agents' : null]);
|
|
|
|
const tableId = tableResult.lastInsertRowid;
|
|
createdTables[tableDef.name] = { id: tableId, existed: false };
|
|
|
|
let orderIdx = 0;
|
|
for (const col of tableDef.columns) {
|
|
await dbRun(`
|
|
INSERT INTO table_columns (table_id, column_name, display_name, type, is_required, config, order_index, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
|
`, [tableId, col.name, col.name, col.type, col.isRequired ? 1 : 0, JSON.stringify(col.config || {}), orderIdx++]);
|
|
}
|
|
}
|
|
|
|
// Step 4: Create default operators
|
|
const operatorIds = {};
|
|
if (createdTables['AI Operators'] && !createdTables['AI Operators'].existed) {
|
|
const defaultOperators = [
|
|
{ name: 'OpenAI', provider: 'openai', api_url: 'https://api.openai.com/v1', status: 'active', description: 'OpenAI GPT models' },
|
|
{ name: 'Anthropic', provider: 'anthropic', api_url: 'https://api.anthropic.com/v1', status: 'active', description: 'Claude models' },
|
|
{ name: 'Google AI', provider: 'google', api_url: 'https://generativelanguage.googleapis.com/v1beta', status: 'active', description: 'Gemini models' },
|
|
{ name: 'OpenRouter', provider: 'openai', api_url: 'https://openrouter.ai/api/v1', status: 'active', description: 'Multi-provider router' },
|
|
{ name: 'Groq', provider: 'groq', api_url: 'https://api.groq.com/openai/v1', status: 'active', description: 'Fast inference' },
|
|
{ name: 'DeepSeek', provider: 'openai', api_url: 'https://api.deepseek.com/v1', status: 'active', description: 'DeepSeek models' },
|
|
{ name: 'Local Ollama', provider: 'local', api_url: 'http://localhost:11434/v1', status: 'inactive', description: 'Local Ollama server' }
|
|
];
|
|
|
|
for (const op of defaultOperators) {
|
|
const result = await dbRun(`
|
|
INSERT INTO table_rows (table_id, base_id, data, created_at, updated_at)
|
|
VALUES (?, ?, ?, datetime('now'), datetime('now'))
|
|
`, [createdTables['AI Operators'].id, generateBaseId('op'), JSON.stringify(op)]);
|
|
operatorIds[op.name] = result.lastInsertRowid;
|
|
}
|
|
}
|
|
|
|
// Create default AI Agents
|
|
if (createdTables['AI Agents'] && !createdTables['AI Agents'].existed) {
|
|
const defaultAgents = getDefaultAgents(operatorIds);
|
|
for (const agent of defaultAgents) {
|
|
await dbRun(`
|
|
INSERT INTO table_rows (table_id, base_id, data, created_at, updated_at)
|
|
VALUES (?, ?, ?, datetime('now'), datetime('now'))
|
|
`, [createdTables['AI Agents'].id, generateBaseId('agent'), JSON.stringify(agent)]);
|
|
}
|
|
}
|
|
|
|
// Create automations for AI Models table
|
|
if (createdTables['AI Models'] && !createdTables['AI Models'].existed) {
|
|
const defaultAutomations = [
|
|
{ name: 'Синхронизация моделей Anthropic', description: 'Автоматическая синхронизация списка моделей Anthropic', trigger_type: 'schedule', trigger_config: JSON.stringify({ cron: '0 3 * * *' }), action_type: 'webhook', action_config: JSON.stringify({ url: '/api/v3/ai/sync-models', method: 'POST', body: { provider: 'anthropic' } }), is_active: 1 },
|
|
{ name: 'Синхронизация моделей OpenAI', description: 'Автоматическая синхронизация списка моделей OpenAI', trigger_type: 'schedule', trigger_config: JSON.stringify({ cron: '0 3 * * *' }), action_type: 'webhook', action_config: JSON.stringify({ url: '/api/v3/ai/sync-models', method: 'POST', body: { provider: 'openai' } }), is_active: 1 }
|
|
];
|
|
|
|
for (const auto of defaultAutomations) {
|
|
await dbRun(`
|
|
INSERT INTO automations (name, description, table_id, trigger_type, trigger_config, action_type, action_config, is_active, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
|
`, [auto.name, auto.description, createdTables['AI Models'].id, auto.trigger_type, auto.trigger_config, auto.action_type, auto.action_config, auto.is_active]);
|
|
}
|
|
}
|
|
|
|
// Create default Storage Provider
|
|
if (createdTables['Storage Providers'] && !createdTables['Storage Providers'].existed) {
|
|
await dbRun(`
|
|
INSERT INTO table_rows (table_id, base_id, data, created_at, updated_at)
|
|
VALUES (?, ?, ?, datetime('now'), datetime('now'))
|
|
`, [createdTables['Storage Providers'].id, generateBaseId('storage'), JSON.stringify({
|
|
name: 'Local Storage', type: 'local', is_default: true, is_enabled: true,
|
|
config: JSON.stringify({ basePath: '/uploads' }),
|
|
created_at: new Date().toISOString(), updated_at: new Date().toISOString()
|
|
})]);
|
|
}
|
|
|
|
// Create default AI Tools
|
|
if (createdTables['AI Tools'] && !createdTables['AI Tools'].existed) {
|
|
const { getDefaultTools } = await import('./setupTablesData.js');
|
|
const defaultTools = getDefaultTools();
|
|
for (const tool of defaultTools) {
|
|
await dbRun(`
|
|
INSERT INTO table_rows (table_id, base_id, data, created_at, updated_at)
|
|
VALUES (?, ?, ?, datetime('now'), datetime('now'))
|
|
`, [createdTables['AI Tools'].id, generateBaseId('tool'), JSON.stringify(tool)]);
|
|
}
|
|
}
|
|
|
|
return success(res, { tables: createdTables, projectId });
|
|
} catch (err) {
|
|
apiLogger.error({ err, context: 'AI Setup' }, 'Error');
|
|
return error(res, 'SETUP_TABLES_ERROR', 'Failed to setup AI tables', 500);
|
|
}
|
|
});
|
|
|
|
export default router;
|