import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import bcrypt from "bcryptjs"; async function authenticate(req: NextRequest) { const authHeader = req.headers.get("authorization"); if (!authHeader?.startsWith("Basic ")) return null; const decoded = Buffer.from(authHeader.slice(6), "base64").toString(); const [email, password] = decoded.split(":"); const user = await prisma.user.findUnique({ where: { email } }); if (!user) return null; const valid = await bcrypt.compare(password, user.passwordHash); return valid ? user : null; } function taskToVTodo(task: { id: string; title: string; note: string | null; completed: boolean; completedAt: Date | null; dueDate: Date | null; priority: number; createdAt: Date; updatedAt: Date }) { const now = new Date().toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"; const priorityMap: Record = { 0: 0, 1: 9, 2: 5, 3: 1 }; let vtodo = [ "BEGIN:VTODO", `UID:${task.id}@checkflow`, `DTSTAMP:${now}`, `CREATED:${task.createdAt.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"}`, `LAST-MODIFIED:${task.updatedAt.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"}`, `SUMMARY:${task.title.replace(/\n/g, "\\n")}`, `STATUS:${task.completed ? "COMPLETED" : "NEEDS-ACTION"}`, `PRIORITY:${priorityMap[task.priority] ?? 0}`, ]; if (task.note) vtodo.push(`DESCRIPTION:${task.note.replace(/\n/g, "\\n")}`); if (task.dueDate) vtodo.push(`DUE:${task.dueDate.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"}`); if (task.completedAt) vtodo.push(`COMPLETED:${task.completedAt.toISOString().replace(/[-:]/g, "").split(".")[0] + "Z"}`); vtodo.push("END:VTODO"); return vtodo.join("\r\n"); } // Handle all CalDAV/CardDAV requests export async function GET(req: NextRequest) { const user = await authenticate(req); if (!user) { return new NextResponse("Unauthorized", { status: 401, headers: { "WWW-Authenticate": 'Basic realm="CheckFlow"' }, }); } const tasks = await prisma.task.findMany({ where: { userId: user.id, parentId: null }, orderBy: { sortOrder: "asc" }, }); const icsContent = [ "BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//CheckFlow//CheckFlow//EN", "CALSCALE:GREGORIAN", "METHOD:PUBLISH", ...tasks.map(taskToVTodo), "END:VCALENDAR", ].join("\r\n"); return new NextResponse(icsContent, { headers: { "Content-Type": "text/calendar; charset=utf-8", "Content-Disposition": "attachment; filename=checkflow.ics", }, }); } // OPTIONS for CORS export async function OPTIONS() { return new NextResponse(null, { headers: { Allow: "GET, OPTIONS", "DAV": "1, 2, calendar-access", }, }); }