redoing commit for pnpm

This commit is contained in:
2024-11-13 13:25:07 -07:00
parent 8d144b8834
commit fd2650dfc3
34 changed files with 369 additions and 346 deletions

3
nextjs/.gitignore vendored
View File

@@ -1,5 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.pnpm-store/
# dependencies
/node_modules
/.pnp

View File

@@ -10,4 +10,13 @@ docker run -it --rm \
-v ~/projects/faculty/1810/2024-fall-alex/modules:/app/storage/intro_to_web \
-v ~/projects/faculty/4850_AdvancedFE/2024-fall-alex/modules:/app/storage/advanced_frontend \
node \
bash -c "npm i && npm run dev -- -H 0.0.0.0"
sh -c "
mkdir -p ~/.npm-global && \
npm config set prefix '~/.npm-global' && \
export PATH=~/.npm-global/bin:\$PATH && \
npm install -g pnpm && \
pnpm install && pnpm start
"
# bash -c "npm i -g pnpm && pnpm i && pnpm run dev -- -H 0.0.0.0"

View File

@@ -17,13 +17,14 @@ const getUrl = (params: { rest: string[] }, req: NextRequest) => {
appendQueryParams(url, req);
return url;
return url;``
};
const proxyResponseHeaders = (response: any) => {
const headers = new Headers();
Object.entries(response.headers).forEach(([key, value]) => {
headers.set(key, value as string);
if (["link", "x-rate-limit-remaining"].includes(key))
headers.set(key, value as string);
});
return headers;
};
@@ -32,21 +33,19 @@ export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ rest: string[] }> }
) {
return withErrorHandling(async () => {
try {
const url = getUrl(await params, req);
try {
const url = getUrl(await params, req);
const response = await axiosClient.get(url.toString());
const headers = proxyResponseHeaders(response);
return new NextResponse(JSON.stringify(response.data), { headers });
} catch (error: any) {
return new NextResponse(
JSON.stringify({ error: error.message || "Canvas GET request failed" }),
{ status: error.response?.status || 500 }
);
}
});
const response = await axiosClient.get(url.toString());
const headers = proxyResponseHeaders(response);
return NextResponse.json(response.data, { headers });
} catch (error: any) {
console.log("canvas get error", error, error?.message);
return NextResponse.json(
JSON.stringify({ error: error.message || "Canvas GET request failed" }),
{ status: error.response?.status || 500 }
);
}
}
export async function POST(

View File

@@ -6,11 +6,11 @@ import NewItemForm from "../../modules/NewItemForm";
import { DraggableItem } from "../../context/drag/draggingContext";
import { useDragStyleContext } from "../../context/drag/dragStyleContext";
import { getLectureForDay } from "@/models/local/lectureUtils";
import { trpc } from "@/services/trpc/utils";
import { useLecturesSuspenseQuery } from "@/hooks/localCourse/lectureHooks";
export function DayTitle({ day, dayAsDate }: { day: string; dayAsDate: Date }) {
const { courseName } = useCourseContext();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const [weeks] = useLecturesSuspenseQuery();
const { setIsDragging } = useDragStyleContext();
const todaysLecture = getLectureForDay(weeks, dayAsDate);
const modal = useModal();

View File

@@ -33,7 +33,7 @@ export function useTodaysItems(day: string) {
}[] = todaysModules
? Object.keys(todaysModules).flatMap((moduleName) =>
todaysModules[moduleName].assignments.map((assignment) => {
const canvasAssignment = canvasAssignments.find(
const canvasAssignment = canvasAssignments?.find(
(c) => c.name === assignment.name
);
return {
@@ -57,7 +57,7 @@ export function useTodaysItems(day: string) {
}[] = todaysModules
? Object.keys(todaysModules).flatMap((moduleName) =>
todaysModules[moduleName].quizzes.map((quiz) => {
const canvasQuiz = canvasQuizzes.find((q) => q.title === quiz.name);
const canvasQuiz = canvasQuizzes?.find((q) => q.title === quiz.name);
return {
moduleName,
quiz,
@@ -79,7 +79,7 @@ export function useTodaysItems(day: string) {
}[] = todaysModules
? Object.keys(todaysModules).flatMap((moduleName) =>
todaysModules[moduleName].pages.map((page) => {
const canvasPage = canvasPages.find((p) => p.title === page.name);
const canvasPage = canvasPages?.find((p) => p.title === page.name);
return {
moduleName,
page,

View File

@@ -18,9 +18,6 @@ export default function CalendarItemsContextProvider({
const { assignmentsAndModules, quizzesAndModules, pagesAndModules } =
useAllCourseDataQuery();
const assignmentsByModuleByDate = assignmentsAndModules.reduce(
(previous, { assignment, moduleName }) => {
const dueDay = getDateOnlyMarkdownString(

View File

@@ -1,6 +1,9 @@
"use client";
import { useUpdateAssignmentMutation } from "@/hooks/localCourse/assignmentHooks";
import { useLectureUpdateMutation } from "@/hooks/localCourse/lectureHooks";
import {
useLecturesSuspenseQuery,
useLectureUpdateMutation,
} from "@/hooks/localCourse/lectureHooks";
import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks";
import { useUpdatePageMutation } from "@/hooks/localCourse/pageHooks";
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
@@ -36,9 +39,7 @@ export function useItemDropOnDay({
const [settings] = useLocalCourseSettingsQuery();
const { courseName } = useCourseContext();
// const { data: weeks } = useLecturesByWeekQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({
courseName: settings.name,
});
const [weeks] = useLecturesSuspenseQuery();
const updateQuizMutation = useUpdateQuizMutation();
const updateLectureMutation = useLectureUpdateMutation();
const updateAssignmentMutation = useUpdateAssignmentMutation();

View File

@@ -1,7 +1,7 @@
"use client";
import { MonacoEditor } from "@/components/editor/MonacoEditor";
import { useLectureUpdateMutation } from "@/hooks/localCourse/lectureHooks";
import { useLecturesSuspenseQuery, useLectureUpdateMutation } from "@/hooks/localCourse/lectureHooks";
import {
lectureToString,
parseLecture,
@@ -17,7 +17,7 @@ import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHoo
export default function EditLecture({ lectureDay }: { lectureDay: string }) {
const { courseName } = useCourseContext();
const [settings] = useLocalCourseSettingsQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const [weeks] = useLecturesSuspenseQuery();
const updateLecture = useLectureUpdateMutation();
const lecture = weeks

View File

@@ -5,6 +5,7 @@ import { getCourseUrl, getLectureUrl } from "@/services/urlUtils";
import { useCourseContext } from "../../../context/courseContext";
import Link from "next/link";
import { trpc } from "@/services/trpc/utils";
import { useLecturesSuspenseQuery } from "@/hooks/localCourse/lectureHooks";
export default function LecturePreviewPage({
lectureDay,
@@ -12,7 +13,7 @@ export default function LecturePreviewPage({
lectureDay: string;
}) {
const { courseName } = useCourseContext();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const [weeks] = useLecturesSuspenseQuery();
const lecture = weeks
.flatMap(({ lectures }) => lectures.map((lecture) => lecture))
.find((l) => l.date === lectureDay);

View File

@@ -21,8 +21,9 @@ import { getModuleItemUrl } from "@/services/urlUtils";
import { useCourseContext } from "../context/courseContext";
import { Expandable } from "../../../../components/Expandable";
import { useDragStyleContext } from "../context/drag/dragStyleContext";
import { useAssignmentsQuery } from "@/hooks/localCourse/assignmentHooks";
import { useQuizzesQueries } from "@/hooks/localCourse/quizHooks";
import { useAssignmentNamesQuery } from "@/hooks/localCourse/assignmentHooks";
import { trpc } from "@/services/trpc/utils";
export default function ExpandableModule({
moduleName,
@@ -30,8 +31,14 @@ export default function ExpandableModule({
moduleName: string;
}) {
const { itemDropOnModule } = useDraggingContext();
const [assignments ] = useAssignmentsQuery(moduleName);
const { courseName } = useCourseContext();
const [assignmentNames] = useAssignmentNamesQuery(moduleName);
const [assignments] = trpc.useSuspenseQueries((t) =>
assignmentNames.map((assignmentName) =>
t.assignment.getAssignment({ courseName, moduleName, assignmentName })
)
);
const [quizzes] = useQuizzesQueries(moduleName);
const [pages] = usePagesQueries(moduleName);
const modal = useModal();

View File

@@ -10,7 +10,7 @@ export function ModuleCanvasStatus({ moduleName }: { moduleName: string }) {
const { data: canvasModules } = useCanvasModulesQuery();
const addToCanvas = useAddCanvasModuleMutation();
const canvasModule = canvasModules.find((c) => c.name === moduleName);
const canvasModule = canvasModules?.find((c) => c.name === moduleName);
return (
<div className="text-slate-400 text-end">

View File

@@ -7,9 +7,9 @@ export default function ModuleList() {
const [moduleNames] = useModuleNamesQuery();
return (
<div>
{/* {moduleNames.map((m) => (
{moduleNames.map((m) => (
<ExpandableModule key={m} moduleName={m} />
))} */}
))}
<div className="flex flex-col justify-center">
<CreateModule />
</div>

View File

@@ -43,7 +43,7 @@ export function AssignmentButtons({
const [isLoading, setIsLoading] = useState(false);
const modal = useModal();
const assignmentInCanvas = canvasAssignments.find(
const assignmentInCanvas = canvasAssignments?.find(
(a) => a.name === assignmentName
);
@@ -61,7 +61,7 @@ export function AssignmentButtons({
</div>
<div className="flex flex-row gap-3 justify-end">
{anythingIsLoading && <Spinner />}
{assignmentInCanvas && !assignmentInCanvas.published && (
{assignmentInCanvas && !assignmentInCanvas?.published && (
<div className="text-rose-300 my-auto">Not Published</div>
)}
{!assignmentInCanvas && (
@@ -125,8 +125,8 @@ export function AssignmentButtons({
<div className="flex justify-around gap-3">
<button
onClick={async () => {
setIsLoading(true);
router.push(getCourseUrl(courseName));
setIsLoading(true);
await deleteLocal.mutateAsync({
moduleName,
assignmentName,

View File

@@ -16,7 +16,7 @@ import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
import { getCourseUrl } from "@/services/urlUtils";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React from "react";
import React, { useState } from "react";
export default function EditPageButtons({
moduleName,
@@ -35,6 +35,7 @@ export default function EditPageButtons({
const deletePageInCanvas = useDeleteCanvasPageMutation();
const deletePageLocal = useDeletePageMutation();
const modal = useModal();
const [loading, setLoading] = useState(false);
const pageInCanvas = canvasPages?.find((p) => p.title === pageName);
@@ -101,13 +102,14 @@ export default function EditPageButtons({
<br />
<div className="flex justify-around gap-3">
<button
onClick={() => {
router.push(getCourseUrl(courseName));
deletePageLocal.mutate({
onClick={async () => {
setLoading(true);
await deletePageLocal.mutateAsync({
moduleName,
pageName,
courseName,
});
router.push(getCourseUrl(courseName));
}}
className="btn-danger"
>
@@ -115,6 +117,7 @@ export default function EditPageButtons({
</button>
<button onClick={closeModal}>No</button>
</div>
{loading && <Spinner />}
</div>
)}
</Modal>

View File

@@ -36,7 +36,7 @@ export function QuizButtons({
const deleteLocal = useDeleteQuizMutation();
const modal = useModal();
const quizInCanvas = canvasQuizzes.find((c) => c.title === quizName);
const quizInCanvas = canvasQuizzes?.find((c) => c.title === quizName);
return (
<div className="p-5 flex flex-row justify-between">
@@ -90,8 +90,8 @@ export function QuizButtons({
<div className="flex justify-around gap-3">
<button
onClick={async () => {
await deleteLocal.mutateAsync({ moduleName, quizName, courseName });
router.push(getCourseUrl(courseName));
deleteLocal.mutate({ moduleName, quizName, courseName });
}}
className="btn-danger"
>

View File

@@ -12,7 +12,7 @@ import { useSetAssignmentGroupsMutation } from "@/hooks/canvas/canvasCourseHooks
export default function AssignmentGroupManagement() {
const [settings] = useLocalCourseSettingsQuery();
const updateSettings = useUpdateLocalCourseSettingsMutation();
const applyInCanvas = useSetAssignmentGroupsMutation(settings.canvasId); // untested
// const applyInCanvas = useSetAssignmentGroupsMutation(settings.canvasId); // untested
const [assignmentGroups, setAssignmentGroups] = useState<
LocalAssignmentGroup[]

View File

@@ -13,7 +13,10 @@ import { useEmptyDirectoriesQuery } from "@/hooks/localCourse/storageDirectoryHo
import { CanvasCourseModel } from "@/models/canvas/courses/canvasCourseModel";
import { CanvasEnrollmentTermModel } from "@/models/canvas/enrollmentTerms/canvasEnrollmentTermModel";
import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType";
import { DayOfWeek } from "@/models/local/localCourseSettings";
import {
DayOfWeek,
LocalCourseSettings,
} from "@/models/local/localCourseSettings";
import { getCourseUrl } from "@/services/urlUtils";
import { useRouter } from "next/navigation";
import React, { useMemo, useState } from "react";
@@ -47,6 +50,9 @@ export default function NewCourseForm() {
const [selectedDirectory, setSelectedDirectory] = useState<
string | undefined
>();
const [courseToImport, setCourseToImport] = useState<
LocalCourseSettings | undefined
>();
const createCourse = useCreateLocalCourseMutation();
const formIsComplete =
@@ -71,17 +77,34 @@ export default function NewCourseForm() {
setSelectedDirectory={setSelectedDirectory}
selectedDaysOfWeek={selectedDaysOfWeek}
setSelectedDaysOfWeek={setSelectedDaysOfWeek}
courseToImport={courseToImport}
setCourseToImport={setCourseToImport}
/>
)}
</SuspenseAndErrorHandling>
<div className="m-3 text-center">
<button
disabled={!formIsComplete || createCourse.isPending}
onClick={() => {
onClick={async () => {
if (formIsComplete) {
createCourse
.mutateAsync({
settings: {
const newSettings: LocalCourseSettings = courseToImport
? {
...courseToImport,
name: selectedDirectory,
daysOfWeek: selectedDaysOfWeek,
canvasId: selectedCanvasCourse.id,
startDate: selectedTerm.start_at ?? "",
endDate: selectedTerm.end_at ?? "",
holidays: [],
assignmentGroups: courseToImport.assignmentGroups.map(
(assignmentGroup) => {
const { canvasId, ...groupWithoutCanvas } =
assignmentGroup;
return groupWithoutCanvas;
}
),
}
: {
name: selectedDirectory,
assignmentGroups: [],
daysOfWeek: selectedDaysOfWeek,
@@ -96,11 +119,12 @@ export default function NewCourseForm() {
defaultFileUploadTypes: ["pdf", "png", "jpg", "jpeg"],
defaultLockHoursOffset: 0,
holidays: [],
},
})
.then(() => {
router.push(getCourseUrl(selectedDirectory));
});
};
await createCourse.mutateAsync({
settings: newSettings,
settingsFromCourseToImport: courseToImport,
});
router.push(getCourseUrl(selectedDirectory));
}
}}
>
@@ -125,6 +149,8 @@ function OtherSettings({
setSelectedDirectory,
selectedDaysOfWeek,
setSelectedDaysOfWeek,
courseToImport,
setCourseToImport,
}: {
selectedTerm: CanvasEnrollmentTermModel;
selectedCanvasCourse: CanvasCourseModel | undefined;
@@ -137,15 +163,21 @@ function OtherSettings({
>;
selectedDaysOfWeek: DayOfWeek[];
setSelectedDaysOfWeek: React.Dispatch<React.SetStateAction<DayOfWeek[]>>;
courseToImport: LocalCourseSettings | undefined;
setCourseToImport: React.Dispatch<
React.SetStateAction<LocalCourseSettings | undefined>
>;
}) {
const { data: canvasCourses } = useCourseListInTermQuery(selectedTerm.id);
const [allSettings] = useLocalCoursesSettingsQuery();
const [emptyDirectories] = useEmptyDirectoriesQuery();
const populatedCanvasCourseIds = allSettings.map((s) => s.canvasId);
const availableCourses = canvasCourses.filter(
(canvas) => !populatedCanvasCourseIds.includes(canvas.id)
);
const availableCourses =
canvasCourses?.filter(
(canvas: CanvasCourseModel) =>
!populatedCanvasCourseIds.includes(canvas.id)
) ?? [];
return (
<>
@@ -183,6 +215,13 @@ function OtherSettings({
}}
/>
</div>
<SelectInput
value={courseToImport}
setValue={setCourseToImport}
label={"(Optional) Course Content to Import"}
options={allSettings}
getOptionName={(c) => c.name}
/>
</>
);
}

View File

@@ -5,12 +5,11 @@ import { getLecturePreviewUrl } from "@/services/urlUtils";
import Link from "next/link";
import { useCourseContext } from "../course/[courseName]/context/courseContext";
import { getLectureForDay } from "@/models/local/lectureUtils";
import { trpc } from "@/services/trpc/utils";
import { useLecturesSuspenseQuery as useLecturesQuery } from "@/hooks/localCourse/lectureHooks";
export default function OneCourseLectures() {
const { courseName } = useCourseContext();
// const { data: weeks } = useLecturesByWeekQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const [weeks] = useLecturesQuery();
const dayAsDate = new Date();
const dayAsString = getDateOnlyMarkdownString(dayAsDate);

View File

@@ -1,11 +1,5 @@
import { canvasAssignmentService } from "@/services/canvas/canvasAssignmentService";
import { canvasService } from "@/services/canvas/canvasService";
import {
useMutation,
useQueryClient,
useSuspenseQueries,
useSuspenseQuery,
} from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { canvasModuleService } from "@/services/canvas/canvasModuleService";
@@ -24,28 +18,12 @@ export const canvasAssignmentKeys = {
export const useCanvasAssignmentsQuery = () => {
const [settings] = useLocalCourseSettingsQuery();
return useSuspenseQuery({
return useQuery({
queryKey: canvasAssignmentKeys.assignments(settings.canvasId),
queryFn: async () => canvasAssignmentService.getAll(settings.canvasId),
});
};
// export const useCanvasAssignmentsQuery = () => {
// const [settings] = useLocalCourseSettingsQuery();
// const { data: allAssignments } = useInnerCanvasAssignmentsQuery();
// return useSuspenseQueries({
// queries: allAssignments.map((a) => ({
// queryKey: canvasAssignmentKeys.assignment(settings.canvasId, a.name),
// queryFn: () => a,
// })),
// combine: (results) => ({
// data: results.map((r) => r.data),
// pending: results.some((r) => r.isPending),
// }),
// });
// };
export const useAddAssignmentToCanvasMutation = () => {
const [settings] = useLocalCourseSettingsQuery();
const { data: canvasModules } = useCanvasModulesQuery();
@@ -60,6 +38,11 @@ export const useAddAssignmentToCanvasMutation = () => {
assignment: LocalAssignment;
moduleName: string;
}) => {
if (!canvasModules) {
console.log("cannot add assignment until modules loaded");
return;
}
const assignmentGroup = settings.assignmentGroups.find(
(g) => g.name === assignment.localAssignmentGroupName
);

View File

@@ -1,7 +1,9 @@
import { CanvasAssignmentGroup } from "@/models/canvas/assignments/canvasAssignmentGroup";
import { CanvasCourseModel } from "@/models/canvas/courses/canvasCourseModel";
import { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
import { canvasAssignmentGroupService } from "@/services/canvas/canvasAssignmentGroupService";
import { canvasService } from "@/services/canvas/canvasService";
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
export const canvasCourseKeys = {
courseDetails: (canavasId: number) =>
@@ -13,34 +15,37 @@ export const canvasCourseKeys = {
};
export const useCourseListInTermQuery = (canvasTermId: number | undefined) =>
useSuspenseQuery({
useQuery({
queryKey: canvasCourseKeys.courseListInTerm(canvasTermId),
queryFn: async () =>
queryFn: async (): Promise<CanvasCourseModel[]> =>
canvasTermId ? await canvasService.getCourses(canvasTermId) : [],
enabled: !!canvasTermId,
});
export const useCanvasCourseQuery = (canvasId: number) =>
useSuspenseQuery({
queryKey: canvasCourseKeys.courseDetails(canvasId),
queryFn: async () => await canvasService.getCourse(canvasId),
});
// export const useCanvasCourseQuery = (canvasId: number) =>
// useQuery({
// queryKey: canvasCourseKeys.courseDetails(canvasId),
// queryFn: async () => await canvasService.getCourse(canvasId),
// });
export const useSetAssignmentGroupsMutation = (canvasId: number) => {
const { data: canvasAssignmentGroups } = useAssignmentGroupsQuery(canvasId);
return useMutation({
mutationFn: async (localAssignmentGroups: LocalAssignmentGroup[]) => {
if (!canvasAssignmentGroups) return;
const localNames = localAssignmentGroups.map((g) => g.name);
const groupsToDelete = canvasAssignmentGroups.filter(
(c) => !localNames.includes(c.name)
(c: CanvasAssignmentGroup) => !localNames.includes(c.name)
);
await Promise.all([
...groupsToDelete.map(
async (g) =>
async (g: CanvasAssignmentGroup) =>
await canvasAssignmentGroupService.delete(canvasId, g.id, g.name)
),
...localAssignmentGroups.map(async (group) => {
const canvasGroup = canvasAssignmentGroups.find(
(c) => c.name === group.name
(c: CanvasAssignmentGroup) => c.name === group.name
);
if (!canvasGroup) {
await canvasAssignmentGroupService.create(canvasId, group);
@@ -55,8 +60,8 @@ export const useSetAssignmentGroupsMutation = (canvasId: number) => {
};
export const useAssignmentGroupsQuery = (canvasId: number) => {
return useSuspenseQuery({
return useQuery({
queryKey: canvasCourseKeys.assignmentGroups(canvasId),
queryFn: async () => await canvasAssignmentGroupService.getAll(canvasId),
queryFn: async (): Promise<CanvasAssignmentGroup[]> => await canvasAssignmentGroupService.getAll(canvasId),
});
};

View File

@@ -1,8 +1,8 @@
import { canvasModuleService } from "@/services/canvas/canvasModuleService";
import {
useMutation,
useQuery,
useQueryClient,
useSuspenseQuery,
} from "@tanstack/react-query";
import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
@@ -12,7 +12,7 @@ export const canvasCourseModuleKeys = {
export const useCanvasModulesQuery = () => {
const [settings] = useLocalCourseSettingsQuery();
return useSuspenseQuery({
return useQuery({
queryKey: canvasCourseModuleKeys.modules(settings.canvasId),
queryFn: async () =>
await canvasModuleService.getCourseModules(settings.canvasId),

View File

@@ -1,10 +1,6 @@
import { LocalCoursePage } from "@/models/local/page/localCoursePage";
import { canvasPageService } from "@/services/canvas/canvasPageService";
import {
useMutation,
useQueryClient,
useSuspenseQuery,
} from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
import { canvasModuleService } from "@/services/canvas/canvasModuleService";
import {
@@ -22,7 +18,7 @@ export const canvasPageKeys = {
export const useCanvasPagesQuery = () => {
const [settings] = useLocalCourseSettingsQuery();
return useSuspenseQuery({
return useQuery({
queryKey: canvasPageKeys.pagesInCourse(settings.canvasId),
queryFn: async () => await canvasPageService.getAll(settings.canvasId),
});
@@ -42,6 +38,10 @@ export const useCreateCanvasPageMutation = () => {
page: LocalCoursePage;
moduleName: string;
}) => {
if (!canvasModules) {
console.log("cannot add page until modules loaded");
return;
}
const canvasPage = await canvasPageService.create(
settings.canvasId,
page

View File

@@ -1,7 +1,7 @@
import {
useMutation,
useQuery,
useQueryClient,
useSuspenseQuery,
} from "@tanstack/react-query";
import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
import { canvasQuizService } from "@/services/canvas/canvasQuizService";
@@ -20,7 +20,7 @@ export const canvasQuizKeys = {
export const useCanvasQuizzesQuery = () => {
const [settings] = useLocalCourseSettingsQuery();
return useSuspenseQuery({
return useQuery({
queryKey: canvasQuizKeys.quizzes(settings.canvasId),
queryFn: async () => canvasQuizService.getAll(settings.canvasId),
});
@@ -40,6 +40,10 @@ export const useAddQuizToCanvasMutation = () => {
quiz: LocalQuiz;
moduleName: string;
}) => {
if (!canvasModules) {
console.log("cannot add quiz until modules loaded");
return;
}
const assignmentGroup = settings.assignmentGroups.find(
(g) => g.name === quiz.localAssignmentGroupName
);

View File

@@ -1,208 +0,0 @@
// import { QueryClient } from "@tanstack/react-query";
// import { localCourseKeys } from "./localCourse/localCourseKeys";
// import { fileStorageService } from "@/services/fileStorage/fileStorageService";
// import { LocalCourseSettings } from "@/models/local/localCourseSettings";
// import { canvasAssignmentService } from "@/services/canvas/canvasAssignmentService";
// import { canvasAssignmentKeys } from "./canvas/canvasAssignmentHooks";
// import { LocalAssignment } from "@/models/local/assignment/localAssignment";
// import { LocalCoursePage } from "@/models/local/page/localCoursePage";
// import { LocalQuiz } from "@/models/local/quiz/localQuiz";
// import { canvasQuizService } from "@/services/canvas/canvasQuizService";
// import { canvasPageService } from "@/services/canvas/canvasPageService";
// import { canvasQuizKeys } from "./canvas/canvasQuizHooks";
// import { canvasPageKeys } from "./canvas/canvasPageHooks";
// // import { getLecturesQueryConfig } from "./localCourse/lectureHooks";
// // https://tanstack.com/query/latest/docs/framework/react/guides/ssr
// export const hydrateCourses = async (queryClient: QueryClient) => {
// const allSettings = await fileStorageService.settings.getAllCoursesSettings();
// await queryClient.prefetchQuery({
// queryKey: localCourseKeys.allCoursesSettings,
// queryFn: () => allSettings,
// });
// await Promise.all(
// allSettings.map(async (settings) => {
// await hydrateCourse(queryClient, settings);
// })
// );
// };
// export const hydrateCourse = async (
// queryClient: QueryClient,
// courseSettings: LocalCourseSettings
// ) => {
// const courseName = courseSettings.name;
// const moduleNames = await fileStorageService.modules.getModuleNames(
// courseName
// );
// // await Promise.all(
// // moduleNames.map(async (moduleName) => {
// // const assignments = await trpcHelpers.assignment.getAllAssignments.fetch({
// // courseName,
// // moduleName,
// // });
// // await Promise.all(
// // assignments.map(
// // async (a) =>
// // await trpcHelpers.assignment.getAssignment.fetch({
// // courseName,
// // moduleName,
// // assignmentName: a.name,
// // })
// // )
// // );
// // })
// // );
// const modulesData = await Promise.all(
// moduleNames.map((moduleName) => loadAllModuleData(courseName, moduleName))
// );
// // await queryClient.prefetchQuery(getLecturesQueryConfig(courseName));
// await queryClient.prefetchQuery({
// queryKey: localCourseKeys.settings(courseName),
// queryFn: () => courseSettings,
// });
// await queryClient.prefetchQuery({
// queryKey: localCourseKeys.moduleNames(courseName),
// queryFn: () => moduleNames,
// });
// await Promise.all(
// modulesData.map((d) => hydrateModuleData(d, courseName, queryClient))
// );
// };
// export const hydrateCanvasCourse = async (
// canvasCourseId: number,
// queryClient: QueryClient
// ) => {
// await Promise.all([
// queryClient.prefetchQuery({
// queryKey: canvasAssignmentKeys.assignments(canvasCourseId),
// queryFn: async () => await canvasAssignmentService.getAll(canvasCourseId),
// }),
// queryClient.prefetchQuery({
// queryKey: canvasQuizKeys.quizzes(canvasCourseId),
// queryFn: async () => await canvasQuizService.getAll(canvasCourseId),
// }),
// queryClient.prefetchQuery({
// queryKey: canvasPageKeys.pagesInCourse(canvasCourseId),
// queryFn: async () => await canvasPageService.getAll(canvasCourseId),
// }),
// ]);
// };
// const loadAllModuleData = async (courseName: string, moduleName: string) => {
// 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 assignmentsLoaded = assignments.filter((a) => a !== null);
// return {
// moduleName,
// // assignments: assignmentsLoaded,
// quizzes,
// pages,
// };
// };
// const hydrateModuleData = async (
// {
// moduleName,
// // assignments,
// quizzes,
// pages,
// }: {
// moduleName: string;
// // assignments: LocalAssignment[];
// quizzes: LocalQuiz[];
// pages: LocalCoursePage[];
// },
// courseName: string,
// queryClient: QueryClient
// ) => {
// // await queryClient.prefetchQuery({
// // queryKey: localCourseKeys.allItemsOfType(
// // courseName,
// // moduleName,
// // "Assignment"
// // ),
// // queryFn: () => assignments,
// // });
// await queryClient.prefetchQuery({
// queryKey: localCourseKeys.allItemsOfType(courseName, moduleName, "Quiz"),
// queryFn: () => quizzes,
// });
// await queryClient.prefetchQuery({
// queryKey: localCourseKeys.allItemsOfType(courseName, moduleName, "Page"),
// queryFn: () => pages,
// });
// // await Promise.all(
// // assignments.map(
// // async (assignment) =>
// // await queryClient.prefetchQuery({
// // queryKey: localCourseKeys.itemOfType(
// // courseName,
// // moduleName,
// // assignment.name,
// // "Assignment"
// // ),
// // queryFn: () => assignment,
// // })
// // )
// // );
// await Promise.all(
// quizzes.map(
// async (quiz) =>
// await queryClient.prefetchQuery({
// queryKey: localCourseKeys.itemOfType(
// courseName,
// moduleName,
// quiz.name,
// "Quiz"
// ),
// queryFn: () => quiz,
// })
// )
// );
// await Promise.all(
// pages.map(
// async (page) =>
// await queryClient.prefetchQuery({
// queryKey: localCourseKeys.itemOfType(
// courseName,
// moduleName,
// page.name,
// "Page"
// ),
// queryFn: () => page,
// })
// )
// );
// };

View File

@@ -14,13 +14,18 @@ export const useAssignmentQuery = (
});
};
export const useAssignmentsQuery = (moduleName: string) => {
export const useAssignmentNamesQuery = (moduleName: string) => {
const { courseName } = useCourseContext();
console.log("rendering all assignments query");
return trpc.assignment.getAllAssignments.useSuspenseQuery({
moduleName,
courseName,
});
return trpc.assignment.getAllAssignments.useSuspenseQuery(
{
moduleName,
courseName,
},
{
select: (assignments) => assignments.map((a) => a.name),
}
);
};
export const useUpdateAssignmentMutation = () => {
@@ -57,8 +62,18 @@ export const useCreateAssignmentMutation = () => {
export const useDeleteAssignmentMutation = () => {
const utils = trpc.useUtils();
return trpc.assignment.deleteAssignment.useMutation({
onSuccess: (_, { courseName, moduleName }) => {
onSuccess: (_, { courseName, moduleName, assignmentName }) => {
utils.assignment.getAllAssignments.invalidate({ courseName, moduleName });
utils.assignment.getAssignment.invalidate(
{
courseName,
moduleName,
assignmentName,
},
{
refetchType: "all",
}
);
},
});
};

View File

@@ -1,5 +1,11 @@
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
import { trpc } from "@/services/trpc/utils";
export const useLecturesSuspenseQuery = () => {
const { courseName } = useCourseContext();
return trpc.lectures.getLectures.useSuspenseQuery({ courseName });
};
export const useLectureUpdateMutation = () => {
const utils = trpc.useUtils();
return trpc.lectures.updateLecture.useMutation({

View File

@@ -41,7 +41,12 @@ export const useDeletePageMutation = () => {
const utils = trpc.useUtils();
return trpc.page.deletePage.useMutation({
onSuccess: (_, { courseName, moduleName, pageName }) => {
utils.page.getAllPages.invalidate({ courseName, moduleName });
utils.page.getAllPages.invalidate(
{ courseName, moduleName },
{
refetchType: "all",
}
);
utils.page.getPage.invalidate({ courseName, moduleName, pageName });
},
});

View File

@@ -41,7 +41,10 @@ export const useDeleteQuizMutation = () => {
const utils = trpc.useUtils();
return trpc.quiz.deleteQuiz.useMutation({
onSuccess: (_, { courseName, moduleName, quizName }) => {
utils.quiz.getAllQuizzes.invalidate({ courseName, moduleName });
utils.quiz.getAllQuizzes.invalidate(
{ courseName, moduleName },
{ refetchType: "all" }
);
utils.quiz.getQuiz.invalidate({ courseName, moduleName, quizName });
},
});

View File

@@ -1,11 +1,10 @@
import { LocalAssignment } from "./assignment/localAssignment";
import { Lecture } from "./lecture";
import { LocalCoursePage } from "./page/localCoursePage";
import { LocalQuiz } from "./quiz/localQuiz";
import {
dateToMarkdownString,
getDateFromString,
getDateFromStringOrThrow,
getDateOnlyMarkdownString,
} from "./timeUtils";
export const prepAssignmentForNewSemester = (
@@ -69,6 +68,24 @@ export const prepPageForNewSemester = (
page.dueAt,
};
};
export const prepLectureForNewSemester = (
lecture: Lecture,
oldSemesterStartDate: string,
newSemesterStartDate: string
): Lecture => {
const updatedText = replaceClassroomUrl(lecture.content);
const newDate = newDateOffset(
lecture.date,
oldSemesterStartDate,
newSemesterStartDate
);
const newDateOnly = newDate?.split(" ")[0];
return {
...lecture,
content: updatedText,
date: newDateOnly ?? lecture.date,
};
};
const replaceClassroomUrl = (value: string) => {
const classroomPattern =

View File

@@ -1,8 +1,14 @@
import { describe, it, expect } from "vitest";
import { LocalAssignment } from "../assignment/localAssignment";
import { prepAssignmentForNewSemester, prepPageForNewSemester, prepQuizForNewSemester } from "../semesterTransferUtils";
import {
prepAssignmentForNewSemester,
prepLectureForNewSemester,
prepPageForNewSemester,
prepQuizForNewSemester,
} from "../semesterTransferUtils";
import { LocalQuiz } from "../quiz/localQuiz";
import { LocalCoursePage } from "../page/localCoursePage";
import { Lecture } from "../lecture";
describe("can take an assignment and template it for a new semester", () => {
it("can sanitize assignment github classroom repo url", () => {
@@ -193,3 +199,24 @@ describe("can prep pages", () => {
expect(sanitizedPage.dueAt).toEqual("01/12/2024 23:59:00");
});
});
describe("can prep lecture", () => {
it("lecture gets new date, github url changes", () => {
const lecture: Lecture = {
name: "test title",
date: "08/30/2023",
content: "test text content",
};
const oldSemesterStartDate = "08/26/2023 23:59:00";
const newSemesterStartDate = "01/08/2024 23:59:00";
const sanitizedLecture = prepLectureForNewSemester(
lecture,
oldSemesterStartDate,
newSemesterStartDate
);
expect(sanitizedLecture.date).toEqual("01/12/2024");
});
});

View File

@@ -13,7 +13,6 @@ import {
getDayOfWeek,
LocalCourseSettings,
} from "@/models/local/localCourseSettings";
import { getWeekNumber } from "@/app/course/[courseName]/calendar/calendarMonthUtils";
import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
export async function getLectures(courseName: string) {

View File

@@ -3,6 +3,17 @@ import { z } from "zod";
import { router } from "../trpc";
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
import { zodLocalCourseSettings } from "@/models/local/localCourseSettings";
import { trpc } from "../utils";
import {
getLectures,
updateLecture,
} from "@/services/fileStorage/lectureFileStorageService";
import {
prepAssignmentForNewSemester,
prepLectureForNewSemester,
prepPageForNewSemester,
prepQuizForNewSemester,
} from "@/models/local/semesterTransferUtils";
export const settingsRouter = router({
allCoursesSettings: publicProcedure.query(async () => {
@@ -25,6 +36,117 @@ export const settingsRouter = router({
return s;
}),
createCourse: publicProcedure
.input(
z.object({
settings: zodLocalCourseSettings,
settingsFromCourseToImport: zodLocalCourseSettings.optional(),
})
)
.mutation(async ({ input: { settings, settingsFromCourseToImport } }) => {
await fileStorageService.settings.updateCourseSettings(
settings.name,
settings
);
if (settingsFromCourseToImport) {
const oldCourseName = settingsFromCourseToImport.name;
const newCourseName = settings.name;
const oldModules = await fileStorageService.modules.getModuleNames(
oldCourseName
);
console.log(
"old course name",
oldCourseName,
"new course name",
newCourseName
);
console.log(
"old start date",
settingsFromCourseToImport.startDate,
"new start date",
settings.startDate
);
await Promise.all(
oldModules.map(async (moduleName) => {
await fileStorageService.modules.createModule(
newCourseName,
moduleName
);
const [oldAssignments, oldQuizzes, oldPages, oldLecturesByWeek] =
await Promise.all([
fileStorageService.assignments.getAssignments(
oldCourseName,
moduleName
),
await fileStorageService.quizzes.getQuizzes(
oldCourseName,
moduleName
),
await fileStorageService.pages.getPages(
oldCourseName,
moduleName
),
await getLectures(oldCourseName),
]);
await Promise.all([
...oldAssignments.map(async (oldAssignment) => {
const newAssignment = prepAssignmentForNewSemester(
oldAssignment,
settingsFromCourseToImport.startDate,
settings.startDate,
);
await fileStorageService.assignments.updateOrCreateAssignment({
courseName: newCourseName,
moduleName,
assignmentName: newAssignment.name,
assignment: newAssignment,
});
}),
...oldQuizzes.map(async (oldQuiz) => {
const newQuiz = prepQuizForNewSemester(
oldQuiz,
settingsFromCourseToImport.startDate,
settings.startDate,
);
await fileStorageService.quizzes.updateQuiz({
courseName: newCourseName,
moduleName,
quizName: newQuiz.name,
quiz: newQuiz,
});
}),
...oldPages.map(async (oldPage) => {
const newPage = prepPageForNewSemester(
oldPage,
settingsFromCourseToImport.startDate,
settings.startDate,
);
await fileStorageService.pages.updatePage({
courseName: newCourseName,
moduleName,
pageName: newPage.name,
page: newPage,
});
}),
...oldLecturesByWeek.flatMap(async (oldLectureByWeek) =>
oldLectureByWeek.lectures.map(async (oldLecture) => {
const newLecture = prepLectureForNewSemester(
oldLecture,
settingsFromCourseToImport.startDate,
settings.startDate,
);
await updateLecture(newCourseName, settings, newLecture);
})
),
]);
})
);
}
}),
updateSettings: publicProcedure
.input(
z.object({
settings: zodLocalCourseSettings,
@@ -36,17 +158,4 @@ export const settingsRouter = router({
settings
);
}),
updateSettings: publicProcedure
.input(
z.object({
settings: zodLocalCourseSettings,
})
)
.mutation(async ({ input: { settings } }) => {
await fileStorageService.settings.updateCourseSettings(
settings.name,
settings
);
}),
});