diff --git a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts index fd03a77..d3a3cb7 100644 --- a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts +++ b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/assignments/[assignmentName]/route.ts @@ -15,3 +15,19 @@ export async function GET( ); return Response.json(settings); } + +export async function PUT( + request: Request, + { + params: { courseName, moduleName, assignmentName }, + }: { params: { courseName: string; moduleName: string; assignmentName: string } } +) { + const assignment = await request.json() + await fileStorageService.updateAssignment( + courseName, + moduleName, + assignmentName, + assignment + ); + return Response.json({}); +} diff --git a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts index ee54feb..6f1cbe1 100644 --- a/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts +++ b/nextjs/src/app/api/courses/[courseName]/modules/[moduleName]/pages/[pageName]/route.ts @@ -13,3 +13,19 @@ export async function GET( ); return Response.json(settings); } + +export async function PUT( + request: Request, + { + params: { courseName, moduleName, pageName }, + }: { params: { courseName: string; moduleName: string; pageName: string } } +) { + const page = await request.json() + await fileStorageService.updatePage( + courseName, + moduleName, + pageName, + page + ); + return Response.json({}); +} diff --git a/nextjs/src/app/course/[courseName]/context/DraggingContextProvider.tsx b/nextjs/src/app/course/[courseName]/context/DraggingContextProvider.tsx index d5acadb..4117a3f 100644 --- a/nextjs/src/app/course/[courseName]/context/DraggingContextProvider.tsx +++ b/nextjs/src/app/course/[courseName]/context/DraggingContextProvider.tsx @@ -4,7 +4,14 @@ import { DraggingContext } from "./draggingContext"; import { useUpdateQuizMutation } from "@/hooks/localCourse/quizHooks"; import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { LocalQuiz } from "@/models/local/quiz/localQuiz"; -import { getDateFromStringOrThrow, dateToMarkdownString } from "@/models/local/timeUtils"; +import { + getDateFromStringOrThrow, + dateToMarkdownString, +} from "@/models/local/timeUtils"; +import { LocalAssignment } from "@/models/local/assignment/localAssignment"; +import { useUpdateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks"; +import { useUpdatePageMutation } from "@/hooks/localCourse/pageHooks"; +import { LocalCoursePage } from "@/models/local/page/localCoursePage"; export default function DraggingContextProvider({ children, @@ -13,6 +20,8 @@ export default function DraggingContextProvider({ localCourseName: string; }) { const updateQuizMutation = useUpdateQuizMutation(); + const updateAssignmentMutation = useUpdateAssignmentMutation(); + const updatePageMutation = useUpdatePageMutation(); const { data: settings } = useLocalCourseSettingsQuery(); const itemDrop = useCallback( @@ -35,12 +44,7 @@ export default function DraggingContextProvider({ const quiz: LocalQuiz = { ...previousQuiz, dueAt: dateToMarkdownString(dayAsDate), - lockAt: - previousQuiz.lockAt && - (getDateFromStringOrThrow(previousQuiz.lockAt, "lockAt date") > - dayAsDate - ? previousQuiz.lockAt - : dateToMarkdownString(dayAsDate)), + lockAt: getLaterDate(previousQuiz.lockAt, dayAsDate), }; updateQuizMutation.mutate({ quiz: quiz, @@ -48,15 +52,46 @@ export default function DraggingContextProvider({ moduleName: itemBeingDragged.sourceModuleName, }); } else if (itemBeingDragged.type === "assignment") { - console.log("dropped assignment"); + updateAssignment(dayAsDate); } else if (itemBeingDragged.type === "page") { console.log("dropped page"); + const previousPage = itemBeingDragged.item as LocalCoursePage; + const page: LocalCoursePage = { + ...previousPage, + dueAt: dateToMarkdownString(dayAsDate), + }; + updatePageMutation.mutate({ + page, + moduleName: itemBeingDragged.sourceModuleName, + pageName: page.name, + }); } } + + function updateAssignment(dayAsDate: Date) { + const previousAssignment = itemBeingDragged.item as LocalAssignment; + const assignment: LocalAssignment = { + ...previousAssignment, + dueAt: dateToMarkdownString(dayAsDate), + lockAt: previousAssignment.lockAt && + (getDateFromStringOrThrow( + previousAssignment.lockAt, + "lockAt date" + ) > dayAsDate + ? previousAssignment.lockAt + : dateToMarkdownString(dayAsDate)), + }; + updateAssignmentMutation.mutate({ + assignment, + moduleName: itemBeingDragged.sourceModuleName, + assignmentName: assignment.name, + }); + } }, [ settings.defaultDueTime.hour, settings.defaultDueTime.minute, + updateAssignmentMutation, updateQuizMutation, ] ); @@ -71,3 +106,14 @@ export default function DraggingContextProvider({ ); } +function getLaterDate( + firstDate: string | undefined, + dayAsDate: Date +): string | undefined { + return ( + firstDate && + (getDateFromStringOrThrow(firstDate, "lockAt date") > dayAsDate + ? firstDate + : dateToMarkdownString(dayAsDate)) + ); +} diff --git a/nextjs/src/hooks/localCourse/assignmentHooks.ts b/nextjs/src/hooks/localCourse/assignmentHooks.ts index 5bb04b4..1d80e5f 100644 --- a/nextjs/src/hooks/localCourse/assignmentHooks.ts +++ b/nextjs/src/hooks/localCourse/assignmentHooks.ts @@ -2,7 +2,7 @@ import axios from "axios"; import { localCourseKeys } from "./localCourseKeys"; import { LocalAssignment } from "@/models/local/assignment/localAssignment"; -import { useSuspenseQuery, useSuspenseQueries } from "@tanstack/react-query"; +import { useSuspenseQuery, useSuspenseQueries, useQueryClient, useMutation } from "@tanstack/react-query"; import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; export const useAssignmentNamesQuery = (moduleName: string) => { @@ -72,3 +72,40 @@ export const useAssignmentsQueries = ( }), }); }; + +export const useUpdateAssignmentMutation = () => { + const { courseName } = useCourseContext(); + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + assignment, + moduleName, + assignmentName, + }: { + assignment: LocalAssignment; + moduleName: string; + assignmentName: string; + }) => { + queryClient.setQueryData( + localCourseKeys.assignment(courseName, moduleName, assignmentName), + assignment + ); + const url = + "/api/courses/" + + encodeURIComponent(courseName) + + "/modules/" + + encodeURIComponent(moduleName) + + "/assignments/" + + encodeURIComponent(assignmentName); + await axios.put(url, assignment); + }, + onSuccess: (_, { moduleName, assignmentName }) => { + queryClient.invalidateQueries({ + queryKey: localCourseKeys.assignment(courseName, moduleName, assignmentName), + }); + queryClient.invalidateQueries({ + queryKey: localCourseKeys.assignmentNames(courseName, moduleName), + }); + }, + }); +}; diff --git a/nextjs/src/hooks/localCourse/pageHooks.ts b/nextjs/src/hooks/localCourse/pageHooks.ts index b91bc84..bdbf00a 100644 --- a/nextjs/src/hooks/localCourse/pageHooks.ts +++ b/nextjs/src/hooks/localCourse/pageHooks.ts @@ -1,6 +1,11 @@ -"use client" +"use client"; import { LocalCoursePage } from "@/models/local/page/localCoursePage"; -import { useSuspenseQueries, useSuspenseQuery } from "@tanstack/react-query"; +import { + useMutation, + useQueryClient, + useSuspenseQueries, + useSuspenseQuery, +} from "@tanstack/react-query"; import axios from "axios"; import { localCourseKeys } from "./localCourseKeys"; import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; @@ -65,3 +70,40 @@ function getPageQueryConfig( }, }; } + +export const useUpdatePageMutation = () => { + const { courseName } = useCourseContext(); + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + page, + moduleName, + pageName, + }: { + page: LocalCoursePage; + moduleName: string; + pageName: string; + }) => { + queryClient.setQueryData( + localCourseKeys.page(courseName, moduleName, pageName), + page + ); + const url = + "/api/courses/" + + encodeURIComponent(courseName) + + "/modules/" + + encodeURIComponent(moduleName) + + "/pages/" + + encodeURIComponent(pageName); + await axios.put(url, page); + }, + onSuccess: (_, { moduleName, pageName }) => { + queryClient.invalidateQueries({ + queryKey: localCourseKeys.page(courseName, moduleName, pageName), + }); + queryClient.invalidateQueries({ + queryKey: localCourseKeys.pageNames(courseName, moduleName), + }); + }, + }); +}; diff --git a/nextjs/src/hooks/localCourse/quizHooks.ts b/nextjs/src/hooks/localCourse/quizHooks.ts index 7b9e1bc..f306f43 100644 --- a/nextjs/src/hooks/localCourse/quizHooks.ts +++ b/nextjs/src/hooks/localCourse/quizHooks.ts @@ -79,6 +79,10 @@ export const useUpdateQuizMutation = () => { moduleName: string; quizName: string; }) => { + queryClient.setQueryData( + localCourseKeys.quiz(courseName, moduleName, quizName), + quiz + ); const url = "/api/courses/" + encodeURIComponent(courseName) + @@ -86,19 +90,15 @@ export const useUpdateQuizMutation = () => { encodeURIComponent(moduleName) + "/quizzes/" + encodeURIComponent(quizName); - queryClient.setQueryData( - localCourseKeys.quiz(courseName, moduleName, quizName), - quiz - ); await axios.put(url, quiz); }, onSuccess: (_, { moduleName, quizName }) => { queryClient.invalidateQueries({ queryKey: localCourseKeys.quiz(courseName, moduleName, quizName), }); - // queryClient.invalidateQueries({ - // queryKey: localCourseKeys.quizNames(courseName, moduleName), - // }); + queryClient.invalidateQueries({ + queryKey: localCourseKeys.quizNames(courseName, moduleName), + }); }, }); }; diff --git a/nextjs/src/services/fileStorage/fileStorageService.ts b/nextjs/src/services/fileStorage/fileStorageService.ts index 3b907c3..ae60fa1 100644 --- a/nextjs/src/services/fileStorage/fileStorageService.ts +++ b/nextjs/src/services/fileStorage/fileStorageService.ts @@ -9,10 +9,11 @@ import { directoryOrFileExists, hasFileSystemEntries, } from "./utils/fileSystemUtils"; -import { localAssignmentMarkdown } from "@/models/local/assignment/localAssignment"; +import { LocalAssignment, localAssignmentMarkdown } from "@/models/local/assignment/localAssignment"; import { LocalQuiz, localQuizMarkdownUtils } from "@/models/local/quiz/localQuiz"; -import { localPageMarkdownUtils } from "@/models/local/page/localCoursePage"; +import { LocalCoursePage, localPageMarkdownUtils } from "@/models/local/page/localCoursePage"; import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils"; +import { assignmentMarkdownSerializer } from "@/models/local/assignment/utils/assignmentMarkdownSerializer"; const basePath = process.env.STORAGE_DIRECTORY ?? "./storage"; console.log("base path", basePath); @@ -136,6 +137,19 @@ export const fileStorageService = { ); return localAssignmentMarkdown.parseMarkdown(rawFile); }, + async updateAssignment(courseName: string, moduleName: string, assignmentName: string, assignment: LocalAssignment) { + const filePath = path.join( + basePath, + courseName, + moduleName, + "assignments", + assignmentName+".md" + ); + + const assignmentMarkdown = assignmentMarkdownSerializer.toMarkdown(assignment); + console.log(`Saving assignment ${filePath}`); + await fs.writeFile(filePath, assignmentMarkdown); + }, async getQuiz(courseName: string, moduleName: string, quizName: string) { const filePath = path.join( @@ -180,6 +194,19 @@ export const fileStorageService = { ); return localPageMarkdownUtils.parseMarkdown(rawFile); }, + async updatePage(courseName: string, moduleName: string, pageName: string, page: LocalCoursePage) { + const filePath = path.join( + basePath, + courseName, + moduleName, + "pages", + pageName+".md" + ); + + const pageMarkdown = localPageMarkdownUtils.toMarkdown(page); + console.log(`Saving page ${filePath}`); + await fs.writeFile(filePath, pageMarkdown); + }, async getEmptyDirectories(): Promise { if (!(await directoryOrFileExists(basePath))) {