diff --git a/Management.Web/Shared/Components/Quiz/QuizQuestionForm.razor b/Management.Web/Shared/Components/Quiz/QuizQuestionForm.razor
index 2841c40..deace05 100644
--- a/Management.Web/Shared/Components/Quiz/QuizQuestionForm.razor
+++ b/Management.Web/Shared/Components/Quiz/QuizQuestionForm.razor
@@ -82,6 +82,11 @@
UpdateQuestion(Question with { Answers = answers });
}
+ private void handleTextUpdate(ChangeEventArgs e)
+ {
+ var newText = e.Value?.ToString() ?? "";
+ UpdateQuestion(Question with { Text = newText });
+ }
}
@@ -99,6 +104,7 @@
id="question_text"
name="question_text"
@bind="text"
+ @oninput="handleTextUpdate"
/>
diff --git a/Management/Features/Configuration/CoursePlanner.cs b/Management/Features/Configuration/CoursePlanner.cs
index be57cc0..9310106 100644
--- a/Management/Features/Configuration/CoursePlanner.cs
+++ b/Management/Features/Configuration/CoursePlanner.cs
@@ -21,7 +21,7 @@ public class CoursePlanner
this.canvas = canvas;
}
- private Timer _debounceTimer;
+ private Timer? _debounceTimer;
private int _debounceInterval = 1000;
private LocalCourse? _localCourse { get; set; }
public LocalCourse? LocalCourse
@@ -54,6 +54,7 @@ public class CoursePlanner
private void saveCourseToFile(LocalCourse courseAsOfDebounce)
{
_debounceTimer?.Dispose();
+
// ignore initial load of course
if (LocalCourse == null)
{
@@ -137,6 +138,8 @@ public class CoursePlanner
LocalCourse = await LocalCourse.SyncAssignmentsWithCanvas(canvasId, CanvasAssignments, canvas);
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
+
+
await syncModuleItemsWithCanvas(canvasId);
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
diff --git a/Management/Features/Configuration/CoursePlannerSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs
similarity index 67%
rename from Management/Features/Configuration/CoursePlannerSyncronizationExtensions.cs
rename to Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs
index c796b64..bac8b8d 100644
--- a/Management/Features/Configuration/CoursePlannerSyncronizationExtensions.cs
+++ b/Management/Features/Configuration/Synchronization/AssignmentSyncronizationExtensions.cs
@@ -1,75 +1,16 @@
using System.Text.RegularExpressions;
using CanvasModel.Assignments;
using CanvasModel.Modules;
+using CanvasModel.Quizzes;
using LocalModels;
using Management.Services.Canvas;
namespace Management.Planner;
-public static partial class CoursePlannerSyncronizationExtensions
+public static partial class AssignmentSyncronizationExtensions
{
- internal static async Task> EnsureAllModulesExistInCanvas(
- this LocalCourse localCourse,
- ulong canvasId,
- IEnumerable canvasModules,
- CanvasService canvas
- )
- {
- var moduleTasks = localCourse.Modules.Select(async module =>
- {
- var canvasModule = canvasModules.FirstOrDefault(cm => cm.Id == module.CanvasId);
- if (canvasModule == null)
- {
- var newModule = await canvas.CreateModule(canvasId, module.Name);
- return module with { CanvasId = newModule.Id };
- }
- else
- return module;
- });
- var newModules = await Task.WhenAll(moduleTasks);
- return newModules ?? throw new Exception("Error ensuring all modules exist in canvas");
- }
- internal static async Task SortCanvasModules(
- this LocalCourse localCourse,
- ulong canvasId,
- IEnumerable canvasModules,
- CanvasService canvas
- )
- {
- var currentCanvasPositions = canvasModules.ToDictionary(m => m.Id, m => m.Position);
- foreach (var (localModule, i) in localCourse.Modules.Select((m, i) => (m, i)))
- {
- var correctPosition = i + 1;
- var moduleCanvasId =
- localModule.CanvasId ?? throw new Exception("cannot sort module if no module canvas id");
- var currentCanvasPosition = currentCanvasPositions[moduleCanvasId];
- if (currentCanvasPosition != correctPosition)
- {
- await canvas.UpdateModule(canvasId, moduleCanvasId, localModule.Name, correctPosition);
- }
- }
- }
-
- internal static async Task SyncModulesWithCanvasData(
- this LocalCourse localCourse,
- ulong canvasId,
- IEnumerable canvasModules,
- CanvasService canvas
- )
- {
- canvasModules = await canvas.GetModules(canvasId);
- return localCourse with
- {
- Modules = localCourse.Modules.Select(m =>
- {
- var canvasModule = canvasModules.FirstOrDefault(cm => cm.Name == m.Name);
- return canvasModule == null ? m : m with { CanvasId = canvasModule.Id };
- })
- };
- }
-
- internal static async Task SyncToCanvas(
+ internal static async Task SyncAssignmentToCanvas(
this LocalCourse localCourse,
ulong canvasId,
LocalAssignment localAssignment,
@@ -114,38 +55,51 @@ public static partial class CoursePlannerSyncronizationExtensions
var localHtmlDescription = localAssignment
.GetDescriptionHtml(courseAssignmentTemplates)
+ .Replace("
", "
")
.Replace(">", "")
.Replace("<", "")
.Replace(">", "")
- .Replace("<", "");
+ .Replace("<", "")
+ .Replace(""", "")
+ .Replace("\"", "");
var canvasHtmlDescription = canvasAssignment.Description;
canvasHtmlDescription = CanvasScriptTagRegex().Replace(canvasHtmlDescription, "");
canvasHtmlDescription = CanvasLinkTagRegex().Replace(canvasHtmlDescription, "");
canvasHtmlDescription = canvasHtmlDescription
+ .Replace("
", "
")
.Replace(">", "")
.Replace("<", "")
.Replace(">", "")
- .Replace("<", "");
+ .Replace("<", "")
+ .Replace(""", "")
+ .Replace("\"", "");
- var dueDatesSame =
+ var canvasComparisonDueDate =
canvasAssignment.DueAt != null
- && new DateTime(
- year: canvasAssignment.DueAt.Value.Year,
- month: canvasAssignment.DueAt.Value.Month,
- day: canvasAssignment.DueAt.Value.Day,
- hour: canvasAssignment.DueAt.Value.Hour,
- minute: canvasAssignment.DueAt.Value.Minute,
- second: canvasAssignment.DueAt.Value.Second
- )
- == new DateTime(
+ ? new DateTime(
+ year: canvasAssignment.DueAt.Value.Year,
+ month: canvasAssignment.DueAt.Value.Month,
+ day: canvasAssignment.DueAt.Value.Day,
+ hour: canvasAssignment.DueAt.Value.Hour,
+ minute: canvasAssignment.DueAt.Value.Minute,
+ second: canvasAssignment.DueAt.Value.Second
+ )
+ : new DateTime();
+ var localComparisonDueDate =
+ canvasAssignment.DueAt != null
+ ? new DateTime(
year: localAssignment.DueAt.Year,
month: localAssignment.DueAt.Month,
day: localAssignment.DueAt.Day,
hour: localAssignment.DueAt.Hour,
minute: localAssignment.DueAt.Minute,
second: localAssignment.DueAt.Second
- );
+ )
+ : new DateTime();
+
+ var dueDatesSame =
+ canvasAssignment.DueAt != null && canvasComparisonDueDate == localComparisonDueDate;
var descriptionSame = canvasHtmlDescription == localHtmlDescription;
var nameSame = canvasAssignment.Name == localAssignment.Name;
@@ -159,6 +113,9 @@ public static partial class CoursePlannerSyncronizationExtensions
{
if (!dueDatesSame)
{
+ Console.WriteLine(JsonSerializer.Serialize(canvasAssignment));
+ Console.WriteLine(canvasComparisonDueDate);
+ Console.WriteLine(localComparisonDueDate);
Console.WriteLine(
$"Due dates different for {localAssignment.Name}, local: {localAssignment.DueAt}, in canvas {canvasAssignment.DueAt}"
);
@@ -219,7 +176,7 @@ public static partial class CoursePlannerSyncronizationExtensions
var moduleTasks = localCourse.Modules.Select(async m =>
{
var assignmentTasks = m.Assignments.Select(
- (a) => localCourse.SyncToCanvas(canvasId, a, canvasAssignments, canvas)
+ (a) => localCourse.SyncAssignmentToCanvas(canvasId, a, canvasAssignments, canvas)
);
var assignments = await Task.WhenAll(assignmentTasks);
return m with { Assignments = assignments };
diff --git a/Management/Features/Configuration/Synchronization/ModuleSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/ModuleSyncronizationExtensions.cs
new file mode 100644
index 0000000..75e0f35
--- /dev/null
+++ b/Management/Features/Configuration/Synchronization/ModuleSyncronizationExtensions.cs
@@ -0,0 +1,72 @@
+using System.Text.RegularExpressions;
+using CanvasModel.Assignments;
+using CanvasModel.Modules;
+using CanvasModel.Quizzes;
+using LocalModels;
+using Management.Services.Canvas;
+
+namespace Management.Planner;
+
+public static partial class ModuleSyncronizationExtensions
+{
+ internal static async Task> EnsureAllModulesExistInCanvas(
+ this LocalCourse localCourse,
+ ulong canvasId,
+ IEnumerable canvasModules,
+ CanvasService canvas
+ )
+ {
+ var moduleTasks = localCourse.Modules.Select(async module =>
+ {
+ var canvasModule = canvasModules.FirstOrDefault(cm => cm.Id == module.CanvasId);
+ if (canvasModule == null)
+ {
+ var newModule = await canvas.CreateModule(canvasId, module.Name);
+ return module with { CanvasId = newModule.Id };
+ }
+ else
+ return module;
+ });
+ var newModules = await Task.WhenAll(moduleTasks);
+ return newModules ?? throw new Exception("Error ensuring all modules exist in canvas");
+ }
+
+ internal static async Task SortCanvasModules(
+ this LocalCourse localCourse,
+ ulong canvasId,
+ IEnumerable canvasModules,
+ CanvasService canvas
+ )
+ {
+ var currentCanvasPositions = canvasModules.ToDictionary(m => m.Id, m => m.Position);
+ foreach (var (localModule, i) in localCourse.Modules.Select((m, i) => (m, i)))
+ {
+ var correctPosition = i + 1;
+ var moduleCanvasId =
+ localModule.CanvasId ?? throw new Exception("cannot sort module if no module canvas id");
+ var currentCanvasPosition = currentCanvasPositions[moduleCanvasId];
+ if (currentCanvasPosition != correctPosition)
+ {
+ await canvas.UpdateModule(canvasId, moduleCanvasId, localModule.Name, correctPosition);
+ }
+ }
+ }
+
+ internal static async Task SyncModulesWithCanvasData(
+ this LocalCourse localCourse,
+ ulong canvasId,
+ IEnumerable canvasModules,
+ CanvasService canvas
+ )
+ {
+ canvasModules = await canvas.GetModules(canvasId);
+ return localCourse with
+ {
+ Modules = localCourse.Modules.Select(m =>
+ {
+ var canvasModule = canvasModules.FirstOrDefault(cm => cm.Name == m.Name);
+ return canvasModule == null ? m : m with { CanvasId = canvasModule.Id };
+ })
+ };
+ }
+}
diff --git a/Management/Features/Configuration/Synchronization/QuizSyncronizationExtensions.cs b/Management/Features/Configuration/Synchronization/QuizSyncronizationExtensions.cs
new file mode 100644
index 0000000..febfd7a
--- /dev/null
+++ b/Management/Features/Configuration/Synchronization/QuizSyncronizationExtensions.cs
@@ -0,0 +1,43 @@
+using System.Text.RegularExpressions;
+using CanvasModel.Assignments;
+using CanvasModel.Modules;
+using CanvasModel.Quizzes;
+using LocalModels;
+using Management.Services.Canvas;
+
+namespace Management.Planner;
+
+public static partial class QuizSyncronizationExtensions
+{
+ internal static async Task SyncQuizToCanvas(
+ this LocalCourse localCourse,
+ ulong canvasId,
+ LocalQuiz localQuiz,
+ IEnumerable canvasQuizzes,
+ CanvasService canvas
+ )
+ {
+ // TODO actually sync
+ return localQuiz;
+ }
+
+ internal static async Task SyncQuizzesWithCanvas(
+ this LocalCourse localCourse,
+ ulong canvasId,
+ IEnumerable canvasQuizzes,
+ CanvasService canvas
+ )
+ {
+ var moduleTasks = localCourse.Modules.Select(async m =>
+ {
+ var quizTasks = m.Quizzes.Select(
+ (q) => localCourse.SyncQuizToCanvas(canvasId, q, canvasQuizzes, canvas)
+ );
+ var quizzes = await Task.WhenAll(quizTasks);
+ return m with { Quizzes = quizzes };
+ });
+
+ var modules = await Task.WhenAll(moduleTasks);
+ return localCourse;
+ }
+}
diff --git a/Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs b/Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs
new file mode 100644
index 0000000..d1613b9
--- /dev/null
+++ b/Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs
@@ -0,0 +1,46 @@
+using CanvasModel.Assignments;
+
+namespace CanvasModel.Quizzes;
+
+public record CanvasQuiz(
+ [property: JsonPropertyName("id")] ulong Id,
+ [property: JsonPropertyName("title")] string Title,
+ [property: JsonPropertyName("html_url")] string HtmlUrl,
+ [property: JsonPropertyName("mobile_url")] string MobileUrl,
+ [property: JsonPropertyName("preview_url")] string PreviewUrl,
+ [property: JsonPropertyName("description")] string Description,
+ [property: JsonPropertyName("quiz_type")] string QuizType,
+ [property: JsonPropertyName("assignment_group_id")] ulong AssignmentGroupId,
+ [property: JsonPropertyName("time_limit")] decimal? TimeLimit,
+ [property: JsonPropertyName("shuffle_answers")] bool? ShuffleAnswers,
+ [property: JsonPropertyName("hide_results")] string? HideResults,
+ [property: JsonPropertyName("show_correct_answers")] bool? ShowCorrectAnswers,
+ [property: JsonPropertyName("show_correct_answers_last_attempt")]
+ bool? ShowCorrectAnswersLastAttempt,
+ [property: JsonPropertyName("show_correct_answers_at")] DateTime? ShowCorrectAnswersAt,
+ [property: JsonPropertyName("hide_correct_answers_at")] DateTime? HideCorrectAnswersAt,
+ [property: JsonPropertyName("one_time_results")] bool? OneTimeResults,
+ [property: JsonPropertyName("scoring_policy")] string? ScoringPolicy,
+ [property: JsonPropertyName("allowed_attempts")] int AllowedAttempts,
+ [property: JsonPropertyName("one_question_at_a_time")] bool? OneQuestionAtATime,
+ [property: JsonPropertyName("question_count")] uint? QuestionCount,
+ [property: JsonPropertyName("points_possible")] decimal? PointsPossible,
+ [property: JsonPropertyName("cant_go_back")] bool? CantGoBack,
+ [property: JsonPropertyName("access_code")] string? AccessCode,
+ [property: JsonPropertyName("ip_filter")] string? IpFilter,
+ [property: JsonPropertyName("due_at")] DateTime? DueAt,
+ [property: JsonPropertyName("lock_at")] DateTime? LockAt,
+ [property: JsonPropertyName("unlock_at")] DateTime? UnlockAt,
+ [property: JsonPropertyName("published")] bool? Published,
+ [property: JsonPropertyName("unpublishable")] bool? Unpublishable,
+ [property: JsonPropertyName("locked_for_user")] bool? LockedForUser,
+ [property: JsonPropertyName("lock_info")] CanvasLockInfo? LockInfo,
+ [property: JsonPropertyName("lock_explanation")] string? LockExplanation,
+ [property: JsonPropertyName("speedgrader_url")] string? SpeedGraderUrl,
+ [property: JsonPropertyName("quiz_extensions_url")] string QuizExtensionsUrl,
+ [property: JsonPropertyName("permissions")] CanvasQuizPermissions Permissions,
+ [property: JsonPropertyName("all_dates")] object AllDates,
+ [property: JsonPropertyName("version_number")] uint? VersionNumber,
+ [property: JsonPropertyName("question_types")] IEnumerable QuestionTypes,
+ [property: JsonPropertyName("anonymous_submissions")] bool? AnonymousSubmissions
+);
diff --git a/Management/Models/CanvasModels/Quizzes/CanvasQuizPermissions.cs b/Management/Models/CanvasModels/Quizzes/CanvasQuizPermissions.cs
new file mode 100644
index 0000000..7a2c261
--- /dev/null
+++ b/Management/Models/CanvasModels/Quizzes/CanvasQuizPermissions.cs
@@ -0,0 +1,27 @@
+
+
+namespace CanvasModel.Quizzes;
+public class CanvasQuizPermissions
+{
+
+ [JsonPropertyName("read")]
+ public bool Read { get; set; }
+
+ [JsonPropertyName("submit")]
+ public bool Submit { get; set; }
+
+ [JsonPropertyName("create")]
+ public bool Create { get; set; }
+
+ [JsonPropertyName("manage")]
+ public bool Manage { get; set; }
+
+ [JsonPropertyName("read_statistics")]
+ public bool ReadStatistics { get; set; }
+
+ [JsonPropertyName("review_grades")]
+ public bool ReviewGrades { get; set; }
+
+ [JsonPropertyName("update")]
+ public bool Update { get; set; }
+}
diff --git a/Management/Models/Local/LocalModules.cs b/Management/Models/Local/LocalModules.cs
index cdd27c1..e1438ff 100644
--- a/Management/Models/Local/LocalModules.cs
+++ b/Management/Models/Local/LocalModules.cs
@@ -3,6 +3,7 @@ namespace LocalModels;
public record LocalModule
{
public string Name { get; init; } = string.Empty;
+ public string Id { get; init; } = string.Empty;
public IEnumerable Assignments { get; init; } =
Enumerable.Empty();
diff --git a/Management/Services/Canvas/CanvasAssignmentService.cs b/Management/Services/Canvas/CanvasAssignmentService.cs
index e40dc2e..03540a7 100644
--- a/Management/Services/Canvas/CanvasAssignmentService.cs
+++ b/Management/Services/Canvas/CanvasAssignmentService.cs
@@ -19,6 +19,7 @@ public class CanvasAssignmentService
{
var url = $"courses/{courseId}/assignments";
var request = new RestRequest(url);
+ request.AddParameter("include[]", "overrides");
var assignmentResponse = await utils.PaginatedRequest>(request);
return assignmentResponse.SelectMany(
assignments =>
@@ -77,6 +78,8 @@ public class CanvasAssignmentService
request.AddHeader("Content-Type", "application/json");
var bodyObj = new { assignment = body };
request.AddBody(bodyObj);
+ Console.WriteLine(url);
+ Console.WriteLine(JsonSerializer.Serialize(bodyObj));
await webRequestor.PutAsync(request);
await CreateRubric(courseId, localAssignment);
diff --git a/Management/Services/WebRequestor.cs b/Management/Services/WebRequestor.cs
index 3c7a77e..1f711d1 100644
--- a/Management/Services/WebRequestor.cs
+++ b/Management/Services/WebRequestor.cs
@@ -13,6 +13,7 @@ public class WebRequestor : IWebRequestor
?? throw new Exception("CANVAS_TOKEN not in environment");
client = new RestClient(BaseUrl);
client.AddDefaultHeader("Authorization", $"Bearer {token}");
+
}
public async Task<(T[]?, RestResponse)> GetManyAsync(RestRequest request)
@@ -48,6 +49,7 @@ public class WebRequestor : IWebRequestor
public async Task PutAsync(RestRequest request)
{
+ request.AddHeader("Content-Type", "application/json");
var response = await client.ExecutePutAsync(request);
// if (!response.IsSuccessful)
// {
@@ -61,6 +63,7 @@ public class WebRequestor : IWebRequestor
public async Task<(T?, RestResponse)> PutAsync(RestRequest request)
{
+ request.AddHeader("Content-Type", "application/json");
var response = await client.ExecutePutAsync(request);
return (deserialize(response), response);
}
diff --git a/requests/assignment.http b/requests/assignment.http
index 3d3c07d..71d6b17 100644
--- a/requests/assignment.http
+++ b/requests/assignment.http
@@ -108,3 +108,20 @@ Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
"unlock_at":null
}
}
+
+
+###
+PUT https://snow.instructure.com/api/v1/courses/872095/assignments/12676639
+Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
+Content-Type: application/json
+
+{
+ "assignment": {
+ "due_at": "2021-09-15T23:59:59Z",
+ "lock_at": "2023-08-22T05:59:00Z"
+ }
+}
+
+###
+GET https://snow.instructure.com/api/v1/courses/872095/assignments/12676639?include[]=overrides
+Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
\ No newline at end of file
diff --git a/requests/quiz.http b/requests/quiz.http
new file mode 100644
index 0000000..8205d63
--- /dev/null
+++ b/requests/quiz.http
@@ -0,0 +1,3 @@
+
+GET https://snow.instructure.com/api/v1/courses/705168
+Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
\ No newline at end of file