mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
smarter canvas updates, only update when a change is detected
This commit is contained in:
@@ -84,6 +84,11 @@
|
|||||||
>
|
>
|
||||||
Sync With Canvas
|
Sync With Canvas
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@if(planner.LoadingCanvasData)
|
||||||
|
{
|
||||||
|
<Spinner />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<CourseDetails />
|
<CourseDetails />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
global using System.Text.Json.Serialization;
|
global using System.Text.Json.Serialization;
|
||||||
global using System.Text.Json;
|
global using System.Text.Json;
|
||||||
global using System.ComponentModel.DataAnnotations;
|
global using System.ComponentModel.DataAnnotations;
|
||||||
|
global using Management.Services.Canvas;
|
||||||
global using CanvasModel.EnrollmentTerms;
|
global using CanvasModel.EnrollmentTerms;
|
||||||
global using CanvasModel.Courses;
|
global using CanvasModel.Courses;
|
||||||
global using CanvasModel;
|
global using CanvasModel;
|
||||||
@@ -19,8 +20,12 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
|
|
||||||
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
|
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
|
||||||
|
builder.Services.AddScoped<CanvasServiceUtils>();
|
||||||
|
builder.Services.AddScoped<CanvasAssignmentService>();
|
||||||
builder.Services.AddScoped<CanvasService, CanvasService>();
|
builder.Services.AddScoped<CanvasService, CanvasService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<YamlManager>();
|
builder.Services.AddScoped<YamlManager>();
|
||||||
builder.Services.AddScoped<CoursePlanner>();
|
builder.Services.AddScoped<CoursePlanner>();
|
||||||
builder.Services.AddScoped<AssignmentDragContainer>();
|
builder.Services.AddScoped<AssignmentDragContainer>();
|
||||||
|
|||||||
@@ -14,19 +14,24 @@
|
|||||||
}
|
}
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
if(
|
|
||||||
planner.CanvasAssignments == null
|
|
||||||
&& planner.LocalCourse != null
|
|
||||||
&& planner.LocalCourse.CanvasId != null
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var canvasId = planner.LocalCourse.CanvasId ?? throw new Exception("no canvas id found for selected course");
|
|
||||||
planner.CanvasAssignments = await canvas.GetAssignments(canvasId);
|
|
||||||
planner.CanvasModules = await canvas.GetModules(canvasId);
|
|
||||||
System.Console.WriteLine(JsonSerializer.Serialize(planner.CanvasAssignments));
|
|
||||||
}
|
|
||||||
await base.OnInitializedAsync();
|
|
||||||
}
|
}
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if(firstRender)
|
||||||
|
{
|
||||||
|
if(
|
||||||
|
planner.CanvasAssignments == null
|
||||||
|
&& planner.LocalCourse != null
|
||||||
|
&& planner.LocalCourse.CanvasId != null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
await planner.LoadCanvasData();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void reload()
|
private void reload()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -140,7 +140,19 @@
|
|||||||
</h4>
|
</h4>
|
||||||
@if(isSyncedWithCanvas)
|
@if(isSyncedWithCanvas)
|
||||||
{
|
{
|
||||||
<div>Synced With Canvas</div>
|
@if(planner.LocalCourse != null
|
||||||
|
&& planner.LocalCourse.CanvasId != null
|
||||||
|
&& planner.CanvasAssignments != null
|
||||||
|
&& planner.CanvasModules != null
|
||||||
|
&& planner.AssignmentNeedsUpdates(Assignment)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
<div>need to update canvas</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div>Canvas is up to date</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ using CanvasModel;
|
|||||||
using LocalModels;
|
using LocalModels;
|
||||||
using CanvasModel.Assignments;
|
using CanvasModel.Assignments;
|
||||||
using CanvasModel.Modules;
|
using CanvasModel.Modules;
|
||||||
|
using Management.Services.Canvas;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
public class CoursePlanner
|
public class CoursePlanner
|
||||||
{
|
{
|
||||||
private readonly YamlManager yamlManager;
|
private readonly YamlManager yamlManager;
|
||||||
private readonly CanvasService canvas;
|
private readonly CanvasService canvas;
|
||||||
|
public bool LoadingCanvasData { get; internal set; } = false;
|
||||||
|
|
||||||
public CoursePlanner(YamlManager yamlManager, CanvasService canvas)
|
public CoursePlanner(YamlManager yamlManager, CanvasService canvas)
|
||||||
{
|
{
|
||||||
@@ -29,7 +32,7 @@ public class CoursePlanner
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var verifiedCourse = verifyCourse(value);
|
var verifiedCourse = cleanupCourse(value);
|
||||||
// ignore initial load of course
|
// ignore initial load of course
|
||||||
if (_localCourse != null)
|
if (_localCourse != null)
|
||||||
{
|
{
|
||||||
@@ -41,7 +44,7 @@ public class CoursePlanner
|
|||||||
}
|
}
|
||||||
public event Action? StateHasChanged;
|
public event Action? StateHasChanged;
|
||||||
|
|
||||||
private LocalCourse verifyCourse(LocalCourse incomingCourse)
|
private LocalCourse cleanupCourse(LocalCourse incomingCourse)
|
||||||
{
|
{
|
||||||
var modulesWithUniqueAssignments = incomingCourse.Modules.Select(
|
var modulesWithUniqueAssignments = incomingCourse.Modules.Select(
|
||||||
module => module with { Assignments = module.Assignments.DistinctBy(a => a.id) }
|
module => module with { Assignments = module.Assignments.DistinctBy(a => a.id) }
|
||||||
@@ -53,8 +56,42 @@ public class CoursePlanner
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<CanvasAssignment>? CanvasAssignments { get; set; } = null;
|
private IEnumerable<CanvasAssignment>? canvasAssignments = null;
|
||||||
public IEnumerable<CanvasModule>? CanvasModules { get; set; } = null;
|
|
||||||
|
public IEnumerable<CanvasAssignment>? CanvasAssignments
|
||||||
|
{
|
||||||
|
get => canvasAssignments;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
canvasAssignments = value;
|
||||||
|
StateHasChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private IEnumerable<CanvasModule>? canvasModules = null;
|
||||||
|
public IEnumerable<CanvasModule>? CanvasModules
|
||||||
|
{
|
||||||
|
get => canvasModules;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
canvasModules = value;
|
||||||
|
StateHasChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadCanvasData()
|
||||||
|
{
|
||||||
|
LoadingCanvasData = true;
|
||||||
|
StateHasChanged?.Invoke();
|
||||||
|
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
var canvasId =
|
||||||
|
LocalCourse?.CanvasId ?? throw new Exception("no canvas id found for selected course");
|
||||||
|
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
||||||
|
CanvasModules = await canvas.GetModules(canvasId);
|
||||||
|
|
||||||
|
LoadingCanvasData = false;
|
||||||
|
StateHasChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SyncWithCanvas()
|
public async Task SyncWithCanvas()
|
||||||
{
|
{
|
||||||
@@ -66,12 +103,18 @@ public class CoursePlanner
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
LoadingCanvasData = true;
|
||||||
|
StateHasChanged?.Invoke();
|
||||||
|
|
||||||
var canvasId =
|
var canvasId =
|
||||||
LocalCourse.CanvasId ?? throw new Exception("no course canvas id to sync with canvas");
|
LocalCourse.CanvasId ?? throw new Exception("no course canvas id to sync with canvas");
|
||||||
await ensureAllModulesCreated(canvasId);
|
await ensureAllModulesCreated(canvasId);
|
||||||
await reloadModules_UpdateLocalModulesWithNewId(canvasId);
|
await reloadModules_UpdateLocalModulesWithNewId(canvasId);
|
||||||
|
|
||||||
await ensureAllAssignmentsCreated_updateIds(canvasId);
|
await ensureAllAssignmentsCreated_updateIds(canvasId);
|
||||||
|
|
||||||
|
LoadingCanvasData = false;
|
||||||
|
StateHasChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task reloadModules_UpdateLocalModulesWithNewId(ulong canvasId)
|
private async Task reloadModules_UpdateLocalModulesWithNewId(ulong canvasId)
|
||||||
@@ -139,21 +182,121 @@ public class CoursePlanner
|
|||||||
throw new Exception(
|
throw new Exception(
|
||||||
"cannot create canvas assignment if local course is null or other values not set"
|
"cannot create canvas assignment if local course is null or other values not set"
|
||||||
);
|
);
|
||||||
|
|
||||||
ulong canvasId = LocalCourse.CanvasId ?? throw new Exception("no canvas id to create course");
|
ulong canvasId = LocalCourse.CanvasId ?? throw new Exception("no canvas id to create course");
|
||||||
var canvasAssignment = await canvas.CreateAssignment(
|
var canvasAssignment = CanvasAssignments.FirstOrDefault(
|
||||||
courseId: canvasId,
|
ca => ca.Id == localAssignment.canvasId
|
||||||
name: localAssignment.name,
|
);
|
||||||
submissionTypes: localAssignment.submission_types,
|
string localHtmlDescription = getAssignmentDescriptionHtml(localAssignment);
|
||||||
description: localAssignment.description,
|
|
||||||
dueAt: localAssignment.due_at,
|
if (canvasAssignment != null)
|
||||||
lockAt: localAssignment.lock_at,
|
{
|
||||||
pointsPossible: localAssignment.points_possible
|
var assignmentNeedsUpdates = AssignmentNeedsUpdates(localAssignment);
|
||||||
|
if (assignmentNeedsUpdates)
|
||||||
|
{
|
||||||
|
await canvas.Assignments.Update(courseId: canvasId, localAssignment, localHtmlDescription);
|
||||||
|
}
|
||||||
|
return localAssignment;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var createdAssignment = await canvas.Assignments.Create(
|
||||||
|
courseId: canvasId,
|
||||||
|
name: localAssignment.name,
|
||||||
|
submissionTypes: localAssignment.submission_types,
|
||||||
|
description: localHtmlDescription,
|
||||||
|
dueAt: localAssignment.due_at,
|
||||||
|
lockAt: localAssignment.lock_at,
|
||||||
|
pointsPossible: localAssignment.points_possible
|
||||||
|
);
|
||||||
|
|
||||||
|
return localAssignment with
|
||||||
|
{
|
||||||
|
canvasId = createdAssignment.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string getAssignmentDescriptionHtml(LocalAssignment localAssignment)
|
||||||
|
{
|
||||||
|
if (LocalCourse == null)
|
||||||
|
throw new Exception(
|
||||||
|
"cannot get assignment description if local course is null or other values not set"
|
||||||
|
);
|
||||||
|
return localAssignment.use_template
|
||||||
|
? AssignmentTemplate.GetHtml(
|
||||||
|
LocalCourse.AssignmentTemplates.First(t => t.Id == localAssignment.template_id),
|
||||||
|
localAssignment
|
||||||
|
)
|
||||||
|
: Markdig.Markdown.ToHtml(localAssignment.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AssignmentNeedsUpdates(LocalAssignment localAssignment)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
LocalCourse == null
|
||||||
|
|| LocalCourse.CanvasId == null
|
||||||
|
|| CanvasAssignments == null
|
||||||
|
|| CanvasModules == null
|
||||||
|
)
|
||||||
|
throw new Exception(
|
||||||
|
"cannot check if assignment needs updates if local course is null or other values not set"
|
||||||
|
);
|
||||||
|
|
||||||
|
var canvasAssignment = CanvasAssignments.First(ca => ca.Id == localAssignment.canvasId);
|
||||||
|
|
||||||
|
var localHtmlDescription = getAssignmentDescriptionHtml(localAssignment);
|
||||||
|
|
||||||
|
var canvasHtmlDescription = canvasAssignment.Description;
|
||||||
|
canvasHtmlDescription = Regex.Replace(canvasHtmlDescription, "<script.*script>", "");
|
||||||
|
canvasHtmlDescription = Regex.Replace(canvasHtmlDescription, "<link .*\">", "");
|
||||||
|
|
||||||
|
var dueDatesSame = canvasAssignment.DueAt == localAssignment.due_at;
|
||||||
|
var descriptionSame = canvasHtmlDescription == localHtmlDescription;
|
||||||
|
var nameSame = canvasAssignment.Name == localAssignment.name;
|
||||||
|
var lockDateSame = canvasAssignment.LockAt == localAssignment.lock_at;
|
||||||
|
var pointsSame = canvasAssignment.PointsPossible == localAssignment.points_possible;
|
||||||
|
var submissionTypesSame = canvasAssignment.SubmissionTypes.SequenceEqual(
|
||||||
|
localAssignment.submission_types.Select(t => t.ToString())
|
||||||
);
|
);
|
||||||
|
|
||||||
return localAssignment with
|
if (!dueDatesSame)
|
||||||
|
Console.WriteLine(
|
||||||
|
$"Due dates different for {localAssignment.name}, local: {localAssignment.due_at}, in canvas {canvasAssignment.DueAt}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!descriptionSame)
|
||||||
{
|
{
|
||||||
canvasId = canvasAssignment.Id
|
Console.WriteLine($"descriptions different for {localAssignment.name}");
|
||||||
};
|
Console.WriteLine("Local Description:");
|
||||||
|
Console.WriteLine(localHtmlDescription);
|
||||||
|
Console.WriteLine("Canvas Description: ");
|
||||||
|
Console.WriteLine(canvasHtmlDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nameSame)
|
||||||
|
Console.WriteLine(
|
||||||
|
$"names different for {localAssignment.name}, local: {localAssignment.name}, in canvas {canvasAssignment.Name}"
|
||||||
|
);
|
||||||
|
if (!lockDateSame)
|
||||||
|
Console.WriteLine(
|
||||||
|
$"Lock Dates different for {localAssignment.name}, local: {localAssignment.lock_at}, in canvas {canvasAssignment.LockAt}"
|
||||||
|
);
|
||||||
|
if (!pointsSame)
|
||||||
|
Console.WriteLine(
|
||||||
|
$"Points different for {localAssignment.name}, local: {localAssignment.points_possible}, in canvas {canvasAssignment.PointsPossible}"
|
||||||
|
);
|
||||||
|
if (!submissionTypesSame)
|
||||||
|
Console.WriteLine(
|
||||||
|
$"Submission Types different for {localAssignment.name}, local: {JsonSerializer.Serialize(localAssignment.submission_types.Select(t => t.ToString()))}, in canvas {JsonSerializer.Serialize(canvasAssignment.SubmissionTypes)}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return !nameSame
|
||||||
|
|| !dueDatesSame
|
||||||
|
|| !lockDateSame
|
||||||
|
|| !descriptionSame
|
||||||
|
|| !pointsSame
|
||||||
|
|| !submissionTypesSame;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using LocalModels;
|
||||||
|
|
||||||
public record RubricItem
|
public record RubricItem
|
||||||
{
|
{
|
||||||
public static readonly string extraCredit = "(Extra Credit) ";
|
public static readonly string extraCredit = "(Extra Credit) ";
|
||||||
|
|||||||
89
Management/Services/Canvas/CanvasAssignmentService.cs
Normal file
89
Management/Services/Canvas/CanvasAssignmentService.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using CanvasModel.Assignments;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace Management.Services.Canvas;
|
||||||
|
|
||||||
|
public class CanvasAssignmentService
|
||||||
|
{
|
||||||
|
private IWebRequestor webRequestor;
|
||||||
|
private readonly CanvasServiceUtils utils;
|
||||||
|
|
||||||
|
public CanvasAssignmentService(IWebRequestor webRequestor, CanvasServiceUtils utils)
|
||||||
|
{
|
||||||
|
this.webRequestor = webRequestor;
|
||||||
|
this.utils = utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CanvasAssignment>> GetAll(ulong courseId)
|
||||||
|
{
|
||||||
|
var url = $"courses/{courseId}/assignments";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var assignmentResponse = await utils.PaginatedRequest<IEnumerable<CanvasAssignment>>(request);
|
||||||
|
return assignmentResponse.SelectMany(
|
||||||
|
assignments =>
|
||||||
|
assignments.Select(
|
||||||
|
a =>
|
||||||
|
a with
|
||||||
|
{
|
||||||
|
DueAt = a.DueAt?.ToLocalTime(),
|
||||||
|
LockAt = a.LockAt?.ToLocalTime()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CanvasAssignment> Create(
|
||||||
|
ulong courseId,
|
||||||
|
string name,
|
||||||
|
IEnumerable<SubmissionType> submissionTypes,
|
||||||
|
string? description,
|
||||||
|
DateTime? dueAt,
|
||||||
|
DateTime? lockAt,
|
||||||
|
int? pointsPossible
|
||||||
|
)
|
||||||
|
{
|
||||||
|
System.Console.WriteLine($"creating assignment: {name}");
|
||||||
|
var url = $"courses/{courseId}/assignments";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var body = new CanvasAssignmentCreationRequest()
|
||||||
|
{
|
||||||
|
name = name,
|
||||||
|
submission_types = submissionTypes.Select(t => t.ToString()),
|
||||||
|
description = description ?? "",
|
||||||
|
due_at = dueAt,
|
||||||
|
lock_at = lockAt,
|
||||||
|
points_possible = pointsPossible ?? 0
|
||||||
|
};
|
||||||
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
var bodyObj = new { assignment = body };
|
||||||
|
request.AddBody(bodyObj);
|
||||||
|
var (canvasAssignment, response) = await webRequestor.PostAsync<CanvasAssignment>(request);
|
||||||
|
if (canvasAssignment == null)
|
||||||
|
throw new Exception("created canvas assignment was null");
|
||||||
|
return canvasAssignment with
|
||||||
|
{
|
||||||
|
DueAt = canvasAssignment.DueAt?.ToLocalTime(),
|
||||||
|
LockAt = canvasAssignment.LockAt?.ToLocalTime()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Update(ulong courseId, LocalAssignment localAssignment, string htmlDescription)
|
||||||
|
{
|
||||||
|
System.Console.WriteLine($"updating assignment: {localAssignment.name}");
|
||||||
|
var url = $"courses/{courseId}/assignments/{localAssignment.canvasId}";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var body = new CanvasAssignmentCreationRequest()
|
||||||
|
{
|
||||||
|
name = localAssignment.name,
|
||||||
|
submission_types = localAssignment.submission_types.Select(t => t.ToString()),
|
||||||
|
description = htmlDescription,
|
||||||
|
due_at = localAssignment.due_at,
|
||||||
|
lock_at = localAssignment.lock_at,
|
||||||
|
points_possible = localAssignment.points_possible
|
||||||
|
};
|
||||||
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
var bodyObj = new { assignment = body };
|
||||||
|
request.AddBody(bodyObj);
|
||||||
|
await webRequestor.PutAsync(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
|
namespace Management.Services.Canvas;
|
||||||
|
|
||||||
public record CanvasAssignmentCreationRequest
|
public record CanvasAssignmentCreationRequest
|
||||||
{
|
{
|
||||||
public string? name { get; set; }
|
public string? name { get; set; }
|
||||||
93
Management/Services/Canvas/CanvasService.cs
Normal file
93
Management/Services/Canvas/CanvasService.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using CanvasModel;
|
||||||
|
using CanvasModel.Assignments;
|
||||||
|
using CanvasModel.Courses;
|
||||||
|
using CanvasModel.EnrollmentTerms;
|
||||||
|
using CanvasModel.Modules;
|
||||||
|
using RestSharp;
|
||||||
|
namespace Management.Services.Canvas;
|
||||||
|
|
||||||
|
public class CanvasService
|
||||||
|
{
|
||||||
|
private readonly IWebRequestor webRequestor;
|
||||||
|
private readonly CanvasServiceUtils utils;
|
||||||
|
|
||||||
|
public CanvasAssignmentService Assignments { get; }
|
||||||
|
|
||||||
|
public CanvasService(
|
||||||
|
IWebRequestor webRequestor,
|
||||||
|
CanvasServiceUtils utils,
|
||||||
|
CanvasAssignmentService Assignments
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.webRequestor = webRequestor;
|
||||||
|
this.utils = utils;
|
||||||
|
this.Assignments = Assignments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EnrollmentTermModel>> GetTerms()
|
||||||
|
{
|
||||||
|
var url = $"accounts/10/terms";
|
||||||
|
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var termResponses = await utils.PaginatedRequest<RedundantEnrollmentTermsResponse>(request);
|
||||||
|
var terms = termResponses.Select(r => r.EnrollmentTerms).SelectMany(s => s).ToArray();
|
||||||
|
return terms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CourseModel>> GetCourses(ulong termId)
|
||||||
|
{
|
||||||
|
var url = $"courses";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var coursesResponse = await utils.PaginatedRequest<IEnumerable<CourseModel>>(request);
|
||||||
|
return coursesResponse.SelectMany(c => c).Where(c => c.EnrollmentTermId == termId).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CourseModel> GetCourse(ulong courseId)
|
||||||
|
{
|
||||||
|
var url = $"courses/{courseId}";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var (data, response) = await webRequestor.GetAsync<CourseModel>(request);
|
||||||
|
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
System.Console.WriteLine(response.Content);
|
||||||
|
System.Console.WriteLine(response.ResponseUri);
|
||||||
|
throw new Exception("error getting course from canvas");
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CanvasModule>> GetModules(ulong courseId)
|
||||||
|
{
|
||||||
|
var url = $"courses/{courseId}/modules";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var modules = await utils.PaginatedRequest<IEnumerable<CanvasModule>>(request);
|
||||||
|
return modules.SelectMany(c => c).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateModule(ulong courseId, string name)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Creating Module: {name}");
|
||||||
|
var url = $"courses/{courseId}/modules";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
request.AddParameter("module[name]", name);
|
||||||
|
|
||||||
|
await webRequestor.PostAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EnrollmentTermModel>> GetCurrentTermsFor(
|
||||||
|
DateTime? _queryDate = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DateTime queryDate = _queryDate ?? DateTime.Now;
|
||||||
|
|
||||||
|
var terms = await GetTerms();
|
||||||
|
|
||||||
|
var currentTerms = terms
|
||||||
|
.Where(t => t.EndAt != null && t.EndAt > queryDate && t.EndAt < queryDate.AddYears(1))
|
||||||
|
.Take(3)
|
||||||
|
.OrderBy(t => t.StartAt);
|
||||||
|
|
||||||
|
return currentTerms;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
Management/Services/Canvas/CanvasServiceUtils.cs
Normal file
58
Management/Services/Canvas/CanvasServiceUtils.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace Management.Services.Canvas;
|
||||||
|
public class CanvasServiceUtils
|
||||||
|
{
|
||||||
|
private const string BaseUrl = "https://snow.instructure.com/api/v1/";
|
||||||
|
private readonly IWebRequestor webRequestor;
|
||||||
|
public CanvasServiceUtils(IWebRequestor webRequestor)
|
||||||
|
{
|
||||||
|
this.webRequestor = webRequestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<IEnumerable<T>> PaginatedRequest<T>(RestRequest request)
|
||||||
|
{
|
||||||
|
var requestCount = 1;
|
||||||
|
request.AddQueryParameter("per_page", "100");
|
||||||
|
var (data, response) = await webRequestor.GetAsync<T>(request);
|
||||||
|
|
||||||
|
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 (nextData, nextResponse) = await webRequestor.GetAsync<T>(nextRequest);
|
||||||
|
if (nextData is not null)
|
||||||
|
returnData = returnData.Append(nextData).ToArray();
|
||||||
|
nextUrl = getNextUrl(nextResponse.Headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Console.WriteLine($"Requesting {typeof(T)} took {requestCount} requests");
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string? getNextUrl(IEnumerable<HeaderParameter>? headers) =>
|
||||||
|
headers
|
||||||
|
?.ToList()
|
||||||
|
.Find(h => h.Name == "Link")
|
||||||
|
?.Value?.ToString()
|
||||||
|
?.Split(",")
|
||||||
|
.Where(url => url.Contains("rel=\"next\""))
|
||||||
|
.FirstOrDefault()
|
||||||
|
?.Split(";")
|
||||||
|
.FirstOrDefault()
|
||||||
|
?.TrimEnd('>')
|
||||||
|
.TrimStart('<')
|
||||||
|
.Replace(" ", "")
|
||||||
|
.Replace(BaseUrl, "");
|
||||||
|
}
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
using CanvasModel;
|
|
||||||
using CanvasModel.Assignments;
|
|
||||||
using CanvasModel.Courses;
|
|
||||||
using CanvasModel.EnrollmentTerms;
|
|
||||||
using CanvasModel.Modules;
|
|
||||||
using RestSharp;
|
|
||||||
|
|
||||||
public interface ICanvasService
|
|
||||||
{
|
|
||||||
Task<IEnumerable<EnrollmentTermModel>> GetCurrentTermsFor(DateTime? _queryDate = null);
|
|
||||||
Task<IEnumerable<EnrollmentTermModel>> GetTerms();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CanvasService : ICanvasService
|
|
||||||
{
|
|
||||||
private const string BaseUrl = "https://snow.instructure.com/api/v1/";
|
|
||||||
private readonly IWebRequestor webRequestor;
|
|
||||||
|
|
||||||
public CanvasService(IWebRequestor webRequestor)
|
|
||||||
{
|
|
||||||
this.webRequestor = webRequestor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<EnrollmentTermModel>> GetTerms()
|
|
||||||
{
|
|
||||||
var url = $"accounts/10/terms";
|
|
||||||
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
var termResponses = await PaginatedRequest<RedundantEnrollmentTermsResponse>(request);
|
|
||||||
var terms = termResponses.Select(r => r.EnrollmentTerms).SelectMany(s => s).ToArray();
|
|
||||||
return terms;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CourseModel>> GetCourses(ulong termId)
|
|
||||||
{
|
|
||||||
var url = $"courses";
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
var coursesResponse = await PaginatedRequest<IEnumerable<CourseModel>>(request);
|
|
||||||
return coursesResponse.SelectMany(c => c).Where(c => c.EnrollmentTermId == termId).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CourseModel> GetCourse(ulong courseId)
|
|
||||||
{
|
|
||||||
var url = $"courses/{courseId}";
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
var (data, response) = await webRequestor.GetAsync<CourseModel>(request);
|
|
||||||
|
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
System.Console.WriteLine(response.Content);
|
|
||||||
System.Console.WriteLine(response.ResponseUri);
|
|
||||||
throw new Exception("error getting course from canvas");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CanvasAssignment>> GetAssignments(ulong courseId)
|
|
||||||
{
|
|
||||||
var url = $"courses/{courseId}/assignments";
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
var assignmentResponse = await PaginatedRequest<IEnumerable<CanvasAssignment>>(request);
|
|
||||||
return assignmentResponse.SelectMany(c => c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<CanvasAssignment> CreateAssignment(
|
|
||||||
ulong courseId,
|
|
||||||
string name,
|
|
||||||
IEnumerable<SubmissionType> submissionTypes,
|
|
||||||
string? description,
|
|
||||||
DateTime? dueAt,
|
|
||||||
DateTime? lockAt,
|
|
||||||
int? pointsPossible
|
|
||||||
)
|
|
||||||
{
|
|
||||||
System.Console.WriteLine($"creating assignment: {name}");
|
|
||||||
var url = $"courses/{courseId}/assignments";
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
var body = new CanvasAssignmentCreationRequest()
|
|
||||||
{
|
|
||||||
name = name,
|
|
||||||
submission_types = submissionTypes.Select(t => t.ToString()),
|
|
||||||
description = description,
|
|
||||||
due_at = dueAt,
|
|
||||||
lock_at = lockAt,
|
|
||||||
points_possible = pointsPossible
|
|
||||||
};
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
|
||||||
var bodyObj = new { assignment = body };
|
|
||||||
request.AddBody(bodyObj);
|
|
||||||
var (canvasAssignment, response) = await webRequestor.PostAsync<CanvasAssignment>(request);
|
|
||||||
if (canvasAssignment == null)
|
|
||||||
throw new Exception("created canvas assignment was null");
|
|
||||||
return canvasAssignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<CanvasModule>> GetModules(ulong courseId)
|
|
||||||
{
|
|
||||||
var url = $"courses/{courseId}/modules";
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
var modules = await PaginatedRequest<IEnumerable<CanvasModule>>(request);
|
|
||||||
return modules.SelectMany(c => c).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateModule(ulong courseId, string name)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Creating Module: {name}");
|
|
||||||
var url = $"courses/{courseId}/modules";
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
request.AddParameter("module[name]", name);
|
|
||||||
|
|
||||||
await webRequestor.PostAsync(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IEnumerable<T>> PaginatedRequest<T>(RestRequest request)
|
|
||||||
{
|
|
||||||
var requestCount = 1;
|
|
||||||
request.AddQueryParameter("per_page", "100");
|
|
||||||
var (data, response) = await webRequestor.GetAsync<T>(request);
|
|
||||||
|
|
||||||
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 (nextData, nextResponse) = await webRequestor.GetAsync<T>(nextRequest);
|
|
||||||
if (nextData is not null)
|
|
||||||
returnData = returnData.Append(nextData).ToArray();
|
|
||||||
nextUrl = getNextUrl(nextResponse.Headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
System.Console.WriteLine($"Requesting {typeof(T)} took {requestCount} requests");
|
|
||||||
|
|
||||||
return returnData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? getNextUrl(IEnumerable<HeaderParameter>? headers) =>
|
|
||||||
headers
|
|
||||||
?.ToList()
|
|
||||||
.Find(h => h.Name == "Link")
|
|
||||||
?.Value?.ToString()
|
|
||||||
?.Split(",")
|
|
||||||
.Where(url => url.Contains("rel=\"next\""))
|
|
||||||
.FirstOrDefault()
|
|
||||||
?.Split(";")
|
|
||||||
.FirstOrDefault()
|
|
||||||
?.TrimEnd('>')
|
|
||||||
.TrimStart('<')
|
|
||||||
.Replace(" ", "")
|
|
||||||
.Replace(BaseUrl, "");
|
|
||||||
|
|
||||||
public async Task<IEnumerable<EnrollmentTermModel>> GetCurrentTermsFor(
|
|
||||||
DateTime? _queryDate = null
|
|
||||||
)
|
|
||||||
{
|
|
||||||
DateTime queryDate = _queryDate ?? DateTime.Now;
|
|
||||||
|
|
||||||
var terms = await GetTerms();
|
|
||||||
|
|
||||||
var currentTerms = terms
|
|
||||||
.Where(t => t.EndAt != null && t.EndAt > queryDate && t.EndAt < queryDate.AddYears(1))
|
|
||||||
.Take(3)
|
|
||||||
.OrderBy(t => t.StartAt);
|
|
||||||
|
|
||||||
return currentTerms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,4 +6,6 @@ public interface IWebRequestor
|
|||||||
Task<(T?, RestResponse)> GetAsync<T>(RestRequest request);
|
Task<(T?, RestResponse)> GetAsync<T>(RestRequest request);
|
||||||
Task<RestResponse> PostAsync(RestRequest request);
|
Task<RestResponse> PostAsync(RestRequest request);
|
||||||
Task<(T?, RestResponse)> PostAsync<T>(RestRequest request);
|
Task<(T?, RestResponse)> PostAsync<T>(RestRequest request);
|
||||||
|
Task<RestResponse> PutAsync(RestRequest request);
|
||||||
|
Task<(T?, RestResponse)> PutAsync<T>(RestRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,26 @@ public class WebRequestor : IWebRequestor
|
|||||||
return (Deserialize<T>(response), response);
|
return (Deserialize<T>(response), response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public T? Deserialize<T>(RestResponse response)
|
public async Task<RestResponse> PutAsync(RestRequest request)
|
||||||
|
{
|
||||||
|
var response = await client.ExecutePutAsync(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)> PutAsync<T>(RestRequest request)
|
||||||
|
{
|
||||||
|
var response = await client.ExecutePutAsync(request);
|
||||||
|
return (Deserialize<T>(response), response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private T? Deserialize<T>(RestResponse response)
|
||||||
{
|
{
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user