Skip to content

Commit

Permalink
delete user and chapter request when accepted (#153)
Browse files Browse the repository at this point in the history
* delete user and chapter request when accepted

* fixed show more information on join chapter InfoTile

* removed console logs

* Update request deletion model

* Add deprecation warning

---------

Co-authored-by: nickbar01234 <[email protected]>
  • Loading branch information
nathan-j-edwards and nickbar01234 authored Apr 12, 2024
1 parent 3433c05 commit a7903f1
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 418 deletions.
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ model File {

model ChapterRequest {
id String @id @default(auto()) @map("_id") @db.ObjectId
// ChapterRequest can only be in pending or accepted state. We don't use DENIED.
approved Approval @default(PENDING)
firstName String
lastName String
Expand Down Expand Up @@ -151,6 +152,7 @@ model Email {

model UserRequest {
id String @id @default(auto()) @map("_id") @db.ObjectId
// @deprecated - We ended up not using this anywhere
approved Approval @default(PENDING)
uid String @unique @db.ObjectId
chapterId String @db.ObjectId
Expand Down
62 changes: 2 additions & 60 deletions src/app/api/handle-chapter-request/route.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,11 @@ import {
HandleChapterRequest,
HandleChapterRequestResponse,
} from "./route.schema";
import { TypedRequest } from "@server/type";

/**
* Describe the interface of SignInRequest.
*/
type IHandleChapterRequest = z.infer<typeof HandleChapterRequest>;

type IHandleChapterRequestResponse = z.infer<
typeof HandleChapterRequestResponse
>;

/**
* Extract all the values of "code".
*/
type HandleChapterRequestResponseCode = z.infer<
typeof HandleChapterRequestResponse
>["code"];

/**
* Extends the parameters of fetch() function to give types to the RequestBody.
*/
interface IRequest extends Omit<RequestInit, "body"> {
body: IHandleChapterRequest;
}

const MOCK_SUCCESS: IHandleChapterRequestResponse = {
code: "SUCCESS",
message: "Chapter request successfully handled",
};

const MOCK_INVALID_REQUEST: IHandleChapterRequestResponse = {
code: "INVALID_REQUEST",
message: "Invalid API request",
};

const MOCK_UNKNOWN: IHandleChapterRequestResponse = {
code: "UNKNOWN",
message: "Unknown error received",
};

const MOCK_CHAPTER_REQUEST_NOT_FOUND: IHandleChapterRequestResponse = {
code: "CHAPTER_REQUEST_NOT_FOUND",
message: "A chapter request associated with the given ID does not exist",
};

/**
* If "mock" is given as a parameter, the function can return mocked data for a specific case. This
* pattern allows frontend developers to use your API before you finished implementing it!
*
* In addition, using Zod schemas to parse the response will make the input/output well-typed, making the code cleaner.
*/
export const handleChapterRequest = async (
request: IRequest,
mock?: HandleChapterRequestResponseCode
request: TypedRequest<z.infer<typeof HandleChapterRequest>>
) => {
if (mock === "SUCCESS") {
return HandleChapterRequestResponse.parse(MOCK_SUCCESS);
} else if (mock === "INVALID_REQUEST") {
return HandleChapterRequestResponse.parse(MOCK_INVALID_REQUEST);
} else if (mock === "UNKNOWN") {
return HandleChapterRequestResponse.parse(MOCK_UNKNOWN);
} else if (mock === "CHAPTER_REQUEST_NOT_FOUND") {
return HandleChapterRequestResponse.parse(MOCK_CHAPTER_REQUEST_NOT_FOUND);
}
const { body, ...options } = request;
const response = await fetch("/api/handle-chapter-request", {
method: "POST",
Expand Down
10 changes: 0 additions & 10 deletions src/app/api/handle-chapter-request/route.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,4 @@ export const HandleChapterRequestResponse = z.discriminatedUnion("code", [
code: z.literal("INVALID_REQUEST"),
message: z.literal("Invalid API request"),
}),
z.object({
code: z.literal("UNKNOWN"),
message: z.literal("Unknown error received"),
}),
z.object({
code: z.literal("CHAPTER_REQUEST_NOT_FOUND"),
message: z.literal(
"A chapter request associated with the given ID does not exist"
),
}),
]);
187 changes: 75 additions & 112 deletions src/app/api/handle-chapter-request/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NextRequest, NextResponse } from "next/server";
import { NextResponse } from "next/server";
import {
HandleChapterRequest,
HandleChapterRequestResponse,
Expand All @@ -9,126 +9,89 @@ import { env } from "@env/server.mjs";
import { prisma } from "@server/db/client";

export const POST = withSession(async ({ req, session }) => {
// Validate the data in the request
// If the data is invalid, return a 400 response
// with a JSON body containing the validation errors
const handleChapterRequest = HandleChapterRequest.safeParse(await req.json());
if (!handleChapterRequest.success) {
return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "INVALID_REQUEST",
message: "Invalid API request",
}),
{ status: 400 }
);
}

// Validate a proper JSON was passed in as well
try {
const handleChapterRequest = HandleChapterRequest.safeParse(
await req.json()
const body = handleChapterRequest.data;
const chapterRequest = await prisma.chapterRequest.findFirst({
where: {
id: body.chapterRequestId,
},
});
if (chapterRequest == null || chapterRequest.approved === "APPROVED") {
// If chapter request does not exist, it means that another admin has denied it.
// If chapter request has been approved, do nothing.
return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "SUCCESS",
message: "Chapter request successfully handled",
})
);
} else if (!body.approved) {
await prisma.chapterRequest.delete({
where: {
id: body.chapterRequestId,
},
});
return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "SUCCESS",
message: "Chapter request successfully handled",
}),
{ status: 200 }
);
if (!handleChapterRequest.success) {
return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "INVALID_REQUEST",
message: "Invalid API request",
}),
{ status: 400 }
);
} else {
const body = handleChapterRequest.data;
// Check if the chapter request even exists
const chapterRequest = await prisma.chapterRequest.findFirst({
where: {
id: String(body.chapterRequestId),
},
});
if (!chapterRequest) {
return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "CHAPTER_REQUEST_NOT_FOUND",
message:
"A chapter request associated with the given ID does not exist",
}),
{ status: 400 }
);
}
// Check if the chapter request has already been approved/denied
if (chapterRequest.approved !== "PENDING") {
return NextResponse.json(
{
code: "INVALID_REQUEST",
message: "Invalid API request",
},
{ status: 400 }
);
}
// If approved, create a new chapter and update approved field of chapter request
if (body.approved === true) {
const createdChapter = await prisma.chapter.create({
data: {
chapterName: chapterRequest.university,
location: chapterRequest.universityAddress,
},
});
await prisma.chapterRequest.update({
where: {
id: body.chapterRequestId,
},
data: {
approved: "APPROVED",
},
});
} else {
// Don't delete chapterRequest on approve to store metadata
await prisma.chapterRequest.update({
where: { id: body.chapterRequestId },
data: { approved: "APPROVED" },
});

const createdChapter = await prisma.chapter.create({
data: {
chapterName: chapterRequest.university,
location: chapterRequest.universityAddress,
},
});

const baseFolder = env.GOOGLE_BASEFOLDER; // TODO: make env variable
const fileMetadata = {
name: [`${chapterRequest.university}-${createdChapter.id}`],
mimeType: "application/vnd.google-apps.folder",
parents: [baseFolder],
};
const fileCreateData = {
resource: fileMetadata,
fields: "id",
};
const baseFolder = env.GOOGLE_BASEFOLDER;
const fileMetadata = {
name: [`${chapterRequest.university}-${createdChapter.id}`],
mimeType: "application/vnd.google-apps.folder",
parents: [baseFolder],
};
const fileCreateData = {
resource: fileMetadata,
fields: "id",
};

const service = await createDriveService(session.user.id);
const file = await (
service as NonNullable<typeof service>
).files.create(fileCreateData);
const googleFolderId = (file as any).data.id;
const service = await createDriveService(session.user.id);
const file = await service.files.create(fileCreateData);
const googleFolderId = file.data.id as string;

await prisma.chapter.update({
where: {
id: createdChapter.id,
},
data: {
chapterFolder: googleFolderId,
},
});
await prisma.chapter.update({
where: {
id: createdChapter.id,
},
data: {
chapterFolder: googleFolderId,
},
});

return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "SUCCESS",
message: "Chapter request successfully handled",
}),
{ status: 200 }
);
}
// If denied just updated approved field of chapter request
await prisma.chapterRequest.update({
where: {
id: body.chapterRequestId,
},
data: {
approved: "DENIED",
},
});
return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "SUCCESS",
message: "Chapter request successfully handled",
}),
{ status: 200 }
);
}
} catch {
return NextResponse.json(
HandleChapterRequestResponse.parse({
code: "UNKNOWN",
message: "Unknown error received",
code: "SUCCESS",
message: "Chapter request successfully handled",
}),
{ status: 500 }
{ status: 200 }
);
}
});
16 changes: 5 additions & 11 deletions src/app/api/user-request/route.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@ import {
JoinChapterRequestResponse,
ManageChapterRequestResponse,
} from "./route.schema";
import { TypedRequest } from "@server/type";

interface IJoinChapterRequest extends Omit<RequestInit, "body"> {
body: z.infer<typeof JoinChapterRequest>;
}

interface IManageChapterRequest extends Omit<RequestInit, "body"> {
body: z.infer<typeof ManageChapterRequest>;
}
type AcceptOrDenyRequest = TypedRequest<z.infer<typeof ManageChapterRequest>>;

export const handleJoinChapterRequest = async (
request: IJoinChapterRequest
request: TypedRequest<z.infer<typeof JoinChapterRequest>>
) => {
const { body, ...options } = request;
const response = await fetch("/api/user-request", {
Expand All @@ -28,10 +23,9 @@ export const handleJoinChapterRequest = async (
};

export const handleManageChapterRequest = async (
request: IManageChapterRequest
request: AcceptOrDenyRequest
) => {
const { body, ...options } = request;
console.log("body", body);
const response = await fetch("/api/user-request", {
method: "DELETE",
body: JSON.stringify(body),
Expand All @@ -42,7 +36,7 @@ export const handleManageChapterRequest = async (
};

export const handleAcceptChapterRequest = async (
request: IManageChapterRequest
request: AcceptOrDenyRequest
) => {
const { body, ...options } = request;
const response = await fetch("/api/user-request", {
Expand Down
Loading

0 comments on commit a7903f1

Please sign in to comment.