UNPKG

plugin-postgresql-connector

Version:

NocoBase plugin for connecting to external PostgreSQL databases

226 lines (208 loc) 5.63 kB
import { DataTypes, Model } from 'sequelize'; import { Database } from '@nocobase/database'; export class SavedQuery extends Model { static init(database: Database) { super.init({ id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, }, connectionId: { type: DataTypes.UUID, allowNull: false, references: { model: 'postgresql_connections', key: 'id', }, validate: { notEmpty: true, }, }, name: { type: DataTypes.STRING, allowNull: false, validate: { notEmpty: true, len: [1, 255], }, }, query: { type: DataTypes.TEXT, allowNull: false, validate: { notEmpty: true, }, }, queryType: { type: DataTypes.ENUM('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'PROCEDURE', 'FUNCTION', 'VIEW', 'OTHER'), allowNull: false, defaultValue: 'SELECT', }, parameters: { type: DataTypes.JSON, defaultValue: [], validate: { isValidParameters(value: any) { if (!Array.isArray(value)) { throw new Error('Parameters must be an array'); } }, }, }, description: { type: DataTypes.TEXT, allowNull: true, }, category: { type: DataTypes.STRING, allowNull: true, defaultValue: 'general', }, tags: { type: DataTypes.JSON, defaultValue: [], validate: { isValidTags(value: any) { if (!Array.isArray(value)) { throw new Error('Tags must be an array'); } }, }, }, isPublic: { type: DataTypes.BOOLEAN, defaultValue: false, }, executionCount: { type: DataTypes.INTEGER, defaultValue: 0, }, lastExecutedAt: { type: DataTypes.DATE, allowNull: true, }, createdBy: { type: DataTypes.UUID, allowNull: true, // For future user association }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW, }, updatedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW, }, }, { sequelize: database.sequelize, modelName: 'SavedQuery', tableName: 'postgresql_saved_queries', timestamps: true, paranoid: true, // Soft delete indexes: [ { fields: ['connectionId'], }, { fields: ['queryType'], }, { fields: ['category'], }, { fields: ['isPublic'], }, { fields: ['createdBy'], }, { fields: ['name', 'connectionId'], unique: true, where: { deletedAt: null, }, }, ], }); } static associate(models: any) { this.belongsTo(models.Connection, { foreignKey: 'connectionId', as: 'connection', onDelete: 'CASCADE', }); // Future: User association // this.belongsTo(models.User, { // foreignKey: 'createdBy', // as: 'creator', // }); } // Instance methods public async execute(): Promise<any> { // This will be implemented in service layer return {}; } public incrementExecutionCount(): void { this.setDataValue('executionCount', this.getDataValue('executionCount') + 1); this.setDataValue('lastExecutedAt', new Date()); } public addTag(tag: string): void { const tags = this.getDataValue('tags') || []; if (!tags.includes(tag)) { tags.push(tag); this.setDataValue('tags', tags); } } public removeTag(tag: string): void { const tags = this.getDataValue('tags') || []; const filteredTags = tags.filter((t: string) => t !== tag); this.setDataValue('tags', filteredTags); } public getParameterNames(): string[] { const query = this.getDataValue('query'); const parameterRegex = /\$\{(\w+)\}/g; const parameters: string[] = []; let match; while ((match = parameterRegex.exec(query)) !== null) { if (!parameters.includes(match[1])) { parameters.push(match[1]); } } return parameters; } // Validate query syntax public validateQuery(): { isValid: boolean; errors: string[] } { const query = this.getDataValue('query').trim(); const errors: string[] = []; if (!query) { errors.push('Query cannot be empty'); } // Basic SQL validation const dangerousKeywords = ['DROP', 'TRUNCATE', 'ALTER', 'CREATE USER', 'GRANT', 'REVOKE']; const upperQuery = query.toUpperCase(); for (const keyword of dangerousKeywords) { if (upperQuery.includes(keyword)) { errors.push(`Potentially dangerous keyword detected: ${keyword}`); } } return { isValid: errors.length === 0, errors, }; } } export default SavedQuery; export interface QueryParameter { name: string; type: string; defaultValue?: any; required: boolean; } export interface SavedQueryData { name: string; query: string; queryType: 'SELECT' | 'PROCEDURE' | 'FUNCTION' | 'VIEW'; parameters?: QueryParameter[]; description?: string; }