feat: SQL Explorer with PostgreSQL schema browser and pre-built queries
The SQL Explorer was querying Trino which has zero tables. Rewrote to use PostgreSQL directly: Backend: - GET /api/analytics/pg-schema: returns all public tables with column names, types, and nullability from information_schema - POST /api/analytics/pg-query: read-only SQL execution against PostgreSQL with SELECT-only enforcement, auto LIMIT, and descriptive error messages for syntax/table/query errors Frontend: - Schema browser shows all PostgreSQL tables with columns and types - Click a table name → generates SELECT * FROM table LIMIT 100 - Pre-built Queries section with 12 seeded queries covering companies, recommendations, trends, market prices, documents, global events, trading decisions, ingestion health, reserve pool, sector exposure - User-saved queries shown separately with delete buttons - Chart builder, Monaco editor, and save functionality preserved Migration 021: seeds 12 pre-built saved queries
This commit is contained in:
@@ -95,6 +95,26 @@ export const handlers = [
|
||||
return HttpResponse.json({ id: 'w1', name: body.name, description: body.description ?? null, active: true }, { status: 201 });
|
||||
}),
|
||||
|
||||
// Analytics: PostgreSQL schema + query
|
||||
http.get('/api/analytics/pg-schema', () => HttpResponse.json({
|
||||
catalog: 'postgresql', schema: 'public',
|
||||
tables: [
|
||||
{ name: 'companies', columns: [{ name: 'id', type: 'uuid', nullable: false }, { name: 'ticker', type: 'character varying', nullable: false }, { name: 'legal_name', type: 'text', nullable: false }] },
|
||||
{ name: 'recommendations', columns: [{ name: 'id', type: 'uuid', nullable: false }, { name: 'ticker', type: 'character varying', nullable: false }, { name: 'action', type: 'character varying', nullable: false }] },
|
||||
],
|
||||
})),
|
||||
http.post('/api/analytics/pg-query', () => HttpResponse.json({
|
||||
columns: [{ name: 'test', type: 'text' }], rows: [['1']], row_count: 1, elapsed_ms: 5,
|
||||
})),
|
||||
http.get('/api/analytics/saved-queries', () => HttpResponse.json([
|
||||
{ id: 'sq1', name: 'Company Overview', description: 'All tracked companies with sector and status', sql_text: 'SELECT ticker, legal_name, sector, active, created_at FROM companies ORDER BY ticker', created_at: '2026-04-10T12:00:00Z' },
|
||||
])),
|
||||
http.post('/api/analytics/saved-queries', async ({ request }) => {
|
||||
const body = await request.json() as Record<string, string>;
|
||||
return HttpResponse.json({ id: 'sq-new', name: body.name, description: '', sql_text: body.sql_text, created_at: '2026-04-10T12:00:00Z' }, { status: 201 });
|
||||
}),
|
||||
http.delete('/api/analytics/saved-queries/:id', () => HttpResponse.json({ status: 'deleted' })),
|
||||
|
||||
// Health
|
||||
http.get('/api/health', () => HttpResponse.json({ status: 'ok' })),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user