From 890e08d1b2cda6f1c39027474489095a1dc21eb4 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Tue, 11 Nov 2025 14:19:03 -0700 Subject: [PATCH] pointer --- globalSettings.yml | 8 ------ .../AddCourseToGlobalSettingsForm.tsx | 25 +++++++++++++------ .../[courseName]/calendar/CalendarMonth.tsx | 2 +- src/features/local/utils/directoriesRouter.ts | 9 +++++++ .../local/utils/fileStorageService.ts | 11 ++++++++ .../local/utils/storageDirectoryHooks.ts | 7 ++++++ 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/globalSettings.yml b/globalSettings.yml index 68350f8..7804350 100644 --- a/globalSettings.yml +++ b/globalSettings.yml @@ -9,23 +9,15 @@ courses: name: UX - path: ./1425/2025-fall-alex/modules/ name: "1425" - - path: ./3840_Telemetry/2025_spring_alex/modules/ - name: Telem and Ops - path: ./4850_AdvancedFE/2026-spring-alex/modules name: Adv Frontend Spring - - path: ./1400/2025_spring_alex/modules/ - name: 1400-old - path: ./1400/2026_spring_alex/modules name: "1400" - - path: ./1405/2025_spring_alex/ - name: 1405 old - path: ./1405/2026_spring_alex name: "1405" - path: ./1810/2026-spring-alex/modules name: Web Intro Spring - path: ./3840_Telemetry/2026_spring_alex name: Telem and Ops New - - path: ./4620_Distributed/2025Spring/modules/ - name: distributed-old - path: ./4620_Distributed/2026-spring-alex/modules name: Distributed diff --git a/src/app/addCourse/AddCourseToGlobalSettingsForm.tsx b/src/app/addCourse/AddCourseToGlobalSettingsForm.tsx index 1a75b37..c39037b 100644 --- a/src/app/addCourse/AddCourseToGlobalSettingsForm.tsx +++ b/src/app/addCourse/AddCourseToGlobalSettingsForm.tsx @@ -22,6 +22,7 @@ import { } from "@/features/local/course/localCourseSettings"; import { useCourseListInTermQuery } from "@/features/canvas/hooks/canvasCourseHooks"; import { useCanvasTermsQuery } from "@/features/canvas/hooks/canvasHooks"; +import { useDirectoryExistsQuery } from "@/features/local/utils/storageDirectoryHooks"; const sampleCompose = `services: canvas_manager: @@ -62,7 +63,6 @@ export default function AddNewCourseToGlobalSettingsForm() { const formIsComplete = selectedTerm && selectedCanvasCourse && selectedDirectory; - return (
{ if (formIsComplete) { - console.log("Creating course with settings:", selectedDirectory, "old course", courseToImport); + console.log( + "Creating course with settings:", + selectedDirectory, + "old course", + courseToImport + ); const newSettings: LocalCourseSettings = courseToImport ? { ...courseToImport, @@ -145,11 +150,6 @@ export default function AddNewCourseToGlobalSettingsForm() {
{createCourse.isPending && } - - {/*
-        
Example docker compose
- {sampleCompose} -
*/} ); } @@ -185,6 +185,8 @@ function OtherSettings({ useCourseListInTermQuery(selectedTerm.id); const { data: allSettings } = useLocalCoursesSettingsQuery(); const [directory, setDirectory] = useState("./"); + const { data: directoryExists, isLoading: directoryExistsLoading } = + useDirectoryExistsQuery(directory); const populatedCanvasCourseIds = allSettings?.map((s) => s.canvasId) ?? []; const availableCourses = @@ -224,6 +226,15 @@ function OtherSettings({ setLastTypedValue={setSelectedDirectory} label={"Storage Folder"} /> +
+ {directoryExistsLoading && } + {!directoryExistsLoading && directoryExists && ( +
Directory must be a new folder
+ )} + {!directoryExistsLoading && directoryExists === false && ( +
✓ New folder
+ )} +

{ className={ "text-2xl transition-all duration-500 " + "hover:text-slate-50 underline hover:scale-105 " + - "flex " + "flex cursor-pointer" } onClick={() => setIsExpanded((e) => !e)} role="button" diff --git a/src/features/local/utils/directoriesRouter.ts b/src/features/local/utils/directoriesRouter.ts index 77b9688..fae7309 100644 --- a/src/features/local/utils/directoriesRouter.ts +++ b/src/features/local/utils/directoriesRouter.ts @@ -25,4 +25,13 @@ export const directoriesRouter = router({ .query(async ({ input: { folderPath } }) => { return await fileStorageService.settings.folderIsCourse(folderPath); }), + directoryExists: publicProcedure + .input( + z.object({ + relativePath: z.string(), + }) + ) + .query(async ({ input: { relativePath } }) => { + return await fileStorageService.directoryExists(relativePath); + }), }); diff --git a/src/features/local/utils/fileStorageService.ts b/src/features/local/utils/fileStorageService.ts index 4ede0eb..a41752c 100644 --- a/src/features/local/utils/fileStorageService.ts +++ b/src/features/local/utils/fileStorageService.ts @@ -72,4 +72,15 @@ export const fileStorageService = { } return { files, folders }; }, + + async directoryExists(relativePath: string): Promise { + const fullPath = path.join(basePath, relativePath); + // Security: ensure fullPath is inside basePath + const resolvedBase = path.resolve(basePath); + const resolvedFull = path.resolve(fullPath); + if (!resolvedFull.startsWith(resolvedBase)) { + return false; + } + return await directoryOrFileExists(fullPath); + }, }; diff --git a/src/features/local/utils/storageDirectoryHooks.ts b/src/features/local/utils/storageDirectoryHooks.ts index 8ac5101..dcfc967 100644 --- a/src/features/local/utils/storageDirectoryHooks.ts +++ b/src/features/local/utils/storageDirectoryHooks.ts @@ -23,3 +23,10 @@ export const useDirectoryIsCourseQuery = (folderPath: string) => { trpc.directories.directoryIsCourse.queryOptions({ folderPath }) ); }; + +export const useDirectoryExistsQuery = (relativePath: string) => { + const trpc = useTRPC(); + return useQuery( + trpc.directories.directoryExists.queryOptions({ relativePath }) + ); +};