trpc stuff

This commit is contained in:
2024-11-08 11:41:20 -07:00
parent 9503b208d2
commit 95d758210c
13 changed files with 213 additions and 73 deletions

View File

@@ -1,16 +1,11 @@
"use client"; "use client";
import { useLocalCoursesSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { useLocalCoursesSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { trpc } from "@/services/trpc/utils";
import { getCourseUrl } from "@/services/urlUtils"; import { getCourseUrl } from "@/services/urlUtils";
import Link from "next/link"; import Link from "next/link";
import { useEffect } from "react";
export default function CourseList() { export default function CourseList() {
const { data: allSettings } = useLocalCoursesSettingsQuery(); const { data: allSettings } = useLocalCoursesSettingsQuery();
const {data} = trpc.sayHello.useQuery()
console.log(data);
return ( return (
<div> <div>
{allSettings.map((settings) => ( {allSettings.map((settings) => (

View File

@@ -8,6 +8,7 @@ import {
getDateOnlyMarkdownString, getDateOnlyMarkdownString,
} from "@/models/local/timeUtils"; } from "@/models/local/timeUtils";
import { useAllCourseDataQuery } from "@/hooks/localCourse/localCourseModuleHooks"; import { useAllCourseDataQuery } from "@/hooks/localCourse/localCourseModuleHooks";
import { trpc } from "@/services/trpc/utils";
export default function CalendarItemsContextProvider({ export default function CalendarItemsContextProvider({
children, children,
@@ -17,6 +18,9 @@ export default function CalendarItemsContextProvider({
const { assignmentsAndModules, quizzesAndModules, pagesAndModules } = const { assignmentsAndModules, quizzesAndModules, pagesAndModules } =
useAllCourseDataQuery(); useAllCourseDataQuery();
const assignmentsByModuleByDate = assignmentsAndModules.reduce( const assignmentsByModuleByDate = assignmentsAndModules.reduce(
(previous, { assignment, moduleName }) => { (previous, { assignment, moduleName }) => {
const dueDay = getDateOnlyMarkdownString( const dueDay = getDateOnlyMarkdownString(

View File

@@ -1,5 +1,4 @@
"use client"; "use client";
import { useAssignmentsQueries } from "@/hooks/localCourse/assignmentHooks";
import { usePagesQueries } from "@/hooks/localCourse/pageHooks"; import { usePagesQueries } from "@/hooks/localCourse/pageHooks";
import { IModuleItem } from "@/models/local/IModuleItem"; import { IModuleItem } from "@/models/local/IModuleItem";
import { import {
@@ -13,13 +12,17 @@ import NewItemForm from "./NewItemForm";
import { ModuleCanvasStatus } from "./ModuleCanvasStatus"; import { ModuleCanvasStatus } from "./ModuleCanvasStatus";
import ClientOnly from "@/components/ClientOnly"; import ClientOnly from "@/components/ClientOnly";
import ExpandIcon from "../../../../components/icons/ExpandIcon"; 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 Link from "next/link";
import { getModuleItemUrl } from "@/services/urlUtils"; import { getModuleItemUrl } from "@/services/urlUtils";
import { useCourseContext } from "../context/courseContext"; import { useCourseContext } from "../context/courseContext";
import { Expandable } from "../../../../components/Expandable"; import { Expandable } from "../../../../components/Expandable";
import { useDragStyleContext } from "../context/drag/dragStyleContext"; import { useDragStyleContext } from "../context/drag/dragStyleContext";
import { useItemsQueries } from "@/hooks/localCourse/courseItemHooks"; import { useItemsQueries } from "@/hooks/localCourse/courseItemHooks";
import { useAssignmentsQuery } from "@/hooks/localCourse/assignmentHooks";
export default function ExpandableModule({ export default function ExpandableModule({
moduleName, moduleName,
@@ -28,7 +31,7 @@ export default function ExpandableModule({
}) { }) {
const { itemDropOnModule } = useDraggingContext(); const { itemDropOnModule } = useDraggingContext();
const { data: assignments } = useAssignmentsQueries(moduleName); const [assignments] = useAssignmentsQuery(moduleName);
const { data: quizzes } = useItemsQueries(moduleName, "Quiz"); const { data: quizzes } = useItemsQueries(moduleName, "Quiz");
const { data: pages } = usePagesQueries(moduleName); const { data: pages } = usePagesQueries(moduleName);
const modal = useModal(); const modal = useModal();

View File

@@ -14,7 +14,9 @@ import {
getDateFromString, getDateFromString,
getDateFromStringOrThrow, getDateFromStringOrThrow,
} from "@/models/local/timeUtils"; } from "@/models/local/timeUtils";
import { trpc } from "@/services/trpc/utils";
import React, { useState } from "react"; import React, { useState } from "react";
import { useCourseContext } from "../context/courseContext";
export default function NewItemForm({ export default function NewItemForm({
moduleName: defaultModuleName, moduleName: defaultModuleName,
@@ -26,10 +28,15 @@ export default function NewItemForm({
onCreate?: () => void; onCreate?: () => void;
}) { }) {
const { data: settings } = useLocalCourseSettingsQuery(); const { data: settings } = useLocalCourseSettingsQuery();
const { courseName } = useCourseContext();
const { data: modules } = useModuleNamesQuery(); const { data: modules } = useModuleNamesQuery();
const [type, setType] = useState<"Assignment" | "Quiz" | "Page">( const [type, setType] = useState<"Assignment" | "Quiz" | "Page">(
"Assignment" "Assignment"
); );
const assignmentCreationMutation = useCreateAssignmentMutation({
courseName,
moduleName: defaultModuleName ?? "",
});
const [moduleName, setModuleName] = useState<string | undefined>( const [moduleName, setModuleName] = useState<string | undefined>(
defaultModuleName defaultModuleName
@@ -50,12 +57,13 @@ export default function NewItemForm({
const [assignmentGroup, setAssignmentGroup] = const [assignmentGroup, setAssignmentGroup] =
useState<LocalAssignmentGroup>(); useState<LocalAssignmentGroup>();
const createAssignment = useCreateAssignmentMutation();
const createPage = useCreatePageMutation(); const createPage = useCreatePageMutation();
const createQuiz = useCreateItemMutation("Quiz"); const createQuiz = useCreateItemMutation("Quiz");
const isPending = const isPending =
createAssignment.isPending || createPage.isPending || createQuiz.isPending; assignmentCreationMutation.isPending ||
createPage.isPending ||
createQuiz.isPending;
return ( return (
<form <form
@@ -84,8 +92,22 @@ export default function NewItemForm({
return; return;
} }
if (type === "Assignment") { if (type === "Assignment") {
createAssignment.mutate({ // createAssignment.mutate({
item: { // item: {
// name,
// description: "",
// localAssignmentGroupName: assignmentGroup?.name ?? "",
// dueAt,
// lockAt,
// submissionTypes: settings.defaultAssignmentSubmissionTypes,
// allowedFileUploadExtensions: settings.defaultFileUploadTypes,
// rubric: [],
// },
// moduleName: moduleName,
// itemName: name,
// });
assignmentCreationMutation.mutate({
assignment: {
name, name,
description: "", description: "",
localAssignmentGroupName: assignmentGroup?.name ?? "", localAssignmentGroupName: assignmentGroup?.name ?? "",
@@ -96,7 +118,8 @@ export default function NewItemForm({
rubric: [], rubric: [],
}, },
moduleName: moduleName, moduleName: moduleName,
itemName: name, assignmentName: name,
courseName,
}); });
} else if (type === "Quiz") { } else if (type === "Quiz") {
createQuiz.mutate({ createQuiz.mutate({

View File

@@ -27,7 +27,7 @@ export default function EditAssignment({
const router = useRouter(); const router = useRouter();
const { courseName } = useCourseContext(); const { courseName } = useCourseContext();
const { data: settings } = useLocalCourseSettingsQuery(); const { data: settings } = useLocalCourseSettingsQuery();
const { data: assignment } = useAssignmentQuery(moduleName, assignmentName); const [assignment] = useAssignmentQuery(moduleName, assignmentName);
const updateAssignment = useUpdateAssignmentMutation(); const updateAssignment = useUpdateAssignmentMutation();
const [assignmentText, setAssignmentText] = useState( const [assignmentText, setAssignmentText] = useState(

View File

@@ -12,6 +12,10 @@ import { canvasPageService } from "@/services/canvas/canvasPageService";
import { canvasQuizKeys } from "./canvas/canvasQuizHooks"; import { canvasQuizKeys } from "./canvas/canvasQuizHooks";
import { canvasPageKeys } from "./canvas/canvasPageHooks"; import { canvasPageKeys } from "./canvas/canvasPageHooks";
import { getLecturesQueryConfig } from "./localCourse/lectureHooks"; import { getLecturesQueryConfig } from "./localCourse/lectureHooks";
import { createServerSideHelpers } from "@trpc/react-query/server";
import { appRouter } from "@/services/trpc/router/app";
import superjson from "superjson";
import { createContext } from "@/services/trpc/context";
// https://tanstack.com/query/latest/docs/framework/react/guides/ssr // https://tanstack.com/query/latest/docs/framework/react/guides/ssr
export const hydrateCourses = async (queryClient: QueryClient) => { export const hydrateCourses = async (queryClient: QueryClient) => {
@@ -32,10 +36,27 @@ export const hydrateCourse = async (
queryClient: QueryClient, queryClient: QueryClient,
courseSettings: LocalCourseSettings courseSettings: LocalCourseSettings
) => { ) => {
const trpcHelpers = createServerSideHelpers({
router: appRouter,
ctx: createContext(),
transformer: superjson,
});
const courseName = courseSettings.name; const courseName = courseSettings.name;
const moduleNames = await fileStorageService.modules.getModuleNames( const moduleNames = await fileStorageService.modules.getModuleNames(
courseName courseName
); );
await Promise.all(
moduleNames.map(
async (moduleName) =>
await trpcHelpers.assignment.getAllAssignments.fetch({
courseName,
moduleName,
})
)
);
const modulesData = await Promise.all( const modulesData = await Promise.all(
moduleNames.map((moduleName) => loadAllModuleData(courseName, moduleName)) moduleNames.map((moduleName) => loadAllModuleData(courseName, moduleName))
); );
@@ -77,36 +98,36 @@ export const hydrateCanvasCourse = async (
}; };
const loadAllModuleData = async (courseName: string, moduleName: string) => { const loadAllModuleData = async (courseName: string, moduleName: string) => {
const [assignmentNames, pages, quizzes] = await Promise.all([ const [pages, quizzes] = await Promise.all([
await fileStorageService.assignments.getAssignmentNames( // await fileStorageService.assignments.getAssignmentNames(
courseName, // courseName,
moduleName // moduleName
), // ),
await fileStorageService.pages.getPages(courseName, moduleName), await fileStorageService.pages.getPages(courseName, moduleName),
await fileStorageService.quizzes.getQuizzes(courseName, moduleName), await fileStorageService.quizzes.getQuizzes(courseName, moduleName),
]); ]);
const [assignments] = await Promise.all([ // const [assignments] = await Promise.all([
await Promise.all( // await Promise.all(
assignmentNames.map(async (assignmentName) => { // assignmentNames.map(async (assignmentName) => {
try { // try {
return await fileStorageService.assignments.getAssignment( // return await fileStorageService.assignments.getAssignment(
courseName, // courseName,
moduleName, // moduleName,
assignmentName // assignmentName
); // );
} catch (error) { // } catch (error) {
console.error(`Error fetching assignment: ${assignmentName}`, error); // console.error(`Error fetching assignment: ${assignmentName}`, error);
return null; // or any other placeholder value // return null; // or any other placeholder value
} // }
}) // })
), // ),
]); // ]);
const assignmentsLoaded = assignments.filter((a) => a !== null); // const assignmentsLoaded = assignments.filter((a) => a !== null);
return { return {
moduleName, moduleName,
assignments: assignmentsLoaded, // assignments: assignmentsLoaded,
quizzes, quizzes,
pages, pages,
}; };

View File

@@ -1,38 +1,65 @@
"use client"; "use client";
import { trpc } from "@/services/trpc/utils";
import { import {
getAllItemsQueryConfig, getAllItemsQueryConfig,
getItemQueryConfig, getItemQueryConfig,
useCreateItemMutation,
useDeleteItemMutation, useDeleteItemMutation,
useItemQuery, useItemQuery,
useItemsQueries, useItemsQueries,
useUpdateItemMutation, useUpdateItemMutation,
} from "./courseItemHooks"; } from "./courseItemHooks";
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
export const getAllAssignmentsQueryConfig = ( // export const getAllAssignmentsQueryConfig = (
courseName: string, // courseName: string,
moduleName: string // moduleName: string
) => getAllItemsQueryConfig(courseName, moduleName, "Assignment"); // ) => getAllItemsQueryConfig(courseName, moduleName, "Assignment");
export const getAssignmentQueryConfig = ( // export const getAssignmentQueryConfig = (
courseName: string, // courseName: string,
moduleName: string, // moduleName: string,
assignmentName: string // assignmentName: string
) => getItemQueryConfig(courseName, moduleName, assignmentName, "Assignment"); // ) => getItemQueryConfig(courseName, moduleName, assignmentName, "Assignment");
export const useAssignmentQuery = ( export const useAssignmentQuery = (
moduleName: string, moduleName: string,
assignmentName: string assignmentName: string
) => useItemQuery(moduleName, assignmentName, "Assignment"); ) => {
const { courseName } = useCourseContext();
return trpc.assignment.getAssignment.useSuspenseQuery({
moduleName,
courseName,
assignmentName,
});
};
export const useAssignmentsQueries = (moduleName: string) => export const useAssignmentsQuery = (moduleName: string) => {
useItemsQueries(moduleName, "Assignment"); const { courseName } = useCourseContext();
return trpc.assignment.getAllAssignments.useSuspenseQuery({
moduleName,
courseName,
});
};
// useItemsQueries(moduleName, "Assignment");
export const useUpdateAssignmentMutation = () => export const useUpdateAssignmentMutation = () =>
useUpdateItemMutation("Assignment"); useUpdateItemMutation("Assignment");
export const useCreateAssignmentMutation = () => export const useCreateAssignmentMutation = ({
useCreateItemMutation("Assignment"); 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 = () => export const useDeleteAssignmentMutation = () =>
useDeleteItemMutation("Assignment"); useDeleteItemMutation("Assignment");

View File

@@ -6,12 +6,12 @@ import {
useSuspenseQuery, useSuspenseQuery,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import { localCourseKeys } from "./localCourseKeys"; import { localCourseKeys } from "./localCourseKeys";
import { getAllAssignmentsQueryConfig } from "./assignmentHooks";
import { getAllItemsQueryConfig } from "./courseItemHooks"; import { getAllItemsQueryConfig } from "./courseItemHooks";
import { import {
createModuleOnServer, createModuleOnServer,
getModuleNamesFromServer, getModuleNamesFromServer,
} from "./localCourseModuleServerActions"; } from "./localCourseModuleServerActions";
import { trpc } from "@/services/trpc/utils";
export const useModuleNamesQuery = () => { export const useModuleNamesQuery = () => {
const { courseName } = useCourseContext(); const { courseName } = useCourseContext();
@@ -42,21 +42,31 @@ export const useAllCourseDataQuery = () => {
const { courseName } = useCourseContext(); const { courseName } = useCourseContext();
const { data: moduleNames } = useModuleNamesQuery(); const { data: moduleNames } = useModuleNamesQuery();
const { data: assignmentsAndModules } = useSuspenseQueries({ const [assignments] = trpc.useSuspenseQueries((t) =>
queries: moduleNames.map((moduleName) => moduleNames.map((moduleName) =>
getAllAssignmentsQueryConfig(courseName, moduleName) t.assignment.getAllAssignments({ courseName, moduleName })
), )
combine: (results) => ({ );
data: results.flatMap((r, i) =>
r.data.map((assignment) => ({ const assignmentsAndModules = moduleNames.flatMap((moduleName, index) => {
moduleName: moduleNames[i], return assignments[index].map((assignment) => ({ moduleName, assignment }));
assignment,
}))
),
pending: results.some((r) => r.isPending),
}),
}); });
// 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({ const { data: quizzesAndModules } = useSuspenseQueries({
queries: moduleNames.map((moduleName) => queries: moduleNames.map((moduleName) =>
getAllItemsQueryConfig(courseName, moduleName, "Quiz") getAllItemsQueryConfig(courseName, moduleName, "Quiz")

View File

@@ -1,3 +1,4 @@
import { z } from "zod";
export enum AssignmentSubmissionType { export enum AssignmentSubmissionType {
ONLINE_TEXT_ENTRY = "online_text_entry", ONLINE_TEXT_ENTRY = "online_text_entry",
ONLINE_UPLOAD = "online_upload", ONLINE_UPLOAD = "online_upload",
@@ -6,6 +7,14 @@ export enum AssignmentSubmissionType {
ONLINE_URL = "online_url", ONLINE_URL = "online_url",
NONE = "none", 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[] = [ export const AssignmentSubmissionTypeList: AssignmentSubmissionType[] = [
AssignmentSubmissionType.ONLINE_TEXT_ENTRY, AssignmentSubmissionType.ONLINE_TEXT_ENTRY,

View File

@@ -1,8 +1,12 @@
import { IModuleItem } from "../IModuleItem"; import { IModuleItem } from "../IModuleItem";
import { AssignmentSubmissionType } from "./assignmentSubmissionType"; import {
import { RubricItem } from "./rubricItem"; AssignmentSubmissionType,
zodAssignmentSubmissionType,
} from "./assignmentSubmissionType";
import { RubricItem, zodRubricItem } from "./rubricItem";
import { assignmentMarkdownParser } from "./utils/assignmentMarkdownParser"; import { assignmentMarkdownParser } from "./utils/assignmentMarkdownParser";
import { assignmentMarkdownSerializer } from "./utils/assignmentMarkdownSerializer"; import { assignmentMarkdownSerializer } from "./utils/assignmentMarkdownSerializer";
import { z } from "zod";
export interface LocalAssignment extends IModuleItem { export interface LocalAssignment extends IModuleItem {
name: string; name: string;
@@ -15,6 +19,16 @@ export interface LocalAssignment extends IModuleItem {
rubric: RubricItem[]; 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 = { export const localAssignmentMarkdown = {
parseMarkdown: assignmentMarkdownParser.parseMarkdown, parseMarkdown: assignmentMarkdownParser.parseMarkdown,

View File

@@ -1,10 +1,16 @@
import { z } from "zod";
export interface RubricItem { export interface RubricItem {
label: string; label: string;
points: number; points: number;
} }
export const zodRubricItem = z.object({
label: z.string(),
points: z.number(),
});
export const rubricItemIsExtraCredit = (item: RubricItem) => { export const rubricItemIsExtraCredit = (item: RubricItem) => {
const extraCredit = '(extra credit)'; const extraCredit = "(extra credit)";
return item.label.toLowerCase().includes(extraCredit.toLowerCase()); return item.label.toLowerCase().includes(extraCredit.toLowerCase());
} };

View File

@@ -1,6 +1,7 @@
import { createContext } from "../context"; import { createContext } from "../context";
import publicProcedure from "../procedures/public"; import publicProcedure from "../procedures/public";
import { createCallerFactory, mergeRouters, router } from "../trpc"; import { createCallerFactory, mergeRouters, router } from "../trpc";
import { assignmentRouter } from "./assignmentRouter";
export const helloRouter = router({ export const helloRouter = router({
sayHello: publicProcedure.query(() => { sayHello: publicProcedure.query(() => {
@@ -10,8 +11,10 @@ export const helloRouter = router({
}), }),
}); });
export const appRouter = router({
export const appRouter = mergeRouters(helloRouter); hello: helloRouter,
assignment: assignmentRouter,
});
export const createCaller = createCallerFactory(appRouter); export const createCaller = createCallerFactory(appRouter);

View File

@@ -2,8 +2,9 @@ import publicProcedure from "../procedures/public";
import { z } from "zod"; import { z } from "zod";
import { router } from "../trpc"; import { router } from "../trpc";
import { fileStorageService } from "@/services/fileStorage/fileStorageService"; import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import { zodLocalAssignment } from "@/models/local/assignment/localAssignment";
export const courseItemRouter = router({ export const assignmentRouter = router({
getAssignment: publicProcedure getAssignment: publicProcedure
.input( .input(
z.object({ z.object({
@@ -32,4 +33,28 @@ export const courseItemRouter = router({
moduleName 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;
}
),
}); });