import 'dotenv/config'; import { PrismaClient, TrashType } from '@prisma/client'; const prisma = new PrismaClient(); /** * Öffentliche Schnittstelle der Kommunalen Abfallwirtschaft Mainz und * Mainz-Bingen AöR (https://lk.kaw-mainz-bingen.de/de/Abfallentsorgung/Abfallkalender), * kein API-Key/Login nötig. CityId 35 = Nackenheim, DistrictId 5 = * Verbandsgemeinde Bodenheim (per Browser-Formular ermittelt). */ const API_BASE = 'https://abfallkalender-api-lk.kaw-mainz-bingen.de/public/frontend/collectiondates'; const CITY_ID = 35; const DISTRICT_ID = 5; const SYNC_MONTHS_AHEAD = 6; // Deren WasteTypeId -> unser TrashType. Sperrmüll (5) und Problemmüllbus (6) // kennt unser Schema nicht und werden übersprungen. const WASTE_TYPE_MAP: Record = { 1: 'RESTMUELL', 2: 'BIOMUELL', 3: 'GELBER_SACK', 4: 'PAPIER', }; interface KawCollectionDate { WasteTypeId: number; WasteTypeName: string; Date: string; // "TT.MM.JJJJ" } interface KawResponse { DataList: KawCollectionDate[]; TotalRecords: number; } function parseGermanDate(value: string): Date { const [day, month, year] = value.split('.').map(Number); return new Date(Date.UTC(year, month - 1, day)); } async function fetchAllPages(dateFrom: string, dateTo: string): Promise { const results: KawCollectionDate[] = []; let page = 1; while (true) { const url = `${API_BASE}?Filter.CityId=${CITY_ID}&Filter.DistrictId=${DISTRICT_ID}` + `&Filter.DateFrom=${dateFrom}&Filter.DateTo=${dateTo}&Filter.CurrentPage=${page}`; const res = await fetch(url); if (!res.ok) throw new Error(`KAW-API antwortete mit Status ${res.status}`); const body = (await res.json()) as KawResponse; results.push(...body.DataList); if (results.length >= body.TotalRecords || body.DataList.length === 0) break; page += 1; } return results; } export async function runTrashCalendarSyncJob(referenceDate: Date = new Date()) { const dateFrom = referenceDate.toISOString().slice(0, 10); const dateTo = new Date(referenceDate); dateTo.setMonth(dateTo.getMonth() + SYNC_MONTHS_AHEAD); const dateToStr = dateTo.toISOString().slice(0, 10); const entries = await fetchAllPages(dateFrom, dateToStr); let created = 0; let skippedUnknownType = 0; for (const entry of entries) { const type = WASTE_TYPE_MAP[entry.WasteTypeId]; if (!type) { skippedUnknownType += 1; continue; } const date = parseGermanDate(entry.Date); const existing = await prisma.trashSchedule.findUnique({ where: { type_date: { type, date } } }); if (existing) continue; await prisma.trashSchedule.create({ data: { type, date } }); created += 1; } return { fetched: entries.length, created, skippedUnknownType }; } if (require.main === module) { runTrashCalendarSyncJob() .then((result) => { console.log( `[trashCalendarSyncJob] ${result.fetched} Termine von der KAW-API geladen, ` + `${result.skippedUnknownType} unbekannte Mülltypen übersprungen.`, ); return prisma.$disconnect(); }) .catch((err) => { console.error('[trashCalendarSyncJob] Fehlgeschlagen:', err); process.exit(1); }); }