diff --git a/booking-app/app/api/db/duplicate/route.ts b/booking-app/app/api/db/duplicate/route.ts index 843ec723..458b8801 100644 --- a/booking-app/app/api/db/duplicate/route.ts +++ b/booking-app/app/api/db/duplicate/route.ts @@ -8,12 +8,11 @@ import { import { TableNames } from "@/components/src/policy"; export async function POST(request: NextRequest) { - const { newCollection } = await request.json(); + const { sourceCollection, newCollection } = await request.json(); + const source = sourceCollection as TableNames; try { - const rows = await serverFetchAllDataFromCollection( - TableNames.SAFETY_TRAINING, - ); + const rows = await serverFetchAllDataFromCollection(source); const rowsWithoutIds = rows.map(row => { const { id, ...other } = row; return other; diff --git a/booking-app/app/api/db/refactor/route.ts b/booking-app/app/api/db/refactor/route.ts new file mode 100644 index 00000000..3e6d9bf5 --- /dev/null +++ b/booking-app/app/api/db/refactor/route.ts @@ -0,0 +1,142 @@ +import { NextRequest, NextResponse } from "next/server"; +import { + serverFetchAllDataFromCollection, + serverSaveDataToFirestore, + serverUpdateInFirestore, +} from "@/lib/firebase/server/adminDb"; + +import { TableNames } from "@/components/src/policy"; +import { Timestamp } from "@firebase/firestore"; +import { getLoggingClient } from "@/lib/googleClient"; + +export const dynamic = "force-dynamic"; + +// DB refactor entrypoint, will call other API endpoints to handle refactor steps +export async function POST(request: NextRequest) { + let logger; + try { + logger = await getLoggingClient(); + + // 1. rename usersLiaison to usersApprovers + const sourceApproverCollection = TableNames.APPROVERS; + const newApproverCollection = "usersApprovers"; + let res = await fetch( + process.env.NEXT_PUBLIC_BASE_URL + "/api/db/duplicate", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + sourceCollection: sourceApproverCollection, + newCollection: newApproverCollection, + }), + }, + ); + + if (!res.ok) { + let json = await res.json(); + throw new Error(`Error ${res.status}: ${json.error}`); + } else { + log( + logger, + `Duplicated ${sourceApproverCollection} to ${newApproverCollection}`, + ); + } + + // 2. every document in usersApprovers needs a numeric approver level + const rows = await serverFetchAllDataFromCollection( + newApproverCollection as TableNames, + ); + + await Promise.all( + rows.map(row => + serverUpdateInFirestore(newApproverCollection, row.id, { level: 1 }), + ), + ); + log(logger, `Added level field to docs in ${newApproverCollection}`); + + // 3. add finalApproverEmail to usersApprovers collection + const date = new Date(); + await serverSaveDataToFirestore(newApproverCollection, { + email: "booking-app-devs+jhanele@itp.nyu.edu", + level: 2, + // createdAt: new Timestamp(date.getSeconds(), 0), + }); + log(logger, `Added final approver doc to ${newApproverCollection}`); + + // 4. rename usersSafetyWhitelist to usersWhitelist + const sourceWhitelistCollection = TableNames.SAFETY_TRAINING; + const newWhitelistCollection = "usersWhitelist"; + res = await fetch(process.env.NEXT_PUBLIC_BASE_URL + "/api/db/duplicate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + sourceCollection: sourceWhitelistCollection, + newCollection: newWhitelistCollection, + }), + }); + + if (!res.ok) { + let json = await res.json(); + throw new Error(`Error ${res.status}: ${json.error}`); + } else { + log( + logger, + `Duplicated ${sourceWhitelistCollection} to ${newWhitelistCollection}`, + ); + } + + // 5. combine bookings and bookingStatus documents + const sourceBookingStatus = TableNames.BOOKING_STATUS; + const destinationBooking = TableNames.BOOKING; + res = await fetch(process.env.NEXT_PUBLIC_BASE_URL + "/api/db/merge", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + source: sourceBookingStatus, + destination: destinationBooking, + }), + }); + + if (!res.ok) { + let json = await res.json(); + throw new Error(`Error ${res.status}: ${json.error}`); + } else { + log(logger, `Merged ${sourceBookingStatus} to ${destinationBooking}`); + } + + return NextResponse.json({ status: 200 }); + } catch (err) { + console.error(err); + log(logger, err); + return NextResponse.json( + { error: "Failed to merge collections" }, + { status: 500 }, + ); + } +} + +function log(logger, msg) { + let logEntry = { + logName: process.env.NEXT_PUBLIC_GCP_LOG_NAME + "/db-refactor", + resource: { type: "global" }, + entries: [ + { + jsonPayload: { + message: msg, + branchName: process.env.NEXT_PUBLIC_BRANCH_NAME, + }, + severity: "INFO", + }, + ], + }; + + logger.entries.write({ + requestBody: logEntry, + }); +} diff --git a/booking-app/components/src/client/routes/admin/components/SyncCalendars.tsx b/booking-app/components/src/client/routes/admin/components/SyncCalendars.tsx index bf179444..97a5b35d 100644 --- a/booking-app/components/src/client/routes/admin/components/SyncCalendars.tsx +++ b/booking-app/components/src/client/routes/admin/components/SyncCalendars.tsx @@ -34,18 +34,11 @@ const SyncCalendars = () => { } }; - // const duplicate = async () => { - // await fetch("/api/db/duplicate", { - // method: "DELETE", - // headers: { - // "Content-Type": "application/json", - // }, - // body: JSON.stringify({ - // source: TableNames.BOOKING_STATUS, - // destination: TableNames.BOOKING, - // }), - // }); - // }; + const refactor = async () => { + await fetch("/api/db/refactor", { + method: "POST", + }); + }; return ( @@ -62,7 +55,12 @@ const SyncCalendars = () => { Sync Calendar Events - {/* Duplicate */} + + Don't press this unless you know what you're doing! + + !!! DB REFACTOR !!! + +
Don't press this unless you know what you're doing!