Initial commit: Cat Sitting Planner with PWA, SQLite, and Webhook Notifications

This commit is contained in:
2026-01-12 20:48:23 +01:00
commit 3121ef223d
52 changed files with 13722 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
import prisma from "@/lib/prisma"
import { NextResponse } from "next/server"
export async function GET(
request: Request,
{ params }: { params: Promise<{ planId: string }> }
) {
const { planId } = await params
const plan = await prisma.plan.findUnique({
where: { id: planId },
include: { bookings: true }
})
if (!plan) {
return new NextResponse("Plan not found", { status: 404 })
}
let icsContent = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//Cat Sitting Planner//EN\n"
for (const booking of plan.bookings) {
if (booking.type === "OWNER_HOME") continue; // Don't put owner home days in calendar? Or maybe do?
const date = new Date(booking.date)
const dateStr = date.toISOString().replace(/-|:|\.\d\d\d/g, "").slice(0, 8)
icsContent += "BEGIN:VEVENT\n"
icsContent += `DTSTART;VALUE=DATE:${dateStr}\n`
icsContent += `DTEND;VALUE=DATE:${dateStr}\n` // Single day event usually ends next day or same day? Calendar usually expects Start and End. For all day: DTSTART:YYYYMMDD, DTEND:YYYYMMDD+1
// Calculate next day for DTEND
const nextDate = new Date(date)
nextDate.setDate(date.getDate() + 1)
const nextDateStr = nextDate.toISOString().replace(/-|:|\.\d\d\d/g, "").slice(0, 8)
icsContent = icsContent.replace(`DTEND;VALUE=DATE:${dateStr}\n`, `DTEND;VALUE=DATE:${nextDateStr}\n`)
icsContent += `SUMMARY:Cat Sitting: ${booking.sitterName}\n`
icsContent += `DESCRIPTION:Cat sitting for plan ${plan.id}\n`
icsContent += "END:VEVENT\n"
}
icsContent += "END:VCALENDAR"
return new NextResponse(icsContent, {
headers: {
"Content-Type": "text/calendar",
"Content-Disposition": `attachment; filename="cat-sitting-${plan.startDate.toISOString().slice(0, 10)}.ics"`
}
})
}