257 lines
7.2 KiB
TypeScript
257 lines
7.2 KiB
TypeScript
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<string, unknown>
|
|
|
|
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))
|
|
)
|
|
|
|
return results
|
|
.filter((result): result is PromiseFulfilledResult<Awaited<ReturnType<typeof fetchTableMetadata>>> =>
|
|
result.status === 'fulfilled'
|
|
)
|
|
.map((result) => result.value)
|
|
}
|
|
|
|
export async function fetchTableMetadata(tableName: string, options?: MetadataOptions) {
|
|
const config = getTableConfig(tableName)
|
|
|
|
if (!config) {
|
|
throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` })
|
|
}
|
|
|
|
const supabase = getSupabaseClient()
|
|
|
|
const baseSelect = config.defaultSelect ?? '*'
|
|
|
|
const countPromise = supabase
|
|
.from(config.table)
|
|
.select(baseSelect, { head: true, count: 'exact' })
|
|
const samplePromise = applyParsedQuery(
|
|
supabase.from(config.table).select(baseSelect),
|
|
options?.parsedQuery ?? null
|
|
)
|
|
.limit(1)
|
|
|
|
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 [{ count, error: countError }, { data: sampleData, error: sampleError }, earliest, latest] =
|
|
await Promise.all([countPromise, samplePromise, earliestPromise, latestPromise])
|
|
|
|
if (countError) {
|
|
throw createError({ statusCode: 500, statusMessage: countError.message })
|
|
}
|
|
|
|
if (sampleError) {
|
|
throw createError({ statusCode: 500, statusMessage: sampleError.message })
|
|
}
|
|
|
|
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 = count && approxRowSize ? approxRowSize * count : null
|
|
|
|
return {
|
|
table: config.table,
|
|
primaryKey: config.primaryKey ?? 'id',
|
|
rowCount: count ?? 0,
|
|
approxSizeBytes,
|
|
columns: columnNames,
|
|
createdAtRange: {
|
|
from: earliest.data?.[0]?.created_at ?? null,
|
|
to: latest.data?.[0]?.created_at ?? null
|
|
},
|
|
sampleRow,
|
|
lastRefreshed: new Date().toISOString()
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|