From 405f139c1b8f8e2937f53480411d6a66ab0c34ae Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Thu, 9 Jan 2025 21:58:07 +1100 Subject: [PATCH] Add user timezone handling to calendar events --- .../calendar/[ownerId]/[projectId]/route.ts | 50 +++++++++++++++---- lib/ops/auth.ts | 24 +++++++++ 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/app/(api)/api/calendar/[ownerId]/[projectId]/route.ts b/app/(api)/api/calendar/[ownerId]/[projectId]/route.ts index b609293..82b72bd 100644 --- a/app/(api)/api/calendar/[ownerId]/[projectId]/route.ts +++ b/app/(api)/api/calendar/[ownerId]/[projectId]/route.ts @@ -1,19 +1,39 @@ import { calendarEvent, project, task, taskList } from "@/drizzle/schema"; +import { getUser } from "@/lib/ops/auth"; import { getDatabaseForOwner } from "@/lib/utils/useDatabase"; import { getVtimezoneComponent } from "@touch4it/ical-timezones"; +import dayjs from "dayjs"; +import tz from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; import { and, desc, eq, lte } from "drizzle-orm"; import ical, { ICalCalendarMethod } from "ical-generator"; +import type { NextRequest } from "next/server"; + +dayjs.extend(utc); +dayjs.extend(tz); export const revalidate = 0; export const dynamic = "force-dynamic"; export async function GET( - _: Request, - props: { params: Promise<{ projectId: string; ownerId: string }> }, + request: NextRequest, + props: { + params: Promise<{ projectId: string; ownerId: string }>; + }, ) { const params = await props.params; + const searchParams = request.nextUrl.searchParams; const { projectId, ownerId } = params; + let timezone: string | null = null; + const userId = searchParams.get("userId"); + if (userId) { + const user = await getUser(userId); + if (user.customData?.timezone) { + timezone = user.customData.timezone; + } + } + const db = await getDatabaseForOwner(ownerId); const projectDetails = await db.query.project @@ -47,21 +67,29 @@ export async function GET( const calendar = ical({ name: projectDetails.name, method: ICalCalendarMethod.PUBLISH, - timezone: { name: "Australia/Sydney", generator: getVtimezoneComponent }, + timezone: timezone + ? { name: timezone, generator: getVtimezoneComponent } + : null, }); for (const event of events) { calendar.createEvent({ id: event.id, - start: event.start, - end: event.end ?? null, + start: timezone + ? dayjs.utc(event.start).tz(timezone).format("YYYY-MM-DDTHH:mm:ssZ") + : event.start, + end: event.end + ? timezone + ? dayjs.utc(event.end).tz(timezone).format("YYYY-MM-DDTHH:mm:ssZ") + : event.end + : null, summary: event.name, description: event.description, allDay: event.allDay, created: event.createdAt, lastModified: event.updatedAt, repeating: event.repeatRule, - timezone: "Australia/Sydney", + timezone, }); } @@ -76,14 +104,18 @@ export async function GET( calendar.createEvent({ id: task.id, - start: task.dueDate, - end: task.dueDate, + start: timezone + ? dayjs.utc(task.dueDate).tz(timezone).format("YYYY-MM-DDTHH:mm:ssZ") + : task.dueDate, + end: timezone + ? dayjs.utc(task.dueDate).tz(timezone).format("YYYY-MM-DDTHH:mm:ssZ") + : task.dueDate, summary: `[${tasklist.name}] ${task.name}`, description: task.description, allDay: true, created: task.createdAt, lastModified: task.updatedAt, - timezone: "Australia/Sydney", + timezone, }); } } diff --git a/lib/ops/auth.ts b/lib/ops/auth.ts index 17efc2d..2c5324e 100644 --- a/lib/ops/auth.ts +++ b/lib/ops/auth.ts @@ -46,6 +46,30 @@ export const fetchAccessToken = async () => { }).toString(), }); }; + +export const getUser = async (userId: string) => { + const { access_token } = await fetchAccessToken().then((res) => res.json()); + if (!access_token) { + throw new Error("Access token not found"); + } + + const { endpoint } = logtoConfig; + const response = await fetch(`${endpoint}api/users/${userId}`, { + method: "GET", + headers: { + Authorization: `Bearer ${access_token}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + console.error("Failed to fetch user", response.status); + throw new Error("Failed to fetch user"); + } + + return await response.json(); +}; + export const updateUser = async ( userId: string, data: {