UNPKG

plugin-postgresql-connector

Version:

NocoBase plugin for connecting to external PostgreSQL databases

417 lines 16.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SchemaController = void 0; const joi_1 = __importDefault(require("joi")); const schemaQuerySchema = joi_1.default.object({ connectionId: joi_1.default.string().uuid().required(), schema: joi_1.default.string().default('public'), }); const tableQuerySchema = joi_1.default.object({ connectionId: joi_1.default.string().uuid().required(), tableName: joi_1.default.string().required(), schema: joi_1.default.string().default('public'), }); const searchObjectsSchema = joi_1.default.object({ connectionId: joi_1.default.string().uuid().required(), searchTerm: joi_1.default.string().min(1).required(), objectTypes: joi_1.default.array().items(joi_1.default.string().valid('table', 'view', 'function')).default(['table', 'view', 'function']), schema: joi_1.default.string().default('public'), }); class SchemaController { constructor(connectionManager, schemaService) { this.connectionManager = connectionManager; this.schemaService = schemaService; } async getDatabaseInfo(ctx) { const { connectionId } = ctx.params; if (!connectionId) { ctx.throw(400, 'Connection ID is required'); } try { await this.validateConnection(connectionId); const databaseInfo = await this.schemaService.getDatabaseInfo(connectionId); ctx.body = { success: true, data: databaseInfo, meta: { fetchedAt: new Date().toISOString(), connectionId, }, }; } catch (error) { ctx.throw(500, `Failed to fetch database info: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getSchemaStatistics(ctx) { const { error, value } = schemaQuerySchema.validate({ connectionId: ctx.params.connectionId, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, schema } = value; try { await this.validateConnection(connectionId); const statistics = await this.schemaService.getSchemaStatistics(connectionId, schema); ctx.body = { success: true, data: statistics, meta: { fetchedAt: new Date().toISOString(), connectionId, schema, }, }; } catch (error) { ctx.throw(500, `Failed to fetch schema statistics: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getTables(ctx) { const { error, value } = schemaQuerySchema.validate({ connectionId: ctx.params.connectionId, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, schema } = value; try { await this.validateConnection(connectionId); const tables = await this.schemaService.getTables(connectionId, schema); ctx.body = { success: true, data: tables, meta: { fetchedAt: new Date().toISOString(), connectionId, schema, totalTables: tables.length, }, }; } catch (error) { ctx.throw(500, `Failed to fetch tables: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getTableColumns(ctx) { const { error, value } = tableQuerySchema.validate({ connectionId: ctx.params.connectionId, tableName: ctx.params.tableName, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, tableName, schema } = value; try { await this.validateConnection(connectionId); const columns = await this.schemaService.getTableColumns(connectionId, tableName, schema); // Group columns by type for easier display const columnsByType = { primary: columns.filter(col => col.is_primary_key), foreign: columns.filter(col => col.is_foreign_key && !col.is_primary_key), regular: columns.filter(col => !col.is_primary_key && !col.is_foreign_key), }; ctx.body = { success: true, data: { columns, columnsByType, totalColumns: columns.length, }, meta: { fetchedAt: new Date().toISOString(), connectionId, tableName, schema, }, }; } catch (error) { ctx.throw(500, `Failed to fetch table columns: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getViews(ctx) { const { error, value } = schemaQuerySchema.validate({ connectionId: ctx.params.connectionId, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, schema } = value; try { await this.validateConnection(connectionId); const views = await this.schemaService.getViews(connectionId, schema); ctx.body = { success: true, data: views, meta: { fetchedAt: new Date().toISOString(), connectionId, schema, totalViews: views.length, }, }; } catch (error) { ctx.throw(500, `Failed to fetch views: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getFunctions(ctx) { const { error, value } = schemaQuerySchema.validate({ connectionId: ctx.params.connectionId, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, schema } = value; try { await this.validateConnection(connectionId); const functions = await this.schemaService.getFunctions(connectionId, schema); // Group functions by type const functionsByType = { functions: functions.filter(f => f.routine_type === 'FUNCTION'), procedures: functions.filter(f => f.routine_type === 'PROCEDURE'), }; ctx.body = { success: true, data: { functions, functionsByType, totalFunctions: functions.length, }, meta: { fetchedAt: new Date().toISOString(), connectionId, schema, }, }; } catch (error) { ctx.throw(500, `Failed to fetch functions: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async searchObjects(ctx) { const { error, value } = searchObjectsSchema.validate({ connectionId: ctx.query.connectionId, searchTerm: ctx.query.searchTerm, objectTypes: ctx.query.objectTypes ? ctx.query.objectTypes.split(',') : undefined, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, searchTerm, objectTypes, schema } = value; try { await this.validateConnection(connectionId); const results = await this.schemaService.searchObjects(connectionId, searchTerm, objectTypes, schema); // Group results by type const resultsByType = { tables: results.filter(r => r.type === 'table'), views: results.filter(r => r.type === 'view'), functions: results.filter(r => r.type === 'FUNCTION' || r.type === 'PROCEDURE'), }; ctx.body = { success: true, data: { results, resultsByType, totalResults: results.length, }, meta: { searchTerm, objectTypes, schema, connectionId, searchedAt: new Date().toISOString(), }, }; } catch (error) { ctx.throw(500, `Failed to search objects: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getTableRelationships(ctx) { const { error, value } = tableQuerySchema.validate({ connectionId: ctx.params.connectionId, tableName: ctx.params.tableName, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, tableName, schema } = value; try { await this.validateConnection(connectionId); const relationships = await this.schemaService.getTableRelationships(connectionId, tableName, schema); // Group relationships by type const relationshipsByType = { primaryKeys: relationships.filter(r => r.constraint_type === 'PRIMARY KEY'), foreignKeys: relationships.filter(r => r.constraint_type === 'FOREIGN KEY'), unique: relationships.filter(r => r.constraint_type === 'UNIQUE'), check: relationships.filter(r => r.constraint_type === 'CHECK'), }; ctx.body = { success: true, data: { relationships, relationshipsByType, totalRelationships: relationships.length, }, meta: { fetchedAt: new Date().toISOString(), connectionId, tableName, schema, }, }; } catch (error) { ctx.throw(500, `Failed to fetch table relationships: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getTableStatistics(ctx) { const { error, value } = tableQuerySchema.validate({ connectionId: ctx.params.connectionId, tableName: ctx.params.tableName, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, tableName, schema } = value; try { await this.validateConnection(connectionId); const statistics = await this.schemaService.getTableStatistics(connectionId, tableName, schema); ctx.body = { success: true, data: statistics, meta: { fetchedAt: new Date().toISOString(), connectionId, tableName, schema, }, }; } catch (error) { ctx.throw(500, `Failed to fetch table statistics: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async getSchemaOverview(ctx) { const { error, value } = schemaQuerySchema.validate({ connectionId: ctx.params.connectionId, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, schema } = value; try { await this.validateConnection(connectionId); // Get comprehensive schema overview const [databaseInfo, statistics, tables, views, functions] = await Promise.all([ this.schemaService.getDatabaseInfo(connectionId), this.schemaService.getSchemaStatistics(connectionId, schema), this.schemaService.getTables(connectionId, schema), this.schemaService.getViews(connectionId, schema), this.schemaService.getFunctions(connectionId, schema), ]); const overview = { databaseInfo, statistics, recentTables: tables.slice(0, 10), // First 10 tables recentViews: views.slice(0, 10), // First 10 views recentFunctions: functions.slice(0, 10), // First 10 functions schemaHealth: { tablesWithoutPrimaryKey: tables.filter(t => !t.column_count || t.column_count === 0).length, emptyTables: tables.filter(t => !t.row_count || t.row_count === 0).length, largestTables: tables .filter(t => t.row_count && t.row_count > 0) .sort((a, b) => (b.row_count || 0) - (a.row_count || 0)) .slice(0, 5), }, }; ctx.body = { success: true, data: overview, meta: { fetchedAt: new Date().toISOString(), connectionId, schema, }, }; } catch (error) { ctx.throw(500, `Failed to fetch schema overview: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async exportSchemaDefinition(ctx) { const { error, value } = schemaQuerySchema.validate({ connectionId: ctx.params.connectionId, schema: ctx.query.schema, }); if (error) { ctx.throw(400, `Validation error: ${error.details[0].message}`); } const { connectionId, schema } = value; const format = ctx.query.format || 'json'; try { await this.validateConnection(connectionId); const [tables, views, functions] = await Promise.all([ this.schemaService.getTables(connectionId, schema), this.schemaService.getViews(connectionId, schema), this.schemaService.getFunctions(connectionId, schema), ]); // Get detailed column information for each table const detailedTables = await Promise.all(tables.map(async (table) => ({ ...table, columns: await this.schemaService.getTableColumns(connectionId, table.table_name, schema), }))); const schemaDefinition = { schema, exportedAt: new Date().toISOString(), tables: detailedTables, views, functions, statistics: { totalTables: tables.length, totalViews: views.length, totalFunctions: functions.length, }, }; if (format === 'json') { ctx.set('Content-Type', 'application/json'); ctx.set('Content-Disposition', `attachment; filename="schema_${schema}_${Date.now()}.json"`); ctx.body = schemaDefinition; } else { ctx.throw(400, 'Unsupported format. Only JSON is supported currently.'); } } catch (error) { ctx.throw(500, `Failed to export schema definition: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Helper methods async validateConnection(connectionId) { const { Connection } = require('../models/Connection'); const connection = await Connection.findOne({ where: { id: connectionId, isActive: true }, }); if (!connection) { throw new Error('Connection not found or inactive'); } } } exports.SchemaController = SchemaController; exports.default = SchemaController; //# sourceMappingURL=schema.js.map