UNPKG

@syngrisi/syngrisi

Version:
205 lines (168 loc) 6.29 kB
import mongoose, { Schema, Document, Model } from 'mongoose'; import { toJSON } from './plugins'; import { PluginExtededModel } from './plugins/utils'; /** * Plugin Settings Model * Stores plugin configurations that override environment variables. * Settings from DB have priority over ENV variables. */ export interface PluginSettingValue { value: unknown; source: 'db' | 'env' | 'default'; } export interface PluginSettingsDocument extends Document { /** Unique plugin identifier (e.g., 'jwt-auth', 'custom-validator') */ pluginName: string; /** Plugin display name for UI */ displayName: string; /** Plugin description */ description?: string; /** Whether plugin is enabled (from UI, overrides ENV) */ enabled: boolean; /** Plugin settings as key-value pairs */ settings: Record<string, unknown>; /** Schema definition for settings (for UI form generation) */ settingsSchema?: PluginSettingSchema[]; /** Last update timestamp */ updatedAt: Date; /** Created timestamp */ createdAt: Date; } export interface PluginSettingSchema { /** Setting key */ key: string; /** Display label */ label: string; /** Description/help text */ description?: string; /** Input type: string, number, boolean, select, password */ type: 'string' | 'number' | 'boolean' | 'select' | 'password'; /** Default value */ defaultValue?: unknown; /** Corresponding environment variable */ envVariable?: string; /** Options for select type */ options?: Array<{ value: string; label: string }>; /** Whether this setting is required */ required?: boolean; } const PluginSettingSchemaDefinition = new Schema({ key: { type: String, required: true }, label: { type: String, required: true }, description: { type: String }, type: { type: String, enum: ['string', 'number', 'boolean', 'select', 'password'], required: true }, defaultValue: { type: Schema.Types.Mixed }, envVariable: { type: String }, options: [{ value: String, label: String }], required: { type: Boolean, default: false }, }, { _id: false }); const PluginSettingsSchema: Schema<PluginSettingsDocument> = new Schema({ pluginName: { type: String, unique: true, required: [true, 'PluginSettings: pluginName is required'], index: true, }, displayName: { type: String, required: [true, 'PluginSettings: displayName is required'], }, description: { type: String, }, enabled: { type: Boolean, default: false, }, settings: { type: Schema.Types.Mixed, default: {}, }, settingsSchema: { type: [PluginSettingSchemaDefinition], default: [], }, }, { timestamps: true, }); PluginSettingsSchema.plugin(toJSON); /** * Static method to get effective config (merged DB + ENV) */ PluginSettingsSchema.statics.getEffectiveConfig = async function ( pluginName: string, envPrefix: string = 'SYNGRISI_PLUGIN_' ): Promise<{ config: Record<string, PluginSettingValue>; enabled: boolean }> { const doc = await this.findOne({ pluginName }); // Build env key from plugin name (e.g., 'jwt-auth' -> 'JWT_AUTH') const envPluginKey = pluginName.toUpperCase().replace(/-/g, '_'); // Check if enabled (DB has priority over ENV) const envEnabledKey = `${envPrefix}${envPluginKey}_ENABLED`; const envEnabled = process.env[envEnabledKey]?.toLowerCase() === 'true'; const dbEnabled = doc?.enabled; // DB takes priority if document exists const enabled = doc ? dbEnabled : envEnabled; const config: Record<string, PluginSettingValue> = {}; // Get schema from document or empty array const schema = doc?.settingsSchema || []; // Fallback: if schema is empty but DB settings exist, return raw DB settings if (schema.length === 0 && doc?.settings && Object.keys(doc.settings).length > 0) { for (const [key, value] of Object.entries(doc.settings)) { config[key] = { value, source: 'db' }; } return { config, enabled }; } for (const field of schema) { const envKey = field.envVariable || `${envPrefix}${envPluginKey}_${field.key.toUpperCase()}`; const envValue = process.env[envKey]; const dbValue = doc?.settings?.[field.key]; // DB value takes priority over ENV if (dbValue !== undefined) { config[field.key] = { value: dbValue, source: 'db' }; } else if (envValue !== undefined) { // Parse env value based on type let parsedValue: unknown = envValue; if (field.type === 'boolean') { parsedValue = envValue.toLowerCase() === 'true'; } else if (field.type === 'number') { parsedValue = parseFloat(envValue); } config[field.key] = { value: parsedValue, source: 'env' }; } else if (field.defaultValue !== undefined) { config[field.key] = { value: field.defaultValue, source: 'default' }; } } return { config, enabled }; }; /** * Static method to upsert plugin settings */ PluginSettingsSchema.statics.upsertSettings = async function ( pluginName: string, updates: Partial<Pick<PluginSettingsDocument, 'enabled' | 'settings' | 'displayName' | 'description' | 'settingsSchema'>> ): Promise<PluginSettingsDocument> { return this.findOneAndUpdate( { pluginName }, { $set: updates }, { upsert: true, new: true, runValidators: true } ); }; export interface PluginSettingsModel extends Model<PluginSettingsDocument> { getEffectiveConfig( pluginName: string, envPrefix?: string ): Promise<{ config: Record<string, PluginSettingValue>; enabled: boolean }>; upsertSettings( pluginName: string, updates: Partial<Pick<PluginSettingsDocument, 'enabled' | 'settings' | 'displayName' | 'description' | 'settingsSchema'>> ): Promise<PluginSettingsDocument>; } const PluginSettings = mongoose.model<PluginSettingsDocument, PluginSettingsModel>( 'VRSPluginSettings', PluginSettingsSchema ); export default PluginSettings;