Sync trash calendar from official Nackenheim/KAW waste collection API
Adds trashCalendarSyncJob.ts, which pulls Restmüll/Biomüll/Gelbe Tonne/ Papiertonne dates for Nackenheim (CityId 35, Verbandsgemeinde Bodenheim) from the public, keyless API behind lk.kaw-mainz-bingen.de's official waste calendar, and upserts them into trash_schedule. Adds a unique (type, date) constraint to prevent duplicate entries on reruns. Run via `npm run trash-sync:run`, same standalone-script pattern as the other reminder jobs.
This commit is contained in:
parent
3b505d231a
commit
39220bddf4
@ -12,6 +12,7 @@
|
|||||||
"dunning:run": "ts-node -r dotenv/config src/jobs/dailyDunningJob.ts",
|
"dunning:run": "ts-node -r dotenv/config src/jobs/dailyDunningJob.ts",
|
||||||
"cleaning-reminder:run": "ts-node -r dotenv/config src/jobs/cleaningReminderJob.ts",
|
"cleaning-reminder:run": "ts-node -r dotenv/config src/jobs/cleaningReminderJob.ts",
|
||||||
"trash-reminder:run": "ts-node -r dotenv/config src/jobs/trashReminderJob.ts",
|
"trash-reminder:run": "ts-node -r dotenv/config src/jobs/trashReminderJob.ts",
|
||||||
|
"trash-sync:run": "ts-node -r dotenv/config src/jobs/trashCalendarSyncJob.ts",
|
||||||
"notice-deadline-reminder:run": "ts-node -r dotenv/config src/jobs/noticeDeadlineReminderJob.ts",
|
"notice-deadline-reminder:run": "ts-node -r dotenv/config src/jobs/noticeDeadlineReminderJob.ts",
|
||||||
"test:webhook": "ts-node -r dotenv/config scripts/send-test-webhook.ts",
|
"test:webhook": "ts-node -r dotenv/config scripts/send-test-webhook.ts",
|
||||||
"prisma:generate": "prisma generate --schema=../prisma/schema.prisma",
|
"prisma:generate": "prisma generate --schema=../prisma/schema.prisma",
|
||||||
|
|||||||
99
backend/src/jobs/trashCalendarSyncJob.ts
Normal file
99
backend/src/jobs/trashCalendarSyncJob.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
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<number, TrashType> = {
|
||||||
|
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<KawCollectionDate[]> {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -590,6 +590,7 @@ model TrashSchedule {
|
|||||||
|
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|
||||||
|
@@unique([type, date])
|
||||||
@@index([date])
|
@@index([date])
|
||||||
@@map("trash_schedule")
|
@@map("trash_schedule")
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user