From d691f817b7c56a6631c4971142cae84fb6a88971 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Sun, 16 Jul 2023 01:08:30 -0600 Subject: [PATCH] data types are better --- Management.Web/Pages/Index.razor | 47 ++++++++------ Management.Web/Program.cs | 3 + .../Shared/Module/ModuleDetail.razor | 47 ++++++-------- Management.Web/Shared/Module/Modules.razor | 4 +- Management.Web/Shared/Module/NewModule.razor | 10 ++- Management.Web/Utils/StorageManagement.cs | 1 + .../Features/Configuration/CoursePlanner.cs | 22 +++---- .../CanvasModels/Courses/CourseModel.cs | 26 +++++--- Management/Models/CourseModule.cs | 17 +++--- Management/Services/CanvasService.cs | 43 ++++++++++--- Management/Services/IWebRequestor.cs | 6 +- Management/Services/WebRequestor.cs | 61 +++++++++++++++++-- requests/semester.http | 8 +++ 13 files changed, 196 insertions(+), 99 deletions(-) diff --git a/Management.Web/Pages/Index.razor b/Management.Web/Pages/Index.razor index 294e915..9bdf81f 100644 --- a/Management.Web/Pages/Index.razor +++ b/Management.Web/Pages/Index.razor @@ -4,8 +4,8 @@ @using CanvasModel.Courses @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@inject CanvasService canvasService -@inject CoursePlanner configurationManagement +@inject CanvasService canvas +@inject CoursePlanner planner @inject ProtectedLocalStorage BrowserStorage @code @@ -30,10 +30,11 @@ } private ulong? selectedCourseId { - get => configurationManagement.Course?.Id; + get => planner.Course?.Id; set { - configurationManagement.Course = courses?.First(c => c.Id == value); + planner.Course = courses?.First(c => c.Id == value); + updateModules(); } } @@ -42,7 +43,7 @@ protected override async Task OnInitializedAsync() { - terms = await canvasService.GetCurrentTermsFor(); + terms = await canvas.GetCurrentTermsFor(); readTermFromConfig(); readDaysFromConfig(); } @@ -54,8 +55,7 @@ var storedConfiguration = await BrowserStorage.GetAsync(semesterConfigurationKey); if (storedConfiguration.Success) { - Console.WriteLine(JsonSerializer.Serialize(storedConfiguration.Value)); - configurationManagement.SemesterCalendar = storedConfiguration.Value; + planner.SemesterCalendar = storedConfiguration.Value; } else { @@ -71,9 +71,7 @@ { loadingCourses = true; - courses = await canvasService.GetCourses((ulong)selectedTermId); - System.Console.WriteLine(courses); - System.Console.WriteLine(selectedTermId); + courses = await canvas.GetCourses((ulong)selectedTermId); loadingCourses = false; } else @@ -81,12 +79,21 @@ StateHasChanged(); } + + private async Task updateModules() + { + if(planner.Course != null) + { + planner.Modules = await canvas.GetModules(planner.Course.Id); + } + StateHasChanged(); + } private void readTermFromConfig() { - if (terms == null || configurationManagement.SemesterCalendar == null) return; + if (terms == null || planner.SemesterCalendar == null) return; foreach (var term in terms) { - var termInConfiguration = configurationManagement.SemesterCalendar.StartDate == term.StartAt; + var termInConfiguration = planner.SemesterCalendar.StartDate == term.StartAt; if (termInConfiguration) { selectedTermId = term.Id; @@ -96,24 +103,22 @@ private void readDaysFromConfig() { - if (terms == null || configurationManagement.SemesterCalendar == null) return; + if (terms == null || planner.SemesterCalendar == null) return; - days = configurationManagement.SemesterCalendar.Days.ToList(); + days = planner.SemesterCalendar.Days.ToList(); } public async void HandleSave() { saved = true; - configurationManagement.SetConfiguration( + planner.SetConfiguration( selectedTerm ?? throw new Exception("cannot save configuration without selecting term"), days.ToArray() ); await BrowserStorage.SetAsync( semesterConfigurationKey, - configurationManagement.SemesterCalendar ?? throw new Exception("Semester Calendar configuration not properly configured") + planner.SemesterCalendar ?? throw new Exception("Semester Calendar configuration not properly configured") ); - - Console.WriteLine(JsonSerializer.Serialize(await BrowserStorage.GetAsync(semesterConfigurationKey))); } } Index @@ -182,8 +187,12 @@ } +@if(planner.Modules != null) +{ +
@JsonSerializer.Serialize(planner.Modules)
+} -@if (configurationManagement.SemesterCalendar is not null) +@if (planner.SemesterCalendar is not null) {
Config complete
} \ No newline at end of file diff --git a/Management.Web/Program.cs b/Management.Web/Program.cs index 4469cc7..c40e0f7 100644 --- a/Management.Web/Program.cs +++ b/Management.Web/Program.cs @@ -1,6 +1,9 @@ global using System.Text.Json.Serialization; global using System.Text.Json; global using System.ComponentModel.DataAnnotations; +global using CanvasModel.EnrollmentTerms; +global using CanvasModel.Courses; +global using CanvasModel; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; diff --git a/Management.Web/Shared/Module/ModuleDetail.razor b/Management.Web/Shared/Module/ModuleDetail.razor index 7d038f0..ba23e43 100644 --- a/Management.Web/Shared/Module/ModuleDetail.razor +++ b/Management.Web/Shared/Module/ModuleDetail.razor @@ -2,39 +2,30 @@ @inject CoursePlanner configurationManagement @code { - [Parameter, EditorRequired] - public int ModuleIndex { get; set; } + [Parameter, EditorRequired] + public CourseModule Module { get; set; } = default!; private bool showAddAssignment { get; set; } = false; - private CourseModule? module - { - get - { - return configurationManagement.Modules.ElementAtOrDefault(ModuleIndex); - } - } } -@if (module != null) + +

@Module.Name

+ +@if (showAddAssignment) { -

@module.Name

- - @if (showAddAssignment) - { -
-
- -
-
- } + @*
+
+ +
+
*@ +} -
Assignments
-
+
Assignments
+
- @foreach (var a in module.Assignments) - { - - } -
-} \ No newline at end of file +@* @foreach (var a in module.Assignments) +{ + +} *@ +
diff --git a/Management.Web/Shared/Module/Modules.razor b/Management.Web/Shared/Module/Modules.razor index f2d7f28..aff458f 100644 --- a/Management.Web/Shared/Module/Modules.razor +++ b/Management.Web/Shared/Module/Modules.razor @@ -34,10 +34,10 @@ else } -@foreach (var i in configurationManagement.Modules.Select((_value, i) => i)) +@foreach (var module in configurationManagement.Modules) {
- + }
diff --git a/Management.Web/Shared/Module/NewModule.razor b/Management.Web/Shared/Module/NewModule.razor index 86ca6e0..583b322 100644 --- a/Management.Web/Shared/Module/NewModule.razor +++ b/Management.Web/Shared/Module/NewModule.razor @@ -1,4 +1,5 @@ -@inject CoursePlanner configurationManagement +@inject CoursePlanner planner +@inject CanvasService canvas @code { @@ -11,8 +12,11 @@ private async Task submitHandler() { - var module = new CourseModule(Name: Name, Assignments: new LocalAssignment[] { }); - configurationManagement.Modules = configurationManagement.Modules.Append(module); + if(planner.Course != null && Name != "") + { + await canvas.CreateModule(planner.Course.Id, Name); + planner.Modules = await canvas.GetModules(planner.Course.Id); + } Name = ""; await OnSubmit.InvokeAsync(); } diff --git a/Management.Web/Utils/StorageManagement.cs b/Management.Web/Utils/StorageManagement.cs index 30fd8cc..e878c76 100644 --- a/Management.Web/Utils/StorageManagement.cs +++ b/Management.Web/Utils/StorageManagement.cs @@ -52,6 +52,7 @@ public class StorageManagement { // var courses = planner.Course = await canvas.GetCourse(storedCourseId.Value); + planner.Modules = await canvas.GetModules(planner.Course.Id); } else { diff --git a/Management/Features/Configuration/CoursePlanner.cs b/Management/Features/Configuration/CoursePlanner.cs index 82a062d..afe58f3 100644 --- a/Management/Features/Configuration/CoursePlanner.cs +++ b/Management/Features/Configuration/CoursePlanner.cs @@ -1,21 +1,19 @@ using CanvasModel.EnrollmentTerms; using CanvasModel.Courses; +using CanvasModel; public class CoursePlanner { - public void SetConfiguration( - EnrollmentTermModel canvasTerm, - DayOfWeek[] daysOfWeek - ) + public void SetConfiguration(EnrollmentTermModel canvasTerm, DayOfWeek[] daysOfWeek) { - var start = canvasTerm.StartAt ?? throw new Exception($"Canvas Term must have a start date. Term: {canvasTerm.Name}"); - var end = canvasTerm.EndAt ?? throw new Exception($"Canvas Term must have a end date. Term: {canvasTerm.Name}"); + var start = + canvasTerm.StartAt + ?? throw new Exception($"Canvas Term must have a start date. Term: {canvasTerm.Name}"); + var end = + canvasTerm.EndAt + ?? throw new Exception($"Canvas Term must have a end date. Term: {canvasTerm.Name}"); - SemesterCalendar = new SemesterCalendarConfig( - StartDate: start, - EndDate: end, - Days: daysOfWeek - ); + SemesterCalendar = new SemesterCalendarConfig(StartDate: start, EndDate: end, Days: daysOfWeek); } public SemesterCalendarConfig? SemesterCalendar { get; set; } = null; @@ -23,4 +21,4 @@ public class CoursePlanner public IEnumerable Modules { get; set; } = new CourseModule[] { }; public IEnumerable Assignments { get; set; } = new LocalAssignment[] { }; public CourseModel? Course { get; set; } = null; -} \ No newline at end of file +} diff --git a/Management/Models/CanvasModels/Courses/CourseModel.cs b/Management/Models/CanvasModels/Courses/CourseModel.cs index e0b907e..ce71973 100644 --- a/Management/Models/CanvasModels/Courses/CourseModel.cs +++ b/Management/Models/CanvasModels/Courses/CourseModel.cs @@ -1,8 +1,8 @@ using CanvasModel.Enrollments; namespace CanvasModel.Courses; -public record CourseModel -( + +public record CourseModel( [property: JsonPropertyName("id")] ulong Id, [property: JsonPropertyName("sis_course_id")] string SisCourseId, [property: JsonPropertyName("uuid")] string Uuid, @@ -33,21 +33,27 @@ public record CourseModel [property: JsonPropertyName("needs_grading_count")] uint? NeedsGradingCount = null, [property: JsonPropertyName("term")] TermModel? Term = null, [property: JsonPropertyName("course_progress")] CourseProgressModel? CourseProgress = null, - [property: JsonPropertyName("apply_assignment_group_weights")] bool? ApplyAssignmentGroupWeights = null, - [property: JsonPropertyName("is_public")] bool? Is= null, + [property: JsonPropertyName("apply_assignment_group_weights")] + bool? ApplyAssignmentGroupWeights = null, + [property: JsonPropertyName("is_public")] bool? Is = null, [property: JsonPropertyName("is_public_to_auth_users")] bool? IsPublicToAuthUsers = null, [property: JsonPropertyName("public_syllabus")] bool? PublicSyllabus = null, [property: JsonPropertyName("public_syllabus_to_auth")] bool? PublicSyllabusToAuth = null, [property: JsonPropertyName("public_description")] string? PublicDescription = null, [property: JsonPropertyName("hide_final_grades")] bool? HideFinalGrades = null, - [property: JsonPropertyName("allow_student_assignment_edits")] bool? AllowStudentAssignmentEdits = null, + [property: JsonPropertyName("allow_student_assignment_edits")] + bool? AllowStudentAssignmentEdits = null, [property: JsonPropertyName("allow_wiki_comments")] bool? AllowWikiComments = null, - [property: JsonPropertyName("allow_student_forum_attachments")] bool? AllowStudentForumAttachments = null, + [property: JsonPropertyName("allow_student_forum_attachments")] + bool? AllowStudentForumAttachments = null, [property: JsonPropertyName("open_enrollment")] bool? OpenEnrollment = null, [property: JsonPropertyName("self_enrollment")] bool? SelfEnrollment = null, - [property: JsonPropertyName("restrict_enrollments_to_courses")] bool? RestrictEnrollmentsToCourseDates = null, + [property: JsonPropertyName("restrict_enrollments_to_courses")] + bool? RestrictEnrollmentsToCourseDates = null, [property: JsonPropertyName("access_restricted_by_date")] bool? AccessRestrictedByDate = null, [property: JsonPropertyName("blueprint")] bool? Blueprint = null, - [property: JsonPropertyName("blueprint_restrictions")] Dictionary? BlueprintRestrictions = null, - [property: JsonPropertyName("blueprint_restrictions_by_object_type")] Dictionary>? BlueprintRestrictionsByObjectType = null -); \ No newline at end of file + [property: JsonPropertyName("blueprint_restrictions")] + Dictionary? BlueprintRestrictions = null, + [property: JsonPropertyName("blueprint_restrictions_by_object_type")] + Dictionary>? BlueprintRestrictionsByObjectType = null +); diff --git a/Management/Models/CourseModule.cs b/Management/Models/CourseModule.cs index 9464ad9..4673239 100644 --- a/Management/Models/CourseModule.cs +++ b/Management/Models/CourseModule.cs @@ -1,12 +1,11 @@ using System.ComponentModel.DataAnnotations; +namespace CanvasModel; + public record CourseModule( - [property: Required] - [property: StringLength(50, ErrorMessage = "Name too long (50 character limit).")] - string Name, - IEnumerable? Assignments = null -) -{ - [JsonInclude] - public IEnumerable Assignments = Assignments ?? new LocalAssignment[] { }; -} \ No newline at end of file + [property: JsonPropertyName("id")] ulong Id, + [property: JsonPropertyName("name")] string Name + // [property: JsonPropertyName("start_at")] DateTime StartAt, + // [property: JsonPropertyName("end_at")] DateTime EndAt, + // [property: JsonPropertyName("description")] string Description +); diff --git a/Management/Services/CanvasService.cs b/Management/Services/CanvasService.cs index 8b17d16..09bb77a 100644 --- a/Management/Services/CanvasService.cs +++ b/Management/Services/CanvasService.cs @@ -1,3 +1,4 @@ +using CanvasModel; using CanvasModel.Courses; using CanvasModel.EnrollmentTerms; using RestSharp; @@ -40,35 +41,59 @@ public class CanvasService : ICanvasService public async Task GetCourse(ulong courseId) { - var url = $"course/${courseId}"; + var url = $"course/{courseId}"; var request = new RestRequest(url); - var response = await webRequestor.GetAsync(request); + var (data, response) = await webRequestor.GetAsync(request); - if (response.Data == null) + if (data == null) { System.Console.WriteLine(response.Content); System.Console.WriteLine(response.ResponseUri); throw new Exception("error getting course from canvas"); } - return response.Data; + return data; + } + + public async Task> GetModules(ulong courseId) + { + var url = $"courses/{courseId}/modules"; + var request = new RestRequest(url); + var modules = await PaginatedRequest>(request); + return modules.SelectMany(c => c).ToArray(); + } + + public async Task CreateModule(ulong courseId, string name) + { + var url = $"courses/{courseId}/modules"; + var request = new RestRequest(url); + request.AddParameter("module[name]", name); + + await webRequestor.PostAsync(request); } private async Task> PaginatedRequest(RestRequest request) { var requestCount = 1; request.AddQueryParameter("per_page", "100"); - RestResponse response = await webRequestor.GetAsync(request); + var (data, response) = await webRequestor.GetAsync(request); - var returnData = response.Data != null ? new T[] { response.Data } : new T[] { }; + if (response.ErrorMessage?.Length > 0) + { + System.Console.WriteLine("error in response"); + System.Console.WriteLine(response.ErrorMessage); + throw new Exception("error in response"); + } + + var returnData = data != null ? new T[] { data } : new T[] { }; var nextUrl = getNextUrl(response.Headers); while (nextUrl is not null) { requestCount += 1; RestRequest nextRequest = new RestRequest(nextUrl); - var nextResponse = await webRequestor.GetAsync(nextRequest); - if (nextResponse.Data is not null) - returnData = returnData.Append(nextResponse.Data).ToArray(); + var (nextData, nextResponse) = await webRequestor.GetAsync(nextRequest); + if (nextData is not null) + returnData = returnData.Append(nextData).ToArray(); nextUrl = getNextUrl(nextResponse.Headers); } diff --git a/Management/Services/IWebRequestor.cs b/Management/Services/IWebRequestor.cs index cda00d0..7523348 100644 --- a/Management/Services/IWebRequestor.cs +++ b/Management/Services/IWebRequestor.cs @@ -2,6 +2,8 @@ using RestSharp; public interface IWebRequestor { - Task> GetManyAsync(RestRequest request); - Task> GetAsync(RestRequest request); + Task<(T[]?, RestResponse)> GetManyAsync(RestRequest request); + Task<(T?, RestResponse)> GetAsync(RestRequest request); + Task PostAsync(RestRequest request); + Task<(T?, RestResponse)> PostAsync(RestRequest request); } diff --git a/Management/Services/WebRequestor.cs b/Management/Services/WebRequestor.cs index b354094..b10faa2 100644 --- a/Management/Services/WebRequestor.cs +++ b/Management/Services/WebRequestor.cs @@ -13,16 +13,67 @@ public class WebRequestor : IWebRequestor ?? throw new Exception("CANVAS_TOKEN not in environment"); client = new RestClient(BaseUrl); client.AddDefaultHeader("Authorization", $"Bearer {token}"); - } - public async Task> GetManyAsync(RestRequest request) + public async Task<(T[]?, RestResponse)> GetManyAsync(RestRequest request) { - return await client.ExecuteGetAsync(request); + var response = await client.ExecuteGetAsync(request); + return (Deserialize(response), response); } - public async Task> GetAsync(RestRequest request) + public async Task<(T?, RestResponse)> GetAsync(RestRequest request) { - return await client.ExecuteGetAsync(request); + var response = await client.ExecuteGetAsync(request); + return (Deserialize(response), response); + } + + public async Task PostAsync(RestRequest request) + { + var response = await client.ExecutePostAsync(request); + if (!response.IsSuccessful) + { + System.Console.WriteLine(response.Content); + System.Console.WriteLine(response.ResponseUri); + System.Console.WriteLine("error with response"); + throw new Exception("error with response"); + } + return response; + } + + public async Task<(T?, RestResponse)> PostAsync(RestRequest request) + { + var response = await client.ExecutePostAsync(request); + return (Deserialize(response), response); + } + + public T? Deserialize(RestResponse response) + { + if (!response.IsSuccessful) + { + System.Console.WriteLine(response.Content); + System.Console.WriteLine(response.ResponseUri); + System.Console.WriteLine(response.ErrorMessage); + System.Console.WriteLine("error with response"); + throw new Exception("error with response"); + } + try + { + var data = JsonSerializer.Deserialize(response.Content); + + if (data == null) + { + System.Console.WriteLine(response.Content); + System.Console.WriteLine(response.ResponseUri); + System.Console.WriteLine("could not parse response, got empty object"); + } + return data; + } + catch (JsonException ex) + { + System.Console.WriteLine(response.ResponseUri); + System.Console.WriteLine(response.Content); + Console.WriteLine($"An error occurred during deserialization: {ex.Message}"); + throw ex; + } } } diff --git a/requests/semester.http b/requests/semester.http index 873609b..b6aaf71 100644 --- a/requests/semester.http +++ b/requests/semester.http @@ -16,4 +16,12 @@ Authorization: Bearer {{$dotenv CANVAS_TOKEN}} ### GET https://snow.instructure.com/api/v1/courses?enrollment_term_id=751&per_page=100 +Authorization: Bearer {{$dotenv CANVAS_TOKEN}} +### + +GET https://snow.instructure.com/api/v1/courses/855351/modules +Authorization: Bearer {{$dotenv CANVAS_TOKEN}} +### + +GET https://snow.instructure.com/api/v1/courses/872095/modules Authorization: Bearer {{$dotenv CANVAS_TOKEN}} \ No newline at end of file