refactor: load server settings from JSON configuration

This commit is contained in:
2026-10-30 16:55:11 +09:00
parent 31d6eac4c2
commit 28a45ff8e3
10 changed files with 104 additions and 28 deletions
+33
View File
@@ -0,0 +1,33 @@
import { readFileSync } from 'node:fs';
export type ServerConfig = {
host: string;
port: number;
mongoUri: string;
mongoDatabase: string;
visitStore: 'mongo' | 'memory';
allowedOrigins: string[];
};
export function loadServerConfig(path: string | URL = new URL('../.config.json', import.meta.url)): ServerConfig {
let contents: string;
try { contents = readFileSync(path, 'utf8'); }
catch { throw new Error('Cannot read .config.json; copy .config.example.json to .config.json and configure the server.'); }
let value: unknown;
try { value = JSON.parse(contents); }
catch { throw new Error('Invalid JSON in .config.json'); }
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid .config.json: expected an object');
const config = value as Record<string, unknown>;
const nonempty = (value: unknown): value is string => typeof value === 'string' && value.trim().length > 0;
for (const field of ['host', 'mongoUri', 'mongoDatabase']) {
if (!nonempty(config[field])) throw new Error(`Invalid .config.json field: ${field}`);
}
if (typeof config.port !== 'number' || !Number.isInteger(config.port) || config.port < 0 || config.port > 65535) {
throw new Error('Invalid .config.json field: port');
}
if (config.visitStore !== 'mongo' && config.visitStore !== 'memory') throw new Error('Invalid .config.json field: visitStore');
if (!Array.isArray(config.allowedOrigins) || !config.allowedOrigins.length || !config.allowedOrigins.every(nonempty)) {
throw new Error('Invalid .config.json field: allowedOrigins');
}
return config as ServerConfig;
}
+8 -9
View File
@@ -1,24 +1,23 @@
import { createApp } from './app.ts';
import { loadServerConfig } from './config.ts';
import { MemoryVisitStore, MongoVisitStore, type VisitStore } from './visits.ts';
async function main() {
const port = Number(process.env.PORT ?? 3000);
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('Invalid PORT');
if (process.env.VISIT_STORE && !['memory', 'mongo'].includes(process.env.VISIT_STORE)) throw new Error('Invalid VISIT_STORE');
const config = loadServerConfig();
let visitStore: VisitStore;
if (process.env.VISIT_STORE === 'memory') {
console.warn('VISIT_STORE=memory: visit history is volatile; development/tests only.');
if (config.visitStore === 'memory') {
console.warn('visitStore=memory: visit history is volatile; development/tests only.');
visitStore = new MemoryVisitStore();
} else {
visitStore = await MongoVisitStore.connect(process.env.MONGODB_URI ?? 'mongodb://172.16.0.7:27017', process.env.MONGODB_DATABASE ?? 'sunrise');
visitStore = await MongoVisitStore.connect(config.mongoUri, config.mongoDatabase);
}
const app = createApp({ visitStore,
allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',').map(origin => origin.trim()).filter(Boolean) });
allowedOrigins: config.allowedOrigins });
const stop = () => { void app.close().catch(error => { console.error('Shutdown failed', error); process.exitCode = 1; }); };
process.once('SIGINT', stop); process.once('SIGTERM', stop);
app.httpServer.once('error', error => { console.error('HTTP server failed', error); process.exitCode = 1; stop(); });
app.httpServer.listen(port, process.env.HOST ?? '127.0.0.1', () => {
console.log(`Sunrise single-instance server listening on ${process.env.HOST ?? '127.0.0.1'}:${(app.httpServer.address() as { port: number }).port}`);
app.httpServer.listen(config.port, config.host, () => {
console.log(`Sunrise single-instance server listening on ${config.host}:${(app.httpServer.address() as { port: number }).port}`);
});
}
void main().catch(error => { console.error('Server startup failed; admission disabled', error); process.exitCode = 1; });