Skip to content

Commit

Permalink
Merge pull request #446 from ITPNYU/riho/syncCalendar
Browse files Browse the repository at this point in the history
Import non-nyu member's events
  • Loading branch information
rlho authored Oct 7, 2024
2 parents 8308063 + 13699b2 commit d595a1d
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 112 deletions.
1 change: 0 additions & 1 deletion booking-app/app/api/safety_training_users/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export async function GET(request: NextRequest) {
range: range,
fields: "values",
});
console.log("emails", response.data.values);

const logEntry = {
logName: process.env.NEXT_PUBLIC_GCP_LOG_NAME + "/safety-training",
Expand Down
229 changes: 131 additions & 98 deletions booking-app/app/api/syncCalendars/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { Booking, MediaServices } from "@/components/src/types";

import { NextResponse } from "next/server";
import { toFirebaseTimestampFromString } from "@/components/src/client/utils/serverDate";
import { TableNames } from "@/components/src/policy";
import { Timestamp } from "@firebase/firestore";
import { Booking, MediaServices } from "@/components/src/types";
import admin from "@/lib/firebase/server/firebaseAdmin";
import { getCalendarClient } from "@/lib/googleClient";
import { toFirebaseTimestampFromString } from "@/components/src/client/utils/serverDate";
import { Timestamp } from "@firebase/firestore";
import { NextResponse } from "next/server";

const db = admin.firestore();
const areRoomIdsSame = (roomIds1: string, roomIds2: string): boolean => {
// Trim and split the room IDs, then sort both arrays
const sortedRoomIds1 = roomIds1.split(',').map(id => id.trim()).sort();
const sortedRoomIds2 = roomIds2.split(',').map(id => id.trim()).sort();

// Compare the two arrays
return sortedRoomIds1.length === sortedRoomIds2.length &&
sortedRoomIds1.every((id, index) => id === sortedRoomIds2[index]);
};
const createBookingWithDefaults = (
partialBooking: Partial<Booking>,
): Booking => {
Expand Down Expand Up @@ -49,17 +57,41 @@ const createBookingWithDefaults = (
...partialBooking,
};
};
const findNyuEmail = (event: any): string => {

const findGuestEmail = (event: any): string => {
const attendees = event.attendees || [];
const nyuEmail = attendees.find(
(attendee: any) => attendee.email && attendee.email.endsWith("@nyu.edu"),
const guestEmail = attendees.find((attendee: any) =>
attendee.email && !attendee.email.endsWith('@group.calendar.google.com')
);
return nyuEmail ? nyuEmail.email : "";
return guestEmail ? guestEmail.email : "";
};
const findRoomIds = (event: any, resources: any[]): string => {
const attendees = event.attendees || [];
const roomIds = new Set<string>();

// Add the roomId of the current resource
const currentResource = resources.find(r => r.calendarId === event.organizer.email);
if (currentResource) {
roomIds.add(currentResource.roomId);
}

// Add other room IDs
attendees.forEach((attendee: any) => {
const resource = resources.find(r => r.calendarId === attendee.email);
if (resource) {
roomIds.add(resource.roomId);
}
});

// Convert to array, sort numerically, and join
return Array.from(roomIds)
.sort((a, b) => parseInt(a) - parseInt(b))
.join(',');
};

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,
Expand All @@ -68,110 +100,111 @@ export async function POST(request: Request) {
}));

let totalNewBookings = 0;
let totalUpdatedBookings = 0;
let targetBookings = 0;

const now = new Date();
const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 1, 1);
const threeMonthsLater = new Date(now.getFullYear(), now.getMonth() + 4, 0);
const timeMin = threeMonthsAgo.toISOString();
const timeMax = threeMonthsLater.toISOString();

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);
}
do {
const events = await calendar.events.list({
calendarId: resource.calendarId,
timeMin: timeMin,
timeMax: timeMax,
maxResults: 500,
singleEvents: true,
orderBy: "startTime",
pageToken: pageToken,
});

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++;
for (const event of events.data.items || []) {
const bookingRef = db
.collection("bookings")
.where("calendarEventId", "==", event.id);
const bookingSnapshot = await bookingRef.get();
const guestEmail = findGuestEmail(event);
const roomIds = findRoomIds(event, resources);

if (bookingSnapshot.empty && guestEmail) {
targetBookings++;
console.log("calendarEventId", event.id);
console.log("title", event.summary);

const calendarEventId = event.id;
const newBooking = createBookingWithDefaults({
title: event.summary || "",
description: event.description || "",
email: guestEmail,
startDate: toFirebaseTimestampFromString(event.start?.dateTime) as Timestamp,
endDate: toFirebaseTimestampFromString(event.end?.dateTime) as Timestamp,
calendarEventId: calendarEventId || "",
roomId: roomIds,
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: guestEmail,
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++;
} else if (!bookingSnapshot.empty) {
// Update existing booking if roomIds contains multiple rooms and is different from the existing roomId
const existingBooking = bookingSnapshot.docs[0];
const existingData = existingBooking.data() as Booking;

if (roomIds.includes(',') && !areRoomIdsSame(roomIds, existingData.roomId)) {
console.log("roomIds",roomIds)
console.log("existingData.roomId",existingData.roomId)
await existingBooking.ref.update({ roomId: roomIds });
console.log(`Updated roomId for Booking ID: ${existingBooking.id}`);
totalUpdatedBookings++;
}
}
}
pageToken = events.data.nextPageToken;
}
while (pageToken);
console.log("targetBookings", targetBookings);
} while (pageToken);
} catch (error) {
console.error(
`Error processing calendar ${resource.calendarId}:`,
error,
);
// Continue with the next calendar
console.error(`Error processing calendar ${resource.calendarId}:`, error);
}
}

console.log("targetBookings", targetBookings);
console.log("totalNewBookings", totalNewBookings);
console.log("totalUpdatedBookings", totalUpdatedBookings);

return NextResponse.json(
{
message: `${totalNewBookings} new bookings have been synchronized.`,
{
message: `${totalNewBookings} new bookings have been synchronized. ${totalUpdatedBookings} existing bookings have been updated with multiple rooms.`
},
{ status: 200 },
{ status: 200 }
);
} catch (error) {
console.error("Error syncing calendars:", error);
return NextResponse.json(
{
error: "An error occurred while syncing calendars.",
},
{ status: 500 },
{ error: "An error occurred while syncing calendars." },
{ status: 500 }
);
}
}
}
6 changes: 0 additions & 6 deletions booking-app/components/src/client/utils/serverDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ export const serverFormatDate = (
timeZone,
});

console.log("Input:", input);
console.log("Parsed Date:", date.toISOString());
console.log("Zoned Date:", zonedDate.toString());
console.log("Formatted Result:", formattedResult);
console.log("Timezone:", timeZone);

return formattedResult;
} catch (error) {
console.error("Error formatting date:", error, "Input:", input);
Expand Down
7 changes: 0 additions & 7 deletions booking-app/lib/googleClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,16 @@ const refreshAccessTokenIfNeeded = async (oauth2Client) => {
try {
const { credentials } = await oauth2Client.refreshAccessToken();
oauth2Client.setCredentials(credentials);
console.log("Access token refreshed successfully");
console.log(
"New token expiry:",
new Date(oauth2Client.credentials.expiry_date)
);
} catch (error) {
console.error("Error refreshing access token:", error);
throw error;
}
} else {
console.log("Using existing access token");
}
};

const getAuthenticatedClient = async () => {
if (!cachedOAuth2Client) {
console.log("Creating new OAuth2 client");
cachedOAuth2Client = createOAuth2Client();
cachedOAuth2Client.setCredentials({
refresh_token: process.env.GOOGLE_REFRESH_TOKEN,
Expand Down

0 comments on commit d595a1d

Please sign in to comment.