128 lines
3.7 KiB
JavaScript
128 lines
3.7 KiB
JavaScript
import { importSpielplan } from '../utils/spielplan-import.js'
|
|
import { importLeagueTables } from '../utils/spielklassen-tables-import.js'
|
|
import { publishImportedSpielplan } from '../utils/spielplan-publish.js'
|
|
import { info as loggerInfo, error as loggerError } from '../utils/logger.js'
|
|
|
|
const TIME_ZONE = 'Europe/Berlin'
|
|
const RUN_HOUR = 7
|
|
const RUN_MINUTE = 0
|
|
const MAX_TIMEOUT = 2_147_483_647
|
|
|
|
let timer = null
|
|
let running = false
|
|
|
|
function getTimeParts(date) {
|
|
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
timeZone: TIME_ZONE,
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false
|
|
}).formatToParts(date)
|
|
|
|
return Object.fromEntries(parts.map((part) => [part.type, part.value]))
|
|
}
|
|
|
|
function getTimeZoneOffset(date) {
|
|
const parts = getTimeParts(date)
|
|
const zonedAsUtc = Date.UTC(
|
|
Number(parts.year),
|
|
Number(parts.month) - 1,
|
|
Number(parts.day),
|
|
Number(parts.hour),
|
|
Number(parts.minute),
|
|
Number(parts.second)
|
|
)
|
|
|
|
return zonedAsUtc - date.getTime()
|
|
}
|
|
|
|
function zonedDateToUtc(year, month, day, hour, minute) {
|
|
const utcGuess = new Date(Date.UTC(year, month - 1, day, hour, minute, 0))
|
|
const offset = getTimeZoneOffset(utcGuess)
|
|
return new Date(utcGuess.getTime() - offset)
|
|
}
|
|
|
|
function nextRunAt(now = new Date()) {
|
|
const parts = getTimeParts(now)
|
|
let year = Number(parts.year)
|
|
let month = Number(parts.month)
|
|
let day = Number(parts.day)
|
|
let candidate = zonedDateToUtc(year, month, day, RUN_HOUR, RUN_MINUTE)
|
|
|
|
if (candidate <= now) {
|
|
const nextDay = zonedDateToUtc(year, month, day + 1, 12, 0)
|
|
const nextParts = getTimeParts(nextDay)
|
|
year = Number(nextParts.year)
|
|
month = Number(nextParts.month)
|
|
day = Number(nextParts.day)
|
|
candidate = zonedDateToUtc(year, month, day, RUN_HOUR, RUN_MINUTE)
|
|
}
|
|
|
|
return candidate
|
|
}
|
|
|
|
async function runImport(reason) {
|
|
if (running) return
|
|
|
|
running = true
|
|
try {
|
|
const spielplan = await importSpielplan()
|
|
loggerInfo(`[spielplan-import] ${reason}: ${spielplan.matchCount} Spiele importiert`, { range: `${spielplan.source.season.dateStart} - ${spielplan.source.season.dateEnd}` })
|
|
|
|
const published = await publishImportedSpielplan({ inputPath: spielplan.jsonFile })
|
|
loggerInfo(`[spielplan-import] ${reason}: Spielplan publiziert`, {
|
|
season: published.seasonSlug,
|
|
internalPath: published.internalSeasonPath
|
|
})
|
|
|
|
try {
|
|
const tables = await importLeagueTables()
|
|
loggerInfo(`[spielplan-import] ${reason}: ${tables.importedCount}/${tables.teamCount} Tabellen importiert`, {
|
|
season: tables.seasonSlug,
|
|
outputFile: tables.outputFile,
|
|
errors: tables.errorCount
|
|
})
|
|
} catch (error) {
|
|
loggerError('[spielplan-import] Tabellen-Import fehlgeschlagen:', { error })
|
|
}
|
|
} catch (error) {
|
|
loggerError('[spielplan-import] Import fehlgeschlagen:', { error })
|
|
} finally {
|
|
running = false
|
|
}
|
|
}
|
|
|
|
function scheduleNext() {
|
|
const runAt = nextRunAt()
|
|
const delay = Math.min(Math.max(runAt.getTime() - Date.now(), 1_000), MAX_TIMEOUT)
|
|
|
|
timer = setTimeout(async () => {
|
|
await runImport('taeglicher Lauf')
|
|
scheduleNext()
|
|
}, delay)
|
|
|
|
timer.unref?.()
|
|
loggerInfo('[spielplan-import] Naechster Lauf', { runAt: runAt.toISOString(), tz: TIME_ZONE, time: `${String(RUN_HOUR).padStart(2, '0')}:${String(RUN_MINUTE).padStart(2, '0')}` })
|
|
}
|
|
|
|
export default defineNitroPlugin((nitroApp) => {
|
|
if (process.env.SPIELPLAN_IMPORT_DISABLED === 'true') {
|
|
loggerInfo('[spielplan-import] Scheduler deaktiviert')
|
|
return
|
|
}
|
|
|
|
scheduleNext()
|
|
|
|
if (process.env.SPIELPLAN_IMPORT_RUN_ON_START === 'true') {
|
|
runImport('Startlauf')
|
|
}
|
|
|
|
nitroApp.hooks.hookOnce('close', () => {
|
|
if (timer) clearTimeout(timer)
|
|
})
|
|
})
|