diff --git a/backend/.turbo/daemon/20e813dde91f3451-turbo.log.2024-07-03 b/backend/.turbo/daemon/20e813dde91f3451-turbo.log.2024-07-03 new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/generated-types/graphql.ts b/backend/src/generated-types/graphql.ts index 130d3c5c1..3ece2a3d9 100644 --- a/backend/src/generated-types/graphql.ts +++ b/backend/src/generated-types/graphql.ts @@ -403,11 +403,6 @@ export type QueryScheduleByIdArgs = { }; -export type QuerySchedulesByUserArgs = { - created_by: Scalars['String']['input']; -}; - - export type QuerySectionArgs = { classNumber: Scalars['String']['input']; courseNumber: Scalars['String']['input']; @@ -445,7 +440,6 @@ export type Schedule = { export type ScheduleInput = { courses?: InputMaybe>; - created_by: Scalars['String']['input']; custom_events?: InputMaybe>; is_public?: InputMaybe; name?: InputMaybe; @@ -867,7 +861,7 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; ping?: Resolver; scheduleByID?: Resolver, ParentType, ContextType, RequireFields>; - schedulesByUser?: Resolver>>, ParentType, ContextType, RequireFields>; + schedulesByUser?: Resolver>>, ParentType, ContextType>; section?: Resolver, ParentType, ContextType, RequireFields>; terms?: Resolver>>, ParentType, ContextType>; user?: Resolver, ParentType, ContextType>; diff --git a/backend/src/modules/schedule/controller.ts b/backend/src/modules/schedule/controller.ts index 3eb1290ec..731f2daa7 100644 --- a/backend/src/modules/schedule/controller.ts +++ b/backend/src/modules/schedule/controller.ts @@ -1,74 +1,103 @@ import { formatSchedule } from "./formatter"; -import { Schedule, ScheduleInput, CustomEventInput, SelectedCourseInput } from "../../generated-types/graphql"; +import { + Schedule, + ScheduleInput, + CustomEventInput, + SelectedCourseInput, +} from "../../generated-types/graphql"; import { ScheduleModel } from "../../models/schedule"; import { omitBy } from "lodash"; - // get the schedules for a user -export async function getSchedulesByUser(userID: string): Promise { - const userSchedules = await ScheduleModel.find({created_by: userID}) +export async function getSchedulesByUser( + context: any +): Promise { + if (!context.user._id) throw new Error("User is not authenticated"); + + const userSchedules = await ScheduleModel.find({ + created_by: context.user._id, + }); if (userSchedules.length == 0) { - throw new Error("No schedules found for this user") + throw new Error("No schedules found for this user"); } - return userSchedules.map(formatSchedule) + return userSchedules.map(formatSchedule); } // get the schedule for a user and a specific term export async function getScheduleByID(id: string): Promise { - const scheduleFromID = await ScheduleModel.findById({_id: id}) + const scheduleFromID = await ScheduleModel.findById({ _id: id }); if (!scheduleFromID) { - throw new Error("No schedules found with this ID") + throw new Error("No schedules found with this ID"); } - return formatSchedule(scheduleFromID) + return formatSchedule(scheduleFromID); } // delete a schedule specified by ObjectID export async function removeSchedule(scheduleID: string): Promise { - const deletedSchedule = await ScheduleModel.findByIdAndDelete(scheduleID) + const deletedSchedule = await ScheduleModel.findByIdAndDelete(scheduleID); if (!deletedSchedule) { - throw new Error("Schedule deletion failed") + throw new Error("Schedule deletion failed"); } - return scheduleID + return scheduleID; } function removeNullEventVals(custom_event: CustomEventInput) { for (const key in custom_event) { - if (custom_event[key as keyof CustomEventInput] === null) { - delete custom_event[key as keyof CustomEventInput]; - } + if (custom_event[key as keyof CustomEventInput] === null) { + delete custom_event[key as keyof CustomEventInput]; + } } } // create a new schedule -//export async function createSchedule(created_by: string, term: TermInput, schedule_name: string | undefined | null, is_public: boolean | null | undefined, class_IDs: string[] | undefined | null, primary_section_IDs: string[] | undefined | null, secondary_section_IDs: string[] | undefined | null): Promise { -export async function createSchedule(main_schedule: ScheduleInput): Promise { +export async function createSchedule( + main_schedule: ScheduleInput, + context: any +): Promise { + if (!context.user._id) throw new Error("User is not authenticated"); if (main_schedule.custom_events) { - main_schedule.custom_events.forEach(removeNullEventVals) + main_schedule.custom_events.forEach(removeNullEventVals); } - const non_null_schedule = omitBy(main_schedule, (value) => value == null) - const newSchedule = await ScheduleModel.create(non_null_schedule) - return formatSchedule(newSchedule) + const non_null_schedule = omitBy(main_schedule, (value) => value == null); + const newSchedule = await ScheduleModel.create({ + ...non_null_schedule, + created_by: context.user._id, + }); + return formatSchedule(newSchedule); } - // update an existing schedule -export async function editSchedule(schedule_ID: string, main_schedule: ScheduleInput): Promise { +export async function editSchedule( + schedule_ID: string, + main_schedule: ScheduleInput +): Promise { if (main_schedule.custom_events) { - main_schedule.custom_events.forEach(removeNullEventVals) + main_schedule.custom_events.forEach(removeNullEventVals); } - const non_null_schedule = omitBy(main_schedule, (value) => value == null) - const updatedSchedule = await ScheduleModel.findByIdAndUpdate(schedule_ID, non_null_schedule, {returnDocument: 'after'}) + const non_null_schedule = omitBy(main_schedule, (value) => value == null); + const updatedSchedule = await ScheduleModel.findByIdAndUpdate( + schedule_ID, + non_null_schedule, + { returnDocument: "after" } + ); if (!updatedSchedule) { - throw new Error("Unable to update existing schedule") + throw new Error("Unable to update existing schedule"); } - return formatSchedule(updatedSchedule) + return formatSchedule(updatedSchedule); } // update class selection in an existing schedule -export async function setClasses(scheduleID: string, courses: SelectedCourseInput[]): Promise { - const existingSchedule = await ScheduleModel.findByIdAndUpdate(scheduleID, {courses: courses}, {returnDocument: 'after'}) +export async function setClasses( + scheduleID: string, + courses: SelectedCourseInput[] +): Promise { + const existingSchedule = await ScheduleModel.findByIdAndUpdate( + scheduleID, + { courses: courses }, + { returnDocument: "after" } + ); if (!existingSchedule) { - throw new Error("Unable to update existing schedule's class selection") + throw new Error("Unable to update existing schedule's class selection"); } - return formatSchedule(existingSchedule) + return formatSchedule(existingSchedule); } diff --git a/backend/src/modules/schedule/generated-types/module-types.ts b/backend/src/modules/schedule/generated-types/module-types.ts index 9d68addcf..b91045ca6 100644 --- a/backend/src/modules/schedule/generated-types/module-types.ts +++ b/backend/src/modules/schedule/generated-types/module-types.ts @@ -13,7 +13,7 @@ export namespace ScheduleModule { interface DefinedInputFields { CustomEventInput: 'start_time' | 'end_time' | 'title' | 'location' | 'description' | 'days_of_week'; SelectedCourseInput: 'class_ID' | 'primary_section_ID' | 'secondary_section_IDs'; - ScheduleInput: 'name' | 'created_by' | 'courses' | 'is_public' | 'term' | 'custom_events'; + ScheduleInput: 'name' | 'courses' | 'is_public' | 'term' | 'custom_events'; }; export type TermOutput = Pick; diff --git a/backend/src/modules/schedule/resolver.ts b/backend/src/modules/schedule/resolver.ts index 74352046d..1a47d866a 100644 --- a/backend/src/modules/schedule/resolver.ts +++ b/backend/src/modules/schedule/resolver.ts @@ -1,30 +1,50 @@ -import { getSchedulesByUser, getScheduleByID, removeSchedule, createSchedule, setClasses, editSchedule } from "./controller"; +import { + getSchedulesByUser, + getScheduleByID, + removeSchedule, + createSchedule, + setClasses, + editSchedule, +} from "./controller"; import { ScheduleModule } from "./generated-types/module-types"; -import { ScheduleInput, SelectedCourseInput } from "../../generated-types/graphql"; +import { + ScheduleInput, + SelectedCourseInput, +} from "../../generated-types/graphql"; const resolvers: ScheduleModule.Resolvers = { Query: { - schedulesByUser(_parent, args: { created_by: string }) { - return getSchedulesByUser(args.created_by); + schedulesByUser(_parent, _args, context) { + return getSchedulesByUser(context); }, scheduleByID(_parent, args: { id: string }) { return getScheduleByID(args.id); - } + }, }, Mutation: { - removeScheduleByID(_parent, args: {id: string}) { + removeScheduleByID(_parent, args: { id: string }) { return removeSchedule(args.id); }, - createNewSchedule(_parent, args: {main_schedule: ScheduleInput}) { - return createSchedule(args.main_schedule) + createNewSchedule( + _parent, + args: { main_schedule: ScheduleInput }, + context + ) { + return createSchedule(args.main_schedule, context); + }, + editExistingSchedule( + _parent, + args: { id: string; main_schedule: ScheduleInput } + ) { + return editSchedule(args.id, args.main_schedule); }, - editExistingSchedule(_parent, args: {id: string, main_schedule: ScheduleInput}) { - return editSchedule(args.id, args.main_schedule) + setSelectedClasses( + _parent, + args: { id: string; courses: SelectedCourseInput[] } + ) { + return setClasses(args.id, args.courses); }, - setSelectedClasses(_parent, args: {id: string, courses: SelectedCourseInput[]}) { - return setClasses(args.id, args.courses) - } - } + }, }; export default resolvers; diff --git a/backend/src/modules/schedule/typedefs/schedule.ts b/backend/src/modules/schedule/typedefs/schedule.ts index 29eb249dc..6ac6b97d9 100644 --- a/backend/src/modules/schedule/typedefs/schedule.ts +++ b/backend/src/modules/schedule/typedefs/schedule.ts @@ -1,7 +1,6 @@ import { gql } from "graphql-tag"; const typedef = gql` - type TermOutput { year: Int! semester: String! @@ -24,10 +23,9 @@ const typedef = gql` input ScheduleInput { name: String - created_by: String!, - courses: [SelectedCourseInput!], - is_public: Boolean, - term: TermInput!, + courses: [SelectedCourseInput!] + is_public: Boolean + term: TermInput! custom_events: [CustomEventInput!] } @@ -92,7 +90,7 @@ const typedef = gql` """ Takes in a user's email and returns all the schedules they created. """ - schedulesByUser(created_by: String!): [Schedule] @auth + schedulesByUser: [Schedule] @auth """ Takes in a schedule's ObjectID and returns a specific schedule. """ @@ -115,8 +113,9 @@ const typedef = gql` """ For the schedule specified by the ID, modifies the courses field and returns the updated schedule. """ - setSelectedClasses(id: ID!, courses: [SelectedCourseInput!]!): Schedule @auth + setSelectedClasses(id: ID!, courses: [SelectedCourseInput!]!): Schedule + @auth } `; -export default typedef; \ No newline at end of file +export default typedef; diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 13927eac7..ac665d131 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -83,7 +83,7 @@ type Query { """ Takes in a user's email and returns all the schedules they created. """ - schedulesByUser(created_by: String!): [Schedule] + schedulesByUser: [Schedule!] """ Takes in a schedule's ObjectID and returns a specific schedule. @@ -675,7 +675,6 @@ input SelectedCourseInput { input ScheduleInput { name: String - created_by: String! courses: [SelectedCourseInput!] is_public: Boolean term: TermInput! diff --git a/frontend/src/app/Schedules/index.tsx b/frontend/src/app/Schedules/index.tsx index 339551109..cdae3587b 100644 --- a/frontend/src/app/Schedules/index.tsx +++ b/frontend/src/app/Schedules/index.tsx @@ -4,11 +4,9 @@ import { Link } from "react-router-dom"; import Button from "@/components/Button"; import { - AccountResponse, CREATE_SCHEDULE, CreateScheduleResponse, DELETE_SCHEDULE, - GET_ACCOUNT, GET_SCHEDULES, GetSchedulesResponse, } from "@/lib/api"; @@ -16,13 +14,7 @@ import { import styles from "./Schedules.module.scss"; export default function Schedules() { - const { data: account } = useQuery(GET_ACCOUNT); - - const { data: schedules } = useQuery(GET_SCHEDULES, { - variables: { - createdBy: account?.user.email, - }, - }); + const { data: schedules } = useQuery(GET_SCHEDULES); const [createSchedule] = useMutation( CREATE_SCHEDULE, @@ -33,33 +25,46 @@ export default function Schedules() { year: 2024, semester: "Spring", }, - createdBy: account?.user.email, }, update(cache, { data }) { const schedule = data?.createNewSchedule; if (!schedule) return; - cache.modify({ - fields: { - schedulesByUser(existingSchedules = []) { - const ref = cache.writeFragment({ - data: schedule, - fragment: gql` - fragment NewSchedule on Schedule { - _id - name - term { - year - semester + const queryResult = cache.readQuery({ + query: GET_SCHEDULES, + }); + + // Initialize the cache + if (queryResult) { + cache.modify({ + fields: { + schedulesByUser: (existingSchedules = []) => { + const ref = cache.writeFragment({ + data: schedule, + fragment: gql` + fragment NewSchedule on Schedule { + _id + name + term { + year + semester + } } - } - `, - }); + `, + }); - return [...existingSchedules, ref]; + return [...existingSchedules, ref]; + }, }, - }, + }); + + return; + } + + cache.writeQuery({ + query: GET_SCHEDULES, + data: { schedulesByUser: [schedule] }, }); }, } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 03b1a3125..92302310a 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -460,15 +460,10 @@ export interface CreateScheduleResponse { } export const CREATE_SCHEDULE = gql` - mutation CreateSchedule( - $name: String! - $term: TermInput! - $createdBy: String! - ) { + mutation CreateSchedule($name: String!, $term: TermInput!) { createNewSchedule( main_schedule: { courses: [] - created_by: $createdBy name: $name custom_events: [] is_public: false @@ -490,8 +485,8 @@ export interface GetSchedulesResponse { } export const GET_SCHEDULES = gql` - query GetSchedules($createdBy: String!) { - schedulesByUser(created_by: $createdBy) { + query GetSchedules { + schedulesByUser { _id name term {