import { getTableConfig, tableNames } from '../data-sources' import type { ParsedQuery, TableConfig } from '../data-sources/types' import { getSupabaseClient } from '../utils/supabase' import { applyParsedQuery } from './query-runner' type GenericObject = Record function isRecord(value: unknown): value is GenericObject { return typeof value === 'object' && value !== null && !Array.isArray(value) } interface MetadataOptions { parsedQuery?: ParsedQuery | null } interface DataFilters { id?: string createdFrom?: string createdTo?: string } interface DataOptions { parsedQuery?: ParsedQuery | null filters?: DataFilters limit?: number } function serializeRow(row: unknown, config: TableConfig): GenericObject | null { if (!isRecord(row) || ('error' in row && (row as any).error === true)) { return null } const transform = config.transforms?.serializeRow return transform ? transform(row) : row } export async function fetchAllTablesMetadata() { const results = await Promise.allSettled( tableNames.map((name) => fetchTableMetadata(name)) ) // Log any errors for debugging results.forEach((result, index) => { if (result.status === 'rejected') { console.error(`Failed to fetch metadata for table ${tableNames[index]}:`, result.reason) } }) return results .filter((result): result is PromiseFulfilledResult>> => result.status === 'fulfilled' ) .map((result) => result.value) } export async function fetchTableMetadata(tableName: string, options?: MetadataOptions) { try { const config = getTableConfig(tableName) if (!config) { throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` }) } const supabase = getSupabaseClient() const baseSelect = config.defaultSelect ?? '*' const samplePromise = applyParsedQuery( supabase.from(config.table).select(baseSelect), options?.parsedQuery ?? null ) .limit(1) // Try to get created_at range, but don't fail if the column doesn't exist const earliestPromise = supabase .from(config.table) .select('created_at') .order('created_at', { ascending: true }) .limit(1) const latestPromise = supabase .from(config.table) .select('created_at') .order('created_at', { ascending: false }) .limit(1) const [sampleResult, earliestResult, latestResult] = await Promise.all([samplePromise, earliestPromise, latestPromise]) const { data: sampleData, error: sampleError } = sampleResult // Handle created_at queries that may fail for views without this column const earliest = earliestResult.error ? { data: null } : earliestResult const latest = latestResult.error ? { data: null } : latestResult if (sampleError) { console.error(`Error fetching sample for ${config.table}:`, { message: sampleError.message, details: sampleError.details, hint: sampleError.hint, code: sampleError.code, full: sampleError }) const errorMsg = sampleError.message || sampleError.hint || sampleError.details || sampleError.code || 'Error desconocido al obtener muestra' throw createError({ statusCode: 500, statusMessage: errorMsg }) } const sampleSet = Array.isArray(sampleData) ? sampleData : [] const sampleRow = serializeRow(sampleSet[0] ?? null, config) const columnNames = sampleRow ? Object.keys(sampleRow) : [] const approxRowSize = sampleRow ? JSON.stringify(sampleRow).length : 0 const approxSizeBytes = null // We'll calculate this from in-memory data return { name: config.name, table: config.table, primaryKey: config.primaryKey ?? 'id', rowCount: 0, // Will be calculated from in-memory data approxSizeBytes, columns: columnNames, createdAtRange: { from: earliest.data?.[0]?.created_at ?? null, to: latest.data?.[0]?.created_at ?? null }, sampleRow, lastRefreshed: new Date().toISOString(), description: config.description } } catch (error: any) { console.error(`Unexpected error in fetchTableMetadata for ${tableName}:`, error) throw createError({ statusCode: 500, statusMessage: error?.message || error?.toString() || 'Error inesperado al obtener metadatos' }) } } export async function fetchTableRecordMetadata(tableName: string, id: string) { const config = getTableConfig(tableName) if (!config) { throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` }) } const supabase = getSupabaseClient() const primaryKey = config.primaryKey ?? 'id' const { data, error } = await supabase .from(config.table) .select(config.defaultSelect ?? '*') .eq(primaryKey, id) .maybeSingle() if (error) { throw createError({ statusCode: 500, statusMessage: error.message }) } if (!data || (typeof data === 'object' && data !== null && 'error' in data)) { if (data && typeof data === 'object' && 'message' in data) { throw createError({ statusCode: 500, statusMessage: String((data as any).message) }) } throw createError({ statusCode: 404, statusMessage: `Registro ${id} no encontrado` }) } return { table: config.table, id, metadata: serializeRow(data, config) } } export async function fetchTableRecord(tableName: string, id: string) { const config = getTableConfig(tableName) if (!config) { throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` }) } const supabase = getSupabaseClient() const primaryKey = config.primaryKey ?? 'id' const { data, error } = await supabase .from(config.table) .select(config.defaultSelect ?? '*') .eq(primaryKey, id) .maybeSingle() if (error) { throw createError({ statusCode: 500, statusMessage: error.message }) } if (!data || (typeof data === 'object' && data !== null && 'error' in data)) { if (data && typeof data === 'object' && 'message' in data) { throw createError({ statusCode: 500, statusMessage: String((data as any).message) }) } throw createError({ statusCode: 404, statusMessage: `Registro ${id} no encontrado` }) } return serializeRow(data, config) } export async function fetchAllData(limitPerTable = 100) { const supabase = getSupabaseClient() const tablePromises = tableNames.map(async (name) => { const config = getTableConfig(name)! const { data, error, count } = await supabase .from(config.table) .select(config.defaultSelect ?? '*', { count: 'exact' }) .limit(limitPerTable) if (error) { throw createError({ statusCode: 500, statusMessage: error.message }) } const rows = Array.isArray(data) ? data : [] return { table: config.table, count: count ?? 0, limit: limitPerTable, records: rows .map((row) => serializeRow(row, config)) .filter((row): row is GenericObject => row !== null) } }) return Promise.all(tablePromises) } export async function fetchTableData(tableName: string, options?: DataOptions) { const config = getTableConfig(tableName) if (!config) { throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` }) } const supabase = getSupabaseClient() const primaryKey = config.primaryKey ?? 'id' const limit = options?.limit ?? 100 let query = supabase .from(config.table) .select(config.defaultSelect ?? '*', { count: 'exact' }) query = applyParsedQuery(query as never, options?.parsedQuery ?? null) as never if (options?.filters?.id) { query = query.eq(primaryKey, options.filters.id) } if (options?.filters?.createdFrom) { query = query.gte('created_at', options.filters.createdFrom) } if (options?.filters?.createdTo) { query = query.lte('created_at', options.filters.createdTo) } if (!options?.parsedQuery?.limit) { query = query.limit(limit) } const { data, error, count } = await query if (error) { throw createError({ statusCode: 500, statusMessage: error.message }) } const rows = Array.isArray(data) ? data : [] const records = rows .map((row) => serializeRow(row, config)) .filter((row): row is GenericObject => row !== null) return { table: config.table, count: count ?? records.length, limit, records } }