Skip to content

Commit

Permalink
Merge pull request #445 from ITPNYU/main
Browse files Browse the repository at this point in the history
Staging release 0904
  • Loading branch information
rlho authored Oct 4, 2024
2 parents 010f972 + 8308063 commit 58b5700
Show file tree
Hide file tree
Showing 72 changed files with 1,785 additions and 1,104 deletions.
57 changes: 57 additions & 0 deletions booking-app/app/api/bookings/export/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Booking, BookingStatus } from "@/components/src/types";
import { NextRequest, NextResponse } from "next/server";

import { TableNames } from "@/components/src/policy";
import { parse } from "json2csv";
import { serverFetchAllDataFromCollection } from "@/lib/firebase/server/adminDb";

export async function GET(request: NextRequest) {
const bookings = await serverFetchAllDataFromCollection<Booking>(
TableNames.BOOKING,
);
const statuses = await serverFetchAllDataFromCollection<BookingStatus>(
TableNames.BOOKING_STATUS,
);

// need to find corresponding status row for booking row
const idsToData: {
[key: string]: {
booking: Booking;
status: BookingStatus;
};
} = {};

for (let booking of bookings) {
const calendarEventId = booking.calendarEventId;
const statusMatch = statuses.filter(
row => row.calendarEventId === calendarEventId,
)[0];
idsToData[calendarEventId] = {
booking,
status: statusMatch,
};
}

const rows = Object.entries(idsToData)
.map(([_, { booking, status }]) => {
const { requestNumber, ...otherBooking } = booking;
return { requestNumber, ...otherBooking, ...status };
})
.sort((a, b) => a.requestNumber - b.requestNumber);

try {
const csv = parse(rows);
return new NextResponse(csv, {
status: 200,
headers: {
"Content-Type": "text/csv",
"Content-Disposition": 'attachment; filename="data.csv"',
},
});
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch CSV data" },
{ status: 400 },
);
}
}
29 changes: 19 additions & 10 deletions booking-app/app/api/bookings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,27 @@ import {
declineUrl,
getBookingToolDeployUrl,
} from "@/components/src/server/ui";
import { deleteEvent, insertEvent } from "@/components/src/server/calendars";
import {
firstApproverEmails,
serverApproveInstantBooking,
serverBookingContents,
serverDeleteDataByCalendarEventId,
serverUpdateDataByCalendarEventId,
} from "@/components/src/server/admin";
import { deleteEvent, insertEvent } from "@/components/src/server/calendars";
import {
serverFormatDate,
toFirebaseTimestampFromString,
} from "@/components/src/client/utils/serverDate";
import {
serverGetNextSequentialId,
serverSaveDataToFirestore,
} from "@/lib/firebase/server/adminDb";

import { DateSelectArg } from "fullcalendar";
import { TableNames } from "@/components/src/policy";
import { Timestamp } from "firebase-admin/firestore";
import { sendHTMLEmail } from "@/app/lib/sendHTMLEmail";
import {
serverGetNextSequentialId,
serverSaveDataToFirestore,
} from "@/lib/firebase/server/adminDb";
import {
serverFormatDate,
toFirebaseTimestampFromString,
} from "@/components/src/client/utils/serverDate";

async function createBookingCalendarEvent(
selectedRooms: RoomSetting[],
Expand Down Expand Up @@ -188,6 +189,7 @@ export async function PUT(request: NextRequest) {
const {
email,
selectedRooms,
allRooms,
bookingCalendarInfo,
data,
isAutoApproval,
Expand All @@ -200,13 +202,20 @@ export async function PUT(request: NextRequest) {
{ status: 500 },
);
}

const existingContents = await serverBookingContents(calendarEventId);
const oldRoomIds = existingContents.roomId.split(",").map(x => x.trim());
const oldRooms = allRooms.filter((room: RoomSetting) =>
oldRoomIds.includes(room.roomId + ""),
);

const selectedRoomIds = selectedRooms
.map((r: { roomId: number }) => r.roomId)
.join(", ");

// delete existing cal events
await Promise.all(
selectedRooms.map(async room => {
oldRooms.map(async room => {
await deleteEvent(room.calendarId, calendarEventId, room.roomId);
}),
);
Expand Down
8 changes: 4 additions & 4 deletions booking-app/app/api/calendarEvents/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
import {
deleteEvent,
insertEvent,
updateEventPrefix,
updateCalendarEvent,
} from "@/components/src/server/calendars";

import { getCalendarClient } from "@/lib/googleClient";
Expand Down Expand Up @@ -101,17 +101,17 @@ export async function GET(req: NextRequest) {
}

export async function PUT(req: NextRequest) {
const { calendarEventId, newPrefix } = await req.json();
const { calendarEventId, newValues } = await req.json();

if (!calendarEventId || !newPrefix) {
if (!calendarEventId || !newValues) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 },
);
}
const contents = await serverBookingContents(calendarEventId);
try {
await updateEventPrefix(calendarEventId, newPrefix, contents);
await updateCalendarEvent(calendarEventId, newValues, contents);
return NextResponse.json(
{ message: "Event updated successfully" },
{ status: 200 },
Expand Down
177 changes: 177 additions & 0 deletions booking-app/app/api/syncCalendars/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { Booking, MediaServices } from "@/components/src/types";

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

const db = admin.firestore();
const createBookingWithDefaults = (
partialBooking: Partial<Booking>,
): Booking => {
return {
title: "",
description: "",
email: "",
firstName: "",
lastName: "",
secondaryName: "",
nNumber: "",
netId: "",
phoneNumber: "",
department: "",
otherDepartment: "",
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 },
);
}
}
6 changes: 2 additions & 4 deletions booking-app/app/book/confirmation/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// app/book/confirmation/page.tsx
import BookingForm from "@/components/src/client/routes/booking/BookingForm";
import BookingFormConfirmationPage from "@/components/src/client/routes/booking/formPages/BookingFormConfirmationPage";
import { FormContextLevel } from "@/components/src/types";
import React from "react";

const Role: React.FC = () => (
<BookingForm>
<BookingFormConfirmationPage />
</BookingForm>
<BookingFormConfirmationPage formContext={FormContextLevel.FULL_FORM} />
);

export default Role;
7 changes: 1 addition & 6 deletions booking-app/app/book/form/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@

"use client";

import BookingForm from "@/components/src/client/routes/booking/BookingForm";
import BookingFormDetailsPage from "@/components/src/client/routes/booking/formPages/BookingFormDetailsPage";
import React from "react";

const Form: React.FC = () => (
<BookingForm>
<BookingFormDetailsPage />
</BookingForm>
);
const Form: React.FC = () => <BookingFormDetailsPage />;

export default Form;
14 changes: 14 additions & 0 deletions booking-app/app/book/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// app/book/layout.tsx
import BookingForm from "@/components/src/client/routes/booking/BookingForm";
import { FormContextLevel } from "@/components/src/types";
import React from "react";

type LayoutProps = {
children: React.ReactNode;
};

const BookingLayout: React.FC<LayoutProps> = ({ children }) => (
<BookingForm formContext={FormContextLevel.FULL_FORM}>{children}</BookingForm>
);

export default BookingLayout;
9 changes: 2 additions & 7 deletions booking-app/app/book/role/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import React from "react";
// app/book/form/page.tsx
import BookingForm from "@/components/src/client/routes/booking/BookingForm";
import UserRolePage from "@/components/src/client/routes/booking/formPages/UserRolePage";
import React from "react";

const Role: React.FC = () => (
<BookingForm>
<UserRolePage />
</BookingForm>
);
const Role: React.FC = () => <UserRolePage />;

export default Role;
Loading

0 comments on commit 58b5700

Please sign in to comment.