From 95d758210c60b8ae3439d8dbd547b4a94dae4853 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Fri, 8 Nov 2024 11:41:20 -0700 Subject: [PATCH] trpc stuff --- nextjs/src/app/CourseList.tsx | 5 -- .../context/CalendarItemsContextProvider.tsx | 4 ++ .../[courseName]/modules/ExpandableModule.tsx | 9 ++- .../[courseName]/modules/NewItemForm.tsx | 33 +++++++-- .../[assignmentName]/EditAssignment.tsx | 2 +- nextjs/src/hooks/hookHydration.ts | 67 ++++++++++++------- .../src/hooks/localCourse/assignmentHooks.ts | 57 +++++++++++----- .../localCourse/localCourseModuleHooks.ts | 38 +++++++---- .../assignment/assignmentSubmissionType.ts | 9 +++ .../local/assignment/localAssignment.ts | 18 ++++- .../src/models/local/assignment/rubricItem.ts | 10 ++- nextjs/src/services/trpc/router/app.ts | 7 +- ...ourseItemRouter.ts => assignmentRouter.ts} | 27 +++++++- 13 files changed, 213 insertions(+), 73 deletions(-) rename nextjs/src/services/trpc/router/{courseItemRouter.ts => assignmentRouter.ts} (57%) diff --git a/nextjs/src/app/CourseList.tsx b/nextjs/src/app/CourseList.tsx index c214d66..bb7ecd5 100644 --- a/nextjs/src/app/CourseList.tsx +++ b/nextjs/src/app/CourseList.tsx @@ -1,16 +1,11 @@ "use client"; import { useLocalCoursesSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; -import { trpc } from "@/services/trpc/utils"; import { getCourseUrl } from "@/services/urlUtils"; import Link from "next/link"; -import { useEffect } from "react"; export default function CourseList() { const { data: allSettings } = useLocalCoursesSettingsQuery(); - const {data} = trpc.sayHello.useQuery() - -console.log(data); return (
{allSettings.map((settings) => ( diff --git a/nextjs/src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx b/nextjs/src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx index 1412c4a..f05e8ae 100644 --- a/nextjs/src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx +++ b/nextjs/src/app/course/[courseName]/context/CalendarItemsContextProvider.tsx @@ -8,6 +8,7 @@ import { getDateOnlyMarkdownString, } from "@/models/local/timeUtils"; import { useAllCourseDataQuery } from "@/hooks/localCourse/localCourseModuleHooks"; +import { trpc } from "@/services/trpc/utils"; export default function CalendarItemsContextProvider({ children, @@ -17,6 +18,9 @@ export default function CalendarItemsContextProvider({ const { assignmentsAndModules, quizzesAndModules, pagesAndModules } = useAllCourseDataQuery(); + + + const assignmentsByModuleByDate = assignmentsAndModules.reduce( (previous, { assignment, moduleName }) => { const dueDay = getDateOnlyMarkdownString( diff --git a/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx b/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx index 4eceb2b..6e7e612 100644 --- a/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx +++ b/nextjs/src/app/course/[courseName]/modules/ExpandableModule.tsx @@ -1,5 +1,4 @@ "use client"; -import { useAssignmentsQueries } from "@/hooks/localCourse/assignmentHooks"; import { usePagesQueries } from "@/hooks/localCourse/pageHooks"; import { IModuleItem } from "@/models/local/IModuleItem"; import { @@ -13,13 +12,17 @@ import NewItemForm from "./NewItemForm"; import { ModuleCanvasStatus } from "./ModuleCanvasStatus"; import ClientOnly from "@/components/ClientOnly"; import ExpandIcon from "../../../../components/icons/ExpandIcon"; -import { DraggableItem, useDraggingContext } from "../context/drag/draggingContext"; +import { + DraggableItem, + useDraggingContext, +} from "../context/drag/draggingContext"; import Link from "next/link"; import { getModuleItemUrl } from "@/services/urlUtils"; import { useCourseContext } from "../context/courseContext"; import { Expandable } from "../../../../components/Expandable"; import { useDragStyleContext } from "../context/drag/dragStyleContext"; import { useItemsQueries } from "@/hooks/localCourse/courseItemHooks"; +import { useAssignmentsQuery } from "@/hooks/localCourse/assignmentHooks"; export default function ExpandableModule({ moduleName, @@ -28,7 +31,7 @@ export default function ExpandableModule({ }) { const { itemDropOnModule } = useDraggingContext(); - const { data: assignments } = useAssignmentsQueries(moduleName); + const [assignments] = useAssignmentsQuery(moduleName); const { data: quizzes } = useItemsQueries(moduleName, "Quiz"); const { data: pages } = usePagesQueries(moduleName); const modal = useModal(); diff --git a/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx b/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx index e4ecff0..107f6a7 100644 --- a/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx +++ b/nextjs/src/app/course/[courseName]/modules/NewItemForm.tsx @@ -14,7 +14,9 @@ import { getDateFromString, getDateFromStringOrThrow, } from "@/models/local/timeUtils"; +import { trpc } from "@/services/trpc/utils"; import React, { useState } from "react"; +import { useCourseContext } from "../context/courseContext"; export default function NewItemForm({ moduleName: defaultModuleName, @@ -26,10 +28,15 @@ export default function NewItemForm({ onCreate?: () => void; }) { const { data: settings } = useLocalCourseSettingsQuery(); + const { courseName } = useCourseContext(); const { data: modules } = useModuleNamesQuery(); const [type, setType] = useState<"Assignment" | "Quiz" | "Page">( "Assignment" ); + const assignmentCreationMutation = useCreateAssignmentMutation({ + courseName, + moduleName: defaultModuleName ?? "", + }); const [moduleName, setModuleName] = useState( defaultModuleName @@ -50,12 +57,13 @@ export default function NewItemForm({ const [assignmentGroup, setAssignmentGroup] = useState(); - const createAssignment = useCreateAssignmentMutation(); const createPage = useCreatePageMutation(); const createQuiz = useCreateItemMutation("Quiz"); const isPending = - createAssignment.isPending || createPage.isPending || createQuiz.isPending; + assignmentCreationMutation.isPending || + createPage.isPending || + createQuiz.isPending; return (
{ @@ -32,10 +36,27 @@ export const hydrateCourse = async ( queryClient: QueryClient, courseSettings: LocalCourseSettings ) => { + const trpcHelpers = createServerSideHelpers({ + router: appRouter, + ctx: createContext(), + transformer: superjson, + }); + const courseName = courseSettings.name; const moduleNames = await fileStorageService.modules.getModuleNames( courseName ); + + await Promise.all( + moduleNames.map( + async (moduleName) => + await trpcHelpers.assignment.getAllAssignments.fetch({ + courseName, + moduleName, + }) + ) + ); + const modulesData = await Promise.all( moduleNames.map((moduleName) => loadAllModuleData(courseName, moduleName)) ); @@ -77,36 +98,36 @@ export const hydrateCanvasCourse = async ( }; const loadAllModuleData = async (courseName: string, moduleName: string) => { - const [assignmentNames, pages, quizzes] = await Promise.all([ - await fileStorageService.assignments.getAssignmentNames( - courseName, - moduleName - ), + const [pages, quizzes] = await Promise.all([ + // await fileStorageService.assignments.getAssignmentNames( + // courseName, + // moduleName + // ), await fileStorageService.pages.getPages(courseName, moduleName), await fileStorageService.quizzes.getQuizzes(courseName, moduleName), ]); - const [assignments] = await Promise.all([ - await Promise.all( - assignmentNames.map(async (assignmentName) => { - try { - return await fileStorageService.assignments.getAssignment( - courseName, - moduleName, - assignmentName - ); - } catch (error) { - console.error(`Error fetching assignment: ${assignmentName}`, error); - return null; // or any other placeholder value - } - }) - ), - ]); + // const [assignments] = await Promise.all([ + // await Promise.all( + // assignmentNames.map(async (assignmentName) => { + // try { + // return await fileStorageService.assignments.getAssignment( + // courseName, + // moduleName, + // assignmentName + // ); + // } catch (error) { + // console.error(`Error fetching assignment: ${assignmentName}`, error); + // return null; // or any other placeholder value + // } + // }) + // ), + // ]); - const assignmentsLoaded = assignments.filter((a) => a !== null); + // const assignmentsLoaded = assignments.filter((a) => a !== null); return { moduleName, - assignments: assignmentsLoaded, + // assignments: assignmentsLoaded, quizzes, pages, }; diff --git a/nextjs/src/hooks/localCourse/assignmentHooks.ts b/nextjs/src/hooks/localCourse/assignmentHooks.ts index 6e907a9..cf8b741 100644 --- a/nextjs/src/hooks/localCourse/assignmentHooks.ts +++ b/nextjs/src/hooks/localCourse/assignmentHooks.ts @@ -1,38 +1,65 @@ "use client"; +import { trpc } from "@/services/trpc/utils"; import { getAllItemsQueryConfig, getItemQueryConfig, - useCreateItemMutation, useDeleteItemMutation, useItemQuery, useItemsQueries, useUpdateItemMutation, } from "./courseItemHooks"; +import { useCourseContext } from "@/app/course/[courseName]/context/courseContext"; -export const getAllAssignmentsQueryConfig = ( - courseName: string, - moduleName: string -) => getAllItemsQueryConfig(courseName, moduleName, "Assignment"); +// export const getAllAssignmentsQueryConfig = ( +// courseName: string, +// moduleName: string +// ) => getAllItemsQueryConfig(courseName, moduleName, "Assignment"); -export const getAssignmentQueryConfig = ( - courseName: string, - moduleName: string, - assignmentName: string -) => getItemQueryConfig(courseName, moduleName, assignmentName, "Assignment"); +// export const getAssignmentQueryConfig = ( +// courseName: string, +// moduleName: string, +// assignmentName: string +// ) => getItemQueryConfig(courseName, moduleName, assignmentName, "Assignment"); export const useAssignmentQuery = ( moduleName: string, assignmentName: string -) => useItemQuery(moduleName, assignmentName, "Assignment"); +) => { + const { courseName } = useCourseContext(); + return trpc.assignment.getAssignment.useSuspenseQuery({ + moduleName, + courseName, + assignmentName, + }); +}; -export const useAssignmentsQueries = (moduleName: string) => - useItemsQueries(moduleName, "Assignment"); +export const useAssignmentsQuery = (moduleName: string) => { + const { courseName } = useCourseContext(); + return trpc.assignment.getAllAssignments.useSuspenseQuery({ + moduleName, + courseName, + }); +}; +// useItemsQueries(moduleName, "Assignment"); export const useUpdateAssignmentMutation = () => useUpdateItemMutation("Assignment"); -export const useCreateAssignmentMutation = () => - useCreateItemMutation("Assignment"); +export const useCreateAssignmentMutation = ({ + courseName, + moduleName, +}: { + courseName: string; + moduleName: string; +}) => { + const utils = trpc.useUtils(); + return trpc.assignment.createAssignment.useMutation({ + onSuccess: () => { + utils.assignment.getAllAssignments.invalidate({ courseName, moduleName }); + }, + }); +}; +// useCreateItemMutation("Assignment"); export const useDeleteAssignmentMutation = () => useDeleteItemMutation("Assignment"); diff --git a/nextjs/src/hooks/localCourse/localCourseModuleHooks.ts b/nextjs/src/hooks/localCourse/localCourseModuleHooks.ts index 91c2974..7220f3c 100644 --- a/nextjs/src/hooks/localCourse/localCourseModuleHooks.ts +++ b/nextjs/src/hooks/localCourse/localCourseModuleHooks.ts @@ -6,12 +6,12 @@ import { useSuspenseQuery, } from "@tanstack/react-query"; import { localCourseKeys } from "./localCourseKeys"; -import { getAllAssignmentsQueryConfig } from "./assignmentHooks"; import { getAllItemsQueryConfig } from "./courseItemHooks"; import { createModuleOnServer, getModuleNamesFromServer, } from "./localCourseModuleServerActions"; +import { trpc } from "@/services/trpc/utils"; export const useModuleNamesQuery = () => { const { courseName } = useCourseContext(); @@ -42,21 +42,31 @@ export const useAllCourseDataQuery = () => { const { courseName } = useCourseContext(); const { data: moduleNames } = useModuleNamesQuery(); - const { data: assignmentsAndModules } = useSuspenseQueries({ - queries: moduleNames.map((moduleName) => - getAllAssignmentsQueryConfig(courseName, moduleName) - ), - combine: (results) => ({ - data: results.flatMap((r, i) => - r.data.map((assignment) => ({ - moduleName: moduleNames[i], - assignment, - })) - ), - pending: results.some((r) => r.isPending), - }), + const [assignments] = trpc.useSuspenseQueries((t) => + moduleNames.map((moduleName) => + t.assignment.getAllAssignments({ courseName, moduleName }) + ) + ); + + const assignmentsAndModules = moduleNames.flatMap((moduleName, index) => { + return assignments[index].map((assignment) => ({ moduleName, assignment })); }); + // const { data: assignmentsAndModules } = useSuspenseQueries({ + // queries: moduleNames.map((moduleName) => + // getAllAssignmentsQueryConfig(courseName, moduleName) + // ), + // combine: (results) => ({ + // data: results.flatMap((r, i) => + // r.data.map((assignment) => ({ + // moduleName: moduleNames[i], + // assignment, + // })) + // ), + // pending: results.some((r) => r.isPending), + // }), + // }); + const { data: quizzesAndModules } = useSuspenseQueries({ queries: moduleNames.map((moduleName) => getAllItemsQueryConfig(courseName, moduleName, "Quiz") diff --git a/nextjs/src/models/local/assignment/assignmentSubmissionType.ts b/nextjs/src/models/local/assignment/assignmentSubmissionType.ts index aaa5c1b..23aa9bb 100644 --- a/nextjs/src/models/local/assignment/assignmentSubmissionType.ts +++ b/nextjs/src/models/local/assignment/assignmentSubmissionType.ts @@ -1,3 +1,4 @@ +import { z } from "zod"; export enum AssignmentSubmissionType { ONLINE_TEXT_ENTRY = "online_text_entry", ONLINE_UPLOAD = "online_upload", @@ -6,6 +7,14 @@ export enum AssignmentSubmissionType { ONLINE_URL = "online_url", NONE = "none", } +export const zodAssignmentSubmissionType = z.enum([ + AssignmentSubmissionType.ONLINE_TEXT_ENTRY, + AssignmentSubmissionType.ONLINE_UPLOAD, + AssignmentSubmissionType.ONLINE_QUIZ, + AssignmentSubmissionType.DISCUSSION_TOPIC, + AssignmentSubmissionType.ONLINE_URL, + AssignmentSubmissionType.NONE, +]); export const AssignmentSubmissionTypeList: AssignmentSubmissionType[] = [ AssignmentSubmissionType.ONLINE_TEXT_ENTRY, diff --git a/nextjs/src/models/local/assignment/localAssignment.ts b/nextjs/src/models/local/assignment/localAssignment.ts index 7f9d21d..d366b9f 100644 --- a/nextjs/src/models/local/assignment/localAssignment.ts +++ b/nextjs/src/models/local/assignment/localAssignment.ts @@ -1,8 +1,12 @@ import { IModuleItem } from "../IModuleItem"; -import { AssignmentSubmissionType } from "./assignmentSubmissionType"; -import { RubricItem } from "./rubricItem"; +import { + AssignmentSubmissionType, + zodAssignmentSubmissionType, +} from "./assignmentSubmissionType"; +import { RubricItem, zodRubricItem } from "./rubricItem"; import { assignmentMarkdownParser } from "./utils/assignmentMarkdownParser"; import { assignmentMarkdownSerializer } from "./utils/assignmentMarkdownSerializer"; +import { z } from "zod"; export interface LocalAssignment extends IModuleItem { name: string; @@ -15,6 +19,16 @@ export interface LocalAssignment extends IModuleItem { rubric: RubricItem[]; } +export const zodLocalAssignment = z.object({ + name: z.string(), + description: z.string(), + lockAt: z.string().optional(), + dueAt: z.string(), + localAssignmentGroupName: z.string(), + submissionTypes: zodAssignmentSubmissionType.array(), + allowedFileUploadExtensions: z.string().array(), + rubric: zodRubricItem.array(), +}); export const localAssignmentMarkdown = { parseMarkdown: assignmentMarkdownParser.parseMarkdown, diff --git a/nextjs/src/models/local/assignment/rubricItem.ts b/nextjs/src/models/local/assignment/rubricItem.ts index e80d517..35272d8 100644 --- a/nextjs/src/models/local/assignment/rubricItem.ts +++ b/nextjs/src/models/local/assignment/rubricItem.ts @@ -1,10 +1,16 @@ +import { z } from "zod"; + export interface RubricItem { label: string; points: number; } +export const zodRubricItem = z.object({ + label: z.string(), + points: z.number(), +}); export const rubricItemIsExtraCredit = (item: RubricItem) => { - const extraCredit = '(extra credit)'; + const extraCredit = "(extra credit)"; return item.label.toLowerCase().includes(extraCredit.toLowerCase()); -} \ No newline at end of file +}; diff --git a/nextjs/src/services/trpc/router/app.ts b/nextjs/src/services/trpc/router/app.ts index a1b73dc..76c6096 100644 --- a/nextjs/src/services/trpc/router/app.ts +++ b/nextjs/src/services/trpc/router/app.ts @@ -1,6 +1,7 @@ import { createContext } from "../context"; import publicProcedure from "../procedures/public"; import { createCallerFactory, mergeRouters, router } from "../trpc"; +import { assignmentRouter } from "./assignmentRouter"; export const helloRouter = router({ sayHello: publicProcedure.query(() => { @@ -10,8 +11,10 @@ export const helloRouter = router({ }), }); - -export const appRouter = mergeRouters(helloRouter); +export const appRouter = router({ + hello: helloRouter, + assignment: assignmentRouter, +}); export const createCaller = createCallerFactory(appRouter); diff --git a/nextjs/src/services/trpc/router/courseItemRouter.ts b/nextjs/src/services/trpc/router/assignmentRouter.ts similarity index 57% rename from nextjs/src/services/trpc/router/courseItemRouter.ts rename to nextjs/src/services/trpc/router/assignmentRouter.ts index ae6ac06..730141f 100644 --- a/nextjs/src/services/trpc/router/courseItemRouter.ts +++ b/nextjs/src/services/trpc/router/assignmentRouter.ts @@ -2,8 +2,9 @@ import publicProcedure from "../procedures/public"; import { z } from "zod"; import { router } from "../trpc"; import { fileStorageService } from "@/services/fileStorage/fileStorageService"; +import { zodLocalAssignment } from "@/models/local/assignment/localAssignment"; -export const courseItemRouter = router({ +export const assignmentRouter = router({ getAssignment: publicProcedure .input( z.object({ @@ -32,4 +33,28 @@ export const courseItemRouter = router({ moduleName ); }), + createAssignment: publicProcedure + .input( + z.object({ + courseName: z.string(), + moduleName: z.string(), + assignmentName: z.string(), + assignment: zodLocalAssignment, + }) + ) + .mutation( + async ({ + input: { courseName, moduleName, assignmentName, assignment }, + ctx, + }) => { + await fileStorageService.assignments.updateOrCreateAssignment({ + courseName, + moduleName, + assignmentName, + assignment, + }); + + ctx; + } + ), });