Discover NocoDB table IDs dynamically instead of hardcoding them
All checks were successful
Deploy HiHala Dashboard / deploy (push) Successful in 7s

Table IDs are now fetched at runtime via the NocoDB meta API using
VITE_NOCODB_BASE_ID, so the same code works against any NocoDB instance
(local or Cloudron). Also adds a migration script for moving data between
instances with correct FK remapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fahed
2026-03-25 17:50:44 +03:00
parent db2617f37d
commit bf996749e5
4 changed files with 325 additions and 15 deletions

View File

@@ -19,14 +19,37 @@ import type {
const NOCODB_URL = import.meta.env.VITE_NOCODB_URL || '';
const NOCODB_TOKEN = import.meta.env.VITE_NOCODB_TOKEN || '';
const NOCODB_BASE_ID = import.meta.env.VITE_NOCODB_BASE_ID || '';
// Table IDs (Cloudron NocoDB)
const NOCODB_TABLES = {
districts: 'mddorhm0boab99m',
museums: 'm1os227987acanj',
dailyStats: 'mbp0qntf9h6qth1',
pilgrimStats: 'mi90dy6w7mt0vp0'
};
// Table IDs discovered dynamically from NocoDB meta API
let discoveredTables: Record<string, string> | null = null;
async function discoverTableIds(): Promise<Record<string, string>> {
if (discoveredTables) return discoveredTables;
if (!NOCODB_BASE_ID) throw new Error('VITE_NOCODB_BASE_ID not configured');
const res = await fetch(
`${NOCODB_URL}/api/v2/meta/bases/${NOCODB_BASE_ID}/tables`,
{ headers: { 'xc-token': NOCODB_TOKEN } }
);
if (!res.ok) throw new Error(`Failed to discover tables: HTTP ${res.status}`);
const json = await res.json();
const tables: Record<string, string> = {};
for (const t of json.list) {
tables[t.title] = t.id;
}
const required = ['Districts', 'Museums', 'DailyStats'];
for (const name of required) {
if (!tables[name]) throw new Error(`Required table '${name}' not found in NocoDB base`);
}
discoveredTables = tables;
console.log('Discovered NocoDB tables:', Object.keys(tables).map(k => `${k}=${tables[k]}`).join(', '));
return tables;
}
// Cache keys
const CACHE_KEY = 'hihala_data_cache';
@@ -42,7 +65,12 @@ export let umrahData: UmrahData = {
// Fetch pilgrim stats from NocoDB and update umrahData
export async function fetchPilgrimStats(): Promise<UmrahData> {
try {
const url = `${NOCODB_URL}/api/v2/tables/${NOCODB_TABLES.pilgrimStats}/records?limit=50`;
const tables = await discoverTableIds();
if (!tables['PilgrimStats']) {
console.warn('PilgrimStats table not found, using defaults');
return umrahData;
}
const url = `${NOCODB_URL}/api/v2/tables/${tables['PilgrimStats']}/records?limit=50`;
const res = await fetch(url, { headers: { 'xc-token': NOCODB_TOKEN } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
@@ -165,12 +193,14 @@ interface MuseumMapEntry {
async function fetchFromNocoDB(): Promise<MuseumRecord[]> {
console.log('Fetching from NocoDB...');
const tables = await discoverTableIds();
// Fetch all three tables in parallel
const [districts, museums, dailyStats] = await Promise.all([
fetchNocoDBTable<NocoDBDistrict>(NOCODB_TABLES.districts),
fetchNocoDBTable<NocoDBMuseum>(NOCODB_TABLES.museums),
fetchNocoDBTable<NocoDBDailyStat>(NOCODB_TABLES.dailyStats)
fetchNocoDBTable<NocoDBDistrict>(tables['Districts']),
fetchNocoDBTable<NocoDBMuseum>(tables['Museums']),
fetchNocoDBTable<NocoDBDailyStat>(tables['DailyStats'])
]);
// Build lookup maps
@@ -223,14 +253,14 @@ async function fetchFromNocoDB(): Promise<MuseumRecord[]> {
export async function fetchData(): Promise<FetchResult> {
// Check if NocoDB is configured
if (!NOCODB_URL || !NOCODB_TOKEN) {
if (!NOCODB_URL || !NOCODB_TOKEN || !NOCODB_BASE_ID) {
// Try cache
const cached = loadFromCache();
if (cached) {
console.warn('NocoDB not configured, using cached data');
return { data: cached.data, fromCache: true, cacheTimestamp: cached.timestamp };
}
throw new Error('NocoDB not configured and no cached data available. Set VITE_NOCODB_URL and VITE_NOCODB_TOKEN in .env.local');
throw new Error('NocoDB not configured and no cached data available. Set VITE_NOCODB_URL, VITE_NOCODB_TOKEN, and VITE_NOCODB_BASE_ID in .env.local');
}
try {
@@ -257,7 +287,7 @@ export async function fetchData(): Promise<FetchResult> {
// Force refresh (bypass cache read, but still write to cache)
export async function refreshData(): Promise<FetchResult> {
if (!NOCODB_URL || !NOCODB_TOKEN) {
if (!NOCODB_URL || !NOCODB_TOKEN || !NOCODB_BASE_ID) {
throw new Error('NocoDB not configured');
}