diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index e7329cc..2f5abe2 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -15,6 +15,7 @@ services:
- NEXT_PUBLIC_ENABLE_FILE_SYNC=true
- REDIS_URL=redis://redis:6379
volumes:
+ # - ./globalSettings.dev.yml:/app/globalSettings.yml
- ./globalSettings.yml:/app/globalSettings.yml
- .:/app
- ~/projects/faculty:/app/storage
diff --git a/docker-compose.yml b/docker-compose.yml
index dc7d54f..b5cff1a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -14,30 +14,8 @@ services:
- REDIS_URL=redis://redis:6379
# - FILE_POLLING=true
volumes:
- # - ~/projects/faculty/3840_Telemetry/2024Spring_alex/modules:/app/storage/spring_2024_telemetry
-
- # - ~/projects/faculty/1400/2025_spring_alex/modules:/app/storage/1400
- # - ~/projects/faculty/1405/2025_spring_alex:/app/storage/1405
- # - ~/projects/faculty/3840_Telemetry/2025_spring_alex/modules:/app/storage/telemetry
- # - ~/projects/faculty/4620_Distributed/2025Spring/modules:/app/storage/distributed
- # - ~/projects/faculty/4620_Distributed/2024Spring/modules:/app/storage/distributed_old
-
-
- - ~/projects/faculty/1810/2025-spring-alex/in-person:/app/storage/intro_to_web_old
- - ~/projects/faculty/1810/2025-fall-alex/modules:/app/storage/intro_to_web
-
- - ~/projects/faculty/4850_AdvancedFE/2025-fall-alex/modules:/app/storage/advanced_frontend
- - ~/projects/faculty/4850_AdvancedFE/2024-fall-alex/modules:/app/storage/advanced_frontend_old
-
- - ~/projects/faculty/1430/2024-fall-alex/modules:/app/storage/ux_old
- - ~/projects/faculty/1430/2025-fall-alex/modules:/app/storage/ux
-
- - ~/projects/faculty/1420/2024-fall/Modules:/app/storage/1420_old
- - ~/projects/faculty/1420/2025-fall-alex/modules:/app/storage/1420
-
- - ~/projects/faculty/1425/2024-fall/Modules:/app/storage/1425_old
- - ~/projects/faculty/1425/2025-fall-alex/modules:/app/storage/1425
-
+ - ./globalSettings.yml:/app/globalSettings.yml
+ - ~/projects/faculty:/app/storage
- ~/projects/facultyFiles:/app/public/images/facultyFiles
diff --git a/globalSettings.dev.yml b/globalSettings.dev.yml
new file mode 100644
index 0000000..e144a29
--- /dev/null
+++ b/globalSettings.dev.yml
@@ -0,0 +1,5 @@
+courses:
+ - path: ./3820_BackEnd/2025-fall/Modules/
+ name: Back-End
+ - path: ./3820_BackEnd/2024-fall/Modules/
+ name: Back-End
diff --git a/requests/module.http b/requests/module.http
new file mode 100644
index 0000000..f172ffd
--- /dev/null
+++ b/requests/module.http
@@ -0,0 +1,12 @@
+
+### doesn't work, there is a form request that does thi son the site, but this isn't it
+POST https://snow.instructure.com/api/v1/courses/1154759/modules/3965250/reorder
+Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
+Content-Type: application/json
+
+{
+ "order": [
+ 29273428, 29274455, 29272498, 29274867, 29272743, 29273425
+ ]
+}
+
diff --git a/src/app/CourseList.tsx b/src/app/CourseList.tsx
index e4c4963..2ded6fe 100644
--- a/src/app/CourseList.tsx
+++ b/src/app/CourseList.tsx
@@ -15,6 +15,8 @@ export default function CourseList() {
const sortedDates = Object.keys(coursesByStartDate).sort();
+ console.log(allSettings, coursesByStartDate);
+
return (
{sortedDates.map((startDate) => (
@@ -29,10 +31,10 @@ export default function CourseList() {
href={getCourseUrl(settings.name)}
shallow={true}
className="
- font-bold text-xl block
- transition-all hover:scale-105 hover:underline hover:text-slate-200
- mb-3
- "
+ font-bold text-xl block
+ transition-all hover:scale-105 hover:underline hover:text-slate-200
+ mb-3
+ "
>
{settings.name}
diff --git a/src/app/course/[courseName]/modules/ExpandableModule.tsx b/src/app/course/[courseName]/modules/ExpandableModule.tsx
index af90c56..2ad91b1 100644
--- a/src/app/course/[courseName]/modules/ExpandableModule.tsx
+++ b/src/app/course/[courseName]/modules/ExpandableModule.tsx
@@ -25,6 +25,9 @@ import { useQuizzesQueries } from "@/features/local/quizzes/quizHooks";
import { useTRPC } from "@/services/serverFunctions/trpcClient";
import { useSuspenseQueries } from "@tanstack/react-query";
import { useAssignmentNamesQuery } from "@/features/local/assignments/assignmentHooks";
+import { useReorderCanvasModuleItemsMutation } from "@/features/canvas/hooks/canvasModuleHooks";
+import { useCanvasModulesQuery } from "@/features/canvas/hooks/canvasModuleHooks";
+import { Spinner } from "@/components/Spinner";
export default function ExpandableModule({
moduleName,
@@ -50,6 +53,8 @@ export default function ExpandableModule({
const { data: quizzes } = useQuizzesQueries(moduleName);
const { data: pages } = usePagesQueries(moduleName);
const modal = useModal();
+ const reorderMutation = useReorderCanvasModuleItemsMutation();
+ const { data: canvasModules } = useCanvasModulesQuery();
const moduleItems: {
type: "assignment" | "quiz" | "page";
@@ -110,6 +115,30 @@ export default function ExpandableModule({
)}
>
<>
+ {!reorderMutation.isPending && (
+
+ )}
+ {reorderMutation.isPending && }
["canvas", canvasId, "module list"] as const,
@@ -28,3 +30,54 @@ export const useAddCanvasModuleMutation = () => {
},
});
};
+
+export const useReorderCanvasModuleItemsMutation = () => {
+ const { data: settings } = useLocalCourseSettingsQuery();
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: async ({
+ moduleId,
+ items,
+ }: {
+ moduleId: number;
+ items: IModuleItem[];
+ }) => {
+ if (!settings?.canvasId) throw new Error("No canvasId in settings");
+
+ const canvasModule = await canvasModuleService.getModuleWithItems(
+ settings.canvasId,
+ moduleId
+ );
+ if (!canvasModule.items) {
+ throw new Error(
+ "cannot sort canvas module items, no items found in module"
+ );
+ }
+ const canvasItems = canvasModule.items;
+
+ // Sort IModuleItems by dueAt
+ const sorted = [...items].sort((a, b) => {
+ const aDate = a.dueAt ? new Date(a.dueAt).getTime() : 0;
+ const bDate = b.dueAt ? new Date(b.dueAt).getTime() : 0;
+ return aDate - bDate;
+ });
+
+ // Map sorted IModuleItems to CanvasModuleItem ids by matching name/title
+ const orderedIds = sorted
+ .map((localItem) => canvasItems.find((canvasItem) => canvasItem.title === localItem.name)?.id)
+ .filter((id): id is number => typeof id === "number");
+
+ return await canvasModuleService.reorderModuleItems(
+ settings.canvasId,
+ moduleId,
+ orderedIds
+ );
+ },
+ onSuccess: (_data) => {
+ if (!settings?.canvasId) return;
+ queryClient.invalidateQueries({
+ queryKey: canvasCourseModuleKeys.modules(settings.canvasId),
+ });
+ },
+ });
+};
diff --git a/src/features/canvas/services/canvasModuleService.ts b/src/features/canvas/services/canvasModuleService.ts
index e157324..62c9e69 100644
--- a/src/features/canvas/services/canvasModuleService.ts
+++ b/src/features/canvas/services/canvasModuleService.ts
@@ -20,6 +20,13 @@ export const canvasModuleService = {
if (!data) throw new Error("Something went wrong updating module item");
},
+ async getModuleWithItems(canvasCourseId: number, moduleId: number) {
+ const url = `${canvasApi}/courses/${canvasCourseId}/modules/${moduleId}`;
+ const params = { include: ["items"] };
+ const response = await axiosClient.get(url, { params });
+ return response.data;
+ },
+
async createModuleItem(
canvasCourseId: number,
canvasModuleId: number,
@@ -63,4 +70,21 @@ export const canvasModuleService = {
const response = await axiosClient.post(url, body);
return response.data.id;
},
+
+ async reorderModuleItems(
+ canvasCourseId: number,
+ canvasModuleId: number,
+ itemIds: number[]
+ ) {
+ for (let i = 0; i < itemIds.length; i++) {
+ const itemId = itemIds[i];
+ const url = `${canvasApi}/courses/${canvasCourseId}/modules/${canvasModuleId}/items/${itemId}`;
+ const body = {
+ module_item: {
+ position: i + 1,
+ },
+ };
+ await axiosClient.put(url, body);
+ }
+ },
};
diff --git a/src/features/local/course/settingsFileStorageService.ts b/src/features/local/course/settingsFileStorageService.ts
index 3cba9e9..7520b45 100644
--- a/src/features/local/course/settingsFileStorageService.ts
+++ b/src/features/local/course/settingsFileStorageService.ts
@@ -15,7 +15,7 @@ import { GlobalSettingsCourse } from "../globalSettings/globalSettingsModels";
const getCourseSettings = async (
course: GlobalSettingsCourse
): Promise => {
- const courseDirectory = await getCoursePathByName(course.name);
+ const courseDirectory = path.join(basePath, course.path);
const settingsPath = path.join(courseDirectory, "settings.yml");
if (!(await directoryOrFileExists(settingsPath))) {
const errorMessage = `could not find settings for ${course.name}, settings file ${settingsPath}`;