-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #445 from ITPNYU/main
Staging release 0904
- Loading branch information
Showing
72 changed files
with
1,785 additions
and
1,104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.