huge refactor with a million of things to make the code much more managable and easier to create new services #3

Merged
boki merged 70 commits from di-refactor into master 2025-06-24 01:43:57 +00:00
16 changed files with 296 additions and 273 deletions
Showing only changes of commit a3459f5865 - Show all commits

View file

@ -3,7 +3,7 @@
* Configures dependency injection for the data pipeline service
*/
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import { getLogger } from '@stock-bot/logger';
import type { AppConfig } from '@stock-bot/config';
@ -14,8 +14,8 @@ const logger = getLogger('data-pipeline-container');
*/
export function setupServiceContainer(
config: AppConfig,
container: ServiceContainer
): ServiceContainer {
container: IServiceContainer
): IServiceContainer {
logger.info('Configuring data pipeline service container...');
// Data pipeline specific configuration

View file

@ -1,6 +1,6 @@
import { getLogger } from '@stock-bot/logger';
import { handlerRegistry, createJobHandler, type HandlerConfig, type ScheduledJobConfig } from '@stock-bot/queue';
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import { exchangeOperations } from './operations';
const logger = getLogger('exchanges-handler');
@ -52,7 +52,7 @@ const exchangesHandlerConfig: HandlerConfig = {
},
};
export function initializeExchangesHandler(container: ServiceContainer) {
export function initializeExchangesHandler(container: IServiceContainer) {
logger.info('Registering exchanges handler...');
// Update operations to use container

View file

@ -1,12 +1,12 @@
import { getLogger } from '@stock-bot/logger';
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload } from '../../../types/job-payloads';
const logger = getLogger('enhanced-sync-clear-postgresql-data');
export async function clearPostgreSQLData(
payload: JobPayload,
container: ServiceContainer
container: IServiceContainer
): Promise<{
exchangesCleared: number;
symbolsCleared: number;

View file

@ -1,12 +1,12 @@
import { getLogger } from '@stock-bot/logger';
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload, SyncStatus } from '../../../types/job-payloads';
const logger = getLogger('enhanced-sync-status');
export async function getSyncStatus(
payload: JobPayload,
container: ServiceContainer
container: IServiceContainer
): Promise<SyncStatus[]> {
logger.info('Getting comprehensive sync status...');

View file

@ -1,12 +1,12 @@
import { getLogger } from '@stock-bot/logger';
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload } from '../../../types/job-payloads';
const logger = getLogger('enhanced-sync-exchange-stats');
export async function getExchangeStats(
payload: JobPayload,
container: ServiceContainer
container: IServiceContainer
): Promise<any> {
logger.info('Getting exchange statistics...');

View file

@ -1,12 +1,12 @@
import { getLogger } from '@stock-bot/logger';
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload } from '../../../types/job-payloads';
const logger = getLogger('enhanced-sync-provider-mapping-stats');
export async function getProviderMappingStats(
payload: JobPayload,
container: ServiceContainer
container: IServiceContainer
): Promise<any> {
logger.info('Getting provider mapping statistics...');

View file

@ -1,17 +1,19 @@
import { getLogger } from '@stock-bot/logger';
import { getMongoDBClient, getPostgreSQLClient } from '../../../clients';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload } from '../../../types/job-payloads';
const logger = getLogger('sync-qm-exchanges');
export async function syncQMExchanges(
payload: JobPayload
payload: JobPayload,
container: IServiceContainer
): Promise<{ processed: number; created: number; updated: number }> {
logger.info('Starting QM exchanges sync...');
try {
const mongoClient = getMongoDBClient();
const postgresClient = getPostgreSQLClient();
const mongoClient = container.mongodb;
const postgresClient = container.postgres;
// 1. Get all QM exchanges from MongoDB
const qmExchanges = await mongoClient.find('qmExchanges', {});

View file

@ -1,10 +1,10 @@
import { getLogger } from '@stock-bot/logger';
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload, SyncResult } from '../../../types/job-payloads';
const logger = getLogger('enhanced-sync-all-exchanges');
export async function syncAllExchanges(payload: JobPayload, container: ServiceContainer): Promise<SyncResult> {
export async function syncAllExchanges(payload: JobPayload, container: IServiceContainer): Promise<SyncResult> {
const clearFirst = payload.clearFirst || true;
logger.info('Starting comprehensive exchange sync...', { clearFirst });
@ -67,7 +67,7 @@ async function clearPostgreSQLData(postgresClient: any): Promise<void> {
logger.info('PostgreSQL data cleared successfully');
}
async function syncEODExchanges(container: ServiceContainer): Promise<SyncResult> {
async function syncEODExchanges(container: IServiceContainer): Promise<SyncResult> {
const mongoClient = container.mongodb;
const exchanges = await mongoClient.find('eodExchanges', { active: true });
const result: SyncResult = { processed: 0, created: 0, updated: 0, skipped: 0, errors: 0 };
@ -96,7 +96,7 @@ async function syncEODExchanges(container: ServiceContainer): Promise<SyncResult
return result;
}
async function syncIBExchanges(container: ServiceContainer): Promise<SyncResult> {
async function syncIBExchanges(container: IServiceContainer): Promise<SyncResult> {
const mongoClient = container.mongodb;
const exchanges = await mongoClient.find('ibExchanges', {});
const result: SyncResult = { processed: 0, created: 0, updated: 0, skipped: 0, errors: 0 };
@ -132,7 +132,7 @@ async function createProviderExchangeMapping(
countryCode: string | null,
currency: string | null,
confidence: number,
container: ServiceContainer
container: IServiceContainer
): Promise<void> {
if (!providerExchangeCode) {
return;
@ -181,7 +181,7 @@ async function findOrCreateMasterExchange(
providerName: string,
countryCode: string | null,
currency: string | null,
container: ServiceContainer
container: IServiceContainer
): Promise<any> {
const postgresClient = container.postgres;
@ -237,7 +237,7 @@ function getBasicExchangeMapping(providerCode: string): string | null {
async function findProviderExchangeMapping(
provider: string,
providerExchangeCode: string,
container: ServiceContainer
container: IServiceContainer
): Promise<any> {
const postgresClient = container.postgres;
const query =
@ -246,7 +246,7 @@ async function findProviderExchangeMapping(
return result.rows[0] || null;
}
async function findExchangeByCode(code: string, container: ServiceContainer): Promise<any> {
async function findExchangeByCode(code: string, container: IServiceContainer): Promise<any> {
const postgresClient = container.postgres;
const query = 'SELECT * FROM exchanges WHERE code = $1';
const result = await postgresClient.query(query, [code]);

View file

@ -1,6 +1,7 @@
import { getLogger } from '@stock-bot/logger';
import type { MasterExchange } from '@stock-bot/mongodb';
import { getMongoDBClient } from '../../../clients';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload } from '../../../types/job-payloads';
const logger = getLogger('sync-ib-exchanges');
@ -15,12 +16,13 @@ interface IBExchange {
}
export async function syncIBExchanges(
payload: JobPayload
payload: JobPayload,
container: IServiceContainer
): Promise<{ syncedCount: number; totalExchanges: number }> {
logger.info('Syncing IB exchanges from database...');
try {
const mongoClient = getMongoDBClient();
const mongoClient = container.mongodb;
const db = mongoClient.getDatabase();
// Filter by country code US and CA
@ -37,7 +39,7 @@ export async function syncIBExchanges(
for (const exchange of ibExchanges) {
try {
await createOrUpdateMasterExchange(exchange);
await createOrUpdateMasterExchange(exchange, container);
syncedCount++;
logger.debug('Synced IB exchange', {
@ -64,8 +66,8 @@ export async function syncIBExchanges(
/**
* Create or update master exchange record 1:1 from IB exchange
*/
async function createOrUpdateMasterExchange(ibExchange: IBExchange): Promise<void> {
const mongoClient = getMongoDBClient();
async function createOrUpdateMasterExchange(ibExchange: IBExchange, container: IServiceContainer): Promise<void> {
const mongoClient = container.mongodb;
const db = mongoClient.getDatabase();
const collection = db.collection<MasterExchange>('masterExchanges');

View file

@ -1,10 +1,14 @@
import { getLogger } from '@stock-bot/logger';
import { getMongoDBClient, getPostgreSQLClient } from '../../../clients';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { JobPayload, SyncResult } from '../../../types/job-payloads';
const logger = getLogger('enhanced-sync-qm-provider-mappings');
export async function syncQMProviderMappings(payload: JobPayload): Promise<SyncResult> {
export async function syncQMProviderMappings(
payload: JobPayload,
container: IServiceContainer
): Promise<SyncResult> {
logger.info('Starting QM provider exchange mappings sync...');
const result: SyncResult = {
@ -16,8 +20,8 @@ export async function syncQMProviderMappings(payload: JobPayload): Promise<SyncR
};
try {
const mongoClient = getMongoDBClient();
const postgresClient = getPostgreSQLClient();
const mongoClient = container.mongodb;
const postgresClient = container.postgres;
// Start transaction
await postgresClient.query('BEGIN');
@ -59,7 +63,8 @@ export async function syncQMProviderMappings(payload: JobPayload): Promise<SyncR
exchange.sampleExchange || exchange.exchangeCode,
exchange.countryCode,
exchange.countryCode === 'CA' ? 'CAD' : 'USD', // Simple currency mapping
0.8 // good confidence for QM data
0.8, // good confidence for QM data
container
);
result.processed++;
@ -75,29 +80,31 @@ export async function syncQMProviderMappings(payload: JobPayload): Promise<SyncR
logger.info('QM provider exchange mappings sync completed', result);
return result;
} catch (error) {
const postgresClient = getPostgreSQLClient();
const postgresClient = container.postgres;
await postgresClient.query('ROLLBACK');
logger.error('QM provider exchange mappings sync failed', { error });
throw error;
}
}
async function createProviderExchangeMapping(
provider: string,
providerExchangeCode: string,
providerExchangeName: string,
countryCode: string | null,
currency: string | null,
confidence: number
confidence: number,
container: IServiceContainer
): Promise<void> {
if (!providerExchangeCode) {
return;
}
const postgresClient = getPostgreSQLClient();
const postgresClient = container.postgres;
// Check if mapping already exists
const existingMapping = await findProviderExchangeMapping(provider, providerExchangeCode);
const existingMapping = await findProviderExchangeMapping(provider, providerExchangeCode, container);
if (existingMapping) {
// Don't override existing mappings to preserve manual work
return;
@ -108,7 +115,8 @@ async function createProviderExchangeMapping(
providerExchangeCode,
providerExchangeName,
countryCode,
currency
currency,
container
);
// Create the provider exchange mapping
@ -133,9 +141,10 @@ async function createProviderExchangeMapping(
async function findProviderExchangeMapping(
provider: string,
providerExchangeCode: string
providerExchangeCode: string,
container: IServiceContainer
): Promise<any> {
const postgresClient = getPostgreSQLClient();
const postgresClient = container.postgres;
const query =
'SELECT * FROM provider_exchange_mappings WHERE provider = $1 AND provider_exchange_code = $2';
const result = await postgresClient.query(query, [provider, providerExchangeCode]);
@ -146,12 +155,13 @@ async function findOrCreateMasterExchange(
providerCode: string,
providerName: string,
countryCode: string | null,
currency: string | null
currency: string | null,
container: IServiceContainer
): Promise<any> {
const postgresClient = getPostgreSQLClient();
const postgresClient = container.postgres;
// First, try to find exact match
let masterExchange = await findExchangeByCode(providerCode);
let masterExchange = await findExchangeByCode(providerCode, container);
if (masterExchange) {
return masterExchange;
@ -160,7 +170,7 @@ async function findOrCreateMasterExchange(
// Try to find by similar codes (basic mapping)
const basicMapping = getBasicExchangeMapping(providerCode);
if (basicMapping) {
masterExchange = await findExchangeByCode(basicMapping);
masterExchange = await findExchangeByCode(basicMapping, container);
if (masterExchange) {
return masterExchange;
}
@ -199,8 +209,8 @@ function getBasicExchangeMapping(providerCode: string): string | null {
return mappings[providerCode.toUpperCase()] || null;
}
async function findExchangeByCode(code: string): Promise<any> {
const postgresClient = getPostgreSQLClient();
async function findExchangeByCode(code: string, container: IServiceContainer): Promise<any> {
const postgresClient = container.postgres;
const query = 'SELECT * FROM exchanges WHERE code = $1';
const result = await postgresClient.query(query, [code]);
return result.rows[0] || null;

View file

@ -3,7 +3,7 @@
* Configures dependency injection for the web API service
*/
import type { ServiceContainer } from '@stock-bot/di';
import type { IServiceContainer } from '@stock-bot/handlers';
import { getLogger } from '@stock-bot/logger';
import type { AppConfig } from '@stock-bot/config';
@ -14,8 +14,8 @@ const logger = getLogger('web-api-container');
*/
export function setupServiceContainer(
config: AppConfig,
container: ServiceContainer
): ServiceContainer {
container: IServiceContainer
): IServiceContainer {
logger.info('Configuring web API service container...');
// Web API specific configuration

View file

@ -4,17 +4,15 @@
*/
import { Hono } from 'hono';
import type { ServiceContainer } from '@stock-bot/di';
import { healthRoutes, exchangeRoutes } from './index';
import type { IServiceContainer } from '@stock-bot/handlers';
import { healthRoutes } from './health.routes';
import { createExchangeRoutes } from './exchange.routes';
export function createRoutes(container: ServiceContainer): Hono {
export function createRoutes(container: IServiceContainer): Hono {
const app = new Hono();
// Add container to context for all routes
app.use('*', async (c, next) => {
c.set('container', container);
await next();
});
// Create routes with container
const exchangeRoutes = createExchangeRoutes(container);
// Mount routes
app.route('/health', healthRoutes);

View file

@ -3,7 +3,8 @@
*/
import { Hono } from 'hono';
import { getLogger } from '@stock-bot/logger';
import { exchangeService } from '../services/exchange.service';
import type { IServiceContainer } from '@stock-bot/handlers';
import { createExchangeService } from '../services/exchange.service';
import { createSuccessResponse, handleError } from '../utils/error-handler';
import {
validateCreateExchange,
@ -13,7 +14,10 @@ import {
} from '../utils/validation';
const logger = getLogger('exchange-routes');
export const exchangeRoutes = new Hono();
export function createExchangeRoutes(container: IServiceContainer) {
const exchangeRoutes = new Hono();
const exchangeService = createExchangeService(container);
// Get all exchanges with provider mapping counts and mappings
exchangeRoutes.get('/', async c => {
@ -253,3 +257,6 @@ exchangeRoutes.get('/stats/summary', async c => {
return handleError(c, error, 'to get exchange statistics');
}
});
return exchangeRoutes;
}

View file

@ -1,5 +1,5 @@
/**
* Routes index - exports all route modules
*/
export { exchangeRoutes } from './exchange.routes';
export { createExchangeRoutes } from './exchange.routes';
export { healthRoutes } from './health.routes';

View file

@ -1,5 +1,5 @@
import { getLogger } from '@stock-bot/logger';
import { getMongoDBClient, getPostgreSQLClient } from '../clients';
import type { IServiceContainer } from '@stock-bot/handlers';
import {
CreateExchangeRequest,
CreateProviderMappingRequest,
@ -15,12 +15,14 @@ import {
const logger = getLogger('exchange-service');
export class ExchangeService {
constructor(private container: IServiceContainer) {}
private get postgresClient() {
return getPostgreSQLClient();
return this.container.postgres;
}
private get mongoClient() {
return getMongoDBClient();
return this.container.mongodb;
}
// Exchanges
@ -375,5 +377,7 @@ export class ExchangeService {
}
}
// Export singleton instance
export const exchangeService = new ExchangeService();
// Export function to create service instance with container
export function createExchangeService(container: IServiceContainer): ExchangeService {
return new ExchangeService(container);
}