Files
hanarang-dashboard/backend/src/events/events.scheduler.ts
narang-ai 914903466c feat(sprint-005): cost monitor + WebSocket realtime
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 업데이트)
2026-04-04 12:28:25 +09:00

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}`);
}
}
}