initial commit

This commit is contained in:
2026-10-13 02:24:39 +03:00
commit 191b80b6b1
80 changed files with 6863 additions and 0 deletions

37
server/maintenance.mjs Normal file
View File

@@ -0,0 +1,37 @@
export function maintenanceEnd(item) {
return Math.min(item.endsAt, item.cancelledAt ?? Infinity, item.completedAt ?? Infinity);
}
export function maintenancePhase(item, now = Date.now()) {
if (item.cancelledAt !== null && item.cancelledAt <= now) return 'cancelled';
if (item.completedAt !== null && item.completedAt <= now) return 'completed';
if (now >= item.endsAt) return 'ended';
return now < item.startsAt ? 'scheduled' : 'active';
}
export function affectsService(item, serviceId, time = Date.now()) {
return (!item.serviceIds.length || item.serviceIds.includes(serviceId)) && item.startsAt <= time && time < maintenanceEnd(item);
}
export function validateMaintenance(input, serviceIds) {
for (const key of ['titleRu', 'titleEn', 'messageRu', 'messageEn']) {
if (typeof input[key] !== 'string' || !input[key].trim() || input[key].length > 2000) throw new Error(`Provide ${key} (1–2000 characters)`);
}
if (!Number.isSafeInteger(input.startsAt) || !Number.isSafeInteger(input.endsAt) || input.endsAt <= input.startsAt) throw new Error('End must be after start');
if (!Array.isArray(input.serviceIds) || input.serviceIds.some(id => !serviceIds.includes(id)) || new Set(input.serviceIds).size !== input.serviceIds.length) throw new Error('Unknown or duplicate service');
return input;
}
export function parseInstant(value) {
const valid = typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && Number.isFinite(Date.parse(value));
if (!valid || new Date(`${value.slice(0, 10)}T00:00:00Z`).toISOString().slice(0, 10) !== value.slice(0, 10)) throw new Error('Use a valid ISO timestamp with an explicit timezone, for example 2026-10-13T02:00:00+03:00');
return Date.parse(value);
}
export function combinedStatus(availability, scenario, maintenance = false) {
if (maintenance) return 'maintenance';
if (availability === 'down' || availability === 'unknown') return availability;
if (scenario && ['down', 'degraded'].includes(scenario.status)) return 'degraded';
if (scenario?.status === 'unknown') return 'unknown';
return availability;
}