Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staging release 09.10 #463

Merged
merged 24 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
47090d6
Import non-nyu member's events
rlho Oct 4, 2024
13699b2
Delete logs
rlho Oct 4, 2024
717ce88
Update Logic For Cancel Log Out
Kaibanda Oct 4, 2024
d595a1d
Merge pull request #446 from ITPNYU/riho/syncCalendar
rlho Oct 7, 2024
769429f
Merge pull request #447 from ITPNYU/Kai_Log_out_Bugfix
rlho Oct 7, 2024
e27d963
Add logs for debugging
rlho Oct 7, 2024
105e540
Fix finding rooom logic
rlho Oct 7, 2024
65724af
Converting an Number to String
rlho Oct 7, 2024
cfcb56a
Record who declined a request and why
rlho Oct 7, 2024
8cf64a3
Merge pull request #454 from ITPNYU/riho/decline_reason
rlho Oct 8, 2024
947b28b
Revert "Record who declined a request and why"
rlho Oct 8, 2024
848474f
Merge pull request #455 from ITPNYU/revert-454-riho/decline_reason
rlho Oct 8, 2024
31f35a4
Record email when someone change status
rlho Oct 8, 2024
ce23346
Revert "Revert "Record who declined a request and why""
rlho Oct 8, 2024
ad8b09b
update status tooltip language
lucia-gomez Oct 8, 2024
7f3fb11
Merge pull request #457 from ITPNYU/riho/decline_reason
rlho Oct 8, 2024
4d72059
Record emaile who changes status
rlho Oct 8, 2024
10c72dc
Merge pull request #456 from ITPNYU/revert-455-revert-454-riho/declin…
rlho Oct 8, 2024
9afe8ef
refactor booking actions component
lucia-gomez Oct 8, 2024
ab017c7
Record decline reason
rlho Oct 8, 2024
1a7a50e
Merge branch 'main' of https://github.com/ITPNYU/booking-app
lucia-gomez Oct 8, 2024
d0a51e9
Merge pull request #459 from ITPNYU/riho/record_decline_reason
rlho Oct 8, 2024
91b7941
Merge branch 'main' of https://github.com/ITPNYU/booking-app
lucia-gomez Oct 8, 2024
302ffec
decline modal in booking table
lucia-gomez Oct 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions booking-app/app/api/approve/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { NextRequest, NextResponse } from "next/server";
import { serverApproveBooking } from "@/components/src/server/admin";

export async function POST(req: NextRequest) {
const { id } = await req.json();
const { id, email } = await req.json();

try {
console.log("id", id);
await serverApproveBooking(id);
await serverApproveBooking(id, email);
return NextResponse.json(
{ message: "Approved successfully" },
{ status: 200 },
Expand Down
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
237 changes: 139 additions & 98 deletions booking-app/app/api/syncCalendars/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
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 => {

const toArray = (ids: string): string[] => {
return ids.includes(',') ? ids.split(',').map(id => id.trim()) : [ids.trim()];
};

const sortedRoomIds1 = toArray(String(roomIds1)).sort();
const sortedRoomIds2 = toArray(String(roomIds2)).sort();

console.log('Comparing room IDs:', { ids1: sortedRoomIds1, ids2: sortedRoomIds2 });

const areEqual = sortedRoomIds1.length === sortedRoomIds2.length &&
sortedRoomIds1.every((id, index) => id === sortedRoomIds2[index]);

console.log('Comparison result:', areEqual);

return areEqual;
};
const createBookingWithDefaults = (
partialBooking: Partial<Booking>,
): Booking => {
Expand Down Expand Up @@ -49,17 +66,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 +109,110 @@ 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,
});

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,
});

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++;
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;
console.log("roomIds",roomIds)
console.log("existingData.roomId",existingData.roomId)
if (roomIds.includes(',') && !areRoomIdsSame(roomIds, 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 }
);
}
}
}
19 changes: 13 additions & 6 deletions booking-app/app/approve/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
"use client";

import React, { Suspense, useState } from "react";
import React, { Suspense, useContext, useState } from "react";

import {
DatabaseContext,
DatabaseProvider,
} from "@/components/src/client/routes/components/Provider";
import { clientApproveBooking } from "@/components/src/server/db";
import { Button } from "@mui/material";
import { useSearchParams } from "next/navigation";
import { clientApproveBooking } from "@/components/src/server/db";

const ApprovePageContent: React.FC = () => {
const searchParams = useSearchParams();
const paramCalendarEventId = searchParams.get("calendarEventId");
const [loading, setLoading] = useState(false);
const [approved, setApproved] = useState(false);
const [error, setError] = useState<string | null>(null);
const { userEmail } = useContext(DatabaseContext);

const handleApprove = async () => {
if (paramCalendarEventId) {
setLoading(true);
setError(null);
try {
await clientApproveBooking(paramCalendarEventId);
await clientApproveBooking(paramCalendarEventId, userEmail);
setApproved(true);
} catch (err) {
setError("Failed to approve booking.");
Expand Down Expand Up @@ -55,9 +60,11 @@ const ApprovePageContent: React.FC = () => {
};

const ApprovePage: React.FC = () => (
<Suspense fallback={<div>Loading...</div>}>
<ApprovePageContent />
</Suspense>
<DatabaseProvider>
<Suspense fallback={<div>Loading...</div>}>
<ApprovePageContent />
</Suspense>
</DatabaseProvider>
);

export default ApprovePage;
Loading
Loading