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,48 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { SettingsService } from '../../modules/settings/settings.module';
|
||||
|
||||
export type EventStatusName = 'Stopped' | 'Running';
|
||||
|
||||
export interface EventStatus {
|
||||
status: EventStatusName;
|
||||
startUtc: string;
|
||||
endUtc: string;
|
||||
serverNowUtc: string;
|
||||
countdownMs: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class EventStatusService {
|
||||
constructor(private readonly settings: SettingsService) {}
|
||||
|
||||
async getStatus(now: Date = new Date()): Promise<EventStatus> {
|
||||
const startStr = await this.settings.get('eventStartUtc');
|
||||
const endStr = await this.settings.get('eventEndUtc');
|
||||
const start = new Date(startStr);
|
||||
const end = new Date(endStr);
|
||||
const nowMs = now.getTime();
|
||||
const startMs = start.getTime();
|
||||
const endMs = end.getTime();
|
||||
|
||||
let status: EventStatusName = 'Stopped';
|
||||
let countdownMs = 0;
|
||||
if (nowMs < startMs) {
|
||||
status = 'Stopped';
|
||||
countdownMs = startMs - nowMs;
|
||||
} else if (nowMs <= endMs) {
|
||||
status = 'Running';
|
||||
countdownMs = endMs - nowMs;
|
||||
} else {
|
||||
status = 'Stopped';
|
||||
countdownMs = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
startUtc: start.toISOString(),
|
||||
endUtc: end.toISOString(),
|
||||
serverNowUtc: now.toISOString(),
|
||||
countdownMs,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
const MAX_FAILS = 5;
|
||||
const BACKOFF_MS = [1_000, 5_000, 30_000, 120_000, 600_000, 1_800_000];
|
||||
|
||||
@Injectable()
|
||||
export class LoginBackoffService {
|
||||
private counters = new Map<string, { count: number; blockedUntil: number }>();
|
||||
|
||||
private key(ip: string, username: string): string {
|
||||
return `${ip}::${username}`;
|
||||
}
|
||||
|
||||
isBlocked(ip: string, username: string, now: number = Date.now()): number {
|
||||
const k = this.key(ip, username);
|
||||
const entry = this.counters.get(k);
|
||||
if (!entry) return 0;
|
||||
if (entry.blockedUntil > now) return entry.blockedUntil - now;
|
||||
return 0;
|
||||
}
|
||||
|
||||
recordFailure(ip: string, username: string, now: number = Date.now()): void {
|
||||
const k = this.key(ip, username);
|
||||
const entry = this.counters.get(k) ?? { count: 0, blockedUntil: 0 };
|
||||
entry.count++;
|
||||
if (entry.count > MAX_FAILS) {
|
||||
const idx = Math.min(entry.count - MAX_FAILS - 1, BACKOFF_MS.length - 1);
|
||||
entry.blockedUntil = now + BACKOFF_MS[idx];
|
||||
} else if (entry.count === MAX_FAILS) {
|
||||
entry.blockedUntil = now + BACKOFF_MS[0];
|
||||
}
|
||||
this.counters.set(k, entry);
|
||||
}
|
||||
|
||||
reset(ip: string, username: string): void {
|
||||
this.counters.delete(this.key(ip, username));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
const WINDOW_MS = 60_000;
|
||||
const MAX_PER_WINDOW = 10;
|
||||
|
||||
@Injectable()
|
||||
export class RegistrationRateLimitService {
|
||||
private hits: Map<string, number[]> = new Map();
|
||||
|
||||
isAllowed(ip: string, now: number = Date.now()): boolean {
|
||||
const arr = (this.hits.get(ip) ?? []).filter((t) => now - t < WINDOW_MS);
|
||||
this.hits.set(ip, arr);
|
||||
return arr.length < MAX_PER_WINDOW;
|
||||
}
|
||||
|
||||
record(ip: string, now: number = Date.now()): void {
|
||||
const arr = (this.hits.get(ip) ?? []).filter((t) => now - t < WINDOW_MS);
|
||||
arr.push(now);
|
||||
this.hits.set(ip, arr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class SseHubService {
|
||||
private readonly eventSubject = new Subject<any>();
|
||||
private readonly scoreboardSubject = new Subject<any>();
|
||||
|
||||
emitEvent(payload: any): void {
|
||||
this.eventSubject.next(payload);
|
||||
}
|
||||
|
||||
emitScoreboard(payload: any): void {
|
||||
this.scoreboardSubject.next(payload);
|
||||
}
|
||||
|
||||
event$(): Observable<any> {
|
||||
return this.eventSubject.asObservable();
|
||||
}
|
||||
|
||||
scoreboard$(): Observable<any> {
|
||||
return this.scoreboardSubject.asObservable();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user