plugin-postgresql-connector
Version:
NocoBase plugin for connecting to external PostgreSQL databases
417 lines • 16.8 kB
JavaScript
;
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