Skip to content

Commit

Permalink
Merge pull request #522 from ITPNYU/feature/final_approve_validation
Browse files Browse the repository at this point in the history
Final approver validation
  • Loading branch information
rlho authored Dec 6, 2024
2 parents cbd1d55 + e4b8cdb commit 8318c0d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 61 deletions.
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 @@ -4,7 +4,6 @@ import { serverApproveBooking } from "@/components/src/server/admin";

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

try {
await serverApproveBooking(id, email);
return NextResponse.json(
Expand All @@ -14,8 +13,8 @@ export async function POST(req: NextRequest) {
} catch (error) {
console.error(`booking_id: ${id} Error approving:`, error);
return NextResponse.json(
{ error: `Failed to approve booking. id: ${id}` },
{ status: 500 },
{ error: error.message },
{ status: error.status },
);
}
}
146 changes: 88 additions & 58 deletions booking-app/components/src/server/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {
serverGetFinalApproverEmail,
serverUpdateInFirestore,
} from "@/lib/firebase/server/adminDb";
import { TableNames, getApprovalCcEmail } from "../policy";
import { ApproverLevel, TableNames, getApprovalCcEmail } from "../policy";
import {
AdminUser,
Approver,
ApproverType,
BookingFormDetails,
BookingStatus,
Expand Down Expand Up @@ -110,68 +112,85 @@ export const serverApproveInstantBooking = (id: string) => {

// both first approve and second approve flows hit here
export const serverApproveBooking = async (id: string, email: string) => {
const bookingStatus = await serverGetDataByCalendarEventId<BookingStatus>(
TableNames.BOOKING,
id
);
const firstApproveDateRange =
bookingStatus && bookingStatus.firstApprovedAt
? bookingStatus.firstApprovedAt.toDate()
: null;
try {
const bookingStatus = await serverGetDataByCalendarEventId<BookingStatus>(
TableNames.BOOKING,
id
);
const isFinalApproval = bookingStatus?.firstApprovedAt?.toDate() ?? null;

console.log("first approve date", firstApproveDateRange);
if (isFinalApproval) {
await finalApprove(id, email);
} else {
await firstApprove(id, email);
}
} catch (error) {
throw error.status ? error : { status: 500, message: error.message };
}
};

// if already first approved, then this is a second approve
if (firstApproveDateRange !== null) {
serverFinalApprove(id, email);
await serverApproveEvent(id);
} else {
console.log("email", email);
serverFirstApprove(id, email);

const response = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/calendarEvents`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
const firstApprove = async (id, email) => {
serverFirstApprove(id, email);

const response = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/calendarEvents`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
calendarEventId: id,
newValues: {
statusPrefix: BookingStatusLabel.PENDING,
},
body: JSON.stringify({
calendarEventId: id,
newValues: {
statusPrefix: BookingStatusLabel.PENDING,
},
}),
}
);
const contents = await serverBookingContents(id);
}),
}
);
const contents = await serverBookingContents(id);

const emailContents = {
...contents,
headerMessage: "This is a request email for final approval.",
};
const recipient = await serverGetFinalApproverEmail();
const formData = {
templateName: "booking_detail",
contents: emailContents,
targetEmail: recipient,
status: BookingStatusLabel.PENDING,
eventTitle: contents.title || "",
requestNumber: contents.requestNumber,
bodyMessage: "",
approverType: ApproverType.FINAL_APPROVER,
const emailContents = {
...contents,
headerMessage: "This is a request email for final approval.",
};
const recipient = await serverGetFinalApproverEmail();
const formData = {
templateName: "booking_detail",
contents: emailContents,
targetEmail: recipient,
status: BookingStatusLabel.PENDING,
eventTitle: contents.title || "",
requestNumber: contents.requestNumber,
bodyMessage: "",
approverType: ApproverType.FINAL_APPROVER,
};
const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/sendEmail`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
};
const finalApprove = async (id, email) => {
const finalApprovers = (await approvers()).filter(
(a) => a.level === ApproverLevel.FINAL
);
const finalApproverEmails = [...(await admins()), ...finalApprovers].map(
(a) => a.email
);

const canPerformSecondApproval = finalApproverEmails.includes(email);
if (!canPerformSecondApproval) {
throw {
success: false,
message:
"Unauthorized: Only final approvers or admin users can perform second approval",
status: 403,
};
const res = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/api/sendEmail`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
}
);
}
serverFinalApprove(id, email);
await serverApproveEvent(id);
};

export const serverSendConfirmationEmail = async (
Expand Down Expand Up @@ -287,14 +306,25 @@ export const serverApproveEvent = async (id: string) => {
);
};

export const approvers = async () => {
export const admins = async (): Promise<AdminUser[]> => {
const fetchedData = await serverFetchAllDataFromCollection(TableNames.ADMINS);
const filtered = fetchedData.map((item: any) => ({
id: item.id,
email: item.email,
createdAt: item.createdAt,
}));
return filtered;
};

export const approvers = async (): Promise<Approver[]> => {
const fetchedData = await serverFetchAllDataFromCollection(
TableNames.APPROVERS
);
const filtered = fetchedData.map((item: any) => ({
id: item.id,
email: item.email,
department: item.department,
level: item.level,
createdAt: item.createdAt,
}));
return filtered;
Expand Down

0 comments on commit 8318c0d

Please sign in to comment.