huge refactor with a million of things to make the code much more managable and easier to create new services #3
6 changed files with 112 additions and 147 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import type { IDataIngestionServices } from '@stock-bot/di';
|
import type { IDataIngestionServices } from '@stock-bot/di';
|
||||||
import { QMHandler } from './qm/qm.handler';
|
import { QMHandler } from './qm/qm.handler';
|
||||||
|
import { WebShareHandler } from './webshare/webshare.handler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize and register all handlers
|
* Initialize and register all handlers
|
||||||
|
|
@ -14,10 +15,11 @@ export function initializeAllHandlers(services: IDataIngestionServices): void {
|
||||||
const qmHandler = new QMHandler(services);
|
const qmHandler = new QMHandler(services);
|
||||||
qmHandler.register();
|
qmHandler.register();
|
||||||
|
|
||||||
// TODO: Add other handlers here as they're converted
|
// WebShare Handler
|
||||||
// const webShareHandler = new WebShareHandler(services);
|
const webShareHandler = new WebShareHandler(services);
|
||||||
// webShareHandler.register();
|
webShareHandler.register();
|
||||||
|
|
||||||
|
// TODO: Add other handlers here as they're converted
|
||||||
// const ibHandler = new IBHandler(services);
|
// const ibHandler = new IBHandler(services);
|
||||||
// ibHandler.register();
|
// ibHandler.register();
|
||||||
}
|
}
|
||||||
|
|
@ -93,13 +93,21 @@ export class QMHandler extends BaseHandler {
|
||||||
immediately: true,
|
immediately: true,
|
||||||
description: 'Comprehensive symbol search using QM API'
|
description: 'Comprehensive symbol search using QM API'
|
||||||
})
|
})
|
||||||
async spiderSymbolSearch(payload: SymbolSpiderJob, context: ExecutionContext): Promise<unknown> {
|
async spiderSymbolSearch(payload: SymbolSpiderJob | undefined, context: ExecutionContext): Promise<unknown> {
|
||||||
this.logger.info('Starting QM spider symbol search', { payload });
|
// Set default payload for scheduled runs
|
||||||
|
const jobPayload: SymbolSpiderJob = payload || {
|
||||||
|
prefix: null,
|
||||||
|
depth: 1,
|
||||||
|
source: 'qm',
|
||||||
|
maxDepth: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
this.logger.info('Starting QM spider symbol search', { payload: jobPayload });
|
||||||
|
|
||||||
// Store spider job info in cache (temporary data)
|
// Store spider job info in cache (temporary data)
|
||||||
const spiderJobId = `spider:qm:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
|
const spiderJobId = `spider:qm:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
|
||||||
const spiderResult = {
|
const spiderResult = {
|
||||||
payload,
|
payload: jobPayload,
|
||||||
startTime: new Date().toISOString(),
|
startTime: new Date().toISOString(),
|
||||||
status: 'started',
|
status: 'started',
|
||||||
jobId: spiderJobId
|
jobId: spiderJobId
|
||||||
|
|
@ -119,18 +127,4 @@ export class QMHandler extends BaseHandler {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide payloads for scheduled jobs
|
|
||||||
*/
|
|
||||||
protected getScheduledJobPayload(operation: string): any {
|
|
||||||
if (operation === 'spiderSymbolSearch') {
|
|
||||||
return {
|
|
||||||
prefix: null,
|
|
||||||
depth: 1,
|
|
||||||
source: 'qm',
|
|
||||||
maxDepth: 4
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,53 @@
|
||||||
/**
|
import type { IDataIngestionServices } from '@stock-bot/di';
|
||||||
* WebShare Provider for proxy management with scheduled updates
|
|
||||||
*/
|
|
||||||
import { getLogger } from '@stock-bot/logger';
|
|
||||||
import {
|
import {
|
||||||
createJobHandler,
|
BaseHandler,
|
||||||
handlerRegistry,
|
Handler,
|
||||||
type HandlerConfigWithSchedule,
|
Operation,
|
||||||
} from '@stock-bot/queue';
|
QueueSchedule,
|
||||||
|
type ExecutionContext
|
||||||
|
} from '@stock-bot/handlers';
|
||||||
import { updateProxies } from '@stock-bot/utils';
|
import { updateProxies } from '@stock-bot/utils';
|
||||||
import type { ServiceContainer } from '@stock-bot/di';
|
|
||||||
|
|
||||||
const logger = getLogger('webshare-provider');
|
@Handler('webshare')
|
||||||
|
export class WebShareHandler extends BaseHandler {
|
||||||
|
constructor(services: IDataIngestionServices) {
|
||||||
|
super(services);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize and register the WebShare provider
|
@Operation('fetch-proxies')
|
||||||
export function initializeWebShareProvider(_container: ServiceContainer) {
|
@QueueSchedule('0 */6 * * *', {
|
||||||
logger.debug('Registering WebShare provider with scheduled jobs...');
|
priority: 3,
|
||||||
|
immediately: true,
|
||||||
const webShareProviderConfig: HandlerConfigWithSchedule = {
|
description: 'Fetch fresh proxies from WebShare API'
|
||||||
name: 'webshare',
|
})
|
||||||
|
async fetchProxies(_input: unknown, _context: ExecutionContext): Promise<unknown> {
|
||||||
operations: {
|
this.logger.info('Fetching proxies from WebShare API');
|
||||||
'fetch-proxies': createJobHandler(async () => {
|
|
||||||
logger.info('Fetching proxies from WebShare API');
|
|
||||||
const { fetchWebShareProxies } = await import('./operations/fetch.operations');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const { fetchWebShareProxies } = await import('./operations/fetch.operations');
|
||||||
const proxies = await fetchWebShareProxies();
|
const proxies = await fetchWebShareProxies();
|
||||||
|
|
||||||
if (proxies.length > 0) {
|
if (proxies.length > 0) {
|
||||||
// Update the centralized proxy manager
|
// Update the centralized proxy manager
|
||||||
await updateProxies(proxies);
|
await updateProxies(proxies);
|
||||||
|
|
||||||
logger.info('Updated proxy manager with WebShare proxies', {
|
this.logger.info('Updated proxy manager with WebShare proxies', {
|
||||||
count: proxies.length,
|
count: proxies.length,
|
||||||
workingCount: proxies.filter(p => p.isWorking !== false).length,
|
workingCount: proxies.filter(p => p.isWorking !== false).length,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cache proxy stats for monitoring
|
||||||
|
await this.cache.set('webshare-proxy-count', proxies.length, 3600);
|
||||||
|
await this.cache.set('webshare-working-count', proxies.filter(p => p.isWorking !== false).length, 3600);
|
||||||
|
await this.cache.set('last-webshare-fetch', new Date().toISOString(), 1800);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
proxiesUpdated: proxies.length,
|
proxiesUpdated: proxies.length,
|
||||||
workingProxies: proxies.filter(p => p.isWorking !== false).length,
|
workingProxies: proxies.filter(p => p.isWorking !== false).length,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
logger.warn('No proxies fetched from WebShare API');
|
this.logger.warn('No proxies fetched from WebShare API');
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
proxiesUpdated: 0,
|
proxiesUpdated: 0,
|
||||||
|
|
@ -50,33 +55,9 @@ export function initializeWebShareProvider(_container: ServiceContainer) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to fetch and update proxies', { error });
|
this.logger.error('Failed to fetch and update proxies', { error });
|
||||||
return {
|
throw error;
|
||||||
success: false,
|
}
|
||||||
proxiesUpdated: 0,
|
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
scheduledJobs: [
|
|
||||||
{
|
|
||||||
type: 'webshare-fetch',
|
|
||||||
operation: 'fetch-proxies',
|
|
||||||
cronPattern: '0 */6 * * *', // Every 6 hours
|
|
||||||
priority: 3,
|
|
||||||
description: 'Fetch fresh proxies from WebShare API',
|
|
||||||
immediately: true, // Run on startup
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
handlerRegistry.registerWithSchedule(webShareProviderConfig);
|
|
||||||
logger.debug('WebShare provider registered successfully');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const webShareProvider = {
|
|
||||||
initialize: (container: ServiceContainer) => initializeWebShareProvider(container),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Modern TC39 Stage 3 decorators for handler registration
|
// Bun-compatible decorators (hybrid approach)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler decorator - marks a class as a handler
|
* Handler decorator - marks a class as a handler
|
||||||
|
|
@ -7,13 +7,12 @@
|
||||||
export function Handler(name: string) {
|
export function Handler(name: string) {
|
||||||
return function <T extends { new (...args: any[]): {} }>(
|
return function <T extends { new (...args: any[]): {} }>(
|
||||||
target: T,
|
target: T,
|
||||||
context: ClassDecoratorContext
|
_context?: any
|
||||||
) {
|
) {
|
||||||
// Store handler name on the constructor
|
// Store handler name on the constructor
|
||||||
(target as any).__handlerName = name;
|
(target as any).__handlerName = name;
|
||||||
(target as any).__needsAutoRegistration = true;
|
(target as any).__needsAutoRegistration = true;
|
||||||
|
|
||||||
console.log('Handler decorator applied', { name, className: context.name });
|
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -22,23 +21,15 @@ export function Handler(name: string) {
|
||||||
* Operation decorator - marks a method as an operation
|
* Operation decorator - marks a method as an operation
|
||||||
* @param name Operation name
|
* @param name Operation name
|
||||||
*/
|
*/
|
||||||
export function Operation(name: string) {
|
export function Operation(name: string): any {
|
||||||
return function (
|
return function (
|
||||||
_target: Function,
|
target: any,
|
||||||
context: ClassMethodDecoratorContext
|
methodName: string,
|
||||||
) {
|
descriptor?: PropertyDescriptor
|
||||||
const methodName = String(context.name);
|
): any {
|
||||||
|
// Store metadata directly on the class constructor
|
||||||
|
const constructor = target.constructor;
|
||||||
|
|
||||||
console.log('Operation decorator applied', {
|
|
||||||
operationName: name,
|
|
||||||
methodName,
|
|
||||||
contextName: context.name,
|
|
||||||
contextKind: context.kind
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use context.addInitializer to run code when the class is constructed
|
|
||||||
context.addInitializer(function(this: any) {
|
|
||||||
const constructor = this.constructor as any;
|
|
||||||
if (!constructor.__operations) {
|
if (!constructor.__operations) {
|
||||||
constructor.__operations = [];
|
constructor.__operations = [];
|
||||||
}
|
}
|
||||||
|
|
@ -47,14 +38,7 @@ export function Operation(name: string) {
|
||||||
method: methodName,
|
method: methodName,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Operation registered via initializer', {
|
return descriptor;
|
||||||
name,
|
|
||||||
methodName,
|
|
||||||
className: constructor.name
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't return anything - just modify metadata
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,16 +54,15 @@ export function QueueSchedule(
|
||||||
immediately?: boolean;
|
immediately?: boolean;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
) {
|
): any {
|
||||||
return function (
|
return function (
|
||||||
_target: Function,
|
target: any,
|
||||||
context: ClassMethodDecoratorContext
|
methodName: string,
|
||||||
) {
|
descriptor?: PropertyDescriptor
|
||||||
const methodName = String(context.name);
|
): any {
|
||||||
|
// Store metadata directly on the class constructor
|
||||||
|
const constructor = target.constructor;
|
||||||
|
|
||||||
// Use context.addInitializer to run code when the class is constructed
|
|
||||||
context.addInitializer(function(this: any) {
|
|
||||||
const constructor = this.constructor as any;
|
|
||||||
if (!constructor.__schedules) {
|
if (!constructor.__schedules) {
|
||||||
constructor.__schedules = [];
|
constructor.__schedules = [];
|
||||||
}
|
}
|
||||||
|
|
@ -88,9 +71,8 @@ export function QueueSchedule(
|
||||||
cronPattern,
|
cronPattern,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Don't return anything - just modify metadata
|
return descriptor;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"types": ["bun-types"],
|
"types": ["bun-types"],
|
||||||
// Modern TC39 decorators configuration
|
// Modern TC39 Stage 3 decorators (TypeScript 5+ default)
|
||||||
"experimentalDecorators": false,
|
"experimentalDecorators": false,
|
||||||
"emitDecoratorMetadata": false,
|
"emitDecoratorMetadata": true,
|
||||||
"useDefineForClassFields": true
|
// Suppress decorator-related type checking issues due to Bun's hybrid implementation
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": true
|
||||||
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@
|
||||||
"disableReferencedProjectLoad": true,
|
"disableReferencedProjectLoad": true,
|
||||||
"disableSourceOfProjectReferenceRedirect": false,
|
"disableSourceOfProjectReferenceRedirect": false,
|
||||||
|
|
||||||
|
// Decorator support for Bun's hybrid implementation
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
|
||||||
// Paths and output
|
// Paths and output
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue