mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-26 23:58:31 -06:00
more refactoring by feature
This commit is contained in:
4
src/features/local/modules/IModuleItem.ts
Normal file
4
src/features/local/modules/IModuleItem.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IModuleItem {
|
||||
name: string;
|
||||
dueAt: string;
|
||||
}
|
||||
173
src/features/local/modules/localCourseModuleHooks.ts
Normal file
173
src/features/local/modules/localCourseModuleHooks.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { useCourseContext } from "@/app/course/[courseName]/context/courseContext";
|
||||
import { useTRPC } from "@/services/serverFunctions/trpcClient";
|
||||
import { CalendarItemsInterface } from "@/app/course/[courseName]/context/calendarItemsContext";
|
||||
import {
|
||||
getDateOnlyMarkdownString,
|
||||
getDateFromStringOrThrow,
|
||||
} from "@/models/local/utils/timeUtils";
|
||||
import {
|
||||
useSuspenseQuery,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
useSuspenseQueries,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
export const useModuleNamesQuery = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const trpc = useTRPC();
|
||||
return useSuspenseQuery(
|
||||
trpc.module.getModuleNames.queryOptions({ courseName })
|
||||
);
|
||||
};
|
||||
|
||||
export const useCreateModuleMutation = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const trpc = useTRPC();
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
trpc.module.createModule.mutationOptions({
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: trpc.module.getModuleNames.queryKey({ courseName }),
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const useCourseQuizzesByModuleByDateQuery = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const { data: moduleNames } = useModuleNamesQuery();
|
||||
const trpc = useTRPC();
|
||||
const quizzesResults = useSuspenseQueries({
|
||||
queries: moduleNames.map((moduleName: string) =>
|
||||
trpc.quiz.getAllQuizzes.queryOptions({ courseName, moduleName })
|
||||
),
|
||||
});
|
||||
const quizzes = quizzesResults.map((result) => result.data ?? []);
|
||||
const quizzesAndModules = moduleNames.flatMap(
|
||||
(moduleName: string, index: number) => {
|
||||
return quizzes[index].map((quiz) => ({ moduleName, quiz }));
|
||||
}
|
||||
);
|
||||
const quizzesByModuleByDate = quizzesAndModules.reduce(
|
||||
(previous, { quiz, moduleName }) => {
|
||||
const dueDay = getDateOnlyMarkdownString(
|
||||
getDateFromStringOrThrow(quiz.dueAt, "due at for quiz in items context")
|
||||
);
|
||||
const previousModules = previous[dueDay] ?? {};
|
||||
const previousModule = previousModules[moduleName] ?? {
|
||||
quizzes: [],
|
||||
};
|
||||
const updatedModule = {
|
||||
...previousModule,
|
||||
quizzes: [...previousModule.quizzes, quiz],
|
||||
};
|
||||
return {
|
||||
...previous,
|
||||
[dueDay]: {
|
||||
...previousModules,
|
||||
[moduleName]: updatedModule,
|
||||
},
|
||||
};
|
||||
},
|
||||
{} as CalendarItemsInterface
|
||||
);
|
||||
return quizzesByModuleByDate;
|
||||
};
|
||||
|
||||
export const useCoursePagesByModuleByDateQuery = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const { data: moduleNames } = useModuleNamesQuery();
|
||||
const trpc = useTRPC();
|
||||
const pagesResults = useSuspenseQueries({
|
||||
queries: moduleNames.map((moduleName: string) =>
|
||||
trpc.page.getAllPages.queryOptions({ courseName, moduleName })
|
||||
),
|
||||
});
|
||||
const pages = pagesResults.map((result) => result.data ?? []);
|
||||
const pagesAndModules = moduleNames.flatMap(
|
||||
(moduleName: string, index: number) => {
|
||||
return pages[index].map((page) => ({ moduleName, page }));
|
||||
}
|
||||
);
|
||||
const pagesByModuleByDate = pagesAndModules.reduce(
|
||||
(
|
||||
previous,
|
||||
{ page, moduleName }
|
||||
) => {
|
||||
const dueDay = getDateOnlyMarkdownString(
|
||||
getDateFromStringOrThrow(page.dueAt, "due at for page in items context")
|
||||
);
|
||||
const previousModules = previous[dueDay] ?? {};
|
||||
const previousModule = previousModules[moduleName] ?? {
|
||||
pages: [],
|
||||
};
|
||||
const updatedModule = {
|
||||
...previousModule,
|
||||
pages: [...previousModule.pages, page],
|
||||
};
|
||||
return {
|
||||
...previous,
|
||||
[dueDay]: {
|
||||
...previousModules,
|
||||
[moduleName]: updatedModule,
|
||||
},
|
||||
};
|
||||
},
|
||||
{} as CalendarItemsInterface
|
||||
);
|
||||
return pagesByModuleByDate;
|
||||
};
|
||||
|
||||
export const useCourseAssignmentsByModuleByDateQuery = () => {
|
||||
const { courseName } = useCourseContext();
|
||||
const { data: moduleNames } = useModuleNamesQuery();
|
||||
const trpc = useTRPC();
|
||||
const assignmentsResults = useSuspenseQueries({
|
||||
queries: moduleNames.map((moduleName: string) =>
|
||||
trpc.assignment.getAllAssignments.queryOptions({ courseName, moduleName })
|
||||
),
|
||||
});
|
||||
const assignments = assignmentsResults.map(
|
||||
(result) => result.data
|
||||
);
|
||||
const assignmentsAndModules = moduleNames.flatMap(
|
||||
(moduleName: string, index: number) => {
|
||||
return assignments[index].map((assignment) => ({
|
||||
moduleName,
|
||||
assignment,
|
||||
}));
|
||||
}
|
||||
);
|
||||
const assignmentsByModuleByDate = assignmentsAndModules.reduce(
|
||||
(
|
||||
previous,
|
||||
{ assignment, moduleName }
|
||||
) => {
|
||||
const dueDay = getDateOnlyMarkdownString(
|
||||
getDateFromStringOrThrow(
|
||||
assignment.dueAt,
|
||||
"due at for assignment in items context"
|
||||
)
|
||||
);
|
||||
const previousModules = previous[dueDay] ?? {};
|
||||
const previousModule = previousModules[moduleName] ?? {
|
||||
assignments: [],
|
||||
};
|
||||
const updatedModule = {
|
||||
...previousModule,
|
||||
assignments: [...previousModule.assignments, assignment],
|
||||
};
|
||||
return {
|
||||
...previous,
|
||||
[dueDay]: {
|
||||
...previousModules,
|
||||
[moduleName]: updatedModule,
|
||||
},
|
||||
};
|
||||
},
|
||||
{} as CalendarItemsInterface
|
||||
);
|
||||
return assignmentsByModuleByDate;
|
||||
};
|
||||
75
src/features/local/modules/localModules.ts
Normal file
75
src/features/local/modules/localModules.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { LocalCoursePage } from "@/features/local/pages/localCoursePageModels";
|
||||
import { LocalAssignment } from "../assignments/models/localAssignment";
|
||||
import { IModuleItem } from "./IModuleItem";
|
||||
import { getDateFromString } from "../../../models/local/utils/timeUtils";
|
||||
import { LocalQuiz } from "@/features/local/quizzes/models/localQuiz";
|
||||
|
||||
export interface LocalModule {
|
||||
name: string;
|
||||
assignments: LocalAssignment[];
|
||||
quizzes: LocalQuiz[];
|
||||
pages: LocalCoursePage[];
|
||||
}
|
||||
|
||||
export const LocalModuleUtils = {
|
||||
getSortedModuleItems(module: LocalModule): IModuleItem[] {
|
||||
return [...module.assignments, ...module.quizzes, ...module.pages].sort(
|
||||
(a, b) =>
|
||||
(getDateFromString(a.dueAt)?.getTime() ?? 0) -
|
||||
(getDateFromString(b.dueAt)?.getTime() ?? 0)
|
||||
);
|
||||
},
|
||||
|
||||
equals(module1: LocalModule, module2: LocalModule): boolean {
|
||||
return (
|
||||
module1.name.toLowerCase() === module2.name.toLowerCase() &&
|
||||
LocalModuleUtils.compareCollections(
|
||||
module1.assignments.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
module2.assignments.sort((a, b) => a.name.localeCompare(b.name))
|
||||
) &&
|
||||
LocalModuleUtils.compareCollections(
|
||||
module1.quizzes.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
module2.quizzes.sort((a, b) => a.name.localeCompare(b.name))
|
||||
) &&
|
||||
LocalModuleUtils.compareCollections(
|
||||
module1.pages.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
module2.pages.sort((a, b) => a.name.localeCompare(b.name))
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
compareCollections<T>(first: T[], second: T[]): boolean {
|
||||
if (first.length !== second.length) return false;
|
||||
|
||||
for (let i = 0; i < first.length; i++) {
|
||||
if (JSON.stringify(first[i]) !== JSON.stringify(second[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
getHashCode(module: LocalModule): number {
|
||||
const hash = new Map<string, number>();
|
||||
hash.set(module.name.toLowerCase(), 1);
|
||||
LocalModuleUtils.addRangeToHash(
|
||||
hash,
|
||||
module.assignments.sort((a, b) => a.name.localeCompare(b.name))
|
||||
);
|
||||
LocalModuleUtils.addRangeToHash(
|
||||
hash,
|
||||
module.quizzes.sort((a, b) => a.name.localeCompare(b.name))
|
||||
);
|
||||
LocalModuleUtils.addRangeToHash(
|
||||
hash,
|
||||
module.pages.sort((a, b) => a.name.localeCompare(b.name))
|
||||
);
|
||||
|
||||
return Array.from(hash.values()).reduce((acc, val) => acc + val, 0);
|
||||
},
|
||||
|
||||
addRangeToHash<T>(hash: Map<string, number>, items: T[]): void {
|
||||
for (const item of items) {
|
||||
hash.set(JSON.stringify(item), 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
27
src/features/local/modules/moduleFileStorageService.ts
Normal file
27
src/features/local/modules/moduleFileStorageService.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { promises as fs } from "fs";
|
||||
import { lectureFolderName } from "../../../services/fileStorage/utils/lectureUtils";
|
||||
import { getCoursePathByName } from "../../../services/fileStorage/globalSettingsFileStorageService";
|
||||
|
||||
export const moduleFileStorageService = {
|
||||
async getModuleNames(courseName: string) {
|
||||
const courseDirectory = await getCoursePathByName(courseName);
|
||||
const moduleDirectories = await fs.readdir(courseDirectory, {
|
||||
withFileTypes: true,
|
||||
});
|
||||
|
||||
const modulePromises = moduleDirectories
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name);
|
||||
|
||||
const modules = await Promise.all(modulePromises);
|
||||
const modulesWithoutLectures = modules.filter(
|
||||
(m) => m !== lectureFolderName
|
||||
);
|
||||
return modulesWithoutLectures.sort((a, b) => a.localeCompare(b));
|
||||
},
|
||||
async createModule(courseName: string, moduleName: string) {
|
||||
const courseDirectory = await getCoursePathByName(courseName);
|
||||
|
||||
await fs.mkdir(courseDirectory + "/" + moduleName, { recursive: true });
|
||||
},
|
||||
};
|
||||
26
src/features/local/modules/moduleRouter.ts
Normal file
26
src/features/local/modules/moduleRouter.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { z } from "zod";
|
||||
import { fileStorageService } from "@/services/fileStorage/fileStorageService";
|
||||
import { router } from "@/services/serverFunctions/trpcSetup";
|
||||
import publicProcedure from "@/services/serverFunctions/procedures/public";
|
||||
|
||||
export const moduleRouter = router({
|
||||
getModuleNames: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
courseName: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ input: { courseName } }) => {
|
||||
return await fileStorageService.modules.getModuleNames(courseName);
|
||||
}),
|
||||
createModule: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
courseName: z.string(),
|
||||
moduleName: z.string(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ input: { courseName, moduleName } }) => {
|
||||
await fileStorageService.modules.createModule(courseName, moduleName);
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user