Sprint 004 non-blocking: - null byte 방지 (harness file write) Sprint 005 본문: - CostLog Prisma 모델 추가 - TASK-016: GET /api/admin/costs + POST /api/admin/costs/record/:name 자매별/모델별/일별 토큰 + 예상 비용 (USD), 기간 필터(day/week/month) - TASK-018: WebSocket Gateway (@WebSocketGateway /ws namespace) sisters:update, activity:new 브로드캐스트 EventsScheduler: 30초 주기 자매 상태 체크 + 브로드캐스트 ActivityService: 새 로그 생성 시 실시간 브로드캐스트 - TASK-017: /admin/costs 비용 대시보드 (BarChart + 요약 카드) - 메인 대시보드 useSocket 훅 + 실시간 연결 상태 표시 - 테스트 22/22 pass, FE 12 routes build 성공 - NEXT_PUBLIC_WS_URL env (.env.local.example 업데이트)
36 lines
1.1 KiB
TypeScript
36 lines
1.1 KiB
TypeScript
import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
import { SistersService } from '../sisters/sisters.service';
|
|
import { EventsGateway } from './events.gateway';
|
|
|
|
@Injectable()
|
|
export class EventsScheduler implements OnModuleInit, OnModuleDestroy {
|
|
private readonly logger = new Logger(EventsScheduler.name);
|
|
private timer: NodeJS.Timeout | null = null;
|
|
private readonly INTERVAL_MS = parseInt(process.env.WS_POLL_INTERVAL_MS ?? '30000', 10);
|
|
|
|
constructor(
|
|
private readonly sistersService: SistersService,
|
|
private readonly gateway: EventsGateway,
|
|
) {}
|
|
|
|
onModuleInit() {
|
|
this.timer = setInterval(() => this.tick(), this.INTERVAL_MS);
|
|
this.logger.log(`WebSocket scheduler started (interval: ${this.INTERVAL_MS}ms)`);
|
|
}
|
|
|
|
onModuleDestroy() {
|
|
if (this.timer) clearInterval(this.timer);
|
|
}
|
|
|
|
private async tick() {
|
|
if (this.gateway.getClientCount() === 0) return;
|
|
|
|
try {
|
|
const sisters = await this.sistersService.getAllSistersStatus();
|
|
this.gateway.broadcastSisterStatus(sisters);
|
|
} catch (e) {
|
|
this.logger.warn(`Scheduler tick failed: ${(e as Error).message}`);
|
|
}
|
|
}
|
|
}
|