AI Implementation feature(820): Project Scaffold and Platform Foundations (#1)
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { Controller, Get, Sse, MessageEvent } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
||||
import { interval, map, merge, mergeMap, Observable, startWith, defer } from 'rxjs';
|
||||
import { Public } from '../../common/decorators/public.decorator';
|
||||
import { SystemService } from './system.service';
|
||||
import { EventStatusService } from '../../common/services/event-status.service';
|
||||
import { SseHubService } from '../../common/services/sse-hub.service';
|
||||
|
||||
@ApiTags('system')
|
||||
@Controller('api/v1')
|
||||
export class SystemController {
|
||||
constructor(
|
||||
private readonly system: SystemService,
|
||||
private readonly statusSvc: EventStatusService,
|
||||
private readonly hub: SseHubService,
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@Get('bootstrap')
|
||||
@ApiOperation({ summary: 'Public bootstrap payload used by the SPA on startup' })
|
||||
bootstrap() {
|
||||
return this.system.bootstrap();
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('event/status')
|
||||
@ApiOperation({ summary: 'Event status derived from UTC timestamps' })
|
||||
eventStatus() {
|
||||
return this.statusSvc.getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Global event status + countdown stream.
|
||||
* Emits a FLATTENED object every second (and whenever the hub pushes).
|
||||
*/
|
||||
@Public()
|
||||
@Sse('event/stream')
|
||||
eventStream(): Observable<MessageEvent> {
|
||||
const tick$ = interval(1000).pipe(
|
||||
startWith(0),
|
||||
mergeMap(() =>
|
||||
defer(() =>
|
||||
this.statusSvc.getStatus().then((s) => ({
|
||||
status: s.status,
|
||||
countdownMs: s.countdownMs,
|
||||
serverNowUtc: s.serverNowUtc,
|
||||
startUtc: s.startUtc,
|
||||
endUtc: s.endUtc,
|
||||
})),
|
||||
),
|
||||
),
|
||||
);
|
||||
const hub$ = this.hub.event$().pipe(
|
||||
map((payload: any) => ({
|
||||
status: payload?.status,
|
||||
countdownMs: payload?.countdownMs,
|
||||
serverNowUtc: payload?.serverNowUtc,
|
||||
startUtc: payload?.startUtc,
|
||||
endUtc: payload?.endUtc,
|
||||
})),
|
||||
);
|
||||
return merge(tick$, hub$).pipe(map((src) => ({ data: src }) as MessageEvent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Live solves stream.
|
||||
* Emits a FLATTENED solve payload whenever a new solve is published.
|
||||
*/
|
||||
@Public()
|
||||
@Sse('scoreboard/stream')
|
||||
scoreboardStream(): Observable<MessageEvent> {
|
||||
return this.hub.scoreboard$().pipe(
|
||||
map((s: any) => ({
|
||||
challengeId: s?.challengeId,
|
||||
userId: s?.userId,
|
||||
pointsAwarded: s?.pointsAwarded,
|
||||
rankBonus: s?.rankBonus,
|
||||
solvedAt: s?.solvedAt,
|
||||
})),
|
||||
map((src) => ({ data: src }) as MessageEvent),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user