smarter canvas updates, only update when a change is detected

This commit is contained in:
2023-07-31 14:06:40 -06:00
parent e987df30d3
commit 70db40867c
13 changed files with 465 additions and 204 deletions

View 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);
}
}

View File

@@ -1,3 +1,6 @@
namespace Management.Services.Canvas;
public record CanvasAssignmentCreationRequest
{
public string? name { get; set; }

View 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;
}
}

View 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, "");
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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)
{