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. # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.pnpm-store/
# dependencies # dependencies
/node_modules /node_modules
/.pnp /.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/1810/2024-fall-alex/modules:/app/storage/intro_to_web \
-v ~/projects/faculty/4850_AdvancedFE/2024-fall-alex/modules:/app/storage/advanced_frontend \ -v ~/projects/faculty/4850_AdvancedFE/2024-fall-alex/modules:/app/storage/advanced_frontend \
node \ 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,12 +17,13 @@ const getUrl = (params: { rest: string[] }, req: NextRequest) => {
appendQueryParams(url, req); appendQueryParams(url, req);
return url; return url;``
}; };
const proxyResponseHeaders = (response: any) => { const proxyResponseHeaders = (response: any) => {
const headers = new Headers(); const headers = new Headers();
Object.entries(response.headers).forEach(([key, value]) => { Object.entries(response.headers).forEach(([key, value]) => {
if (["link", "x-rate-limit-remaining"].includes(key))
headers.set(key, value as string); headers.set(key, value as string);
}); });
return headers; return headers;
@@ -32,21 +33,19 @@ export async function GET(
req: NextRequest, req: NextRequest,
{ params }: { params: Promise<{ rest: string[] }> } { params }: { params: Promise<{ rest: string[] }> }
) { ) {
return withErrorHandling(async () => {
try { try {
const url = getUrl(await params, req); const url = getUrl(await params, req);
const response = await axiosClient.get(url.toString()); const response = await axiosClient.get(url.toString());
const headers = proxyResponseHeaders(response); const headers = proxyResponseHeaders(response);
return new NextResponse(JSON.stringify(response.data), { headers }); return NextResponse.json(response.data, { headers });
} catch (error: any) { } catch (error: any) {
return new NextResponse( console.log("canvas get error", error, error?.message);
return NextResponse.json(
JSON.stringify({ error: error.message || "Canvas GET request failed" }), JSON.stringify({ error: error.message || "Canvas GET request failed" }),
{ status: error.response?.status || 500 } { status: error.response?.status || 500 }
); );
} }
});
} }
export async function POST( export async function POST(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,8 +21,9 @@ 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 { useAssignmentsQuery } from "@/hooks/localCourse/assignmentHooks";
import { useQuizzesQueries } from "@/hooks/localCourse/quizHooks"; import { useQuizzesQueries } from "@/hooks/localCourse/quizHooks";
import { useAssignmentNamesQuery } from "@/hooks/localCourse/assignmentHooks";
import { trpc } from "@/services/trpc/utils";
export default function ExpandableModule({ export default function ExpandableModule({
moduleName, moduleName,
@@ -30,8 +31,14 @@ export default function ExpandableModule({
moduleName: string; moduleName: string;
}) { }) {
const { itemDropOnModule } = useDraggingContext(); const { itemDropOnModule } = useDraggingContext();
const { courseName } = useCourseContext();
const [assignmentNames] = useAssignmentNamesQuery(moduleName);
const [assignments ] = useAssignmentsQuery(moduleName); const [assignments] = trpc.useSuspenseQueries((t) =>
assignmentNames.map((assignmentName) =>
t.assignment.getAssignment({ courseName, moduleName, assignmentName })
)
);
const [quizzes] = useQuizzesQueries(moduleName); const [quizzes] = useQuizzesQueries(moduleName);
const [pages] = usePagesQueries(moduleName); const [pages] = usePagesQueries(moduleName);
const modal = useModal(); const modal = useModal();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,10 @@ import { useEmptyDirectoriesQuery } from "@/hooks/localCourse/storageDirectoryHo
import { CanvasCourseModel } from "@/models/canvas/courses/canvasCourseModel"; import { CanvasCourseModel } from "@/models/canvas/courses/canvasCourseModel";
import { CanvasEnrollmentTermModel } from "@/models/canvas/enrollmentTerms/canvasEnrollmentTermModel"; import { CanvasEnrollmentTermModel } from "@/models/canvas/enrollmentTerms/canvasEnrollmentTermModel";
import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType"; 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 { getCourseUrl } from "@/services/urlUtils";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
@@ -47,6 +50,9 @@ export default function NewCourseForm() {
const [selectedDirectory, setSelectedDirectory] = useState< const [selectedDirectory, setSelectedDirectory] = useState<
string | undefined string | undefined
>(); >();
const [courseToImport, setCourseToImport] = useState<
LocalCourseSettings | undefined
>();
const createCourse = useCreateLocalCourseMutation(); const createCourse = useCreateLocalCourseMutation();
const formIsComplete = const formIsComplete =
@@ -71,17 +77,34 @@ export default function NewCourseForm() {
setSelectedDirectory={setSelectedDirectory} setSelectedDirectory={setSelectedDirectory}
selectedDaysOfWeek={selectedDaysOfWeek} selectedDaysOfWeek={selectedDaysOfWeek}
setSelectedDaysOfWeek={setSelectedDaysOfWeek} setSelectedDaysOfWeek={setSelectedDaysOfWeek}
courseToImport={courseToImport}
setCourseToImport={setCourseToImport}
/> />
)} )}
</SuspenseAndErrorHandling> </SuspenseAndErrorHandling>
<div className="m-3 text-center"> <div className="m-3 text-center">
<button <button
disabled={!formIsComplete || createCourse.isPending} disabled={!formIsComplete || createCourse.isPending}
onClick={() => { onClick={async () => {
if (formIsComplete) { if (formIsComplete) {
createCourse const newSettings: LocalCourseSettings = courseToImport
.mutateAsync({ ? {
settings: { ...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, name: selectedDirectory,
assignmentGroups: [], assignmentGroups: [],
daysOfWeek: selectedDaysOfWeek, daysOfWeek: selectedDaysOfWeek,
@@ -96,11 +119,12 @@ export default function NewCourseForm() {
defaultFileUploadTypes: ["pdf", "png", "jpg", "jpeg"], defaultFileUploadTypes: ["pdf", "png", "jpg", "jpeg"],
defaultLockHoursOffset: 0, defaultLockHoursOffset: 0,
holidays: [], holidays: [],
}, };
}) await createCourse.mutateAsync({
.then(() => { settings: newSettings,
router.push(getCourseUrl(selectedDirectory)); settingsFromCourseToImport: courseToImport,
}); });
router.push(getCourseUrl(selectedDirectory));
} }
}} }}
> >
@@ -125,6 +149,8 @@ function OtherSettings({
setSelectedDirectory, setSelectedDirectory,
selectedDaysOfWeek, selectedDaysOfWeek,
setSelectedDaysOfWeek, setSelectedDaysOfWeek,
courseToImport,
setCourseToImport,
}: { }: {
selectedTerm: CanvasEnrollmentTermModel; selectedTerm: CanvasEnrollmentTermModel;
selectedCanvasCourse: CanvasCourseModel | undefined; selectedCanvasCourse: CanvasCourseModel | undefined;
@@ -137,15 +163,21 @@ function OtherSettings({
>; >;
selectedDaysOfWeek: DayOfWeek[]; selectedDaysOfWeek: DayOfWeek[];
setSelectedDaysOfWeek: React.Dispatch<React.SetStateAction<DayOfWeek[]>>; setSelectedDaysOfWeek: React.Dispatch<React.SetStateAction<DayOfWeek[]>>;
courseToImport: LocalCourseSettings | undefined;
setCourseToImport: React.Dispatch<
React.SetStateAction<LocalCourseSettings | undefined>
>;
}) { }) {
const { data: canvasCourses } = useCourseListInTermQuery(selectedTerm.id); const { data: canvasCourses } = useCourseListInTermQuery(selectedTerm.id);
const [allSettings] = useLocalCoursesSettingsQuery(); const [allSettings] = useLocalCoursesSettingsQuery();
const [emptyDirectories] = useEmptyDirectoriesQuery(); const [emptyDirectories] = useEmptyDirectoriesQuery();
const populatedCanvasCourseIds = allSettings.map((s) => s.canvasId); const populatedCanvasCourseIds = allSettings.map((s) => s.canvasId);
const availableCourses = canvasCourses.filter( const availableCourses =
(canvas) => !populatedCanvasCourseIds.includes(canvas.id) canvasCourses?.filter(
); (canvas: CanvasCourseModel) =>
!populatedCanvasCourseIds.includes(canvas.id)
) ?? [];
return ( return (
<> <>
@@ -183,6 +215,13 @@ function OtherSettings({
}} }}
/> />
</div> </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 Link from "next/link";
import { useCourseContext } from "../course/[courseName]/context/courseContext"; import { useCourseContext } from "../course/[courseName]/context/courseContext";
import { getLectureForDay } from "@/models/local/lectureUtils"; import { getLectureForDay } from "@/models/local/lectureUtils";
import { trpc } from "@/services/trpc/utils"; import { useLecturesSuspenseQuery as useLecturesQuery } from "@/hooks/localCourse/lectureHooks";
export default function OneCourseLectures() { export default function OneCourseLectures() {
const { courseName } = useCourseContext(); const { courseName } = useCourseContext();
// const { data: weeks } = useLecturesByWeekQuery(); const [weeks] = useLecturesQuery();
const [weeks] = trpc.lectures.getLectures.useSuspenseQuery({ courseName });
const dayAsDate = new Date(); const dayAsDate = new Date();
const dayAsString = getDateOnlyMarkdownString(dayAsDate); const dayAsString = getDateOnlyMarkdownString(dayAsDate);

View File

@@ -1,11 +1,5 @@
import { canvasAssignmentService } from "@/services/canvas/canvasAssignmentService"; import { canvasAssignmentService } from "@/services/canvas/canvasAssignmentService";
import { canvasService } from "@/services/canvas/canvasService"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
useMutation,
useQueryClient,
useSuspenseQueries,
useSuspenseQuery,
} from "@tanstack/react-query";
import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks"; import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
import { LocalAssignment } from "@/models/local/assignment/localAssignment"; import { LocalAssignment } from "@/models/local/assignment/localAssignment";
import { canvasModuleService } from "@/services/canvas/canvasModuleService"; import { canvasModuleService } from "@/services/canvas/canvasModuleService";
@@ -24,28 +18,12 @@ export const canvasAssignmentKeys = {
export const useCanvasAssignmentsQuery = () => { export const useCanvasAssignmentsQuery = () => {
const [settings] = useLocalCourseSettingsQuery(); const [settings] = useLocalCourseSettingsQuery();
return useSuspenseQuery({ return useQuery({
queryKey: canvasAssignmentKeys.assignments(settings.canvasId), queryKey: canvasAssignmentKeys.assignments(settings.canvasId),
queryFn: async () => canvasAssignmentService.getAll(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 = () => { export const useAddAssignmentToCanvasMutation = () => {
const [settings] = useLocalCourseSettingsQuery(); const [settings] = useLocalCourseSettingsQuery();
const { data: canvasModules } = useCanvasModulesQuery(); const { data: canvasModules } = useCanvasModulesQuery();
@@ -60,6 +38,11 @@ export const useAddAssignmentToCanvasMutation = () => {
assignment: LocalAssignment; assignment: LocalAssignment;
moduleName: string; moduleName: string;
}) => { }) => {
if (!canvasModules) {
console.log("cannot add assignment until modules loaded");
return;
}
const assignmentGroup = settings.assignmentGroups.find( const assignmentGroup = settings.assignmentGroups.find(
(g) => g.name === assignment.localAssignmentGroupName (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 { LocalAssignmentGroup } from "@/models/local/assignment/localAssignmentGroup";
import { canvasAssignmentGroupService } from "@/services/canvas/canvasAssignmentGroupService"; import { canvasAssignmentGroupService } from "@/services/canvas/canvasAssignmentGroupService";
import { canvasService } from "@/services/canvas/canvasService"; import { canvasService } from "@/services/canvas/canvasService";
import { useMutation, useSuspenseQuery } from "@tanstack/react-query"; import { useMutation, useQuery } from "@tanstack/react-query";
export const canvasCourseKeys = { export const canvasCourseKeys = {
courseDetails: (canavasId: number) => courseDetails: (canavasId: number) =>
@@ -13,34 +15,37 @@ export const canvasCourseKeys = {
}; };
export const useCourseListInTermQuery = (canvasTermId: number | undefined) => export const useCourseListInTermQuery = (canvasTermId: number | undefined) =>
useSuspenseQuery({ useQuery({
queryKey: canvasCourseKeys.courseListInTerm(canvasTermId), queryKey: canvasCourseKeys.courseListInTerm(canvasTermId),
queryFn: async () => queryFn: async (): Promise<CanvasCourseModel[]> =>
canvasTermId ? await canvasService.getCourses(canvasTermId) : [], canvasTermId ? await canvasService.getCourses(canvasTermId) : [],
enabled: !!canvasTermId,
}); });
export const useCanvasCourseQuery = (canvasId: number) => // export const useCanvasCourseQuery = (canvasId: number) =>
useSuspenseQuery({ // useQuery({
queryKey: canvasCourseKeys.courseDetails(canvasId), // queryKey: canvasCourseKeys.courseDetails(canvasId),
queryFn: async () => await canvasService.getCourse(canvasId), // queryFn: async () => await canvasService.getCourse(canvasId),
}); // });
export const useSetAssignmentGroupsMutation = (canvasId: number) => { export const useSetAssignmentGroupsMutation = (canvasId: number) => {
const { data: canvasAssignmentGroups } = useAssignmentGroupsQuery(canvasId); const { data: canvasAssignmentGroups } = useAssignmentGroupsQuery(canvasId);
return useMutation({ return useMutation({
mutationFn: async (localAssignmentGroups: LocalAssignmentGroup[]) => { mutationFn: async (localAssignmentGroups: LocalAssignmentGroup[]) => {
if (!canvasAssignmentGroups) return;
const localNames = localAssignmentGroups.map((g) => g.name); const localNames = localAssignmentGroups.map((g) => g.name);
const groupsToDelete = canvasAssignmentGroups.filter( const groupsToDelete = canvasAssignmentGroups.filter(
(c) => !localNames.includes(c.name) (c: CanvasAssignmentGroup) => !localNames.includes(c.name)
); );
await Promise.all([ await Promise.all([
...groupsToDelete.map( ...groupsToDelete.map(
async (g) => async (g: CanvasAssignmentGroup) =>
await canvasAssignmentGroupService.delete(canvasId, g.id, g.name) await canvasAssignmentGroupService.delete(canvasId, g.id, g.name)
), ),
...localAssignmentGroups.map(async (group) => { ...localAssignmentGroups.map(async (group) => {
const canvasGroup = canvasAssignmentGroups.find( const canvasGroup = canvasAssignmentGroups.find(
(c) => c.name === group.name (c: CanvasAssignmentGroup) => c.name === group.name
); );
if (!canvasGroup) { if (!canvasGroup) {
await canvasAssignmentGroupService.create(canvasId, group); await canvasAssignmentGroupService.create(canvasId, group);
@@ -55,8 +60,8 @@ export const useSetAssignmentGroupsMutation = (canvasId: number) => {
}; };
export const useAssignmentGroupsQuery = (canvasId: number) => { export const useAssignmentGroupsQuery = (canvasId: number) => {
return useSuspenseQuery({ return useQuery({
queryKey: canvasCourseKeys.assignmentGroups(canvasId), 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 { canvasModuleService } from "@/services/canvas/canvasModuleService";
import { import {
useMutation, useMutation,
useQuery,
useQueryClient, useQueryClient,
useSuspenseQuery,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks"; import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
@@ -12,7 +12,7 @@ export const canvasCourseModuleKeys = {
export const useCanvasModulesQuery = () => { export const useCanvasModulesQuery = () => {
const [settings] = useLocalCourseSettingsQuery(); const [settings] = useLocalCourseSettingsQuery();
return useSuspenseQuery({ return useQuery({
queryKey: canvasCourseModuleKeys.modules(settings.canvasId), queryKey: canvasCourseModuleKeys.modules(settings.canvasId),
queryFn: async () => queryFn: async () =>
await canvasModuleService.getCourseModules(settings.canvasId), await canvasModuleService.getCourseModules(settings.canvasId),

View File

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

View File

@@ -1,7 +1,7 @@
import { import {
useMutation, useMutation,
useQuery,
useQueryClient, useQueryClient,
useSuspenseQuery,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks"; import { useLocalCourseSettingsQuery } from "../localCourse/localCoursesHooks";
import { canvasQuizService } from "@/services/canvas/canvasQuizService"; import { canvasQuizService } from "@/services/canvas/canvasQuizService";
@@ -20,7 +20,7 @@ export const canvasQuizKeys = {
export const useCanvasQuizzesQuery = () => { export const useCanvasQuizzesQuery = () => {
const [settings] = useLocalCourseSettingsQuery(); const [settings] = useLocalCourseSettingsQuery();
return useSuspenseQuery({ return useQuery({
queryKey: canvasQuizKeys.quizzes(settings.canvasId), queryKey: canvasQuizKeys.quizzes(settings.canvasId),
queryFn: async () => canvasQuizService.getAll(settings.canvasId), queryFn: async () => canvasQuizService.getAll(settings.canvasId),
}); });
@@ -40,6 +40,10 @@ export const useAddQuizToCanvasMutation = () => {
quiz: LocalQuiz; quiz: LocalQuiz;
moduleName: string; moduleName: string;
}) => { }) => {
if (!canvasModules) {
console.log("cannot add quiz until modules loaded");
return;
}
const assignmentGroup = settings.assignmentGroups.find( const assignmentGroup = settings.assignmentGroups.find(
(g) => g.name === quiz.localAssignmentGroupName (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(); const { courseName } = useCourseContext();
console.log("rendering all assignments query"); console.log("rendering all assignments query");
return trpc.assignment.getAllAssignments.useSuspenseQuery({ return trpc.assignment.getAllAssignments.useSuspenseQuery(
{
moduleName, moduleName,
courseName, courseName,
}); },
{
select: (assignments) => assignments.map((a) => a.name),
}
);
}; };
export const useUpdateAssignmentMutation = () => { export const useUpdateAssignmentMutation = () => {
@@ -57,8 +62,18 @@ export const useCreateAssignmentMutation = () => {
export const useDeleteAssignmentMutation = () => { export const useDeleteAssignmentMutation = () => {
const utils = trpc.useUtils(); const utils = trpc.useUtils();
return trpc.assignment.deleteAssignment.useMutation({ return trpc.assignment.deleteAssignment.useMutation({
onSuccess: (_, { courseName, moduleName }) => { onSuccess: (_, { courseName, moduleName, assignmentName }) => {
utils.assignment.getAllAssignments.invalidate({ courseName, moduleName }); 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"; import { trpc } from "@/services/trpc/utils";
export const useLecturesSuspenseQuery = () => {
const { courseName } = useCourseContext();
return trpc.lectures.getLectures.useSuspenseQuery({ courseName });
};
export const useLectureUpdateMutation = () => { export const useLectureUpdateMutation = () => {
const utils = trpc.useUtils(); const utils = trpc.useUtils();
return trpc.lectures.updateLecture.useMutation({ return trpc.lectures.updateLecture.useMutation({

View File

@@ -41,7 +41,12 @@ export const useDeletePageMutation = () => {
const utils = trpc.useUtils(); const utils = trpc.useUtils();
return trpc.page.deletePage.useMutation({ return trpc.page.deletePage.useMutation({
onSuccess: (_, { courseName, moduleName, pageName }) => { 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 }); utils.page.getPage.invalidate({ courseName, moduleName, pageName });
}, },
}); });

View File

@@ -41,7 +41,10 @@ export const useDeleteQuizMutation = () => {
const utils = trpc.useUtils(); const utils = trpc.useUtils();
return trpc.quiz.deleteQuiz.useMutation({ return trpc.quiz.deleteQuiz.useMutation({
onSuccess: (_, { courseName, moduleName, quizName }) => { 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 }); utils.quiz.getQuiz.invalidate({ courseName, moduleName, quizName });
}, },
}); });

View File

@@ -1,11 +1,10 @@
import { LocalAssignment } from "./assignment/localAssignment"; import { LocalAssignment } from "./assignment/localAssignment";
import { Lecture } from "./lecture";
import { LocalCoursePage } from "./page/localCoursePage"; import { LocalCoursePage } from "./page/localCoursePage";
import { LocalQuiz } from "./quiz/localQuiz"; import { LocalQuiz } from "./quiz/localQuiz";
import { import {
dateToMarkdownString, dateToMarkdownString,
getDateFromString,
getDateFromStringOrThrow, getDateFromStringOrThrow,
getDateOnlyMarkdownString,
} from "./timeUtils"; } from "./timeUtils";
export const prepAssignmentForNewSemester = ( export const prepAssignmentForNewSemester = (
@@ -69,6 +68,24 @@ export const prepPageForNewSemester = (
page.dueAt, 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 replaceClassroomUrl = (value: string) => {
const classroomPattern = const classroomPattern =

View File

@@ -1,8 +1,14 @@
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import { LocalAssignment } from "../assignment/localAssignment"; import { LocalAssignment } from "../assignment/localAssignment";
import { prepAssignmentForNewSemester, prepPageForNewSemester, prepQuizForNewSemester } from "../semesterTransferUtils"; import {
prepAssignmentForNewSemester,
prepLectureForNewSemester,
prepPageForNewSemester,
prepQuizForNewSemester,
} from "../semesterTransferUtils";
import { LocalQuiz } from "../quiz/localQuiz"; import { LocalQuiz } from "../quiz/localQuiz";
import { LocalCoursePage } from "../page/localCoursePage"; import { LocalCoursePage } from "../page/localCoursePage";
import { Lecture } from "../lecture";
describe("can take an assignment and template it for a new semester", () => { describe("can take an assignment and template it for a new semester", () => {
it("can sanitize assignment github classroom repo url", () => { 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"); 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, getDayOfWeek,
LocalCourseSettings, LocalCourseSettings,
} from "@/models/local/localCourseSettings"; } from "@/models/local/localCourseSettings";
import { getWeekNumber } from "@/app/course/[courseName]/calendar/calendarMonthUtils";
import { getDateFromStringOrThrow } from "@/models/local/timeUtils"; import { getDateFromStringOrThrow } from "@/models/local/timeUtils";
export async function getLectures(courseName: string) { export async function getLectures(courseName: string) {

View File

@@ -3,6 +3,17 @@ 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 { zodLocalCourseSettings } from "@/models/local/localCourseSettings"; 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({ export const settingsRouter = router({
allCoursesSettings: publicProcedure.query(async () => { allCoursesSettings: publicProcedure.query(async () => {
@@ -28,13 +39,112 @@ export const settingsRouter = router({
.input( .input(
z.object({ z.object({
settings: zodLocalCourseSettings, settings: zodLocalCourseSettings,
settingsFromCourseToImport: zodLocalCourseSettings.optional(),
}) })
) )
.mutation(async ({ input: { settings } }) => { .mutation(async ({ input: { settings, settingsFromCourseToImport } }) => {
await fileStorageService.settings.updateCourseSettings( await fileStorageService.settings.updateCourseSettings(
settings.name, settings.name,
settings 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 updateSettings: publicProcedure
.input( .input(
@@ -48,5 +158,4 @@ export const settingsRouter = router({
settings settings
); );
}), }),
}); });

View File

@@ -5,7 +5,7 @@ Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
### ###
GET https://snow.instructure.com/api/v1/courses/871954/assignments GET https://snow.instructure.com/api/v1/courses/1013058/assignments
Authorization: Bearer {{$dotenv CANVAS_TOKEN}} Authorization: Bearer {{$dotenv CANVAS_TOKEN}}

View File

@@ -1,5 +1,5 @@
GET https://snow.instructure.com/api/v1/courses/958185/quizzes GET https://snow.instructure.com/api/v1/courses/1013058/quizzes
Authorization: Bearer {{$dotenv CANVAS_TOKEN}} Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
### ###