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

Monitor GradTrak #742

Draft
wants to merge 6 commits into
base: gql
Choose a base branch
from
Draft
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 apps/backend/src/modules/grade-distribution/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum Letter {

interface Grade {
letter: Letter;
percentage: number;
count: number;
}

Expand Down Expand Up @@ -96,10 +97,13 @@ export const getDistribution = (distributions: GradeDistributionType[]) => {
}
);

const total = Object.values(distribution).reduce((acc, count) => acc + count);

return Object.entries(distribution).map(
([field, count]) =>
({
letter: letters[field],
percentage: count / total,
count,
}) as Grade
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default gql`

type Grade @cacheControl(maxAge: 1) {
letter: String!
percentage: Float!
count: Int!
}

Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Class from "./class";
import Common from "./common";
import Course from "./course";
import Enrollment from "./enrollment";
import Plan from "./plan";
import GradeDistribution from "./grade-distribution";
import Schedule from "./schedule";
import Term from "./term";
Expand All @@ -20,6 +21,7 @@ const modules = [
Course,
Class,
Enrollment,
Plan
];

export const resolvers = merge(modules.map((module) => module.resolver));
Expand Down
258 changes: 258 additions & 0 deletions apps/backend/src/modules/plan/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import { omitBy } from "lodash";

import { PlanTermModel, PlanModel, SelectedCourseModel, CustomEventModel, MajorReqModel } from "@repo/common";

import {
CustomEventInput,
PlanTerm,
Plan,
PlanTermInput,
SelectedCourseInput,
PlanInput,
Colleges
} from "../../generated-types/graphql";
import { formatPlanTerm, formatPlan } from "./formatter";

// General University Requirements
const UniReqs = [
"AC",
"AH",
"AI",
"CW",
"QR",
"RCA",
"RCB"
];
// Different College Requirements
const LnSReqs = [
"LnS_AL",
"LnS_BS",
"LnS_HS",
"LnS_IS",
"LnS_PV",
"LnS_PS",
"LnS_SBS"
];
const CoEReqs = [
"CoE_HSS"
];
const HaasReqs = [
"HAAS_AL",
"HAAS_BS",
"HAAS_HS",
"HAAS_IS",
"HAAS_PV",
"HAAS_PS",
"HAAS_SBS"
];

// get plan for a user
export async function getPlanByUser(
context: any
): Promise<Plan | null> {
if (!context.user.email) throw new Error("Unauthorized");

const gt = await PlanModel.findOne({ userEmail: context.user.email });
if (!gt) {
throw new Error("No Plan found for this user");
}
return formatPlan(gt);
}

// delete a planTerm specified by ObjectID
export async function removePlanTerm(planTermID: string, context: any): Promise<string> {
if (!context.user.email) throw new Error("Unauthorized");

// check if planTerm belongs to plan
const gt = await PlanModel.findOne({ userEmail: context.user.email });
if (!gt) {
throw new Error("No Plan found for this user");
}
const planTermIndex = gt.planTerms.findIndex((sem) => sem._id as string == planTermID);
if (planTermIndex === -1) {
throw new Error("PlanTerm does not exist in user's plan");
}
gt.planTerms.splice(planTermIndex, 1);
await gt.save();

return planTermID;
}

function removeNullEventVals(customEvent: CustomEventInput) {
for (const key in customEvent) {
if (customEvent[key as keyof CustomEventInput] === null) {
delete customEvent[key as keyof CustomEventInput];
}
}
}

// create a new planTerm
export async function createPlanTerm(
mainPlanTerm: PlanTermInput,
context: any
): Promise<PlanTerm> {
if (!context.user.email) throw new Error("Unauthorized");
if (mainPlanTerm.customEvents) {
mainPlanTerm.customEvents.forEach(removeNullEventVals);
}
const nonNullPlanTerm = omitBy(mainPlanTerm, (value) => value == null);
nonNullPlanTerm.userEmail = context.user.email;
const newPlanTerm = new PlanTermModel({
...nonNullPlanTerm
});

// add to plan
const gt = await PlanModel.findOne({ userEmail: context.user.email });
if (!gt) {
throw new Error("No Plan found for this user");
}
gt.planTerms.push(newPlanTerm);
await gt.save();

return formatPlanTerm(newPlanTerm);
}

// update an existing planTerm
export async function editPlanTerm(
planTermID: string,
mainPlanTerm: PlanTermInput,
context: any
): Promise<PlanTerm> {
if (!context.user.email) throw new Error("Unauthorized");
const gt = await PlanModel.findOne({ userEmail: context.user.email });
if (!gt) {
throw new Error("No Plan found for this user");
}
if (mainPlanTerm.customEvents) {
mainPlanTerm.customEvents.forEach(removeNullEventVals);
}
const nonNullPlanTerm = omitBy(mainPlanTerm, (value) => value == null);
nonNullPlanTerm.userEmail = context.user.email;
const updatedPlanTerm = new PlanTermModel({
...nonNullPlanTerm
});
const planTermIndex = gt.planTerms.findIndex((sem) => sem._id as string == planTermID);
if (planTermIndex === -1) {
// update miscellanous
if (gt.miscellaneous._id == planTermID) {
gt.miscellaneous = updatedPlanTerm;
await gt.save();
return formatPlanTerm(updatedPlanTerm);
}
throw new Error("PlanTerm does not exist in user's plan");
}
gt.planTerms[planTermIndex] = updatedPlanTerm;
await gt.save();
return formatPlanTerm(updatedPlanTerm);
}

// update class selection in an existing planTerm
export async function setClasses(
planTermID: string,
courses: SelectedCourseInput[],
customEvents: CustomEventInput[],
context: any
): Promise<PlanTerm> {
if (!context.user.email) throw new Error("Unauthorized");
const gt = await PlanModel.findOne({ userEmail: context.user.email });
if (!gt) {
throw new Error("No Plan found for this user");
}
const planTermIndex = gt.planTerms.findIndex((sem) => sem._id as string == planTermID);
if (planTermIndex === -1) {
// update miscellaneous
if (gt.miscellaneous._id == planTermID) {
gt.miscellaneous.courses = courses.map(courseInput => new SelectedCourseModel(courseInput));
gt.miscellaneous.customEvents = customEvents.map(customEventInput => new CustomEventModel(customEventInput));
await gt.save();
return formatPlanTerm(gt.miscellaneous);
}
throw new Error("PlanTerm does not exist in user's plan");
}
gt.planTerms[planTermIndex].courses = courses.map(courseInput => new SelectedCourseModel(courseInput));
gt.planTerms[planTermIndex].customEvents = customEvents.map(customEventInput => new CustomEventModel(customEventInput));
await gt.save();
return formatPlanTerm(gt.planTerms[planTermIndex]);
}

// create a new plan
export async function createPlan(
college: Colleges,
context: any
): Promise<Plan> {
if (!context.user.email) throw new Error("Unauthorized");
// if existing plan, overwrite
const gt = await PlanModel.findOne({ userEmail: context.user.email });
if (gt) {
throw new Error("User already has existing plan");
}
const miscellaneous = new PlanTermModel({
name: "Miscellaneous",
courses: [],
customEvents: [],
userEmail: context.user.email,
year: -1,
term: "Misc",
});

let collegeReqs = [""];

// set college
if (college as String == "LnS") {
collegeReqs = LnSReqs;
} else if (college as String == "CoE") {
collegeReqs = CoEReqs;
} else if (college as String == "HAAS") {
collegeReqs = HaasReqs;
} else {
collegeReqs = [];
}

const newPlan = await PlanModel.create({
userEmail: context.user.email,
planTerms: [],
miscellaneous: miscellaneous,
collegeReqs: collegeReqs,
uniReqs: UniReqs,
majorReqs: [],
});
return formatPlan(newPlan);
}

export async function editPlan(
plan: PlanInput,
context: any
): Promise<Plan> {
if (!context.user.email) throw new Error("Unauthorized");
const gt = await PlanModel.findOne({ userEmail: context.user.email });
if (!gt) {
throw new Error("No Plan found for this user");
}
// set college
if (plan.college as String == "LnS") {
gt.collegeReqs = LnSReqs;
} else if (plan.college as String == "CoE") {
gt.collegeReqs = CoEReqs;
} else if (plan.college as String == "HAAS") {
gt.collegeReqs = HaasReqs;
} else {
gt.collegeReqs = [];
}
// set major reqs
gt.majorReqs = plan.majorReqs.map(majorReqInput => new MajorReqModel(majorReqInput));

await gt.save();
return formatPlan(gt);
}

export async function deletePlan(
context: any
): Promise<string> {
if (!context.user.email) throw new Error("Unauthorized");
console.log(context.user);
await PlanModel.deleteOne({ userEmail: context.user.email })
.catch(err => {
return err;
});
return context.user.email;
}
73 changes: 73 additions & 0 deletions apps/backend/src/modules/plan/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
PlanCustomEventType,
PlanTermType,
SelectedCourseType,
PlanType,
MajorReqType,
} from "@repo/common";

import { PlanModule } from "./generated-types/module-types";

export function formatPlan(
plan: PlanType
): PlanModule.Plan {
return {
userEmail: plan.userEmail,
planTerms: plan.planTerms.map(formatPlanTerm),
miscellaneous: formatPlanTerm(plan.miscellaneous),
uniReqs: plan.uniReqs,
collegeReqs: plan.collegeReqs,
majorReqs: plan.majorReqs.map(formatMajorReq),
created: plan.createdAt.toISOString(),
revised: plan.updatedAt.toISOString(),
};
}

export function formatPlanTerm(
planTerm: PlanTermType
): PlanModule.PlanTerm {
return {
_id: planTerm._id as string,
name: planTerm.name,
userEmail: planTerm.userEmail,
courses: planTerm.courses.map(formatCourse),
year: planTerm.year,
term: planTerm.term,
customEvents: planTerm.customEvents
? planTerm.customEvents.map(formatCustomEvents)
: undefined,
};
}

export function formatMajorReq(
majorReq: MajorReqType
): PlanModule.MajorReq {
return {
name: majorReq.name,
major: majorReq.major,
numCoursesRequired: majorReq.numCoursesRequired,
satisfyingCourseIds: majorReq.satisfyingCourseIds,
isMinor: majorReq.isMinor ? majorReq.isMinor : false
}
}

function formatCustomEvents(
customEvent: PlanCustomEventType
): PlanModule.CustomEvent {
return {
title: customEvent.title,
description: customEvent.description,
collegeReqs: customEvent.collegeReqs,
uniReqs: customEvent.uniReqs
};
}

function formatCourse(
course: SelectedCourseType
): PlanModule.SelectedCourse {
return {
classID: course.classID,
collegeReqs: course.collegeReqs,
uniReqs: course.uniReqs
};
}
7 changes: 7 additions & 0 deletions apps/backend/src/modules/plan/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import resolver from "./resolver";
import typeDef from "./typedefs/plan";

export default {
resolver,
typeDef,
};
Loading