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 })
+ );
+};