feat: bootstrap NestJS backend with auth, stations, users, health-check, now-playing

This commit is contained in:
nk
2026-06-02 13:54:00 +03:00
commit 8aadd62e3c
47 changed files with 13234 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { HealthCheckService } from './health-check.service';
@Module({
providers: [HealthCheckService],
})
export class HealthCheckModule {}

View File

@@ -0,0 +1,72 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class HealthCheckService {
private readonly logger = new Logger(HealthCheckService.name);
constructor(private readonly prisma: PrismaService) {}
@Cron(CronExpression.EVERY_HOUR)
async checkAllStations() {
this.logger.log('Starting hourly station health check...');
const stations = await this.prisma.station.findMany();
let onlineCount = 0;
let offlineCount = 0;
for (const station of stations) {
try {
const isOnline = await this.checkStation(station.streamUrl);
await this.prisma.station.update({
where: { id: station.id },
data: { isOnline, lastCheckAt: new Date() },
});
if (isOnline) onlineCount++;
else offlineCount++;
} catch (error) {
this.logger.warn(
`Failed to check station ${station.name}: ${error.message}`,
);
await this.prisma.station.update({
where: { id: station.id },
data: { isOnline: false, lastCheckAt: new Date() },
});
offlineCount++;
}
}
this.logger.log(
`Health check complete. Online: ${onlineCount}, Offline: ${offlineCount}`,
);
}
private async checkStation(url: string): Promise<boolean> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, {
method: 'HEAD',
signal: controller.signal,
});
clearTimeout(timeout);
return response.status >= 200 && response.status < 400;
} catch {
clearTimeout(timeout);
// Fallback to GET if HEAD fails
try {
const controller2 = new AbortController();
const timeout2 = setTimeout(() => controller2.abort(), 10000);
const response = await fetch(url, {
method: 'GET',
signal: controller2.signal,
});
clearTimeout(timeout2);
return response.status >= 200 && response.status < 400;
} catch {
return false;
}
}
}
}