/** * Metabase API utility * * Handles authentication and requests to Metabase API */ const config = useRuntimeConfig() const METABASE_URL = config.metabaseUrl || 'http://metabase:3000' const METABASE_API_KEY = config.metabaseApiKey || '' const METABASE_EMAIL = config.metabaseEmail || 'claudeCode0@nucleoriofrio.com' const METABASE_PASSWORD = config.metabasePassword || 'vK^NyZdZDH#p' let sessionToken: string | null = null let tokenExpiry: number = 0 /** * Get a valid Metabase session token * Reuses existing token if still valid, otherwise requests a new one */ export async function getMetabaseToken(): Promise { // Check if we have a valid token if (sessionToken && Date.now() < tokenExpiry) { return sessionToken } // Request a new token try { const response = await $fetch<{ id: string }>(`${METABASE_URL}/api/session`, { method: 'POST', body: { username: METABASE_EMAIL, password: METABASE_PASSWORD } }) sessionToken = response.id // Tokens expire after 14 days by default, but we'll refresh after 13 days to be safe tokenExpiry = Date.now() + (13 * 24 * 60 * 60 * 1000) console.log('[Metabase] New session token obtained') return sessionToken } catch (error) { console.error('[Metabase] Failed to obtain session token:', error) throw createError({ statusCode: 500, statusMessage: 'Failed to authenticate with Metabase' }) } } /** * Make an authenticated request to Metabase API * Supports both API Key and Session Token authentication */ export async function metabaseFetch( endpoint: string, options: RequestInit = {} ): Promise { const headers: Record = { 'Content-Type': 'application/json', ...options.headers as Record } // Prefer API Key if available if (METABASE_API_KEY) { headers['X-API-KEY'] = METABASE_API_KEY } else { const token = await getMetabaseToken() headers['X-Metabase-Session'] = token } try { const response = await $fetch(`${METABASE_URL}${endpoint}`, { ...options, headers }) return response } catch (error: any) { console.error(`[Metabase] Request failed for ${endpoint}:`, error) // If using session token and it's invalid, clear it and retry once if (error.statusCode === 401 && !METABASE_API_KEY) { console.log('[Metabase] Token invalid, clearing and retrying...') sessionToken = null tokenExpiry = 0 // Retry once with fresh token const newToken = await getMetabaseToken() return await $fetch(`${METABASE_URL}${endpoint}`, { ...options, headers: { ...options.headers, 'X-Metabase-Session': newToken, 'Content-Type': 'application/json' } }) } throw createError({ statusCode: error.statusCode || 500, statusMessage: error.message || 'Metabase request failed' }) } } /** * Get database metadata */ export async function getMetabaseDatabases() { return metabaseFetch('/api/database') } /** * Get tables from a specific database */ export async function getMetabaseTables(databaseId: number) { return metabaseFetch(`/api/database/${databaseId}/metadata`) } /** * Execute a query against a table */ export async function queryMetabaseTable(databaseId: number, tableId: number, query: any = {}) { return metabaseFetch('/api/dataset', { method: 'POST', body: { database: databaseId, type: 'query', query: { 'source-table': tableId, ...query } } }) } /** * Get all cards/questions */ export async function getMetabaseCards(filter?: string) { const endpoint = filter ? `/api/card?f=${filter}` : '/api/card' return metabaseFetch(endpoint) } /** * Get a specific card by ID */ export async function getMetabaseCard(cardId: number) { return metabaseFetch(`/api/card/${cardId}`) } /** * Execute a card query with optional parameters */ export async function executeCardQuery(cardId: number, parameters?: any[]) { const body = parameters ? { parameters } : {} return metabaseFetch(`/api/card/${cardId}/query`, { method: 'POST', body }) } /** * Execute a card query with cache (GET) */ export async function executeCardQueryCached(cardId: number) { return metabaseFetch(`/api/card/${cardId}/query`) }