662 lines
20 KiB
TypeScript
662 lines
20 KiB
TypeScript
import { readFile, writeFile, mkdir, access, constants } from 'fs/promises';
|
|
import { existsSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from 'crypto';
|
|
import type { NocoDBSettings, SMTPConfig } from '~/utils/types';
|
|
|
|
interface AdminConfiguration {
|
|
nocodb: NocoDBSettings;
|
|
recaptcha?: {
|
|
siteKey: string;
|
|
secretKey: string; // Will be encrypted
|
|
};
|
|
registration?: {
|
|
membershipFee: number;
|
|
iban: string;
|
|
accountHolder: string;
|
|
};
|
|
smtp?: {
|
|
host: string;
|
|
port: number;
|
|
secure: boolean;
|
|
username: string;
|
|
password: string; // Will be encrypted
|
|
fromAddress: string;
|
|
fromName: string;
|
|
};
|
|
lastUpdated: string;
|
|
updatedBy: string;
|
|
}
|
|
|
|
interface EffectiveNocoDB {
|
|
url: string;
|
|
token: string;
|
|
baseId: string;
|
|
tables: {
|
|
members: string;
|
|
events: string;
|
|
rsvps: string;
|
|
[tableName: string]: string;
|
|
};
|
|
}
|
|
|
|
// Support both Docker (/app/data) and local development (./data) paths
|
|
const getConfigDir = () => {
|
|
// Check if running in Docker container
|
|
if (process.env.NODE_ENV === 'production' || existsSync('/app/data')) {
|
|
return '/app/data';
|
|
}
|
|
// Local development
|
|
return './data';
|
|
};
|
|
|
|
const CONFIG_DIR = getConfigDir();
|
|
const CONFIG_FILE = join(CONFIG_DIR, 'admin-config.json');
|
|
const BACKUP_FILE = join(CONFIG_DIR, 'admin-config.backup.json');
|
|
const LOG_FILE = join(CONFIG_DIR, 'admin-config.log');
|
|
|
|
// In-memory cache for runtime configuration
|
|
let configCache: AdminConfiguration | null = null;
|
|
|
|
/**
|
|
* Ensure configuration directory exists
|
|
*/
|
|
async function ensureConfigDir(): Promise<void> {
|
|
try {
|
|
await access(CONFIG_DIR, constants.F_OK);
|
|
} catch {
|
|
await mkdir(CONFIG_DIR, { recursive: true });
|
|
console.log(`[admin-config] Created config directory: ${CONFIG_DIR}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encrypt sensitive data using the app's encryption key
|
|
*/
|
|
function encryptSensitiveData(data: string): string {
|
|
const runtimeConfig = useRuntimeConfig();
|
|
const encryptionKey = runtimeConfig.encryptionKey;
|
|
|
|
if (!encryptionKey || encryptionKey.length < 16) {
|
|
console.warn('[admin-config] No encryption key found or key too short, storing data as-is');
|
|
return data;
|
|
}
|
|
|
|
try {
|
|
// Use AES-256-GCM for modern encryption
|
|
const algorithm = 'aes-256-gcm';
|
|
const iv = randomBytes(16);
|
|
const salt = randomBytes(16);
|
|
|
|
// Derive key from the encryption key
|
|
const key = pbkdf2Sync(encryptionKey, salt, 100000, 32, 'sha256');
|
|
|
|
const cipher = createCipheriv(algorithm, key, iv);
|
|
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
|
|
const authTag = cipher.getAuthTag();
|
|
|
|
// Combine salt, iv, authTag, and encrypted data
|
|
const combined = salt.toString('hex') + ':' + iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
|
|
return combined;
|
|
} catch (error) {
|
|
console.error('[admin-config] Encryption failed:', error);
|
|
return data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrypt sensitive data using the app's encryption key
|
|
*/
|
|
function decryptSensitiveData(encryptedData: string): string {
|
|
const runtimeConfig = useRuntimeConfig();
|
|
const encryptionKey = runtimeConfig.encryptionKey;
|
|
|
|
if (!encryptionKey || encryptionKey.length < 16) {
|
|
return encryptedData;
|
|
}
|
|
|
|
try {
|
|
// Check if data contains our modern format with colons
|
|
if (!encryptedData.includes(':')) {
|
|
// Legacy data, return as-is
|
|
console.warn('[admin-config] Legacy encrypted data format detected, returning as-is');
|
|
return encryptedData;
|
|
}
|
|
|
|
const parts = encryptedData.split(':');
|
|
if (parts.length !== 4) {
|
|
throw new Error('Invalid encrypted data format');
|
|
}
|
|
|
|
const [saltHex, ivHex, authTagHex, encrypted] = parts;
|
|
const salt = Buffer.from(saltHex, 'hex');
|
|
const iv = Buffer.from(ivHex, 'hex');
|
|
const authTag = Buffer.from(authTagHex, 'hex');
|
|
|
|
// Derive key from the encryption key
|
|
const key = pbkdf2Sync(encryptionKey, salt, 100000, 32, 'sha256');
|
|
|
|
const algorithm = 'aes-256-gcm';
|
|
const decipher = createDecipheriv(algorithm, key, iv);
|
|
decipher.setAuthTag(authTag);
|
|
|
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return decrypted;
|
|
} catch (error) {
|
|
console.error('[admin-config] Decryption failed:', error);
|
|
return encryptedData;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log configuration changes for audit trail
|
|
*/
|
|
async function logConfigChange(action: string, user: string, details?: any): Promise<void> {
|
|
const logEntry = {
|
|
timestamp: new Date().toISOString(),
|
|
action,
|
|
user,
|
|
details: details || {}
|
|
};
|
|
|
|
try {
|
|
await ensureConfigDir();
|
|
const logLine = JSON.stringify(logEntry) + '\n';
|
|
await writeFile(LOG_FILE, logLine, { flag: 'a' });
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to write log:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create backup of current configuration before changes
|
|
*/
|
|
async function createBackup(): Promise<void> {
|
|
try {
|
|
await access(CONFIG_FILE, constants.F_OK);
|
|
const currentConfig = await readFile(CONFIG_FILE, 'utf-8');
|
|
await writeFile(BACKUP_FILE, currentConfig, 'utf-8');
|
|
console.log('[admin-config] Configuration backup created');
|
|
} catch (error) {
|
|
// No existing config to backup
|
|
console.log('[admin-config] No existing config to backup');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load configuration from file
|
|
*/
|
|
export async function loadAdminConfig(): Promise<AdminConfiguration | null> {
|
|
try {
|
|
await ensureConfigDir();
|
|
const configData = await readFile(CONFIG_FILE, 'utf-8');
|
|
const config: AdminConfiguration = JSON.parse(configData);
|
|
|
|
// Decrypt sensitive data
|
|
if (config.nocodb.apiKey) {
|
|
config.nocodb.apiKey = decryptSensitiveData(config.nocodb.apiKey);
|
|
}
|
|
if (config.recaptcha?.secretKey) {
|
|
config.recaptcha.secretKey = decryptSensitiveData(config.recaptcha.secretKey);
|
|
}
|
|
if (config.smtp?.password) {
|
|
config.smtp.password = decryptSensitiveData(config.smtp.password);
|
|
}
|
|
|
|
console.log('[admin-config] Configuration loaded from file');
|
|
configCache = config;
|
|
|
|
// Update global nocodb configuration
|
|
try {
|
|
const { setGlobalNocoDBConfig } = await import('./nocodb');
|
|
setGlobalNocoDBConfig(getEffectiveNocoDBConfig());
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to update global configuration:', error);
|
|
}
|
|
|
|
return config;
|
|
} catch (error) {
|
|
console.log('[admin-config] No configuration file found, using defaults');
|
|
configCache = null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save configuration to file
|
|
*/
|
|
export async function saveAdminConfig(config: NocoDBSettings, updatedBy: string): Promise<void> {
|
|
try {
|
|
await ensureConfigDir();
|
|
await createBackup();
|
|
|
|
// Prepare configuration with encrypted sensitive data
|
|
const adminConfig: AdminConfiguration = {
|
|
nocodb: {
|
|
...config,
|
|
apiKey: config.apiKey ? encryptSensitiveData(config.apiKey) : ''
|
|
},
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy
|
|
};
|
|
|
|
const configJson = JSON.stringify(adminConfig, null, 2);
|
|
await writeFile(CONFIG_FILE, configJson, 'utf-8');
|
|
|
|
// Update cache with unencrypted data for runtime use
|
|
configCache = {
|
|
...adminConfig,
|
|
nocodb: { ...config } // Keep original unencrypted data in cache
|
|
};
|
|
|
|
console.log('[admin-config] Configuration saved to file');
|
|
await logConfigChange('CONFIG_SAVED', updatedBy, { url: config.url, baseId: config.baseId });
|
|
|
|
// Update global nocodb configuration immediately
|
|
try {
|
|
const { setGlobalNocoDBConfig } = await import('./nocodb');
|
|
setGlobalNocoDBConfig(getEffectiveNocoDBConfig());
|
|
console.log('[admin-config] Global configuration updated immediately');
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to update global configuration after save:', error);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to save configuration:', error);
|
|
await logConfigChange('CONFIG_SAVE_FAILED', updatedBy, { error: error instanceof Error ? error.message : String(error) });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get effective NocoDB configuration (file config merged with env defaults)
|
|
*/
|
|
export function getEffectiveNocoDBConfig(): EffectiveNocoDB {
|
|
const runtimeConfig = useRuntimeConfig();
|
|
|
|
// Start with environment variables as defaults
|
|
const envConfig = {
|
|
url: runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
|
|
token: runtimeConfig.nocodb?.token || '',
|
|
baseId: runtimeConfig.nocodb?.baseId || '',
|
|
tables: {
|
|
members: 'members-table-id',
|
|
events: (runtimeConfig.nocodb as any)?.eventsTableId || '',
|
|
rsvps: (runtimeConfig.nocodb as any)?.rsvpTableId || ''
|
|
} // Default table mapping
|
|
};
|
|
|
|
// Override with file configuration if available
|
|
if (configCache?.nocodb) {
|
|
return {
|
|
url: configCache.nocodb.url || envConfig.url,
|
|
token: configCache.nocodb.apiKey || envConfig.token,
|
|
baseId: configCache.nocodb.baseId || envConfig.baseId,
|
|
tables: configCache.nocodb.tables || envConfig.tables
|
|
};
|
|
}
|
|
|
|
return envConfig;
|
|
}
|
|
|
|
/**
|
|
* Set effective NocoDB configuration (updates cache and saves to file)
|
|
*/
|
|
export async function setEffectiveNocoDBConfig(config: EffectiveNocoDB): Promise<void> {
|
|
try {
|
|
await ensureConfigDir();
|
|
await createBackup();
|
|
|
|
// Get current config or create new one
|
|
const currentConfig = configCache || await loadAdminConfig() || {
|
|
nocodb: { url: '', apiKey: '', baseId: '', tables: { members: '', events: '', rsvps: '' } },
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy: 'system'
|
|
};
|
|
|
|
// Update with new configuration
|
|
const updatedConfig: AdminConfiguration = {
|
|
...currentConfig,
|
|
nocodb: {
|
|
url: config.url,
|
|
apiKey: config.token,
|
|
baseId: config.baseId,
|
|
tables: config.tables
|
|
},
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy: 'admin-api'
|
|
};
|
|
|
|
// Encrypt sensitive data for storage
|
|
const configForStorage = {
|
|
...updatedConfig,
|
|
nocodb: {
|
|
...updatedConfig.nocodb,
|
|
apiKey: updatedConfig.nocodb.apiKey ? encryptSensitiveData(updatedConfig.nocodb.apiKey) : ''
|
|
}
|
|
};
|
|
|
|
const configJson = JSON.stringify(configForStorage, null, 2);
|
|
await writeFile(CONFIG_FILE, configJson, 'utf-8');
|
|
|
|
// Update cache with unencrypted data for runtime use
|
|
configCache = updatedConfig;
|
|
|
|
console.log('[admin-config] Effective NocoDB configuration updated');
|
|
|
|
// Update global nocodb configuration immediately
|
|
try {
|
|
const { setGlobalNocoDBConfig } = await import('./nocodb');
|
|
setGlobalNocoDBConfig(config);
|
|
console.log('[admin-config] Global configuration updated immediately');
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to update global configuration after save:', error);
|
|
}
|
|
|
|
await logConfigChange('EFFECTIVE_CONFIG_UPDATED', 'admin-api', {
|
|
url: config.url,
|
|
baseId: config.baseId,
|
|
tables: Object.keys(config.tables)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to set effective NocoDB configuration:', error);
|
|
await logConfigChange('EFFECTIVE_CONFIG_UPDATE_FAILED', 'admin-api', {
|
|
error: error instanceof Error ? error.message : String(error)
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current configuration for display (with masked sensitive data)
|
|
*/
|
|
export async function getCurrentConfig(): Promise<NocoDBSettings> {
|
|
const config = configCache || await loadAdminConfig();
|
|
const runtimeConfig = useRuntimeConfig();
|
|
|
|
if (config?.nocodb) {
|
|
return {
|
|
url: config.nocodb.url || runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
|
|
apiKey: config.nocodb.apiKey ? '••••••••••••••••' : '',
|
|
baseId: config.nocodb.baseId || runtimeConfig.nocodb?.baseId || '',
|
|
tables: config.nocodb.tables || {
|
|
members: 'members-table-id',
|
|
events: '',
|
|
rsvps: ''
|
|
}
|
|
};
|
|
}
|
|
|
|
// Return defaults from environment if no file config
|
|
return {
|
|
url: runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
|
|
apiKey: runtimeConfig.nocodb?.token ? '••••••••••••••••' : '',
|
|
baseId: runtimeConfig.nocodb?.baseId || '',
|
|
tables: {
|
|
members: 'members-table-id',
|
|
events: '',
|
|
rsvps: ''
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Save reCAPTCHA configuration
|
|
*/
|
|
export async function saveRecaptchaConfig(config: { siteKey: string; secretKey: string }, updatedBy: string): Promise<void> {
|
|
try {
|
|
await ensureConfigDir();
|
|
await createBackup();
|
|
|
|
const currentConfig = configCache || await loadAdminConfig() || {
|
|
nocodb: { url: '', apiKey: '', baseId: '', tables: { members: '', events: '', rsvps: '' } },
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy: 'system'
|
|
};
|
|
|
|
const updatedConfig: AdminConfiguration = {
|
|
...currentConfig,
|
|
recaptcha: {
|
|
siteKey: config.siteKey,
|
|
secretKey: encryptSensitiveData(config.secretKey)
|
|
},
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy
|
|
};
|
|
|
|
const configJson = JSON.stringify(updatedConfig, null, 2);
|
|
await writeFile(CONFIG_FILE, configJson, 'utf-8');
|
|
|
|
// Update cache with unencrypted data
|
|
configCache = {
|
|
...updatedConfig,
|
|
recaptcha: {
|
|
siteKey: config.siteKey,
|
|
secretKey: config.secretKey
|
|
}
|
|
};
|
|
|
|
console.log('[admin-config] reCAPTCHA configuration saved');
|
|
await logConfigChange('RECAPTCHA_CONFIG_SAVED', updatedBy, { siteKey: config.siteKey });
|
|
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to save reCAPTCHA configuration:', error);
|
|
await logConfigChange('RECAPTCHA_CONFIG_SAVE_FAILED', updatedBy, { error: error instanceof Error ? error.message : String(error) });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save registration configuration
|
|
*/
|
|
export async function saveRegistrationConfig(config: { membershipFee: number; iban: string; accountHolder: string }, updatedBy: string): Promise<void> {
|
|
try {
|
|
await ensureConfigDir();
|
|
await createBackup();
|
|
|
|
const currentConfig = configCache || await loadAdminConfig() || {
|
|
nocodb: { url: '', apiKey: '', baseId: '', tables: { members: '', events: '', rsvps: '' } },
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy: 'system'
|
|
};
|
|
|
|
const updatedConfig: AdminConfiguration = {
|
|
...currentConfig,
|
|
registration: config,
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy
|
|
};
|
|
|
|
const configJson = JSON.stringify(updatedConfig, null, 2);
|
|
await writeFile(CONFIG_FILE, configJson, 'utf-8');
|
|
|
|
configCache = updatedConfig;
|
|
|
|
console.log('[admin-config] Registration configuration saved');
|
|
await logConfigChange('REGISTRATION_CONFIG_SAVED', updatedBy, { membershipFee: config.membershipFee });
|
|
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to save registration configuration:', error);
|
|
await logConfigChange('REGISTRATION_CONFIG_SAVE_FAILED', updatedBy, { error: error instanceof Error ? error.message : String(error) });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get reCAPTCHA configuration
|
|
*/
|
|
export function getRecaptchaConfig(): { siteKey: string; secretKey: string } {
|
|
const config = configCache?.recaptcha || { siteKey: '', secretKey: '' };
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Get registration configuration
|
|
*/
|
|
export function getRegistrationConfig(): { membershipFee: number; iban: string; accountHolder: string } {
|
|
const config = configCache?.registration || {
|
|
membershipFee: 50,
|
|
iban: '',
|
|
accountHolder: ''
|
|
};
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Save SMTP configuration
|
|
*/
|
|
export async function saveSMTPConfig(config: SMTPConfig, updatedBy: string): Promise<void> {
|
|
try {
|
|
await ensureConfigDir();
|
|
await createBackup();
|
|
|
|
const currentConfig = configCache || await loadAdminConfig() || {
|
|
nocodb: { url: '', apiKey: '', baseId: '', tables: { members: '', events: '', rsvps: '' } },
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy: 'system'
|
|
};
|
|
|
|
const updatedConfig: AdminConfiguration = {
|
|
...currentConfig,
|
|
smtp: {
|
|
...config,
|
|
password: encryptSensitiveData(config.password)
|
|
},
|
|
lastUpdated: new Date().toISOString(),
|
|
updatedBy
|
|
};
|
|
|
|
const configJson = JSON.stringify(updatedConfig, null, 2);
|
|
await writeFile(CONFIG_FILE, configJson, 'utf-8');
|
|
|
|
// Update cache with unencrypted data
|
|
configCache = {
|
|
...updatedConfig,
|
|
smtp: { ...config } // Keep original unencrypted data in cache
|
|
};
|
|
|
|
console.log('[admin-config] SMTP configuration saved');
|
|
await logConfigChange('SMTP_CONFIG_SAVED', updatedBy, { host: config.host, fromAddress: config.fromAddress });
|
|
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to save SMTP configuration:', error);
|
|
await logConfigChange('SMTP_CONFIG_SAVE_FAILED', updatedBy, { error: error instanceof Error ? error.message : String(error) });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get SMTP configuration
|
|
*/
|
|
export function getSMTPConfig(): SMTPConfig {
|
|
const config = configCache?.smtp || {
|
|
host: '',
|
|
port: 587,
|
|
secure: false,
|
|
username: '',
|
|
password: '',
|
|
fromAddress: '',
|
|
fromName: 'MonacoUSA Portal'
|
|
};
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Force reload configuration from file and update cache
|
|
*/
|
|
export async function reloadAdminConfig(): Promise<void> {
|
|
console.log('[admin-config] Force reloading configuration from file...');
|
|
|
|
try {
|
|
// Clear cache to force reload
|
|
configCache = null;
|
|
|
|
// Load fresh from file
|
|
await loadAdminConfig();
|
|
|
|
// Update global nocodb configuration
|
|
try {
|
|
const { setGlobalNocoDBConfig } = await import('./nocodb');
|
|
setGlobalNocoDBConfig(getEffectiveNocoDBConfig());
|
|
console.log('[admin-config] Global configuration updated after reload');
|
|
} catch (error) {
|
|
console.error('[admin-config] Failed to update global configuration after reload:', error);
|
|
}
|
|
|
|
console.log('[admin-config] ✅ Configuration reloaded successfully');
|
|
} catch (error) {
|
|
console.error('[admin-config] ❌ Failed to reload configuration:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize configuration system on server startup
|
|
*/
|
|
export async function initAdminConfig(): Promise<void> {
|
|
console.log('[admin-config] Initializing admin configuration system...');
|
|
|
|
try {
|
|
await ensureConfigDir();
|
|
|
|
// Load configuration and initialize cache
|
|
const config = await loadAdminConfig();
|
|
|
|
if (config) {
|
|
console.log('[admin-config] ✅ Configuration loaded from existing file');
|
|
console.log('[admin-config] Configuration summary:', {
|
|
nocodb: {
|
|
url: config.nocodb.url || 'not set',
|
|
hasApiKey: !!config.nocodb.apiKey,
|
|
baseId: config.nocodb.baseId || 'not set'
|
|
},
|
|
smtp: {
|
|
host: config.smtp?.host || 'not set',
|
|
port: config.smtp?.port || 'not set',
|
|
hasAuth: !!(config.smtp?.username && config.smtp?.password)
|
|
},
|
|
recaptcha: {
|
|
hasSiteKey: !!config.recaptcha?.siteKey,
|
|
hasSecretKey: !!config.recaptcha?.secretKey
|
|
},
|
|
registration: {
|
|
membershipFee: config.registration?.membershipFee || 'not set',
|
|
hasIban: !!config.registration?.iban
|
|
}
|
|
});
|
|
} else {
|
|
console.log('[admin-config] ⚠️ No existing configuration file found, using defaults');
|
|
}
|
|
|
|
console.log('[admin-config] ✅ Admin configuration system initialized');
|
|
} catch (error) {
|
|
console.error('[admin-config] ❌ Failed to initialize admin configuration:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get configuration status for health checks
|
|
*/
|
|
export function getConfigStatus(): {
|
|
loaded: boolean;
|
|
nocodb: boolean;
|
|
smtp: boolean;
|
|
recaptcha: boolean;
|
|
registration: boolean;
|
|
} {
|
|
const config = configCache;
|
|
|
|
return {
|
|
loaded: !!config,
|
|
nocodb: !!(config?.nocodb?.url && config?.nocodb?.apiKey && config?.nocodb?.baseId),
|
|
smtp: !!(config?.smtp?.host && config?.smtp?.port),
|
|
recaptcha: !!(config?.recaptcha?.siteKey && config?.recaptcha?.secretKey),
|
|
registration: !!(config?.registration?.membershipFee && config?.registration?.iban)
|
|
};
|
|
}
|