Checkpoint: Initial stable CheckFlow base before i18n and demo mode
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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<number, number> = { 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",
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user