mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 15:18:32 -06:00
smarter canvas updates, only update when a change is detected
This commit is contained in:
@@ -84,6 +84,11 @@
|
||||
>
|
||||
Sync With Canvas
|
||||
</button>
|
||||
|
||||
@if(planner.LoadingCanvasData)
|
||||
{
|
||||
<Spinner />
|
||||
}
|
||||
</div>
|
||||
<CourseDetails />
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
global using System.Text.Json.Serialization;
|
||||
global using System.Text.Json;
|
||||
global using System.ComponentModel.DataAnnotations;
|
||||
global using Management.Services.Canvas;
|
||||
global using CanvasModel.EnrollmentTerms;
|
||||
global using CanvasModel.Courses;
|
||||
global using CanvasModel;
|
||||
@@ -19,8 +20,12 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddServerSideBlazor();
|
||||
|
||||
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
|
||||
builder.Services.AddScoped<CanvasServiceUtils>();
|
||||
builder.Services.AddScoped<CanvasAssignmentService>();
|
||||
builder.Services.AddScoped<CanvasService, CanvasService>();
|
||||
|
||||
builder.Services.AddScoped<YamlManager>();
|
||||
builder.Services.AddScoped<CoursePlanner>();
|
||||
builder.Services.AddScoped<AssignmentDragContainer>();
|
||||
|
||||
@@ -14,19 +14,24 @@
|
||||
}
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -140,7 +140,19 @@
|
||||
</h4>
|
||||
@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
|
||||
{
|
||||
|
||||
@@ -4,11 +4,14 @@ using CanvasModel;
|
||||
using LocalModels;
|
||||
using CanvasModel.Assignments;
|
||||
using CanvasModel.Modules;
|
||||
using Management.Services.Canvas;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public class CoursePlanner
|
||||
{
|
||||
private readonly YamlManager yamlManager;
|
||||
private readonly CanvasService canvas;
|
||||
public bool LoadingCanvasData { get; internal set; } = false;
|
||||
|
||||
public CoursePlanner(YamlManager yamlManager, CanvasService canvas)
|
||||
{
|
||||
@@ -29,7 +32,7 @@ public class CoursePlanner
|
||||
return;
|
||||
}
|
||||
|
||||
var verifiedCourse = verifyCourse(value);
|
||||
var verifiedCourse = cleanupCourse(value);
|
||||
// ignore initial load of course
|
||||
if (_localCourse != null)
|
||||
{
|
||||
@@ -41,7 +44,7 @@ public class CoursePlanner
|
||||
}
|
||||
public event Action? StateHasChanged;
|
||||
|
||||
private LocalCourse verifyCourse(LocalCourse incomingCourse)
|
||||
private LocalCourse cleanupCourse(LocalCourse incomingCourse)
|
||||
{
|
||||
var modulesWithUniqueAssignments = incomingCourse.Modules.Select(
|
||||
module => module with { Assignments = module.Assignments.DistinctBy(a => a.id) }
|
||||
@@ -53,8 +56,42 @@ public class CoursePlanner
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<CanvasAssignment>? CanvasAssignments { get; set; } = null;
|
||||
public IEnumerable<CanvasModule>? CanvasModules { get; set; } = null;
|
||||
private IEnumerable<CanvasAssignment>? canvasAssignments = 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()
|
||||
{
|
||||
@@ -66,12 +103,18 @@ public class CoursePlanner
|
||||
)
|
||||
return;
|
||||
|
||||
LoadingCanvasData = true;
|
||||
StateHasChanged?.Invoke();
|
||||
|
||||
var canvasId =
|
||||
LocalCourse.CanvasId ?? throw new Exception("no course canvas id to sync with canvas");
|
||||
await ensureAllModulesCreated(canvasId);
|
||||
await reloadModules_UpdateLocalModulesWithNewId(canvasId);
|
||||
|
||||
await ensureAllAssignmentsCreated_updateIds(canvasId);
|
||||
|
||||
LoadingCanvasData = false;
|
||||
StateHasChanged?.Invoke();
|
||||
}
|
||||
|
||||
private async Task reloadModules_UpdateLocalModulesWithNewId(ulong canvasId)
|
||||
@@ -139,21 +182,121 @@ public class CoursePlanner
|
||||
throw new Exception(
|
||||
"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");
|
||||
var canvasAssignment = await canvas.CreateAssignment(
|
||||
courseId: canvasId,
|
||||
name: localAssignment.name,
|
||||
submissionTypes: localAssignment.submission_types,
|
||||
description: localAssignment.description,
|
||||
dueAt: localAssignment.due_at,
|
||||
lockAt: localAssignment.lock_at,
|
||||
pointsPossible: localAssignment.points_possible
|
||||
var canvasAssignment = CanvasAssignments.FirstOrDefault(
|
||||
ca => ca.Id == localAssignment.canvasId
|
||||
);
|
||||
string localHtmlDescription = getAssignmentDescriptionHtml(localAssignment);
|
||||
|
||||
if (canvasAssignment != null)
|
||||
{
|
||||
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()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using LocalModels;
|
||||
|
||||
public record RubricItem
|
||||
{
|
||||
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 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<RestResponse> PostAsync(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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user