Skip to content

Commit

Permalink
Merge pull request #431 from ITPNYU/riho/import_calendar_event
Browse files Browse the repository at this point in the history
Import pregame events to firebase
  • Loading branch information
rlho authored Sep 30, 2024
2 parents 50a360b + 97809e1 commit 337ccd0
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 404 deletions.
175 changes: 175 additions & 0 deletions booking-app/app/api/syncCalendars/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { toFirebaseTimestampFromString } from "@/components/src/client/utils/serverDate";
import { TableNames } from "@/components/src/policy";
import { Booking, MediaServices } from "@/components/src/types";
import admin from "@/lib/firebase/server/firebaseAdmin";
import { getCalendarClient } from "@/lib/googleClient";
import { Timestamp } from "@firebase/firestore";
import { NextResponse } from "next/server";

const db = admin.firestore();
const createBookingWithDefaults = (
partialBooking: Partial<Booking>,
): Booking => {
return {
title: "",
description: "",
email: "",
firstName: "",
lastName: "",
secondaryName: "",
nNumber: "",
netId: "",
phoneNumber: "",
department: "",
role: "",
sponsorFirstName: "",
sponsorLastName: "",
sponsorEmail: "",
bookingType: "",
attendeeAffiliation: "",
roomSetup: "",
setupDetails: "",
mediaServices: "",
mediaServicesDetails: "",
catering: "",
hireSecurity: "",
expectedAttendance: "",
cateringService: "",
chartFieldForCatering: "",
chartFieldForSecurity: "",
chartFieldForRoomSetup: "",
calendarEventId: "",
roomId: "",
requestNumber: 0,
equipmentCheckedOut: false,
startDate: null,
endDate: null,
...partialBooking,
};
};
const findNyuEmail = (event: any): string => {
const attendees = event.attendees || [];
const nyuEmail = attendees.find(
(attendee: any) => attendee.email && attendee.email.endsWith("@nyu.edu"),
);
return nyuEmail ? nyuEmail.email : "";
};
export async function POST(request: Request) {
try {
const calendar = await getCalendarClient();
// Fetch all calendar IDs from the Resource table
const resourcesSnapshot = await db.collection("resources").get();
const resources = resourcesSnapshot.docs.map(doc => ({
id: doc.id,
calendarId: doc.data().calendarId,
roomId: doc.data().roomId,
}));

let totalNewBookings = 0;
let targetBookings = 0;
for (const resource of resources) {
try {
// Fetch events for each calendar
let pageToken: string | undefined;
const now = new Date();
const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth(), 1);
const threeMonthsLater = new Date(
now.getFullYear(),
now.getMonth() + 4,
0,
);
const timeMin = threeMonthsAgo.toISOString();
const timeMax = threeMonthsLater.toISOString();
const events = await calendar.events.list({
calendarId: resource.calendarId,
timeMin: timeMin,
timeMax: timeMax,
maxResults: 500, // Maximum allowed by Google Calendar API
singleEvents: true,
orderBy: "startTime",
pageToken: pageToken,
});

for (const event of events.data.items || []) {
const bookingRef = db
.collection("bookings")
.where("calendarEventId", "==", event.id);
const bookingSnapshot = await bookingRef.get();
const nyuEmail = findNyuEmail(event);
if (bookingSnapshot.empty && nyuEmail) {
targetBookings++;
console.log("calendarEventId", event.id);
console.log("title", event.summary);
}

if (bookingSnapshot.empty && nyuEmail) {
// Create a new booking
const calendarEventId = event.id;
const newBooking = createBookingWithDefaults({
title: event.summary || "",
description: event.description || "",
email: nyuEmail || "",
startDate: toFirebaseTimestampFromString(
event.start?.dateTime,
) as Timestamp,
endDate: toFirebaseTimestampFromString(
event.end?.dateTime,
) as Timestamp,
calendarEventId: calendarEventId || "",
equipmentCheckedOut: true,
roomId: resource.roomId,
mediaServices: MediaServices.CHECKOUT_EQUIPMENT,
});
console.log("newBooking", newBooking);
const bookingDocRef = await db
.collection(TableNames.BOOKING)
.add(newBooking);

console.log(`New Booking created with ID: ${bookingDocRef.id}`);

const newBookingStatus = {
calendarEventId: calendarEventId,
email: nyuEmail,
requestedAt: admin.firestore.FieldValue.serverTimestamp(),
firstApprovedAt: admin.firestore.FieldValue.serverTimestamp(),
finalApprovedAt: admin.firestore.FieldValue.serverTimestamp(),
};
console.log("newBookingStatus", newBookingStatus);
const statusDocRef = await db
.collection(TableNames.BOOKING_STATUS)
.add(newBookingStatus);
console.log(
`New BookingStatus created with ID: ${statusDocRef.id}`,
);

totalNewBookings++;
}
pageToken = events.data.nextPageToken;
}
while (pageToken);
console.log("targetBookings", targetBookings);
} catch (error) {
console.error(
`Error processing calendar ${resource.calendarId}:`,
error,
);
// Continue with the next calendar
}
}

return NextResponse.json(
{
message: `${totalNewBookings} new bookings have been synchronized.`,
},
{ status: 200 },
);
} catch (error) {
console.error("Error syncing calendars:", error);
return NextResponse.json(
{
error: "An error occurred while syncing calendars.",
},
{ status: 500 },
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Grid from "@mui/material/Unstable_Grid2";
import { Liaisons } from "./Liaisons";
import { PAUsers } from "./PAUsers";
import SafetyTrainedUsers from "./SafetyTraining";
import SyncCalendars from "./SyncCalendars";

const tabs = [
{ label: "Safety Training", id: "safetyTraining" },
Expand All @@ -22,6 +23,7 @@ const tabs = [
{ label: "Booking Types", id: "bookingTypes" },
{ label: "Policy Settings", id: "policy" },
{ label: "Export", id: "export" },
{ label: "Sync Calendars", id: "syncCalendars" },
];

export default function Settings() {
Expand Down Expand Up @@ -55,6 +57,7 @@ export default function Settings() {
{tab === "bookingTypes" && <BookingTypes />}
{tab === "policy" && <FinalApproverSetting />}
{tab === "export" && <ExportDatabase />}
{tab === "syncCalendars" && <SyncCalendars />}
</Grid>
</Grid>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Box, Button, Typography } from "@mui/material";
import React, { useState } from "react";
import AlertToast from "../../components/AlertToast";

const SyncCalendars = () => {
const [loading, setLoading] = useState(false);
const [showAlert, setShowAlert] = useState(false);
const [alertSeverity, setAlertSeverity] = useState<"error" | "success">(
"success"
);
const [message, setMessage] = useState("");

const handleSync = async () => {
setLoading(true);
setShowAlert(false);
try {
const response = await fetch("/api/syncCalendars", { method: "POST" });
const data = await response.json();
if (response.ok) {
setMessage(`Sync successful: ${data.message}`);
setAlertSeverity("success");
} else {
setMessage(`Error: ${data.error}`);
setAlertSeverity("error");
}
} catch (error) {
setMessage("An error occurred while syncing calendars.");
setAlertSeverity("error");
} finally {
setLoading(false);
setShowAlert(true);
}
};

return (
<Box>
<Typography variant="h6">
{" "}
Sync Current Semester Calendar Events
</Typography>
<p>
This function saves existing events from the current semester's calendar
to the database.
</p>
<Box sx={{ marginTop: 2 }}>
<Button onClick={handleSync} variant="contained" disabled={loading}>
Sync Calendar Events
</Button>
</Box>
<AlertToast
message={message}
severity={alertSeverity}
open={showAlert}
handleClose={() => setShowAlert(false)}
/>
</Box>
);
};

export default SyncCalendars;
19 changes: 12 additions & 7 deletions booking-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion booking-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
"eslint-plugin-prettier": "^5.2.1",
"postcss": "^8",
"prettier": "^3.3.3",
"prisma": "^5.16.1",
"tailwindcss": "^3.4.1",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
Expand Down
Loading

0 comments on commit 337ccd0

Please sign in to comment.