Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start chapter responsive design #168

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ model File {
id String @id @default(auto()) @map("_id") @db.ObjectId
date DateTime // will zero out the hours
filetype String

// Use with Google API
fileId String
url String

seniorId String @db.ObjectId
senior Senior @relation(fields: [seniorId], references: [id], onDelete: Cascade)
Tags String[]
Expand Down
35 changes: 26 additions & 9 deletions src/app/api/chapter-request/route.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,38 @@ import { z } from "zod";
questions String
*/
export const ChapterRequest = z.object({
firstName: z.string().min(1, "Please provide a first name"),
lastName: z.string().min(1, "Please provide a last name"),
universityEmail: z.string().email("This is not a valid email"),
firstName: z
.string()
.min(1, "Please provide a first name")
.max(100, "You can provide at most 100 characters"),
lastName: z
.string()
.min(1, "Please provide a last name")
.max(100, "You can provide at most 100 characters"),
universityEmail: z.string().max(100).email("Please provide a valid email"),
phoneNumber: z.string().length(10, "Phone number must be 10 digits"),
university: z.string().min(1, "Please provide a university"),
universityAddress: z.string().min(1, "Please provide an address"),
university: z
.string()
.min(1, "Please provide a university")
.max(100, "You can provide at most 100 characters"),
universityAddress: z
.string()
.min(1, "Please provide an address")
.max(100, "You can provide at most 100 characters"),
leadershipExperience: z
.string()
.min(1, "Please state some leadership experience"),
.min(1, "Please state some leadership experience")
.max(250, "You can provide at most 250 characters"),
motivation: z
.string()
.min(1, "Please describe your motivation in joining the legacy project"),
.min(1, "Please describe your motivation in joining the Legacy Project")
.max(250, "You can provide at most 250 characters"),
// TODO: Figure out if availabilities should have a better type
availabilities: z.string().min(1, "Please provide some times"),
questions: z.string(),
availabilities: z
.string()
.min(1, "Please provide some availability")
.max(100, "You can provide at most 100 characters"),
questions: z.string().max(100, "You can provide at most 100 characters"),
}) satisfies z.ZodType<Prisma.ChapterRequestCreateInput>;

export const ChapterRequestResponse = z.discriminatedUnion("code", [
Expand Down
6 changes: 0 additions & 6 deletions src/app/api/file/[fileId]/route.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@ import { z } from "zod";
import { FileResponse } from "./route.schema";
import { File } from "@server/model";

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

/**
* Extends the parameters of fetch() function to give types to the RequestBody.
*/
interface IUpdateRequest extends Omit<RequestInit, "body"> {
fileId: string;
body: IFile;
Expand Down
150 changes: 35 additions & 115 deletions src/app/api/file/[fileId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import { withSession } from "@server/decorator";
import { driveV3 } from "@server/service";
import moment from "moment";

export const PATCH = withSession(async (request) => {
const body = await request.req.json();
const nextParams: { fileId: string } = request.params.params;
export const PATCH = withSession(async ({ params, session, req }) => {
const body = await req.json();
const nextParams: { fileId: string } = params.params;
const { fileId } = nextParams;
const fileRequest = File.safeParse(body);

if (!fileRequest.success) {
console.log(fileRequest.error);
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
Expand All @@ -26,57 +25,31 @@ export const PATCH = withSession(async (request) => {
);
} else {
const fileData = fileRequest.data;

// Check that user has this senior assigned to them
const user = await prisma.user.findFirst({
const maybeFile = await prisma.file.findFirst({
where: {
id: request.session.user.id,
id: fileId,
},
include: { senior: true },
});

if (user === null || user.SeniorIDs === null) {
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
message: "Not a valid request",
}),
{ status: 400 }
);
}
const otherFiles = await prisma.file.findMany({
where: {
date: fileData.date,
seniorId: fileData.seniorId,
id: {
not: fileId,
},
},
});

if (
!user.SeniorIDs.some((seniorId: string) => seniorId === fileData.seniorId)
otherFiles.length > 0 ||
maybeFile == null ||
!maybeFile.senior.StudentIDs.includes(session.user.id)
) {
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
message: "Not a valid request",
}),
{ status: 400 }
);
}

// Get senior from database
const foundSenior = await prisma.senior.findUnique({
where: { id: fileData.seniorId },
});

if (foundSenior === null) {
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
message: "Not a valid request",
}),
{ status: 400 }
);
}

// query that date doesn't already exist
const foundFile = await prisma.file.findFirst({
where: { date: fileData.date, seniorId: fileData.seniorId },
});

if (foundFile !== null) {
// If there are other files with the same date, then the user can't take it
// If file is not found then file is deleted
// If senior doesn't include current user then they have been deselected
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
Expand All @@ -91,30 +64,13 @@ export const PATCH = withSession(async (request) => {
const body = { name: formatted_date };

const fileUpdateData = {
fileId: fileId,
fileId: maybeFile.fileId,
resource: body,
};

await driveV3.files.update(fileUpdateData);

const file = await prisma.file.findFirst({
where: {
url: fileData.url,
},
});

if (file === null) {
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
message: "Not a valid request",
}),
{ status: 400 }
);
}

await prisma.file.update({
where: { id: file.id },
where: { id: maybeFile.id },
data: { date: fileData.date, Tags: fileData.Tags },
});

Expand All @@ -128,68 +84,32 @@ export const PATCH = withSession(async (request) => {
}
});

export const DELETE = withSession(async (request) => {
const nextParams: { fileId: string } = request.params.params;
export const DELETE = withSession(async ({ params, session }) => {
const nextParams: { fileId: string } = params.params;
const { fileId } = nextParams;

// Check that user has this senior assigned to them
const user = await prisma.user.findFirst({
where: {
id: request.session.user.id,
},
});

if (user === null || user.SeniorIDs === null) {
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
message: "Not a valid request",
}),
{ status: 400 }
);
}
const file = await prisma.file.findFirst({
where: {
url: `https://docs.google.com/document/d/${fileId}`,
id: fileId,
},
include: {
senior: true,
},
});

if (file === null) {
if (file == null || !file.senior.StudentIDs.includes(session.user.id)) {
// File has been deleted or student has been deselected
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
message: "Not a valid request",
}),
{ status: 400 }
);
}

if (!user.SeniorIDs.some((seniorId: string) => seniorId === file.seniorId)) {
return NextResponse.json(
FileResponse.parse({
code: "NOT_AUTHORIZED",
message: "Senior not assigned to user",
}),
{ status: 404 }
);
}

// Get senior from database
const foundSenior = await prisma.senior.findUnique({
where: { id: file.seniorId },
});
if (foundSenior === null) {
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
message: "Not a valid request",
code: "SUCCESS_DELETE",
message: "File successfully deleted",
}),
{ status: 400 }
{ status: 200 }
);
}

await driveV3.files.delete({
fileId: fileId,
fileId: file.fileId,
});

await prisma.file.delete({
Expand Down
26 changes: 13 additions & 13 deletions src/app/api/file/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,19 @@ export const POST = withSession(async (request) => {
id: request.session.user.id,
},
});
const otherFiles = await prisma.file.findMany({
where: {
date: fileData.date,
seniorId: fileData.seniorId,
},
});

if (user === null || user.SeniorIDs === null) {
if (
user === null ||
user.SeniorIDs === null ||
!user.SeniorIDs.some((seniorId) => seniorId === fileData.seniorId) ||
otherFiles.length > 0
) {
return NextResponse.json(
FileResponse.parse({
code: "INVALID_REQUEST",
Expand All @@ -38,18 +49,6 @@ export const POST = withSession(async (request) => {
);
}

if (
!user.SeniorIDs.some((seniorId: string) => seniorId === fileData.seniorId)
) {
return NextResponse.json(
FileResponse.parse({
code: "NOT_AUTHORIZED",
message: "Senior not assigned to user",
}),
{ status: 404 }
);
}

// Get senior from database
const foundSenior = await prisma.senior.findUnique({
where: { id: fileData.seniorId },
Expand Down Expand Up @@ -88,6 +87,7 @@ export const POST = withSession(async (request) => {
data: {
date: fileData.date,
filetype: fileData.filetype,
fileId: googleFileId ?? "",
url: `https://docs.google.com/document/d/${googleFileId}`,
seniorId: fileData.seniorId,
Tags: fileData.Tags,
Expand Down
7 changes: 4 additions & 3 deletions src/app/api/senior/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,14 @@ export const PATCH = withSessionAndRole(
);
const studentIDsToAdd = seniorBody.StudentIDs;

const { StudentIDs: _, ...other } = seniorBody;
const senior = await prisma.senior.update({
where: { id: seniorId },
data: {
...seniorBody,
...other,
Students: {
connect: studentIDsToAdd.map((id) => ({ id })),
disconnect: studentIDsToRemove.map((id) => ({ id })),
connect: studentIDsToAdd.map((id) => ({ id: id })),
disconnect: studentIDsToRemove.map((id) => ({ id: id })),
},
},
});
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/user/[uid]/edit-seniors/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export const PATCH = withSession(
where: { id: uid },
data: {
Seniors: {
connect: seniorIDsToAdd.map((id) => ({ id })),
disconnect: seniorIDsToRemove.map((id) => ({ id })),
connect: seniorIDsToAdd.map((id) => ({ id: id })),
disconnect: seniorIDsToRemove.map((id) => ({ id: id })),
},
},
});
Expand Down
8 changes: 7 additions & 1 deletion src/app/private/chapter-leader/seniors/[seniorId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PathNav from "@components/PathNav";
import { DisplaySenior } from "@components/senior";
import { prisma } from "@server/db/client";
import { getServerSessionOrRedirect } from "@server/utils";
import { seniorFullName } from "@utils";

interface LayoutProps {
Expand All @@ -10,6 +11,7 @@ interface LayoutProps {
}

const Page = async ({ params }: LayoutProps) => {
const session = await getServerSessionOrRedirect();
const senior = await prisma.senior.findFirstOrThrow({
where: { id: params.seniorId },
include: {
Expand All @@ -33,7 +35,11 @@ const Page = async ({ params }: LayoutProps) => {
},
]}
/>
<DisplaySenior editable canAddFile={false} senior={senior} />
<DisplaySenior
editable
canAddFile={senior.StudentIDs.includes(session.user?.id ?? "")}
senior={senior}
/>
</div>
);
};
Expand Down
Loading
Loading