"Automatische Warnungen bei Auslauf/Kündigungsfristen" was only a
passive dashboard view (GET /contracts/notice-deadlines, isUrgent
flag) — nobody gets notified unless the landlord happens to open the
cockpit. Adds a daily job (same pattern as dailyDunningJob.ts) that
pushes landlords/admins when a contract's notice deadline falls
within 30 days. Dedup is via the Notification history (payload.
contractId within a 14-day cooldown) rather than a new Contract
column, consistent with contracts.ts's existing "no extra model
needed" approach.
sendPushNotification() (services/notificationService.ts) already called
the real OneSignal API correctly, but two things made it unreachable:
no endpoint ever set User.pushToken, and no job existed for the two
push-based requirements beyond rent dunning — cleaning check-in
reminders and the trash-calendar evening push. Both CleaningTask and
TrashSchedule already had an unused `reminderSentAt` column, so the
schema anticipated this and was just never wired up.
- PUT /v1/auth/push-token: registers the current device's OneSignal
Player ID.
- jobs/cleaningReminderJob.ts, jobs/trashReminderJob.ts: same
find-due/send-once/mark-reminderSentAt pattern as the existing
dailyDunningJob.ts. Safe to run without OneSignal credentials
configured — sendPushNotification degrades to an in-app Notification
log when ONESIGNAL_APP_ID/API_KEY or a user's pushToken are absent.
Not done here (needs real third-party setup from the user, same as the
Cloudflare tunnel token pattern): ONESIGNAL_APP_ID/API_KEY in .env, and
a frontend OneSignal Web SDK integration (service worker, VAPID keys)
to actually populate pushToken from a real browser.
Implements the "1-Klick-PDF-Export für Handwerker" requirement from
the ticketing module spec, which existed only as a schema stub
(HandoverProtocol.pdfUrl) with no actual generator anywhere in the
codebase. Adds a server-side PDF endpoint (pdfkit, no headless
browser needed) summarizing a ticket's category, priority, status,
room, reporter and description, plus a "PDF" button in the landlord
cockpit that downloads it via an authenticated blob fetch (the API
uses a Bearer token, not cookies, so a plain <a href> wouldn't carry
auth).
ts-node resolved node_modules relative to the script path, so running
from prisma/seed.ts (outside backend/) never found bcryptjs or
@prisma/client in backend/node_modules — the seed silently failed on
every container start. Moving the script under backend/prisma/ fixes
resolution; also adds a missing .gitignore for node_modules.