fix(calendar): address CTO review follow-up items

- Capture DTSTAMP once before loop instead of new Date() per event
- Return plain text 401 for auth errors (calendar clients can't parse JSON)
- Use encodeURIComponent for Content-Disposition filename

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Flea Flicker
2026-03-26 04:10:40 +00:00
parent 8ca120d521
commit 1e84823656
+7 -6
View File
@@ -33,7 +33,8 @@ function buildIcalFeed(
petName: string | null; petName: string | null;
serviceName: string | null; serviceName: string | null;
}>, }>,
staffName: string staffName: string,
dtstamp: string
): string { ): string {
const lines: string[] = [ const lines: string[] = [
"BEGIN:VCALENDAR", "BEGIN:VCALENDAR",
@@ -53,7 +54,7 @@ function buildIcalFeed(
lines.push( lines.push(
"BEGIN:VEVENT", "BEGIN:VEVENT",
`UID:${appt.id}@groombook`, `UID:${appt.id}@groombook`,
`DTSTAMP:${formatIcalDate(new Date())}`, `DTSTAMP:${dtstamp}`,
`DTSTART:${formatIcalDate(new Date(appt.startTime))}`, `DTSTART:${formatIcalDate(new Date(appt.startTime))}`,
`DTEND:${formatIcalDate(new Date(appt.endTime))}`, `DTEND:${formatIcalDate(new Date(appt.endTime))}`,
`SUMMARY:${escapeIcalText(summary)}`, `SUMMARY:${escapeIcalText(summary)}`,
@@ -74,7 +75,7 @@ calendarRouter.get("/:staffId.ics", async (c) => {
const token = c.req.query("token") as string; const token = c.req.query("token") as string;
if (!token) { if (!token) {
return c.json({ error: "Missing token parameter" }, 401); return c.text("Unauthorized", 401);
} }
const [staffMember] = await db const [staffMember] = await db
@@ -84,7 +85,7 @@ calendarRouter.get("/:staffId.ics", async (c) => {
.limit(1); .limit(1);
if (!staffMember || staffMember.icalToken !== token) { if (!staffMember || staffMember.icalToken !== token) {
return c.json({ error: "Invalid token" }, 401); return c.text("Unauthorized", 401);
} }
const now = new Date(); const now = new Date();
@@ -113,10 +114,10 @@ calendarRouter.get("/:staffId.ics", async (c) => {
) )
.orderBy(appointments.startTime); .orderBy(appointments.startTime);
const ical = buildIcalFeed(rows, staffMember.name); const ical = buildIcalFeed(rows, staffMember.name, formatIcalDate(new Date()));
return c.text(ical, 200, { return c.text(ical, 200, {
"Content-Type": "text/calendar; charset=utf-8", "Content-Type": "text/calendar; charset=utf-8",
"Content-Disposition": `inline; filename="${staffMember.name.replace(/\s+/g, "_")}_calendar.ics"`, "Content-Disposition": `inline; filename="${encodeURIComponent(staffMember.name)}_calendar.ics"`,
}); });
}); });