Files
HIPCTF2/backend/src/modules/admin/general.service.ts
T
2026-07-22 16:16:20 +00:00

105 lines
3.6 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import { ConfigService } from '@nestjs/config';
import { SettingsService } from '../settings/settings.module';
import { ThemeLoaderService } from '../../common/utils/theme-loader.service';
import { SseHubService } from '../../common/services/sse-hub.service';
import { SETTINGS_KEYS } from '../../config/env.schema';
import { GeneralSettingsPayload } from './dto/general.dto';
export interface GeneralSettingsView {
pageTitle: string;
logo: string;
welcomeMarkdown: string;
themeKey: string;
eventStartUtc: string;
eventEndUtc: string;
defaultChallengeIp: string;
registrationsEnabled: boolean;
}
export interface ThemeView {
id: string;
key: string;
name: string;
}
@Injectable()
export class AdminGeneralService {
constructor(
private readonly settings: SettingsService,
private readonly themes: ThemeLoaderService,
private readonly hub: SseHubService,
private readonly config: ConfigService,
) {}
async getSettings(): Promise<GeneralSettingsView> {
const [pageTitle, logo, welcomeMarkdown, themeKey, eventStartUtc, eventEndUtc, defaultChallengeIp, registrationsEnabled] =
await Promise.all([
this.settings.get(SETTINGS_KEYS.PAGE_TITLE, 'HIPCTF'),
this.settings.get(SETTINGS_KEYS.LOGO, ''),
this.settings.get(SETTINGS_KEYS.WELCOME_MARKDOWN, ''),
this.settings.get(SETTINGS_KEYS.THEME_KEY, 'classic'),
this.settings.get(SETTINGS_KEYS.EVENT_START_UTC, ''),
this.settings.get(SETTINGS_KEYS.EVENT_END_UTC, ''),
this.settings.get(SETTINGS_KEYS.DEFAULT_CHALLENGE_IP, '127.0.0.1'),
this.settings.get(SETTINGS_KEYS.REGISTRATIONS_ENABLED, 'false'),
]);
return {
pageTitle,
logo,
welcomeMarkdown,
themeKey,
eventStartUtc,
eventEndUtc,
defaultChallengeIp,
registrationsEnabled: registrationsEnabled === 'true',
};
}
async updateSettings(payload: GeneralSettingsPayload): Promise<GeneralSettingsView> {
await Promise.all([
this.settings.set(SETTINGS_KEYS.PAGE_TITLE, payload.pageTitle),
this.settings.set(SETTINGS_KEYS.LOGO, payload.logo),
this.settings.set(SETTINGS_KEYS.WELCOME_MARKDOWN, payload.welcomeMarkdown),
this.settings.set(SETTINGS_KEYS.THEME_KEY, payload.themeKey),
this.settings.set(SETTINGS_KEYS.EVENT_START_UTC, payload.eventStartUtc),
this.settings.set(SETTINGS_KEYS.EVENT_END_UTC, payload.eventEndUtc),
this.settings.set(SETTINGS_KEYS.DEFAULT_CHALLENGE_IP, payload.defaultChallengeIp),
this.settings.set(SETTINGS_KEYS.REGISTRATIONS_ENABLED, payload.registrationsEnabled ? 'true' : 'false'),
]);
this.hub.emitEvent({
topic: 'general',
themeKey: payload.themeKey,
registrationsEnabled: payload.registrationsEnabled,
});
return this.getSettings();
}
listThemes(): ThemeView[] {
const themesDir = path.resolve(this.config.get<string>('THEMES_DIR', './themes'));
let present = new Set<string>();
try {
if (fs.existsSync(themesDir)) {
for (const f of fs.readdirSync(themesDir)) {
if (!f.endsWith('.json')) continue;
try {
const raw = fs.readFileSync(path.join(themesDir, f), 'utf-8');
const parsed = JSON.parse(raw) as { id?: string };
if (parsed?.id) present.add(parsed.id);
} catch {
/* skip unreadable */
}
}
}
} catch {
/* swallow */
}
return this.themes
.listThemes()
.filter((t) => present.has(t.id))
.map((t) => ({ id: t.id, key: t.id, name: t.name }));
}
}