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 { QMHandler } from './qm/qm.handler';
|
||||
import { WebShareHandler } from './webshare/webshare.handler';
|
||||
|
||||
/**
|
||||
* Initialize and register all handlers
|
||||
|
|
@ -14,10 +15,11 @@ export function initializeAllHandlers(services: IDataIngestionServices): void {
|
|||
const qmHandler = new QMHandler(services);
|
||||
qmHandler.register();
|
||||
|
||||
// TODO: Add other handlers here as they're converted
|
||||
// const webShareHandler = new WebShareHandler(services);
|
||||
// webShareHandler.register();
|
||||
// WebShare Handler
|
||||
const webShareHandler = new WebShareHandler(services);
|
||||
webShareHandler.register();
|
||||
|
||||
// TODO: Add other handlers here as they're converted
|
||||
// const ibHandler = new IBHandler(services);
|
||||
// ibHandler.register();
|
||||
}
|
||||
|
|
@ -93,13 +93,21 @@ export class QMHandler extends BaseHandler {
|
|||
immediately: true,
|
||||
description: 'Comprehensive symbol search using QM API'
|
||||
})
|
||||
async spiderSymbolSearch(payload: SymbolSpiderJob, context: ExecutionContext): Promise<unknown> {
|
||||
this.logger.info('Starting QM spider symbol search', { payload });
|
||||
async spiderSymbolSearch(payload: SymbolSpiderJob | undefined, context: ExecutionContext): Promise<unknown> {
|
||||
// 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)
|
||||
const spiderJobId = `spider:qm:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
|
||||
const spiderResult = {
|
||||
payload,
|
||||
payload: jobPayload,
|
||||
startTime: new Date().toISOString(),
|
||||
status: 'started',
|
||||
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,82 +1,63 @@
|
|||
/**
|
||||
* WebShare Provider for proxy management with scheduled updates
|
||||
*/
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import type { IDataIngestionServices } from '@stock-bot/di';
|
||||
import {
|
||||
createJobHandler,
|
||||
handlerRegistry,
|
||||
type HandlerConfigWithSchedule,
|
||||
} from '@stock-bot/queue';
|
||||
BaseHandler,
|
||||
Handler,
|
||||
Operation,
|
||||
QueueSchedule,
|
||||
type ExecutionContext
|
||||
} from '@stock-bot/handlers';
|
||||
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
|
||||
export function initializeWebShareProvider(_container: ServiceContainer) {
|
||||
logger.debug('Registering WebShare provider with scheduled jobs...');
|
||||
@Operation('fetch-proxies')
|
||||
@QueueSchedule('0 */6 * * *', {
|
||||
priority: 3,
|
||||
immediately: true,
|
||||
description: 'Fetch fresh proxies from WebShare API'
|
||||
})
|
||||
async fetchProxies(_input: unknown, _context: ExecutionContext): Promise<unknown> {
|
||||
this.logger.info('Fetching proxies from WebShare API');
|
||||
|
||||
const webShareProviderConfig: HandlerConfigWithSchedule = {
|
||||
name: 'webshare',
|
||||
try {
|
||||
const { fetchWebShareProxies } = await import('./operations/fetch.operations');
|
||||
const proxies = await fetchWebShareProxies();
|
||||
|
||||
operations: {
|
||||
'fetch-proxies': createJobHandler(async () => {
|
||||
logger.info('Fetching proxies from WebShare API');
|
||||
const { fetchWebShareProxies } = await import('./operations/fetch.operations');
|
||||
if (proxies.length > 0) {
|
||||
// Update the centralized proxy manager
|
||||
await updateProxies(proxies);
|
||||
|
||||
try {
|
||||
const proxies = await fetchWebShareProxies();
|
||||
this.logger.info('Updated proxy manager with WebShare proxies', {
|
||||
count: proxies.length,
|
||||
workingCount: proxies.filter(p => p.isWorking !== false).length,
|
||||
});
|
||||
|
||||
if (proxies.length > 0) {
|
||||
// Update the centralized proxy manager
|
||||
await updateProxies(proxies);
|
||||
// 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);
|
||||
|
||||
logger.info('Updated proxy manager with WebShare proxies', {
|
||||
count: proxies.length,
|
||||
workingCount: proxies.filter(p => p.isWorking !== false).length,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
proxiesUpdated: proxies.length,
|
||||
workingProxies: proxies.filter(p => p.isWorking !== false).length,
|
||||
};
|
||||
} else {
|
||||
logger.warn('No proxies fetched from WebShare API');
|
||||
return {
|
||||
success: false,
|
||||
proxiesUpdated: 0,
|
||||
error: 'No proxies returned from API',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch and update proxies', { error });
|
||||
return {
|
||||
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');
|
||||
return {
|
||||
success: true,
|
||||
proxiesUpdated: proxies.length,
|
||||
workingProxies: proxies.filter(p => p.isWorking !== false).length,
|
||||
};
|
||||
} else {
|
||||
this.logger.warn('No proxies fetched from WebShare API');
|
||||
return {
|
||||
success: false,
|
||||
proxiesUpdated: 0,
|
||||
error: 'No proxies returned from API',
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to fetch and update proxies', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -7,13 +7,12 @@
|
|||
export function Handler(name: string) {
|
||||
return function <T extends { new (...args: any[]): {} }>(
|
||||
target: T,
|
||||
context: ClassDecoratorContext
|
||||
_context?: any
|
||||
) {
|
||||
// Store handler name on the constructor
|
||||
(target as any).__handlerName = name;
|
||||
(target as any).__needsAutoRegistration = true;
|
||||
|
||||
console.log('Handler decorator applied', { name, className: context.name });
|
||||
return target;
|
||||
};
|
||||
}
|
||||
|
|
@ -22,39 +21,24 @@ export function Handler(name: string) {
|
|||
* Operation decorator - marks a method as an operation
|
||||
* @param name Operation name
|
||||
*/
|
||||
export function Operation(name: string) {
|
||||
export function Operation(name: string): any {
|
||||
return function (
|
||||
_target: Function,
|
||||
context: ClassMethodDecoratorContext
|
||||
) {
|
||||
const methodName = String(context.name);
|
||||
target: any,
|
||||
methodName: string,
|
||||
descriptor?: PropertyDescriptor
|
||||
): 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
|
||||
if (!constructor.__operations) {
|
||||
constructor.__operations = [];
|
||||
}
|
||||
constructor.__operations.push({
|
||||
name,
|
||||
method: methodName,
|
||||
});
|
||||
|
||||
// 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) {
|
||||
constructor.__operations = [];
|
||||
}
|
||||
constructor.__operations.push({
|
||||
name,
|
||||
method: methodName,
|
||||
});
|
||||
|
||||
console.log('Operation registered via initializer', {
|
||||
name,
|
||||
methodName,
|
||||
className: constructor.name
|
||||
});
|
||||
});
|
||||
|
||||
// Don't return anything - just modify metadata
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -70,27 +54,25 @@ export function QueueSchedule(
|
|||
immediately?: boolean;
|
||||
description?: string;
|
||||
}
|
||||
) {
|
||||
): any {
|
||||
return function (
|
||||
_target: Function,
|
||||
context: ClassMethodDecoratorContext
|
||||
) {
|
||||
const methodName = String(context.name);
|
||||
target: any,
|
||||
methodName: string,
|
||||
descriptor?: PropertyDescriptor
|
||||
): 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) {
|
||||
constructor.__schedules = [];
|
||||
}
|
||||
constructor.__schedules.push({
|
||||
operation: methodName,
|
||||
cronPattern,
|
||||
...options,
|
||||
});
|
||||
if (!constructor.__schedules) {
|
||||
constructor.__schedules = [];
|
||||
}
|
||||
constructor.__schedules.push({
|
||||
operation: methodName,
|
||||
cronPattern,
|
||||
...options,
|
||||
});
|
||||
|
||||
// Don't return anything - just modify metadata
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@
|
|||
"composite": true,
|
||||
"incremental": true,
|
||||
"types": ["bun-types"],
|
||||
// Modern TC39 decorators configuration
|
||||
// Modern TC39 Stage 3 decorators (TypeScript 5+ default)
|
||||
"experimentalDecorators": false,
|
||||
"emitDecoratorMetadata": false,
|
||||
"useDefineForClassFields": true
|
||||
"emitDecoratorMetadata": true,
|
||||
// Suppress decorator-related type checking issues due to Bun's hybrid implementation
|
||||
"skipLibCheck": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
"disableReferencedProjectLoad": true,
|
||||
"disableSourceOfProjectReferenceRedirect": false,
|
||||
|
||||
// Decorator support for Bun's hybrid implementation
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
||||
// Paths and output
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue