Refactor NocoDB settings to support dynamic table configuration and update related validation
Build And Push Image / docker (push) Successful in 3m13s Details

This commit is contained in:
Matt 2025-08-07 20:43:39 +02:00
parent d0d7a34ae7
commit 22a74c6b33
6 changed files with 97 additions and 55 deletions

View File

@ -68,7 +68,7 @@
/>
</v-col>
<v-col cols="12" md="6">
<v-col cols="12">
<v-text-field
v-model="form.baseId"
label="Base ID"
@ -80,18 +80,25 @@
:error-messages="getFieldError('baseId')"
/>
</v-col>
<v-col cols="12">
<h3 class="text-h6 mb-4 text-primary">Table Configuration</h3>
</v-col>
<v-col cols="12" md="6">
<v-col cols="12">
<v-text-field
v-model="form.tableId"
v-model="form.tables.members"
label="Members Table ID"
variant="outlined"
:rules="[rules.required]"
required
placeholder="members-table-id"
:error="hasFieldError('tableId')"
:error-messages="getFieldError('tableId')"
:error="hasFieldError('tables.members')"
:error-messages="getFieldError('tables.members')"
/>
<div class="text-caption text-medium-emphasis mt-1">
Configure the table ID for the Members functionality
</div>
</v-col>
<v-col cols="12">
@ -210,7 +217,7 @@ const form = ref<NocoDBSettings>({
url: 'https://database.monacousa.org',
apiKey: '',
baseId: '',
tableId: ''
tables: { members: '' }
});
// Error handling
@ -261,6 +268,10 @@ const loadSettings = async () => {
if (response.success && response.data) {
form.value = { ...response.data };
// Ensure tables object exists
if (!form.value.tables) {
form.value.tables = { members: '' };
}
}
} catch (error: any) {
console.error('Failed to load NocoDB settings:', error);
@ -352,7 +363,7 @@ const resetForm = () => {
url: 'https://database.monacousa.org',
apiKey: '',
baseId: '',
tableId: ''
tables: { members: '' }
};
clearFieldErrors();
connectionStatus.value = null;

View File

@ -32,10 +32,10 @@ export default defineEventHandler(async (event) => {
const body = await readBody(event) as NocoDBSettings;
// Validate required fields
if (!body.url || !body.apiKey || !body.baseId || !body.tableId) {
if (!body.url || !body.apiKey || !body.baseId || !body.tables) {
throw createError({
statusCode: 400,
statusMessage: 'All fields are required: url, apiKey, baseId, tableId'
statusMessage: 'All fields are required: url, apiKey, baseId, tables'
});
}
@ -50,7 +50,7 @@ export default defineEventHandler(async (event) => {
console.log('[api/admin/nocodb-config.post] Saving NocoDB configuration...');
console.log('[api/admin/nocodb-config.post] URL:', body.url);
console.log('[api/admin/nocodb-config.post] Base ID:', body.baseId);
console.log('[api/admin/nocodb-config.post] Table ID:', body.tableId);
console.log('[api/admin/nocodb-config.post] Tables:', Object.keys(body.tables));
console.log('[api/admin/nocodb-config.post] API Key: [REDACTED]');
// Save configuration using the new admin config system

View File

@ -32,10 +32,10 @@ export default defineEventHandler(async (event) => {
const body = await readBody(event) as NocoDBSettings;
// Validate required fields
if (!body.url || !body.apiKey || !body.baseId || !body.tableId) {
if (!body.url || !body.apiKey || !body.baseId || !body.tables || Object.keys(body.tables).length === 0) {
return {
success: false,
message: 'All fields are required to test connection'
message: 'All fields are required to test connection (url, apiKey, baseId, and at least one table)'
};
}
@ -50,11 +50,13 @@ export default defineEventHandler(async (event) => {
console.log('[api/admin/nocodb-test.post] Testing NocoDB connection...');
console.log('[api/admin/nocodb-test.post] URL:', body.url);
console.log('[api/admin/nocodb-test.post] Base ID:', body.baseId);
console.log('[api/admin/nocodb-test.post] Table ID:', body.tableId);
console.log('[api/admin/nocodb-test.post] Tables:', Object.keys(body.tables));
try {
// Test connection by making a simple request to NocoDB
const testUrl = `${body.url}/api/v2/tables/${body.tableId}/records?limit=1`;
// Test connection by making a simple request to NocoDB using the first table
const firstTableName = Object.keys(body.tables)[0];
const firstTableId = body.tables[firstTableName];
const testUrl = `${body.url}/api/v2/tables/${firstTableId}/records?limit=1`;
console.log('[api/admin/nocodb-test.post] Testing URL:', testUrl);

View File

@ -1,4 +1,5 @@
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 } from '~/utils/types';
@ -13,10 +14,20 @@ interface EffectiveNocoDB {
url: string;
token: string;
baseId: string;
tableId: string;
tables: { [tableName: string]: string };
}
const CONFIG_DIR = '/app/data';
// 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');
@ -169,6 +180,15 @@ export async function loadAdminConfig(): Promise<AdminConfiguration | null> {
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');
@ -207,6 +227,15 @@ export async function saveAdminConfig(config: NocoDBSettings, updatedBy: string)
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) });
@ -225,7 +254,7 @@ export function getEffectiveNocoDBConfig(): EffectiveNocoDB {
url: runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
token: runtimeConfig.nocodb?.token || '',
baseId: runtimeConfig.nocodb?.baseId || '',
tableId: 'members-table-id' // This would be configured
tables: { members: 'members-table-id' } // Default table mapping
};
// Override with file configuration if available
@ -234,7 +263,7 @@ export function getEffectiveNocoDBConfig(): EffectiveNocoDB {
url: configCache.nocodb.url || envConfig.url,
token: configCache.nocodb.apiKey || envConfig.token,
baseId: configCache.nocodb.baseId || envConfig.baseId,
tableId: configCache.nocodb.tableId || envConfig.tableId
tables: configCache.nocodb.tables || envConfig.tables
};
}
@ -253,7 +282,7 @@ export async function getCurrentConfig(): Promise<NocoDBSettings> {
url: config.nocodb.url || runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
apiKey: config.nocodb.apiKey ? '••••••••••••••••' : '',
baseId: config.nocodb.baseId || runtimeConfig.nocodb?.baseId || '',
tableId: config.nocodb.tableId || 'members-table-id'
tables: config.nocodb.tables || { members: 'members-table-id' }
};
}
@ -262,7 +291,7 @@ export async function getCurrentConfig(): Promise<NocoDBSettings> {
url: runtimeConfig.nocodb?.url || 'https://database.monacousa.org',
apiKey: runtimeConfig.nocodb?.token ? '••••••••••••••••' : '',
baseId: runtimeConfig.nocodb?.baseId || '',
tableId: 'members-table-id'
tables: { members: 'members-table-id' }
};
}

View File

@ -28,17 +28,13 @@ export interface EntityResponse<T> {
// Dynamic table ID getter - will use configured table ID from admin panel
export const getTableId = (tableName: 'Members'): string => {
try {
// Try to get table ID from persistent configuration
const { getEffectiveNocoDBConfig } = require('./admin-config');
const effectiveConfig = getEffectiveNocoDBConfig();
if (tableName === 'Members' && effectiveConfig.tableId) {
console.log(`[nocodb] Using configured table ID for ${tableName}:`, effectiveConfig.tableId);
return effectiveConfig.tableId;
// Try to get table ID from global configuration first
if (globalNocoDBConfig?.tables && tableName === 'Members') {
const tableId = globalNocoDBConfig.tables['members'] || globalNocoDBConfig.tables['Members'];
if (tableId) {
console.log(`[nocodb] Using global table ID for ${tableName}:`, tableId);
return tableId;
}
} catch (error) {
console.log(`[nocodb] Admin config not available for table ID, using fallback`);
}
// Fallback to default
@ -102,31 +98,35 @@ export const formatNationalitiesAsString = (nationalities: string[]): string =>
return nationalities.filter(n => n && n.trim()).join(',');
};
// Global variable to store effective configuration
let globalNocoDBConfig: any = null;
// Function to set the global configuration (called by the admin-config system)
export const setGlobalNocoDBConfig = (config: any) => {
globalNocoDBConfig = config;
console.log('[nocodb] Global configuration updated:', config.url);
};
export const getNocoDbConfiguration = () => {
try {
// Try to use the persistent configuration system
const { getEffectiveNocoDBConfig } = require('./admin-config');
const effectiveConfig = getEffectiveNocoDBConfig();
const config = {
url: effectiveConfig.url,
token: effectiveConfig.token,
baseId: effectiveConfig.baseId
// Try to use the global configuration first
if (globalNocoDBConfig) {
console.log('[nocodb] Using global configuration - URL:', globalNocoDBConfig.url);
return {
url: globalNocoDBConfig.url,
token: globalNocoDBConfig.token,
baseId: globalNocoDBConfig.baseId
};
console.log('[nocodb] Using effective configuration - URL:', config.url);
return config;
} catch (error) {
// Fallback to runtime config if admin config is not available
console.log('[nocodb] Admin config not available, using runtime config');
const config = useRuntimeConfig().nocodb;
const fallbackConfig = {
...config,
url: config.url || 'https://database.monacousa.org'
};
console.log('[nocodb] Fallback configuration URL:', fallbackConfig.url);
return fallbackConfig;
}
// Fallback to runtime config
console.log('[nocodb] Global config not available, using runtime config');
const config = useRuntimeConfig().nocodb;
const fallbackConfig = {
...config,
url: config.url || 'https://database.monacousa.org'
};
console.log('[nocodb] Fallback configuration URL:', fallbackConfig.url);
return fallbackConfig;
};
export const createTableUrl = (table: Table | string) => {

View File

@ -139,10 +139,10 @@ export interface Member {
// Admin-only NocoDB Configuration
export interface NocoDBSettings {
tableId: string;
url: string;
apiKey: string;
baseId: string;
url: string;
tables: { [tableName: string]: string }; // e.g., { "members": "m2sri3jqfqutiy5", "events": "evt123abc", ... }
}
export interface MemberResponse {