diff --git a/Management.Test/Management.Test.csproj b/Management.Test/Management.Test.csproj index 1c65369..abf1b49 100644 --- a/Management.Test/Management.Test.csproj +++ b/Management.Test/Management.Test.csproj @@ -12,6 +12,7 @@ + diff --git a/Management.Test/Markdown/FileStorageTests.cs b/Management.Test/Markdown/FileStorageTests.cs new file mode 100644 index 0000000..a6a8bc2 --- /dev/null +++ b/Management.Test/Markdown/FileStorageTests.cs @@ -0,0 +1,261 @@ +using LocalModels; +using Management.Services; +using Microsoft.Extensions.Logging.Abstractions; +using NSubstitute; +using NUnit.Framework.Internal; + +public class FileStorageTests +{ + private FileStorageManager fileManager { get; set; } + + + private string setupTempDirectory() + { + var tempDirectory = Path.GetTempPath(); + var storageDirectory = tempDirectory + "fileStorageTests"; + Console.WriteLine(storageDirectory); + if (!Directory.Exists(storageDirectory)) + Directory.CreateDirectory(storageDirectory); + else + { + var di = new DirectoryInfo(storageDirectory); + + foreach (FileInfo file in di.GetFiles()) + { + file.Delete(); + } + foreach (DirectoryInfo dir in di.GetDirectories()) + { + dir.Delete(true); + } + } + + return storageDirectory; + } + + [SetUp] + public void SetUp() + { + var storageDirectory = setupTempDirectory(); + + var fileManagerLogger = new MyLogger(NullLogger.Instance); + var markdownLoaderLogger = new MyLogger(NullLogger.Instance); + + Environment.SetEnvironmentVariable("storageDirectory", storageDirectory); + var markdownLoader = new CourseMarkdownLoader(markdownLoaderLogger); + fileManager = new FileStorageManager(fileManagerLogger, markdownLoader); + } + + [Test] + public async Task EmptyCourse_CanBeSavedAndLoaded() + { + LocalCourse testCourse = new LocalCourse + { + Settings = new() + { + Name = "test empty course", + }, + Modules = [] + }; + + await fileManager.SaveCourseAsync(testCourse); + + var loadedCourses = await fileManager.LoadSavedMarkdownCourses(); + var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); + + loadedCourse.Should().BeEquivalentTo(testCourse); + } + + [Test] + public async Task CourseSettings_CanBeSavedAndLoaded() + { + LocalCourse testCourse = new() + { + Settings = new() + { + AssignmentGroups = [], + Name = "Test Course with settings", + DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], + StartDate = new DateTime(), + EndDate = new DateTime(), + DefaultDueTime = new() { Hour = 1, Minute = 59 }, + }, + Modules = [] + }; + + await fileManager.SaveCourseAsync(testCourse); + + var loadedCourses = await fileManager.LoadSavedMarkdownCourses(); + var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); + + loadedCourse.Settings.Should().BeEquivalentTo(testCourse.Settings); + } + + + [Test] + public async Task EmptyCourseModules_CanBeSavedAndLoaded() + { + LocalCourse testCourse = new() + { + Settings = new() + { + Name = "Test Course with modules", + }, + Modules = [ + new() { + Name="test module 1", + Assignments= [], + Quizzes=[] + } + ] + }; + + await fileManager.SaveCourseAsync(testCourse); + + var loadedCourses = await fileManager.LoadSavedMarkdownCourses(); + var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); + + loadedCourse.Modules.Should().BeEquivalentTo(testCourse.Modules); + } + + [Test] + public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded() + { + LocalCourse testCourse = new() + { + Settings = new() + { + Name = "Test Course with modules and assignments", + }, + Modules = [ + new() { + Name="test module 1 with assignments", + Assignments=[ + new () { + Name="test assignment", + Description ="here is the description", + DueAt = new DateTime(), + LockAt = new DateTime(), + SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], + LocalAssignmentGroupName = "Final Project", + Rubric = [ + new() {Points = 4, Label="do task 1"}, + new() {Points = 2, Label="do task 2"}, + ] + } + ], + Quizzes=[] + } + ] + }; + + await fileManager.SaveCourseAsync(testCourse); + + var loadedCourses = await fileManager.LoadSavedMarkdownCourses(); + var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); + + loadedCourse.Modules.First().Assignments.Should().BeEquivalentTo(testCourse.Modules.First().Assignments); + } + + + [Test] + public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded() + { + LocalCourse testCourse = new() + { + Settings = new() { Name = "Test Course with modules and quiz" }, + Modules = [ + new() { + Name="test module 1 with quiz", + Assignments=[], + Quizzes=[ + new() { + Name = "Test Quiz", + Description = "quiz description", + LockAt = new DateTime(2022, 10, 3, 12, 5, 0), + DueAt = new DateTime(2022, 10, 3, 12, 5, 0), + ShuffleAnswers = true, + OneQuestionAtATime = true, + LocalAssignmentGroupName = "Assignments", + Questions=[ + new () { + Text = "test essay", + QuestionType = QuestionType.ESSAY, + Points = 1 + } + ] + } + ] + } + ] + }; + + await fileManager.SaveCourseAsync(testCourse); + + var loadedCourses = await fileManager.LoadSavedMarkdownCourses(); + var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); + + loadedCourse.Modules.First().Quizzes.Should().BeEquivalentTo(testCourse.Modules.First().Quizzes); + } + + + [Test] + public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData() + { + LocalCourse testCourse = new (){ + Settings = new () { + AssignmentGroups = [], + Name = "Test Course with lots of data", + DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], + StartDate = new DateTime(), + EndDate = new DateTime(), + DefaultDueTime = new() { Hour = 1, Minute = 59 }, + }, + Modules = [ + new() { + Name= "new test module", + Assignments = [ + new() { + Name="test assignment", + Description ="here is the description", + DueAt = new DateTime(), + LockAt = new DateTime(), + SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], + LocalAssignmentGroupName = "Final Project", + Rubric = [ + new() { Points = 4, Label="do task 1" }, + new() { Points = 2, Label="do task 2" }, + ] + } + ], + Quizzes = [ + new() { + Name = "Test Quiz", + Description = "quiz description", + LockAt = new DateTime(), + DueAt = new DateTime(), + ShuffleAnswers = true, + OneQuestionAtATime = false, + LocalAssignmentGroupName = "someId", + AllowedAttempts = -1, + Questions = [ + new() { + Text = "test short answer", + QuestionType = QuestionType.SHORT_ANSWER, + Points = 1 + } + ] + } + ] + } + ] + }; + + await fileManager.SaveCourseAsync(testCourse); + + var loadedCourses = await fileManager.LoadSavedMarkdownCourses(); + var loadedCourse = loadedCourses.First(c => c.Settings.Name == testCourse.Settings.Name); + + loadedCourse.Should().BeEquivalentTo(testCourse); + } +} \ No newline at end of file diff --git a/Management.Test/ViewModels/MonthDetailTests.cs b/Management.Test/ViewModels/MonthDetailTests.cs index e79e878..9865fa2 100644 --- a/Management.Test/ViewModels/MonthDetailTests.cs +++ b/Management.Test/ViewModels/MonthDetailTests.cs @@ -5,9 +5,11 @@ public class MonthDetailTests [Test] public void TestCanGetMonthName() { - var detail = new MonthDetail(); var calendarMonth = new CalendarMonth(2022, 2); - detail.Month = calendarMonth; + var detail = new MonthDetail() + { + Month = calendarMonth + }; detail.MonthName.Should().Be("February"); } diff --git a/Management.Web/Pages/AssignmentFormPage.razor b/Management.Web/Pages/AssignmentFormPage.razor index 96c9ff5..677a781 100644 --- a/Management.Web/Pages/AssignmentFormPage.razor +++ b/Management.Web/Pages/AssignmentFormPage.razor @@ -3,7 +3,6 @@ @using CanvasModel.EnrollmentTerms @using Management.Web.Shared.Components.AssignmentForm @using Management.Web.Shared.Course -@using Management.Web.Shared.Module.Assignment.Templates @using Management.Web.Shared.Semester @using CanvasModel.Courses @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage diff --git a/Management.Web/Pages/Course.razor b/Management.Web/Pages/Course.razor index b0fd274..3275b55 100644 --- a/Management.Web/Pages/Course.razor +++ b/Management.Web/Pages/Course.razor @@ -1,7 +1,6 @@ @page "/course/{CourseName}" @using CanvasModel.EnrollmentTerms @using Management.Web.Shared.Course -@using Management.Web.Shared.Module.Assignment.Templates @using Management.Web.Shared.Semester @using CanvasModel.Courses @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage diff --git a/Management.Web/Pages/Index.razor b/Management.Web/Pages/Index.razor index b8c5cda..628df27 100644 --- a/Management.Web/Pages/Index.razor +++ b/Management.Web/Pages/Index.razor @@ -1,7 +1,6 @@ @page "/" @using CanvasModel.EnrollmentTerms @using Management.Web.Shared.Course -@using Management.Web.Shared.Module.Assignment.Templates @using Management.Web.Shared.Semester @using CanvasModel.Courses @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage diff --git a/Management.Web/Pages/QuizFormPage.razor b/Management.Web/Pages/QuizFormPage.razor index 832a37b..41fc042 100644 --- a/Management.Web/Pages/QuizFormPage.razor +++ b/Management.Web/Pages/QuizFormPage.razor @@ -4,7 +4,6 @@ @using CanvasModel.Quizzes @using Management.Web.Shared.Components.AssignmentForm @using Management.Web.Shared.Course -@using Management.Web.Shared.Module.Assignment.Templates @using Management.Web.Shared.Semester @using CanvasModel.Courses @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage diff --git a/Management.Web/Shared/Components/AssignmentForm/AssignmentMarkdownEditor.razor b/Management.Web/Shared/Components/AssignmentForm/AssignmentMarkdownEditor.razor index 8c4adbb..8817a2a 100644 --- a/Management.Web/Shared/Components/AssignmentForm/AssignmentMarkdownEditor.razor +++ b/Management.Web/Shared/Components/AssignmentForm/AssignmentMarkdownEditor.razor @@ -32,15 +32,6 @@ public string? TemplateId { get; set; } - public Dictionary VariableValues { get; set; } = new Dictionary(); - - private AssignmentTemplate? selectedTemplate => - planner - .LocalCourse? - .Settings - .AssignmentTemplates - .FirstOrDefault(t => t.Id == TemplateId); - private void handleChange(string newRawAssignment) { rawText = newRawAssignment; diff --git a/Management.Web/Shared/Module/Assignment/Templates/AssignmentTemplateManagement.razor b/Management.Web/Shared/Module/Assignment/Templates/AssignmentTemplateManagement.razor deleted file mode 100644 index eff634f..0000000 --- a/Management.Web/Shared/Module/Assignment/Templates/AssignmentTemplateManagement.razor +++ /dev/null @@ -1,125 +0,0 @@ -@using Management.Web.Shared.Components - - -@inject CoursePlanner planner - -@code -{ - private Modal modal { get; set; } = default!; - private string newTemplateName { get; set; } = ""; - protected override void OnInitialized() - { - planner.StateHasChanged += reload; - } - private void reload() - { - this.InvokeAsync(this.StateHasChanged); - } - public void Dispose() - { - planner.StateHasChanged -= reload; - } - - private string? _selectedTemplateId; - private string? selectedTemplateId - { - get { return _selectedTemplateId; } - set { _selectedTemplateId = value; } - } - - private AssignmentTemplate? selectedTemplate => - planner - .LocalCourse? - .Settings - .AssignmentTemplates - .FirstOrDefault(t => t.Id == selectedTemplateId); - - private void newTemplate() - { - if (planner.LocalCourse != null) - { - var newOne = new AssignmentTemplate() - { - Id=Guid.NewGuid().ToString(), - Name=newTemplateName - }; - planner.LocalCourse = planner.LocalCourse with - { - Settings = planner.LocalCourse.Settings with - { - AssignmentTemplates = planner.LocalCourse.Settings.AssignmentTemplates.Append(newOne) - } - }; - newTemplateName = ""; - } - } - -} - modal.Show())" -> - Manage Assignment Templates - - -@if(planner.LocalCourse != null) -{ - - - Assignment Templates - - - - - - Templates - - - @foreach (var template in planner.LocalCourse.Settings.AssignmentTemplates) - { - @template.Name - } - - - - - - - New Template Name - - - - New Template - - - - - @if(selectedTemplate != null) - { - - } - - - - -} diff --git a/Management.Web/Shared/Module/Assignment/Templates/TemplateEditor.razor b/Management.Web/Shared/Module/Assignment/Templates/TemplateEditor.razor deleted file mode 100644 index d646a25..0000000 --- a/Management.Web/Shared/Module/Assignment/Templates/TemplateEditor.razor +++ /dev/null @@ -1,96 +0,0 @@ -@using Markdig - -@inject CoursePlanner planner - -@code -{ - [Parameter, EditorRequired] - public AssignmentTemplate Template { get; set; } = default!; - - protected override void OnInitialized() - { - base.OnInitialized(); - } - public string Preview => Markdown.ToHtml(Template.Markdown); - - private void SetName(string newName) - { - if(planner.LocalCourse != null) - { - var newTemplates = planner.LocalCourse.Settings.AssignmentTemplates.Select(t => - t.Id == Template.Id - ? t with { Name=newName } - : t - ); - planner.LocalCourse = planner.LocalCourse with - { - Settings = planner.LocalCourse.Settings with - { - AssignmentTemplates=newTemplates - } - }; - } - } - private void SetMarkdown(string newMarkdown) - { - if(planner.LocalCourse != null) - { - var newTemplates = planner.LocalCourse.Settings.AssignmentTemplates.Select(t => - t.Id == Template.Id - ? t with { Markdown=newMarkdown } - : t - ); - planner.LocalCourse = planner.LocalCourse with - { - Settings = planner.LocalCourse.Settings with - { - AssignmentTemplates=newTemplates - } - }; - } - } -} - - - - { - var newValue = (string) (e.Value ?? ""); - SetName(newValue); - })" - > - - - - - - { - var newValue = (string) (e.Value ?? ""); - SetMarkdown(newValue); - })" - /> - - - @((MarkupString) Preview) - - - - -Detected Template Variables - - - - @foreach (var variable in AssignmentTemplate.GetVariables(Template.Markdown)) - { - @variable - } - - - \ No newline at end of file diff --git a/Management/Models/Local/Assignment/AssignmentSubmissionType.cs b/Management/Models/Local/Assignment/AssignmentSubmissionType.cs new file mode 100644 index 0000000..c9170dd --- /dev/null +++ b/Management/Models/Local/Assignment/AssignmentSubmissionType.cs @@ -0,0 +1,28 @@ +namespace LocalModels; + +public static class AssignmentSubmissionType +{ + public static readonly string ONLINE_TEXT_ENTRY = "online_text_entry"; + public static readonly string ONLINE_UPLOAD = "online_upload"; + public static readonly string ONLINE_QUIZ = "online_quiz"; + // public static readonly string ON_PAPER = "on_paper"; + public static readonly string DISCUSSION_TOPIC = "discussion_topic"; + // public static readonly string EXTERNAL_TOOL = "external_tool"; + public static readonly string ONLINE_URL = "online_url"; + // public static readonly string MEDIA_RECORDING = "media_recording"; + // public static readonly string STUDENT_ANNOTATION = "student_annotation"; + public static readonly string NONE = "none"; + public static readonly IEnumerable AllTypes = new string[] + { + ONLINE_TEXT_ENTRY, + ONLINE_UPLOAD, + ONLINE_QUIZ, + // ON_PAPER, + DISCUSSION_TOPIC, + // EXTERNAL_TOOL, + ONLINE_URL, + // MEDIA_RECORDING, + // STUDENT_ANNOTATION, + NONE, + }; +} diff --git a/Management/Models/Local/AssignmentTemplate.cs b/Management/Models/Local/Assignment/AssignmentTemplate.cs similarity index 100% rename from Management/Models/Local/AssignmentTemplate.cs rename to Management/Models/Local/Assignment/AssignmentTemplate.cs diff --git a/Management/Models/Local/LocalAssignment.cs b/Management/Models/Local/Assignment/LocalAssignment.cs similarity index 83% rename from Management/Models/Local/LocalAssignment.cs rename to Management/Models/Local/Assignment/LocalAssignment.cs index 2d833c5..86b11a9 100644 --- a/Management/Models/Local/LocalAssignment.cs +++ b/Management/Models/Local/Assignment/LocalAssignment.cs @@ -6,41 +6,6 @@ using YamlDotNet.Serialization; namespace LocalModels; -public record RubricItem -{ - public static readonly string extraCredit = "(Extra Credit) "; - public required string Label { get; set; } - public required int Points { get; set; } - public bool IsExtraCredit => Label.Contains(extraCredit.ToLower(), StringComparison.CurrentCultureIgnoreCase); -} - -public static class AssignmentSubmissionType -{ - public static readonly string ONLINE_TEXT_ENTRY = "online_text_entry"; - public static readonly string ONLINE_UPLOAD = "online_upload"; - public static readonly string ONLINE_QUIZ = "online_quiz"; - // public static readonly string ON_PAPER = "on_paper"; - public static readonly string DISCUSSION_TOPIC = "discussion_topic"; - // public static readonly string EXTERNAL_TOOL = "external_tool"; - public static readonly string ONLINE_URL = "online_url"; - // public static readonly string MEDIA_RECORDING = "media_recording"; - // public static readonly string STUDENT_ANNOTATION = "student_annotation"; - public static readonly string NONE = "none"; - public static readonly IEnumerable AllTypes = new string[] - { - ONLINE_TEXT_ENTRY, - ONLINE_UPLOAD, - ONLINE_QUIZ, - // ON_PAPER, - DISCUSSION_TOPIC, - // EXTERNAL_TOOL, - ONLINE_URL, - // MEDIA_RECORDING, - // STUDENT_ANNOTATION, - NONE, - }; -} - public record LocalAssignment { // public ulong? CanvasId { get; init; } = null; diff --git a/Management/Models/Local/LocalAssignmentGroup.cs b/Management/Models/Local/Assignment/LocalAssignmentGroup.cs similarity index 100% rename from Management/Models/Local/LocalAssignmentGroup.cs rename to Management/Models/Local/Assignment/LocalAssignmentGroup.cs diff --git a/Management/Models/Local/Assignment/RubricItem.cs b/Management/Models/Local/Assignment/RubricItem.cs new file mode 100644 index 0000000..a54dcd8 --- /dev/null +++ b/Management/Models/Local/Assignment/RubricItem.cs @@ -0,0 +1,9 @@ +namespace LocalModels; + +public record RubricItem +{ + public static readonly string extraCredit = "(Extra Credit) "; + public required string Label { get; set; } + public required int Points { get; set; } + public bool IsExtraCredit => Label.Contains(extraCredit.ToLower(), StringComparison.CurrentCultureIgnoreCase); +} diff --git a/Management/Models/Local/LocalCourse.cs b/Management/Models/Local/LocalCourse.cs index 41f8bf1..ce2d6cb 100644 --- a/Management/Models/Local/LocalCourse.cs +++ b/Management/Models/Local/LocalCourse.cs @@ -18,8 +18,6 @@ public record LocalCourseSettings public DateTime StartDate { get; init; } public DateTime EndDate { get; init; } public SimpleTimeOnly DefaultDueTime { get; init; } = new SimpleTimeOnly(); - public IEnumerable AssignmentTemplates { get; init; } = - Enumerable.Empty(); public string ToYaml() { diff --git a/Management/Models/Local/LocalQuiz.cs b/Management/Models/Local/Quiz/LocalQuiz.cs similarity index 100% rename from Management/Models/Local/LocalQuiz.cs rename to Management/Models/Local/Quiz/LocalQuiz.cs diff --git a/Management/Models/Local/LocalQuizQuestion.cs b/Management/Models/Local/Quiz/LocalQuizQuestion.cs similarity index 100% rename from Management/Models/Local/LocalQuizQuestion.cs rename to Management/Models/Local/Quiz/LocalQuizQuestion.cs diff --git a/Management/Models/Local/LocalQuizQuestionAnswer.cs b/Management/Models/Local/Quiz/LocalQuizQuestionAnswer.cs similarity index 100% rename from Management/Models/Local/LocalQuizQuestionAnswer.cs rename to Management/Models/Local/Quiz/LocalQuizQuestionAnswer.cs diff --git a/Management/Services/Files/FileConfiguration.cs b/Management/Services/Files/FileConfiguration.cs new file mode 100644 index 0000000..4f1bc1d --- /dev/null +++ b/Management/Services/Files/FileConfiguration.cs @@ -0,0 +1,17 @@ +using Management.Services; + +public class FileConfiguration +{ + + public static string GetBasePath() + { + string? storageDirectory = Environment.GetEnvironmentVariable("storageDirectory"); + var basePath = storageDirectory ?? Path.GetFullPath("../storage"); + + if (!Directory.Exists(basePath)) + throw new Exception("storage folder not found"); + + return basePath; + + } +} \ No newline at end of file diff --git a/Management/Services/FileStorageManager.cs b/Management/Services/Files/FileStorageManager.cs similarity index 66% rename from Management/Services/FileStorageManager.cs rename to Management/Services/Files/FileStorageManager.cs index f25d471..d5ef579 100644 --- a/Management/Services/FileStorageManager.cs +++ b/Management/Services/Files/FileStorageManager.cs @@ -5,14 +5,22 @@ using YamlDotNet.Serialization; public class FileStorageManager { private readonly MyLogger logger; - private static readonly string _basePath = "../storage"; + private readonly CourseMarkdownLoader _courseMarkdownLoader; + private readonly string _basePath; - public FileStorageManager(MyLogger logger) + public FileStorageManager( + MyLogger logger, + CourseMarkdownLoader courseMarkdownLoader + ) { - if (!Directory.Exists(_basePath)) - throw new Exception("storage folder not found"); this.logger = logger; + _courseMarkdownLoader = courseMarkdownLoader; + _basePath = FileConfiguration.GetBasePath(); + + logger.Log("Using storage directory: " + _basePath); + } + public string CourseToYaml(LocalCourse course) { var serializer = new SerializerBuilder().DisableAliases().Build(); @@ -131,6 +139,7 @@ public class FileStorageManager } removeOldAssignments(assignmentsDirectory, module); } + private void removeOldAssignments(string path, LocalModule module) { var existingFiles = Directory.EnumerateFiles(path); @@ -166,68 +175,9 @@ public class FileStorageManager return courses; } - // public async Task LoadCourseByName(string courseName) - // { - // var courseDirectory = $"{_basePath}/{courseName}"; - // if (!Directory.Exists(courseDirectory)) - // { - // var errorMessage = $"error loading course by name, could not find folder {courseDirectory}"; - // logger.Log(errorMessage); - // throw new LoadCourseFromFileException(errorMessage); - // } - // var settingsPath = $"{courseDirectory}/settings.yml"; - // if (!Directory.Exists(settingsPath)) - // { - // var errorMessage = $"error loading course by name, settings file {settingsPath}"; - // logger.Log(errorMessage); - // throw new LoadCourseFromFileException(errorMessage); - // } + public async Task> LoadSavedMarkdownCourses() + { + return await _courseMarkdownLoader.LoadSavedMarkdownCourses(); + } - // var settingsString = await File.ReadAllTextAsync(settingsPath); - // var settings = LocalCourseSettings.ParseYaml(settingsString); - - // var modulePaths = Directory.GetDirectories(courseDirectory); - // var modules = modulePaths - // .Select(LoadModuleFromPath) - // .ToArray(); - - // } - - // public async Task LoadModuleFromPath(string modulePath) - // { - // var assignmentsPath = $"{modulePath}/assignments"; - // if (!Directory.Exists(assignmentsPath)) - // { - // var errorMessage = $"error loading course by name, assignments folder does not exist in {modulePath}"; - // logger.Log(errorMessage); - // throw new LoadCourseFromFileException(errorMessage); - // } - - // var quizzesPath = $"{modulePath}/quizzes"; - // if (!Directory.Exists(quizzesPath)) - // { - // var errorMessage = $"error loading course by name, quizzes folder does not exist in {modulePath}"; - // logger.Log(errorMessage); - // throw new LoadCourseFromFileException(errorMessage); - // } - - - // var assignments = LoadAssignmentsFromPath(assignmentsPath); - // var quizzes = LoadQuizzesFromPath(quizzesPath); - - - // } - // public async Task> LoadAssignmentsFromPath(string assignmentsFolder) - // { - - // } - // public async Task> LoadQuizzesFromPath(string quizzesFolder) - // { - - // } -} - - -public class LoadCourseFromFileException(string message) : Exception(message) -{ } \ No newline at end of file diff --git a/Management/Services/Files/LoadCourseFromFileException.cs b/Management/Services/Files/LoadCourseFromFileException.cs new file mode 100644 index 0000000..bc4b6b9 --- /dev/null +++ b/Management/Services/Files/LoadCourseFromFileException.cs @@ -0,0 +1,3 @@ +public class LoadCourseFromFileException(string message) : Exception(message) +{ +} diff --git a/Management/Services/Files/LoadMarkdownCourse.cs b/Management/Services/Files/LoadMarkdownCourse.cs new file mode 100644 index 0000000..91fe2e1 --- /dev/null +++ b/Management/Services/Files/LoadMarkdownCourse.cs @@ -0,0 +1,127 @@ +using System.Reflection.Metadata.Ecma335; +using LocalModels; +using Management.Services; +using YamlDotNet.Serialization; + +public class CourseMarkdownLoader +{ + private readonly MyLogger logger; + private readonly string _basePath; + + public CourseMarkdownLoader(MyLogger logger) + { + this.logger = logger; + _basePath = FileConfiguration.GetBasePath(); + } + + public async Task> LoadSavedMarkdownCourses() + { + var courseDirectories = Directory.GetDirectories(_basePath); + + var courses = await Task.WhenAll( + courseDirectories.Select(async n => await LoadCourseByPath(n)) + ); + return courses; + } + + public async Task LoadCourseByPath(string courseDirectory) + { + if (!Directory.Exists(courseDirectory)) + { + var errorMessage = $"error loading course by name, could not find folder {courseDirectory}"; + logger.Log(errorMessage); + throw new LoadCourseFromFileException(errorMessage); + } + + LocalCourseSettings settings = await loadCourseSettings(courseDirectory); + var modules = await loadCourseModules(courseDirectory); + + return new() + { + Settings = settings, + Modules = modules + }; + + + } + + private async Task loadCourseSettings(string courseDirectory) + { + var settingsPath = $"{courseDirectory}/settings.yml"; + if (!File.Exists(settingsPath)) + { + var errorMessage = $"error loading course by name, settings file {settingsPath}"; + logger.Log(errorMessage); + throw new LoadCourseFromFileException(errorMessage); + } + + var settingsString = await File.ReadAllTextAsync(settingsPath); + var settings = LocalCourseSettings.ParseYaml(settingsString); + return settings; + } + + private async Task> loadCourseModules(string courseDirectory) + { + var modulePaths = Directory.GetDirectories(courseDirectory); + var modules = await Task.WhenAll( + modulePaths + .Select(loadModuleFromPath) + ); + return modules; + } + + private async Task loadModuleFromPath(string modulePath) + { + var moduleName = Path.GetFileName(modulePath); + var assignments = await loadAssignmentsFromPath(modulePath); + var quizzes = await loadQuizzesFromPath(modulePath); + + return new LocalModule() + { + Name = moduleName, + Assignments = assignments, + Quizzes = quizzes, + }; + } + + private async Task> loadAssignmentsFromPath(string modulePath) + { + var assignmentsPath = $"{modulePath}/assignments"; + if (!Directory.Exists(assignmentsPath)) + { + var errorMessage = $"error loading course by name, assignments folder does not exist in {modulePath}"; + logger.Log(errorMessage); + throw new LoadCourseFromFileException(errorMessage); + } + var assignmentFiles = Directory.GetFiles(assignmentsPath); + var assignmentPromises = assignmentFiles + .Select(async filePath => + { + var rawFile = await File.ReadAllTextAsync(filePath); + return LocalAssignment.ParseMarkdown(rawFile); + }) + .ToArray(); + return await Task.WhenAll(assignmentPromises); + } + + private async Task> loadQuizzesFromPath(string modulePath) + { + var quizzesPath = $"{modulePath}/quizzes"; + if (!Directory.Exists(quizzesPath)) + { + var errorMessage = $"error loading course by name, quizzes folder does not exist in {modulePath}"; + logger.Log(errorMessage); + throw new LoadCourseFromFileException(errorMessage); + } + + var quizFiles = Directory.GetFiles(quizzesPath); + var quizPromises = quizFiles + .Select(async path => + { + var rawQuiz = await File.ReadAllTextAsync(path); + return LocalQuiz.ParseMarkdown(rawQuiz); + }); + + return await Task.WhenAll(quizPromises); + } +} \ No newline at end of file