From 45d55d35a88cc09b27645a0bd595dc27e5357a51 Mon Sep 17 00:00:00 2001 From: Carson Davis Date: Sat, 7 Mar 2026 16:42:44 -0800 Subject: [PATCH] feat: invite volunteer to hardcoded calendar on register --- server/common/calendar.ts | 64 +++++++++++++++++++++++++++++++++++++++ server/package.json | 1 + server/routes/clinics.js | 28 +++++++++++++++-- 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 server/common/calendar.ts diff --git a/server/common/calendar.ts b/server/common/calendar.ts new file mode 100644 index 00000000..e88053c6 --- /dev/null +++ b/server/common/calendar.ts @@ -0,0 +1,64 @@ +import { google } from "googleapis"; + +export async function sendCalendarInvite( + email: string, + clinicDetails: { + name: string; + description?: string; + location?: string; + start_time: Date; + end_time: Date; + date: Date; + } +) { + try { + const oauth2Client = new google.auth.OAuth2( + process.env.GOOGLE_CLIENT_ID, + process.env.GOOGLE_CLIENT_SECRET + ); + + oauth2Client.setCredentials({ + // TODO: Use refresh token from database instead of env + refresh_token: process.env.GOOGLE_REFRESH_TOKEN, + }); + + oauth2Client.on("tokens", (tokens) => { + if (tokens.refresh_token) { + // store the refresh_token in my database! + console.log(tokens.refresh_token); + } + console.log(tokens.access_token); + }); + + const calendar = google.calendar({ version: "v3", auth: oauth2Client }); + + const { name, description, start_time, end_time, location } = clinicDetails; + + const event = { + summary: name || "Clinic Event", + description: description || "Thank you for registering!", + location: location || "", + start: { + dateTime: new Date(start_time).toISOString(), + }, + end: { + dateTime: new Date(end_time).toISOString(), + }, + attendees: [{ email }], + reminders: { + useDefault: true, + }, + }; + + const response = await calendar.events.insert({ + calendarId: process.env.GOOGLE_CALENDAR_ID, + requestBody: event, + sendUpdates: "all", + }); + + return { success: true, event: response.data }; + } catch (error) { + console.error("Error creating calendar event:", error); + return { success: false, error: (error as Error).message }; + } +} diff --git a/server/package.json b/server/package.json index 7e66985e..590f5af3 100644 --- a/server/package.json +++ b/server/package.json @@ -24,6 +24,7 @@ "dotenv": "^10.0.0", "express": "^4.17.1", "firebase-admin": "^12.4.0", + "googleapis": "^171.4.0", "mongoose": "^8.2.0", "mysql": "^2.18.1", "node-schedule": "^2.1.1", diff --git a/server/routes/clinics.js b/server/routes/clinics.js index 2fc3d19d..f71e010b 100644 --- a/server/routes/clinics.js +++ b/server/routes/clinics.js @@ -1,3 +1,4 @@ +import { sendCalendarInvite } from "@/common/calendar"; import { keysToCamel } from "@/common/utils"; import { db } from "@/db/db-pgp"; import { Router } from "express"; @@ -34,7 +35,8 @@ clinicsRouter.post("/", async (req, res) => { (type && !allowedLocationTypes.includes(type)) ) { return res.status(400).json({ - message: "Invalid location or type. Must be 'In-Person', 'Hybrid', or 'Virtual'.", + message: + "Invalid location or type. Must be 'In-Person', 'Hybrid', or 'Virtual'.", }); } @@ -122,7 +124,8 @@ clinicsRouter.put("/:id", async (req, res) => { (type && !allowedLocationTypes.includes(type)) ) { return res.status(400).json({ - message: "Invalid location or type. Must be 'In-Person', 'Hybrid', or 'Virtual'.", + message: + "Invalid location or type. Must be 'In-Person', 'Hybrid', or 'Virtual'.", }); } @@ -281,7 +284,7 @@ clinicsRouter.post("/:clinicId/registrations", async (req, res) => { try { const { clinicId } = req.params; const { volunteerId } = req.body; - const data = await db.query( + const data = await db.query( ` INSERT INTO clinic_registration (volunteer_id, clinic_id, has_attended) VALUES ($1, $2, false) @@ -290,6 +293,25 @@ clinicsRouter.post("/:clinicId/registrations", async (req, res) => { [volunteerId, clinicId] ); + // Fetch volunteer email and clinic details for the calendar invite + try { + const volunteer = await db.query( + "SELECT email FROM volunteers WHERE id = $1", + [volunteerId] + ); + const clinic = await db.query("SELECT * FROM clinics WHERE id = $1", [ + clinicId, + ]); + + if (volunteer.length > 0 && clinic.length > 0) { + sendCalendarInvite(volunteer[0].email, clinic[0]).catch((err) => { + console.error("Failed to send calendar invite:", err); + }); + } + } catch (inviteError) { + console.error("Error fetching data for calendar invite:", inviteError); + } + res.status(200).json(keysToCamel(data)); } catch (err) { res.status(500).send(err.message);