refactor: remove Pinia state management and IndexedDB storage
Remove all client-side state management and data caching as the application no longer needs to manage data analysis features. Changes: - Remove Pinia store system (app/stores/ directory) - metadata.ts: Store for table metadata - tableDataFactory.ts: Factory for creating table data stores - README.md: Comprehensive store documentation - Remove IndexedDB storage utility (app/utils/storage.ts) - Remove Pinia dependencies from package.json - @pinia/nuxt - pinia - Remove Pinia module from nuxt.config.ts - Remove vendor-pinia chunk configuration - Remove server services (server/services/ directory) - table-service.ts: Supabase-dependent table operations - query-parser.ts: Query parsing utilities - query-runner.ts: Query execution utilities The application now operates as a stateless authentication portal without client-side data caching or state management.
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
import type { ParsedQuery, QueryFilter } from '../data-sources/types'
|
||||
|
||||
function isValidFilter(filter: Partial<QueryFilter>): filter is QueryFilter {
|
||||
return typeof filter.field === 'string' && filter.field.length > 0 && filter.value !== undefined
|
||||
}
|
||||
|
||||
function decodeBase64Url(value: string) {
|
||||
const normalized = value.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const paddingNeeded = (4 - (normalized.length % 4)) % 4
|
||||
const padded = normalized + '='.repeat(paddingNeeded)
|
||||
return Buffer.from(padded, 'base64').toString('utf-8')
|
||||
}
|
||||
|
||||
export function parseQuerySegment(segment?: string | string[]): ParsedQuery | null {
|
||||
if (!segment) {
|
||||
return null
|
||||
}
|
||||
|
||||
const value = Array.isArray(segment) ? segment[0] : segment
|
||||
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = decodeBase64Url(value)
|
||||
const parsed = JSON.parse(decoded)
|
||||
|
||||
const result: ParsedQuery = {
|
||||
filters: []
|
||||
}
|
||||
|
||||
if (Array.isArray(parsed.filters)) {
|
||||
result.filters = parsed.filters.filter(isValidFilter)
|
||||
}
|
||||
|
||||
if (parsed.limit && Number.isInteger(parsed.limit) && parsed.limit > 0) {
|
||||
result.limit = Math.min(parsed.limit, 500)
|
||||
}
|
||||
|
||||
if (parsed.offset && Number.isInteger(parsed.offset) && parsed.offset >= 0) {
|
||||
result.offset = parsed.offset
|
||||
}
|
||||
|
||||
if (parsed.orderBy && typeof parsed.orderBy.field === 'string') {
|
||||
result.orderBy = {
|
||||
field: parsed.orderBy.field,
|
||||
ascending: parsed.orderBy.ascending !== false
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse query segment', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { ParsedQuery } from '../data-sources/types'
|
||||
|
||||
type PostgrestOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike'
|
||||
|
||||
export function applyParsedQuery(builder: any, parsed: ParsedQuery | null) {
|
||||
if (!parsed) {
|
||||
return builder
|
||||
}
|
||||
|
||||
for (const filter of parsed.filters) {
|
||||
const operator: PostgrestOperator = (filter.operator ?? 'eq') as PostgrestOperator
|
||||
|
||||
if (typeof builder[operator] === 'function') {
|
||||
const value = operator === 'like' || operator === 'ilike' ? String(filter.value) : filter.value
|
||||
builder = builder[operator](filter.field, value)
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed.orderBy && typeof builder.order === 'function') {
|
||||
builder = builder.order(parsed.orderBy.field, {
|
||||
ascending: parsed.orderBy.ascending !== false
|
||||
})
|
||||
}
|
||||
|
||||
if (parsed.limit && parsed.offset !== undefined && typeof builder.range === 'function') {
|
||||
const from = parsed.offset
|
||||
const to = parsed.offset + parsed.limit - 1
|
||||
builder = builder.range(from, to)
|
||||
} else if (parsed.limit && typeof builder.limit === 'function') {
|
||||
builder = builder.limit(parsed.limit)
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
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
|
||||
offset?: 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<Awaited<ReturnType<typeof fetchTableMetadata>>> =>
|
||||
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
|
||||
// For views with 'fecha' field, try that first, otherwise use 'created_at'
|
||||
const orderField = baseSelect.includes('fecha') ? 'fecha' : 'created_at'
|
||||
|
||||
const earliestPromise = supabase
|
||||
.from(config.table)
|
||||
.select(baseSelect)
|
||||
.order(orderField, { ascending: true })
|
||||
.limit(1)
|
||||
|
||||
const latestPromise = supabase
|
||||
.from(config.table)
|
||||
.select(baseSelect)
|
||||
.order(orderField, { 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 as { data: any[] | null }
|
||||
const latest = latestResult.error ? { data: null } : latestResult as { data: any[] | null }
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (options?.offset !== undefined && options.offset > 0) {
|
||||
query = query.range(options.offset, options.offset + limit - 1)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user