mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
moving v2 to top level
This commit is contained in:
97
src/services/fileStorage/assignmentsFileStorageService.ts
Normal file
97
src/services/fileStorage/assignmentsFileStorageService.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
localAssignmentMarkdown,
|
||||
LocalAssignment,
|
||||
} from "@/models/local/assignment/localAssignment";
|
||||
import { assignmentMarkdownSerializer } from "@/models/local/assignment/utils/assignmentMarkdownSerializer";
|
||||
import path from "path";
|
||||
import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils";
|
||||
import { promises as fs } from "fs";
|
||||
import { courseItemFileStorageService } from "./courseItemFileStorageService";
|
||||
|
||||
const getAssignmentNames = async (courseName: string, moduleName: string) => {
|
||||
const filePath = path.join(basePath, courseName, moduleName, "assignments");
|
||||
if (!(await directoryOrFileExists(filePath))) {
|
||||
console.log(
|
||||
`Error loading course by name, assignments folder does not exist in ${filePath}`
|
||||
);
|
||||
// await fs.mkdir(filePath);
|
||||
return [];
|
||||
}
|
||||
|
||||
const assignmentFiles = await fs.readdir(filePath);
|
||||
return assignmentFiles.map((f) => f.replace(/\.md$/, ""));
|
||||
};
|
||||
const getAssignment = async (
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
assignmentName: string
|
||||
) => {
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"assignments",
|
||||
assignmentName + ".md"
|
||||
);
|
||||
const rawFile = (await fs.readFile(filePath, "utf-8")).replace(/\r\n/g, "\n");
|
||||
return localAssignmentMarkdown.parseMarkdown(rawFile);
|
||||
};
|
||||
export const assignmentsFileStorageService = {
|
||||
getAssignmentNames,
|
||||
getAssignment,
|
||||
async getAssignments(courseName: string, moduleName: string) {
|
||||
return await courseItemFileStorageService.getItems(
|
||||
courseName,
|
||||
moduleName,
|
||||
"Assignment"
|
||||
);
|
||||
},
|
||||
async updateOrCreateAssignment({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
assignment,
|
||||
}: {
|
||||
courseName: string;
|
||||
moduleName: string;
|
||||
assignmentName: string;
|
||||
assignment: LocalAssignment;
|
||||
}) {
|
||||
const folder = path.join(basePath, courseName, moduleName, "assignments");
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"assignments",
|
||||
assignmentName + ".md"
|
||||
);
|
||||
|
||||
const assignmentMarkdown =
|
||||
assignmentMarkdownSerializer.toMarkdown(assignment);
|
||||
console.log(`Saving assignment ${filePath}`);
|
||||
|
||||
|
||||
await fs.writeFile(filePath, assignmentMarkdown);
|
||||
},
|
||||
async delete({
|
||||
courseName,
|
||||
moduleName,
|
||||
assignmentName,
|
||||
}: {
|
||||
courseName: string;
|
||||
moduleName: string;
|
||||
assignmentName: string;
|
||||
}) {
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"assignments",
|
||||
assignmentName + ".md"
|
||||
);
|
||||
console.log("removing assignment", filePath);
|
||||
await fs.unlink(filePath);
|
||||
},
|
||||
};
|
||||
134
src/services/fileStorage/courseItemFileStorageService.ts
Normal file
134
src/services/fileStorage/courseItemFileStorageService.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import path from "path";
|
||||
import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils";
|
||||
import fs from "fs/promises";
|
||||
import {
|
||||
LocalAssignment,
|
||||
localAssignmentMarkdown,
|
||||
} from "@/models/local/assignment/localAssignment";
|
||||
import {
|
||||
LocalQuiz,
|
||||
localQuizMarkdownUtils,
|
||||
} from "@/models/local/quiz/localQuiz";
|
||||
import {
|
||||
LocalCoursePage,
|
||||
localPageMarkdownUtils,
|
||||
} from "@/models/local/page/localCoursePage";
|
||||
import { assignmentMarkdownSerializer } from "@/models/local/assignment/utils/assignmentMarkdownSerializer";
|
||||
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
|
||||
import {
|
||||
CourseItemReturnType,
|
||||
CourseItemType,
|
||||
typeToFolder,
|
||||
} from "@/models/local/courseItemTypes";
|
||||
|
||||
const getItemFileNames = async (
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
type: CourseItemType
|
||||
) => {
|
||||
const folder = typeToFolder[type];
|
||||
const filePath = path.join(basePath, courseName, moduleName, folder);
|
||||
if (!(await directoryOrFileExists(filePath))) {
|
||||
console.log(
|
||||
`Error loading ${type}, ${folder} folder does not exist in ${filePath}`
|
||||
);
|
||||
await fs.mkdir(filePath);
|
||||
}
|
||||
|
||||
const itemFiles = await fs.readdir(filePath);
|
||||
return itemFiles.map((f) => f.replace(/\.md$/, ""));
|
||||
};
|
||||
|
||||
const getItem = async <T extends CourseItemType>(
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
name: string,
|
||||
type: T
|
||||
): Promise<CourseItemReturnType<T>> => {
|
||||
const folder = typeToFolder[type];
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
folder,
|
||||
name + ".md"
|
||||
);
|
||||
const rawFile = (await fs.readFile(filePath, "utf-8")).replace(/\r\n/g, "\n");
|
||||
if (type === "Assignment") {
|
||||
return localAssignmentMarkdown.parseMarkdown(
|
||||
rawFile
|
||||
) as CourseItemReturnType<T>;
|
||||
} else if (type === "Quiz") {
|
||||
return localQuizMarkdownUtils.parseMarkdown(
|
||||
rawFile
|
||||
) as CourseItemReturnType<T>;
|
||||
} else if (type === "Page") {
|
||||
return localPageMarkdownUtils.parseMarkdown(
|
||||
rawFile
|
||||
) as CourseItemReturnType<T>;
|
||||
}
|
||||
|
||||
throw Error(`cannot read item, invalid type: ${type} in ${filePath}`);
|
||||
};
|
||||
|
||||
export const courseItemFileStorageService = {
|
||||
getItem,
|
||||
getItems: async <T extends CourseItemType>(
|
||||
courseName: string,
|
||||
moduleName: string,
|
||||
type: T
|
||||
): Promise<CourseItemReturnType<T>[]> => {
|
||||
const fileNames = await getItemFileNames(courseName, moduleName, type);
|
||||
const items = (
|
||||
await Promise.all(
|
||||
fileNames.map(async (name) => {
|
||||
try {
|
||||
const item = await getItem(courseName, moduleName, name, type);
|
||||
return item;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter((a) => a !== null);
|
||||
return items;
|
||||
},
|
||||
async updateOrCreateAssignment({
|
||||
courseName,
|
||||
moduleName,
|
||||
name,
|
||||
item,
|
||||
type,
|
||||
}: {
|
||||
courseName: string;
|
||||
moduleName: string;
|
||||
name: string;
|
||||
item: LocalAssignment | LocalQuiz | LocalCoursePage;
|
||||
type: CourseItemType;
|
||||
}) {
|
||||
const typeFolder = typeToFolder[type];
|
||||
const folder = path.join(basePath, courseName, moduleName, typeFolder);
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
typeFolder,
|
||||
name + ".md"
|
||||
);
|
||||
|
||||
const markdownDictionary: {
|
||||
[key in CourseItemType]: () => string;
|
||||
} = {
|
||||
Assignment: () =>
|
||||
assignmentMarkdownSerializer.toMarkdown(item as LocalAssignment),
|
||||
Quiz: () => quizMarkdownUtils.toMarkdown(item as LocalQuiz),
|
||||
Page: () => localPageMarkdownUtils.toMarkdown(item as LocalCoursePage),
|
||||
};
|
||||
const itemMarkdown = markdownDictionary[type]();
|
||||
|
||||
console.log(`Saving ${type} ${filePath}`);
|
||||
await fs.writeFile(filePath, itemMarkdown);
|
||||
},
|
||||
};
|
||||
54
src/services/fileStorage/fileStorageService.ts
Normal file
54
src/services/fileStorage/fileStorageService.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { basePath, directoryOrFileExists } from "./utils/fileSystemUtils";
|
||||
import { assignmentsFileStorageService } from "./assignmentsFileStorageService";
|
||||
import { quizFileStorageService } from "./quizFileStorageService";
|
||||
import { pageFileStorageService } from "./pageFileStorageService";
|
||||
import { moduleFileStorageService } from "./moduleFileStorageService";
|
||||
import { settingsFileStorageService } from "./settingsFileStorageService";
|
||||
|
||||
export const fileStorageService = {
|
||||
settings: settingsFileStorageService,
|
||||
modules: moduleFileStorageService,
|
||||
assignments: assignmentsFileStorageService,
|
||||
quizzes: quizFileStorageService,
|
||||
pages: pageFileStorageService,
|
||||
|
||||
async getEmptyDirectories(): Promise<string[]> {
|
||||
if (!(await directoryOrFileExists(basePath))) {
|
||||
throw new Error(
|
||||
`Cannot get empty directories, ${basePath} does not exist`
|
||||
);
|
||||
}
|
||||
|
||||
const directories = await fs.readdir(basePath, { withFileTypes: true });
|
||||
const emptyDirectories = (
|
||||
await Promise.all(
|
||||
directories
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => path.join(dirent.name))
|
||||
.map(async (directory) => {
|
||||
return {
|
||||
directory,
|
||||
files: await fs.readdir(path.join(basePath, directory)),
|
||||
};
|
||||
})
|
||||
)
|
||||
)
|
||||
.filter(({ files }) => files.length === 0)
|
||||
.map(({ directory }) => directory);
|
||||
|
||||
return emptyDirectories;
|
||||
},
|
||||
|
||||
async createCourseFolderForTesting(courseName: string) {
|
||||
const courseDirectory = path.join(basePath, courseName);
|
||||
|
||||
await fs.mkdir(courseDirectory, { recursive: true });
|
||||
},
|
||||
async createModuleFolderForTesting(courseName: string, moduleName: string) {
|
||||
const courseDirectory = path.join(basePath, courseName, moduleName);
|
||||
|
||||
await fs.mkdir(courseDirectory, { recursive: true });
|
||||
},
|
||||
};
|
||||
121
src/services/fileStorage/lectureFileStorageService.ts
Normal file
121
src/services/fileStorage/lectureFileStorageService.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
import path from "path";
|
||||
import { basePath } from "./utils/fileSystemUtils";
|
||||
import fs from "fs/promises";
|
||||
import {
|
||||
getLectureWeekName,
|
||||
lectureFolderName,
|
||||
lectureToString,
|
||||
parseLecture,
|
||||
} from "./utils/lectureUtils";
|
||||
import { Lecture } from "@/models/local/lecture";
|
||||
import {
|
||||
getDayOfWeek,
|
||||
LocalCourseSettings,
|
||||
} from "@/models/local/localCourseSettings";
|
||||
import { getDateFromStringOrThrow } from "@/models/local/utils/timeUtils";
|
||||
|
||||
export async function getLectures(courseName: string) {
|
||||
const courseLectureRoot = path.join(basePath, courseName, lectureFolderName);
|
||||
if (!(await directoryExists(courseLectureRoot))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(courseLectureRoot, { withFileTypes: true });
|
||||
const lectureWeekFolders = entries
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name);
|
||||
|
||||
const lecturesByWeek = await Promise.all(
|
||||
lectureWeekFolders.map(async (weekName) => {
|
||||
const weekBasePath = path.join(courseLectureRoot, weekName);
|
||||
const fileNames = await fs.readdir(weekBasePath);
|
||||
const lectures = await Promise.all(
|
||||
fileNames.map(async (fileName) => {
|
||||
const filePath = path.join(weekBasePath, fileName);
|
||||
const fileContent = await fs.readFile(filePath, "utf-8");
|
||||
const lecture = parseLecture(fileContent);
|
||||
return lecture;
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
weekName,
|
||||
lectures,
|
||||
};
|
||||
})
|
||||
);
|
||||
return lecturesByWeek;
|
||||
}
|
||||
|
||||
export async function updateLecture(
|
||||
courseName: string,
|
||||
courseSettings: LocalCourseSettings,
|
||||
lecture: Lecture
|
||||
) {
|
||||
const courseLectureRoot = path.join(basePath, courseName, lectureFolderName);
|
||||
const lectureDate = getDateFromStringOrThrow(
|
||||
lecture.date,
|
||||
"lecture start date in update lecture"
|
||||
);
|
||||
|
||||
const weekFolderName = getLectureWeekName(
|
||||
courseSettings.startDate,
|
||||
lecture.date
|
||||
);
|
||||
const weekPath = path.join(courseLectureRoot, weekFolderName);
|
||||
if (!(await directoryExists(weekPath))) {
|
||||
await fs.mkdir(weekPath, { recursive: true });
|
||||
}
|
||||
|
||||
const lecturePath = path.join(
|
||||
weekPath,
|
||||
`${lectureDate.getDay()}-${getDayOfWeek(lectureDate)}.md`
|
||||
);
|
||||
const lectureContents = lectureToString(lecture);
|
||||
await fs.writeFile(lecturePath, lectureContents);
|
||||
}
|
||||
|
||||
export async function deleteLecture(
|
||||
courseName: string,
|
||||
courseSettings: LocalCourseSettings,
|
||||
dayAsString: string
|
||||
) {
|
||||
console.log("deleting lecture", courseName, dayAsString);
|
||||
const lectureDate = getDateFromStringOrThrow(
|
||||
dayAsString,
|
||||
"lecture start date in update lecture"
|
||||
);
|
||||
|
||||
const weekFolderName = getLectureWeekName(
|
||||
courseSettings.startDate,
|
||||
dayAsString
|
||||
);
|
||||
|
||||
const courseLectureRoot = path.join(basePath, courseName, lectureFolderName);
|
||||
const weekPath = path.join(courseLectureRoot, weekFolderName);
|
||||
const lecturePath = path.join(
|
||||
weekPath,
|
||||
`${lectureDate.getDay()}-${getDayOfWeek(lectureDate)}.md`
|
||||
);
|
||||
try {
|
||||
await fs.access(lecturePath); // throws error if no file
|
||||
await fs.unlink(lecturePath);
|
||||
console.log(`File deleted: ${lecturePath}`);
|
||||
} catch (error: any) {
|
||||
if (error?.code === "ENOENT") {
|
||||
console.log(`Cannot delete lecture, file does not exist: ${lecturePath}`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const directoryExists = async (path: string): Promise<boolean> => {
|
||||
try {
|
||||
const stat = await fs.stat(path);
|
||||
return stat.isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
28
src/services/fileStorage/moduleFileStorageService.ts
Normal file
28
src/services/fileStorage/moduleFileStorageService.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { basePath } from "./utils/fileSystemUtils";
|
||||
import { lectureFolderName } from "./utils/lectureUtils";
|
||||
|
||||
export const moduleFileStorageService = {
|
||||
async getModuleNames(courseName: string) {
|
||||
const courseDirectory = path.join(basePath, 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 = path.join(basePath, courseName);
|
||||
|
||||
await fs.mkdir(courseDirectory + "/" + moduleName, { recursive: true });
|
||||
},
|
||||
};
|
||||
79
src/services/fileStorage/pageFileStorageService.ts
Normal file
79
src/services/fileStorage/pageFileStorageService.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
localPageMarkdownUtils,
|
||||
LocalCoursePage,
|
||||
} from "@/models/local/page/localCoursePage";
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { basePath } from "./utils/fileSystemUtils";
|
||||
import { courseItemFileStorageService } from "./courseItemFileStorageService";
|
||||
|
||||
export const pageFileStorageService = {
|
||||
getPage: async (courseName: string, moduleName: string, name: string) =>
|
||||
await courseItemFileStorageService.getItem(
|
||||
courseName,
|
||||
moduleName,
|
||||
name,
|
||||
"Page"
|
||||
),
|
||||
getPages: async (courseName: string, moduleName: string) =>
|
||||
await courseItemFileStorageService.getItems(courseName, moduleName, "Page"),
|
||||
|
||||
async updatePage({
|
||||
courseName,
|
||||
moduleName,
|
||||
pageName,
|
||||
page,
|
||||
}: {
|
||||
courseName: string;
|
||||
moduleName: string;
|
||||
pageName: string;
|
||||
page: LocalCoursePage;
|
||||
}) {
|
||||
const folder = path.join(basePath, courseName, moduleName, "pages");
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"pages",
|
||||
page.name + ".md"
|
||||
);
|
||||
|
||||
const pageMarkdown = localPageMarkdownUtils.toMarkdown(page);
|
||||
console.log(`Saving page ${filePath}`);
|
||||
await fs.writeFile(filePath, pageMarkdown);
|
||||
|
||||
const pageNameIsChanged = pageName !== page.name;
|
||||
if (pageNameIsChanged) {
|
||||
console.log("removing old page after name change " + pageName);
|
||||
const oldFilePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"pages",
|
||||
pageName + ".md"
|
||||
);
|
||||
await fs.unlink(oldFilePath);
|
||||
}
|
||||
},
|
||||
async delete({
|
||||
courseName,
|
||||
moduleName,
|
||||
pageName,
|
||||
}: {
|
||||
courseName: string;
|
||||
moduleName: string;
|
||||
pageName: string;
|
||||
}) {
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"pages",
|
||||
pageName + ".md"
|
||||
);
|
||||
console.log("removing page", filePath);
|
||||
await fs.unlink(filePath);
|
||||
},
|
||||
};
|
||||
63
src/services/fileStorage/quizFileStorageService.ts
Normal file
63
src/services/fileStorage/quizFileStorageService.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { LocalQuiz } from "@/models/local/quiz/localQuiz";
|
||||
import { quizMarkdownUtils } from "@/models/local/quiz/utils/quizMarkdownUtils";
|
||||
import path from "path";
|
||||
import { basePath } from "./utils/fileSystemUtils";
|
||||
import { promises as fs } from "fs";
|
||||
import { courseItemFileStorageService } from "./courseItemFileStorageService";
|
||||
|
||||
export const quizFileStorageService = {
|
||||
getQuiz: async (courseName: string, moduleName: string, quizName: string) =>
|
||||
await courseItemFileStorageService.getItem(
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName,
|
||||
"Quiz"
|
||||
),
|
||||
getQuizzes: async (courseName: string, moduleName: string) =>
|
||||
await courseItemFileStorageService.getItems(courseName, moduleName, "Quiz"),
|
||||
|
||||
async updateQuiz({
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName,
|
||||
quiz,
|
||||
}: {
|
||||
courseName: string;
|
||||
moduleName: string;
|
||||
quizName: string;
|
||||
quiz: LocalQuiz;
|
||||
}) {
|
||||
const folder = path.join(basePath, courseName, moduleName, "quizzes");
|
||||
await fs.mkdir(folder, { recursive: true });
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"quizzes",
|
||||
quizName + ".md"
|
||||
);
|
||||
|
||||
const quizMarkdown = quizMarkdownUtils.toMarkdown(quiz);
|
||||
console.log(`Saving quiz ${filePath}`);
|
||||
await fs.writeFile(filePath, quizMarkdown);
|
||||
},
|
||||
async delete({
|
||||
courseName,
|
||||
moduleName,
|
||||
quizName,
|
||||
}: {
|
||||
courseName: string;
|
||||
moduleName: string;
|
||||
quizName: string;
|
||||
}) {
|
||||
const filePath = path.join(
|
||||
basePath,
|
||||
courseName,
|
||||
moduleName,
|
||||
"quizzes",
|
||||
quizName + ".md"
|
||||
);
|
||||
console.log("removing quiz", filePath);
|
||||
await fs.unlink(filePath);
|
||||
},
|
||||
};
|
||||
86
src/services/fileStorage/settingsFileStorageService.ts
Normal file
86
src/services/fileStorage/settingsFileStorageService.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
LocalCourseSettings,
|
||||
localCourseYamlUtils,
|
||||
} from "@/models/local/localCourseSettings";
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import {
|
||||
basePath,
|
||||
directoryOrFileExists,
|
||||
getCourseNames,
|
||||
} from "./utils/fileSystemUtils";
|
||||
import { AssignmentSubmissionType } from "@/models/local/assignment/assignmentSubmissionType";
|
||||
|
||||
const getCourseSettings = async (
|
||||
courseName: string
|
||||
): Promise<LocalCourseSettings> => {
|
||||
const courseDirectory = path.join(basePath, courseName);
|
||||
const settingsPath = path.join(courseDirectory, "settings.yml");
|
||||
if (!(await directoryOrFileExists(settingsPath))) {
|
||||
const errorMessage = `could not find settings for ${courseName}, settings file ${settingsPath}`;
|
||||
console.log(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const settingsString = await fs.readFile(settingsPath, "utf-8");
|
||||
|
||||
const settingsFromFile =
|
||||
localCourseYamlUtils.parseSettingYaml(settingsString);
|
||||
|
||||
const settings: LocalCourseSettings = populateDefaultValues(settingsFromFile);
|
||||
|
||||
const folderName = path.basename(courseDirectory);
|
||||
return { ...settings, name: folderName };
|
||||
};
|
||||
|
||||
const populateDefaultValues = (settingsFromFile: LocalCourseSettings) => {
|
||||
const defaultSubmissionType = [
|
||||
AssignmentSubmissionType.ONLINE_TEXT_ENTRY,
|
||||
AssignmentSubmissionType.ONLINE_UPLOAD,
|
||||
];
|
||||
const defaultFileUploadTypes = ["pdf", "jpg", "jpeg"];
|
||||
|
||||
const settings: LocalCourseSettings = {
|
||||
...settingsFromFile,
|
||||
defaultAssignmentSubmissionTypes:
|
||||
settingsFromFile.defaultAssignmentSubmissionTypes ||
|
||||
defaultSubmissionType,
|
||||
defaultFileUploadTypes:
|
||||
settingsFromFile.defaultFileUploadTypes || defaultFileUploadTypes,
|
||||
holidays: Array.isArray(settingsFromFile.holidays)
|
||||
? settingsFromFile.holidays
|
||||
: [],
|
||||
assets: Array.isArray(settingsFromFile.assets)
|
||||
? settingsFromFile.assets
|
||||
: [],
|
||||
};
|
||||
return settings;
|
||||
};
|
||||
|
||||
export const settingsFileStorageService = {
|
||||
getCourseSettings,
|
||||
async getAllCoursesSettings() {
|
||||
const courses = await getCourseNames();
|
||||
|
||||
const courseSettings = await Promise.all(
|
||||
courses.map(async (c) => await getCourseSettings(c))
|
||||
);
|
||||
return courseSettings;
|
||||
},
|
||||
|
||||
async updateCourseSettings(
|
||||
courseName: string,
|
||||
settings: LocalCourseSettings
|
||||
) {
|
||||
const courseDirectory = path.join(basePath, courseName);
|
||||
const settingsPath = path.join(courseDirectory, "settings.yml");
|
||||
|
||||
const { name, ...settingsWithoutName } = settings;
|
||||
|
||||
const settingsMarkdown =
|
||||
localCourseYamlUtils.settingsToYaml(settingsWithoutName);
|
||||
|
||||
console.log(`Saving settings ${settingsPath}`);
|
||||
await fs.writeFile(settingsPath, settingsMarkdown);
|
||||
},
|
||||
};
|
||||
52
src/services/fileStorage/utils/fileSystemUtils.ts
Normal file
52
src/services/fileStorage/utils/fileSystemUtils.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
|
||||
export const hasFileSystemEntries = async (
|
||||
directoryPath: string
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const entries = await fs.readdir(directoryPath);
|
||||
return entries.length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
export const directoryOrFileExists = async (directoryPath: string): Promise<boolean> => {
|
||||
try {
|
||||
await fs.access(directoryPath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export async function getCourseNames() {
|
||||
console.log("loading course ids");
|
||||
const courseDirectories = await fs.readdir(basePath, {
|
||||
withFileTypes: true,
|
||||
});
|
||||
const coursePromises = await Promise.all(
|
||||
courseDirectories
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map(async (dirent) => {
|
||||
const coursePath = path.join(basePath, dirent.name);
|
||||
const settingsPath = path.join(coursePath, "settings.yml");
|
||||
const hasSettings = await directoryOrFileExists(settingsPath);
|
||||
return {
|
||||
dirent,
|
||||
hasSettings,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const courseNamesFromDirectories = coursePromises
|
||||
.filter(({ hasSettings }) => hasSettings)
|
||||
.map(({ dirent }) => dirent.name);
|
||||
|
||||
return courseNamesFromDirectories;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const basePath = process.env.STORAGE_DIRECTORY ?? "./storage";
|
||||
console.log("base path", basePath);
|
||||
48
src/services/fileStorage/utils/lectureUtils.ts
Normal file
48
src/services/fileStorage/utils/lectureUtils.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { getWeekNumber } from "@/app/course/[courseName]/calendar/calendarMonthUtils";
|
||||
import { extractLabelValue } from "@/models/local/assignment/utils/markdownUtils";
|
||||
import { Lecture } from "@/models/local/lecture";
|
||||
import { getDateFromStringOrThrow } from "@/models/local/utils/timeUtils";
|
||||
|
||||
export function parseLecture(fileContent: string): Lecture {
|
||||
try {
|
||||
const settings = fileContent.split("---\n")[0];
|
||||
const name = extractLabelValue(settings, "Name");
|
||||
const date = extractLabelValue(settings, "Date");
|
||||
|
||||
const content = fileContent.split("---\n").slice(1).join("---\n").trim();
|
||||
return {
|
||||
name,
|
||||
date,
|
||||
content,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error parsing lecture: ", fileContent);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function lectureToString(lecture: Lecture) {
|
||||
return `Name: ${lecture.name}
|
||||
Date: ${lecture.date}
|
||||
---
|
||||
${lecture.content}`;
|
||||
}
|
||||
|
||||
export const lectureFolderName = "00 - lectures";
|
||||
|
||||
export function getLectureWeekName(semesterStart: string, lectureDate: string) {
|
||||
const startDate = getDateFromStringOrThrow(
|
||||
semesterStart,
|
||||
"semester start date in update lecture"
|
||||
);
|
||||
const targetDate = getDateFromStringOrThrow(
|
||||
lectureDate,
|
||||
"lecture start date in update lecture"
|
||||
);
|
||||
const weekNumber = getWeekNumber(startDate, targetDate)
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
|
||||
const weekName = `week-${weekNumber}`;
|
||||
return weekName;
|
||||
}
|
||||
Reference in New Issue
Block a user