moved most api stuff to web-api and built out a better monitoring solution for web-app
This commit is contained in:
parent
fbff428e90
commit
da1c52a841
45 changed files with 2986 additions and 312 deletions
|
|
@ -1,23 +1,29 @@
|
|||
/**
|
||||
* Route factory for web API service
|
||||
* Creates routes with access to the service container
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { IServiceContainer } from '@stock-bot/handlers';
|
||||
import { createHealthRoutes } from './health.routes';
|
||||
import { createExchangeRoutes } from './exchange.routes';
|
||||
|
||||
export function createRoutes(container: IServiceContainer): Hono {
|
||||
const app = new Hono();
|
||||
|
||||
// Create routes with container
|
||||
const healthRoutes = createHealthRoutes(container);
|
||||
const exchangeRoutes = createExchangeRoutes(container);
|
||||
|
||||
// Mount routes
|
||||
app.route('/health', healthRoutes);
|
||||
app.route('/api/exchanges', exchangeRoutes);
|
||||
|
||||
return app;
|
||||
/**
|
||||
* Route factory for web API service
|
||||
* Creates routes with access to the service container
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { IServiceContainer } from '@stock-bot/handlers';
|
||||
import { createHealthRoutes } from './health.routes';
|
||||
import { createExchangeRoutes } from './exchange.routes';
|
||||
import { createMonitoringRoutes } from './monitoring.routes';
|
||||
import { createPipelineRoutes } from './pipeline.routes';
|
||||
|
||||
export function createRoutes(container: IServiceContainer): Hono {
|
||||
const app = new Hono();
|
||||
|
||||
// Create routes with container
|
||||
const healthRoutes = createHealthRoutes(container);
|
||||
const exchangeRoutes = createExchangeRoutes(container);
|
||||
const monitoringRoutes = createMonitoringRoutes(container);
|
||||
const pipelineRoutes = createPipelineRoutes(container);
|
||||
|
||||
// Mount routes
|
||||
app.route('/health', healthRoutes);
|
||||
app.route('/api/exchanges', exchangeRoutes);
|
||||
app.route('/api/system/monitoring', monitoringRoutes);
|
||||
app.route('/api/pipeline', pipelineRoutes);
|
||||
|
||||
return app;
|
||||
}
|
||||
183
apps/stock/web-api/src/routes/monitoring.routes.ts
Normal file
183
apps/stock/web-api/src/routes/monitoring.routes.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* Monitoring routes for system health and metrics
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { IServiceContainer } from '@stock-bot/handlers';
|
||||
import { MonitoringService } from '../services/monitoring.service';
|
||||
|
||||
export function createMonitoringRoutes(container: IServiceContainer) {
|
||||
const monitoring = new Hono();
|
||||
const monitoringService = new MonitoringService(container);
|
||||
|
||||
/**
|
||||
* Get overall system health
|
||||
*/
|
||||
monitoring.get('/', async (c) => {
|
||||
try {
|
||||
const health = await monitoringService.getSystemHealth();
|
||||
|
||||
// Set appropriate status code based on health
|
||||
const statusCode = health.status === 'healthy' ? 200 :
|
||||
health.status === 'degraded' ? 503 : 500;
|
||||
|
||||
return c.json(health, statusCode);
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
status: 'error',
|
||||
message: 'Failed to retrieve system health',
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get cache/Dragonfly statistics
|
||||
*/
|
||||
monitoring.get('/cache', async (c) => {
|
||||
try {
|
||||
const stats = await monitoringService.getCacheStats();
|
||||
return c.json(stats);
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
error: 'Failed to retrieve cache statistics',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get queue statistics
|
||||
*/
|
||||
monitoring.get('/queues', async (c) => {
|
||||
try {
|
||||
const stats = await monitoringService.getQueueStats();
|
||||
return c.json({ queues: stats });
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
error: 'Failed to retrieve queue statistics',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get specific queue statistics
|
||||
*/
|
||||
monitoring.get('/queues/:name', async (c) => {
|
||||
try {
|
||||
const queueName = c.req.param('name');
|
||||
const stats = await monitoringService.getQueueStats();
|
||||
const queueStats = stats.find(q => q.name === queueName);
|
||||
|
||||
if (!queueStats) {
|
||||
return c.json({
|
||||
error: 'Queue not found',
|
||||
message: `Queue '${queueName}' does not exist`,
|
||||
}, 404);
|
||||
}
|
||||
|
||||
return c.json(queueStats);
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
error: 'Failed to retrieve queue statistics',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get database statistics
|
||||
*/
|
||||
monitoring.get('/databases', async (c) => {
|
||||
try {
|
||||
const stats = await monitoringService.getDatabaseStats();
|
||||
return c.json({ databases: stats });
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
error: 'Failed to retrieve database statistics',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get specific database statistics
|
||||
*/
|
||||
monitoring.get('/databases/:type', async (c) => {
|
||||
try {
|
||||
const dbType = c.req.param('type') as 'postgres' | 'mongodb' | 'questdb';
|
||||
const stats = await monitoringService.getDatabaseStats();
|
||||
const dbStats = stats.find(db => db.type === dbType);
|
||||
|
||||
if (!dbStats) {
|
||||
return c.json({
|
||||
error: 'Database not found',
|
||||
message: `Database type '${dbType}' not found or not enabled`,
|
||||
}, 404);
|
||||
}
|
||||
|
||||
return c.json(dbStats);
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
error: 'Failed to retrieve database statistics',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get service metrics
|
||||
*/
|
||||
monitoring.get('/metrics', async (c) => {
|
||||
try {
|
||||
const metrics = await monitoringService.getServiceMetrics();
|
||||
return c.json(metrics);
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
error: 'Failed to retrieve service metrics',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get detailed cache info (Redis INFO command output)
|
||||
*/
|
||||
monitoring.get('/cache/info', async (c) => {
|
||||
try {
|
||||
if (!container.cache) {
|
||||
return c.json({
|
||||
error: 'Cache not available',
|
||||
message: 'Cache service is not enabled',
|
||||
}, 503);
|
||||
}
|
||||
|
||||
const info = await container.cache.info();
|
||||
const stats = await monitoringService.getCacheStats();
|
||||
|
||||
return c.json({
|
||||
parsed: stats,
|
||||
raw: info,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json({
|
||||
error: 'Failed to retrieve cache info',
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Health check endpoint for monitoring
|
||||
*/
|
||||
monitoring.get('/ping', (c) => {
|
||||
return c.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'monitoring',
|
||||
});
|
||||
});
|
||||
|
||||
return monitoring;
|
||||
}
|
||||
135
apps/stock/web-api/src/routes/pipeline.routes.ts
Normal file
135
apps/stock/web-api/src/routes/pipeline.routes.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
* Pipeline Routes
|
||||
* API endpoints for data pipeline operations
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { IServiceContainer } from '@stock-bot/handlers';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import { PipelineService } from '../services/pipeline.service';
|
||||
|
||||
const logger = getLogger('pipeline-routes');
|
||||
|
||||
export function createPipelineRoutes(container: IServiceContainer) {
|
||||
const pipeline = new Hono();
|
||||
const pipelineService = new PipelineService(container);
|
||||
|
||||
// Symbol sync endpoints
|
||||
pipeline.post('/symbols', async c => {
|
||||
try {
|
||||
const result = await pipelineService.syncQMSymbols();
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in POST /symbols', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
pipeline.post('/symbols/:provider', async c => {
|
||||
try {
|
||||
const provider = c.req.param('provider');
|
||||
const result = await pipelineService.syncProviderSymbols(provider);
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in POST /symbols/:provider', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Exchange sync endpoints
|
||||
pipeline.post('/exchanges', async c => {
|
||||
try {
|
||||
const result = await pipelineService.syncQMExchanges();
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in POST /exchanges', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
pipeline.post('/exchanges/all', async c => {
|
||||
try {
|
||||
const clearFirst = c.req.query('clear') === 'true';
|
||||
const result = await pipelineService.syncAllExchanges(clearFirst);
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in POST /exchanges/all', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Provider mapping sync endpoints
|
||||
pipeline.post('/provider-mappings/qm', async c => {
|
||||
try {
|
||||
const result = await pipelineService.syncQMProviderMappings();
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in POST /provider-mappings/qm', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
pipeline.post('/provider-mappings/ib', async c => {
|
||||
try {
|
||||
const result = await pipelineService.syncIBExchanges();
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in POST /provider-mappings/ib', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Status endpoint
|
||||
pipeline.get('/status', async c => {
|
||||
try {
|
||||
const result = await pipelineService.getSyncStatus();
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in GET /status', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear data endpoint
|
||||
pipeline.post('/clear/postgresql', async c => {
|
||||
try {
|
||||
const dataType = c.req.query('type') as 'exchanges' | 'provider_mappings' | 'all';
|
||||
const result = await pipelineService.clearPostgreSQLData(dataType || 'all');
|
||||
return c.json(result, result.success ? 200 : 503);
|
||||
} catch (error) {
|
||||
logger.error('Error in POST /clear/postgresql', { error });
|
||||
return c.json({ success: false, error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Statistics endpoints
|
||||
pipeline.get('/stats/exchanges', async c => {
|
||||
try {
|
||||
const result = await pipelineService.getExchangeStats();
|
||||
if (result.success) {
|
||||
return c.json(result.data);
|
||||
} else {
|
||||
return c.json({ error: result.error }, 503);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in GET /stats/exchanges', { error });
|
||||
return c.json({ error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
pipeline.get('/stats/provider-mappings', async c => {
|
||||
try {
|
||||
const result = await pipelineService.getProviderMappingStats();
|
||||
if (result.success) {
|
||||
return c.json(result.data);
|
||||
} else {
|
||||
return c.json({ error: result.error }, 503);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in GET /stats/provider-mappings', { error });
|
||||
return c.json({ error: 'Internal server error' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue