From 6793e76c00820e98b9d7caeae1a8336f9315271a Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Fri, 17 Mar 2023 13:00:02 +0100 Subject: [PATCH 01/15] Created Page model and Service interface --- Epsilon.Canvas.Abstractions/Model/Page.cs | 21 +++++++++++++++++++ .../Service/IPageHttpService.cs | 10 +++++++++ 2 files changed, 31 insertions(+) create mode 100644 Epsilon.Canvas.Abstractions/Model/Page.cs create mode 100644 Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs diff --git a/Epsilon.Canvas.Abstractions/Model/Page.cs b/Epsilon.Canvas.Abstractions/Model/Page.cs new file mode 100644 index 00000000..05b1ad77 --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/Page.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace Epsilon.Canvas.Abstractions.Model; + +public record Page( + [property: JsonPropertyName("title")] string? Title, + [property: JsonPropertyName("created_at")] string? CreatedAt, + [property: JsonPropertyName("url")] string? Url, + [property: JsonPropertyName("editing_roles")] string? EditingRoles, + [property: JsonPropertyName("page_id")] string? PageId, + [property: JsonPropertyName("last_edited_by")] string? LastEditedBy, + [property: JsonPropertyName("published")] string? Published, + [property: JsonPropertyName("hide_from_students")] string? HideFromStudents, + [property: JsonPropertyName("front_page")] string? FrontPage, + [property: JsonPropertyName("html_url")] string? HTMLUrl, + [property: JsonPropertyName("todo_date")] string? TodoDate, + [property: JsonPropertyName("publish_at")] string? PublishAt, + [property: JsonPropertyName("updated_at")] string? UpdatedAt, + [property: JsonPropertyName("locked_for_user")] string? LockedForUser, + [property: JsonPropertyName("body")] string? Body +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs new file mode 100644 index 00000000..9c22d8cf --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs @@ -0,0 +1,10 @@ +using Epsilon.Canvas.Abstractions.Model; + +namespace Epsilon.Canvas.Abstractions.Service; + +public interface IPageHttpService +{ + Task<Page?> GetPageByName(int courseId, string pageName); + + Task<IEnumerable<Page>?> GetAll(int courseId, IEnumerable<string> include); +} \ No newline at end of file From 2e6e2731f242ffd6c5907a95a11a858ab490da4e Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Fri, 17 Mar 2023 14:34:06 +0100 Subject: [PATCH 02/15] Created PageHttpService.cs --- Epsilon.Abstractions/Model/CoursePage.cs | 20 +++++++++++ Epsilon.Canvas.Abstractions/Model/Page.cs | 17 +++++----- .../CanvasModuleCollectionFetcher.cs | 6 +++- .../CanvasServiceCollectionExtensions.cs | 1 + Epsilon.Canvas/Service/PageHttpService.cs | 33 +++++++++++++++++++ 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 Epsilon.Abstractions/Model/CoursePage.cs create mode 100644 Epsilon.Canvas/Service/PageHttpService.cs diff --git a/Epsilon.Abstractions/Model/CoursePage.cs b/Epsilon.Abstractions/Model/CoursePage.cs new file mode 100644 index 00000000..dc68fac2 --- /dev/null +++ b/Epsilon.Abstractions/Model/CoursePage.cs @@ -0,0 +1,20 @@ +namespace Epsilon.Abstractions.Model; + +public class CoursePage +{ + public string Title { get; set; } + public string CreatedAt { get; set; } + public string Url { get; set; } + public string EditingRoles { get; set; } + public string PageId { get; set; } + public string LastEditedBy { get; set; } + public string Published { get; set; } + public string HideFromStudents { get; set; } + public string FrontPage { get; set; } + public string HTMLUrl { get; set; } + public string TodoDate { get; set; } + public string PublishedAt { get; set; } + public string UpdatedAt { get; set; } + public string LockedForUser { get; set; } + public string Body { get; set; } +} \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/Page.cs b/Epsilon.Canvas.Abstractions/Model/Page.cs index 05b1ad77..2a82460d 100644 --- a/Epsilon.Canvas.Abstractions/Model/Page.cs +++ b/Epsilon.Canvas.Abstractions/Model/Page.cs @@ -7,15 +7,14 @@ public record Page( [property: JsonPropertyName("created_at")] string? CreatedAt, [property: JsonPropertyName("url")] string? Url, [property: JsonPropertyName("editing_roles")] string? EditingRoles, - [property: JsonPropertyName("page_id")] string? PageId, - [property: JsonPropertyName("last_edited_by")] string? LastEditedBy, - [property: JsonPropertyName("published")] string? Published, - [property: JsonPropertyName("hide_from_students")] string? HideFromStudents, - [property: JsonPropertyName("front_page")] string? FrontPage, + [property: JsonPropertyName("page_id")] int? PageId, + [property: JsonPropertyName("published")] bool? Published, + [property: JsonPropertyName("hide_from_students")] bool? HideFromStudents, + [property: JsonPropertyName("front_page")] bool? FrontPage, [property: JsonPropertyName("html_url")] string? HTMLUrl, - [property: JsonPropertyName("todo_date")] string? TodoDate, - [property: JsonPropertyName("publish_at")] string? PublishAt, - [property: JsonPropertyName("updated_at")] string? UpdatedAt, - [property: JsonPropertyName("locked_for_user")] string? LockedForUser, + [property: JsonPropertyName("todo_date")] DateTime? TodoDate, + [property: JsonPropertyName("publish_at")] DateTime? PublishAt, + [property: JsonPropertyName("updated_at")] DateTime? UpdatedAt, + [property: JsonPropertyName("locked_for_user")] bool? LockedForUser, [property: JsonPropertyName("body")] string? Body ); \ No newline at end of file diff --git a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs index 2c6c7db0..136469dd 100644 --- a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs +++ b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs @@ -10,19 +10,23 @@ public class CanvasModuleCollectionFetcher : ICanvasModuleCollectionFetcher { private readonly IModuleHttpService _moduleService; private readonly IOutcomeHttpService _outcomeService; + private readonly IPageHttpService _pageService; public CanvasModuleCollectionFetcher( ILogger<CanvasModuleCollectionFetcher> logger, IModuleHttpService moduleService, - IOutcomeHttpService outcomeService + IOutcomeHttpService outcomeService, + IPageHttpService pageService ) { _moduleService = moduleService; _outcomeService = outcomeService; + _pageService = pageService; } public async IAsyncEnumerable<ModuleOutcomeResultCollection> GetAll(int courseId, IEnumerable<string>? allowedModules) { + var pages = await _pageService.GetPageByName(courseId,"front_page"); var response = await _outcomeService.GetResults(courseId, new[] { "outcomes", "alignments" }); var modules = await _moduleService.GetAll(courseId, new[] { "items" }); diff --git a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs index 9888c720..04989038 100644 --- a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs +++ b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs @@ -32,6 +32,7 @@ public static IServiceCollection AddCanvas(this IServiceCollection services, ICo services.AddHttpClient<IAssignmentHttpService, AssignmentHttpService>(CanvasHttpClient); services.AddHttpClient<IOutcomeHttpService, OutcomeHttpService>(CanvasHttpClient); services.AddHttpClient<ISubmissionHttpService, SubmissionHttpService>(CanvasHttpClient); + services.AddHttpClient<IPageHttpService, PageHttpService>(CanvasHttpClient); services.AddScoped<ILinkHeaderConverter, LinkHeaderConverter>(); diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs new file mode 100644 index 00000000..ad017066 --- /dev/null +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -0,0 +1,33 @@ +using System.Net.Http.Json; +using Epsilon.Abstractions.Http; +using Epsilon.Canvas.Abstractions.Converter; +using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Service; + +namespace Epsilon.Canvas.Service; + +public class PageHttpService : HttpService, IPageHttpService +{ + private readonly ILinkHeaderConverter _headerConverter; + + public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) : base(client) + { + _headerConverter = headerConverter; + } + + public async Task<Page?> GetPageByName(int courseId, string pageName) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); + var response = await Client.SendAsync(request); + + return await response.Content.ReadFromJsonAsync<Page>(); + } + + public async Task<IEnumerable<Page>?> GetAll(int courseId, IEnumerable<string> include) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); + var response = await Client.SendAsync(request); + + return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); + } +} \ No newline at end of file From a2036fbf29fef96e3ba7f3f0897bc94df8afbb22 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Fri, 24 Mar 2023 13:42:40 +0100 Subject: [PATCH 03/15] Added GetPageImages method --- Epsilon.Canvas/Epsilon.Canvas.csproj | 1 + Epsilon.Canvas/Service/PageHttpService.cs | 85 +++++++++++++++++++++-- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/Epsilon.Canvas/Epsilon.Canvas.csproj b/Epsilon.Canvas/Epsilon.Canvas.csproj index c4c618d3..5077c19b 100644 --- a/Epsilon.Canvas/Epsilon.Canvas.csproj +++ b/Epsilon.Canvas/Epsilon.Canvas.csproj @@ -12,6 +12,7 @@ </ItemGroup> <ItemGroup> + <PackageReference Include="HtmlAgilityPack" Version="1.11.46" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" /> diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index ad017066..8c503737 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -1,8 +1,10 @@ -using System.Net.Http.Json; +using System.Net; +using System.Net.Http.Json; using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Converter; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; +using HtmlAgilityPack; namespace Epsilon.Canvas.Service; @@ -18,16 +20,89 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) public async Task<Page?> GetPageByName(int courseId, string pageName) { var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); - var response = await Client.SendAsync(request); + var response = await Client.SendAsync(request); + + if(response.StatusCode == HttpStatusCode.NotFound) + { + throw new Exception("Page not found"); + } + + if(response.StatusCode == HttpStatusCode.Forbidden) + { + throw new Exception("Page forbidden"); + } + + if(response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new Exception("Page unauthorized"); + } + + if (response.StatusCode == HttpStatusCode.OK) + { + var page = await response.Content.ReadFromJsonAsync<Page>(); + this.GetPageImages(page); + return page; + } - return await response.Content.ReadFromJsonAsync<Page>(); + return null; } public async Task<IEnumerable<Page>?> GetAll(int courseId, IEnumerable<string> include) { var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); var response = await Client.SendAsync(request); + + if(response.StatusCode == HttpStatusCode.NotFound) + { + throw new Exception("Not found"); + } + + if(response.StatusCode == HttpStatusCode.Forbidden) + { + throw new Exception("Forbidden"); + } + + if(response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new Exception("Unauthorized"); + } - return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); + if (response.StatusCode == HttpStatusCode.OK) + { + return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); + } + + return null; + } + + private async Task GetPageImages(Page page) + { + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(page.Body); + + string imageSrc = htmlDoc.DocumentNode + .SelectNodes("//img") + .First() + .Attributes["src"].Value; + + string imageAlt = htmlDoc.DocumentNode + .SelectNodes("//img") + .First() + .Attributes["alt"].Value; + + if(imageSrc == null || imageAlt == null) + { + throw new Exception("No image found"); + } + + try + { + byte[] imageBytes = await Client.GetByteArrayAsync(imageSrc); + await File.WriteAllBytesAsync(imageAlt, imageBytes); + } + catch(Exception e) + { + throw new Exception(e.Message); + } } -} \ No newline at end of file +} From 20a7e0d83f2037736b1f6703a2bec3471e264829 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Fri, 24 Mar 2023 15:25:31 +0100 Subject: [PATCH 04/15] Changed GetPageImages so that it replaces the img src with the base64 string of the image. --- Epsilon.Canvas/Service/PageHttpService.cs | 34 ++++++++--------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index 8c503737..92f566c5 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -75,34 +75,24 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) return null; } - private async Task GetPageImages(Page page) + private async Task<string> GetPageImages(Page page) { var htmlDoc = new HtmlDocument(); htmlDoc.LoadHtml(page.Body); - - string imageSrc = htmlDoc.DocumentNode - .SelectNodes("//img") - .First() - .Attributes["src"].Value; - string imageAlt = htmlDoc.DocumentNode - .SelectNodes("//img") - .First() - .Attributes["alt"].Value; - - if(imageSrc == null || imageAlt == null) - { - throw new Exception("No image found"); - } - - try + foreach (var node in htmlDoc.DocumentNode.SelectNodes("//img")) { + string imageSrc = htmlDoc.DocumentNode + .SelectNodes("//img") + .First() + .Attributes["src"].Value; + byte[] imageBytes = await Client.GetByteArrayAsync(imageSrc); - await File.WriteAllBytesAsync(imageAlt, imageBytes); - } - catch(Exception e) - { - throw new Exception(e.Message); + string imageBase64 = Convert.ToBase64String(imageBytes); + + node.SetAttributeValue("src", $"data:image/jpeg;base64,{imageBase64}"); } + + return htmlDoc.DocumentNode.WriteTo(); } } From 76daec6d43b558320f0877a9a79766f692ae759c Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 14:05:16 +0200 Subject: [PATCH 05/15] Added persona to word exporter --- .../Export/IExportDataPackager.cs | 2 +- Epsilon.Abstractions/Http/HttpService.cs | 2 +- Epsilon.Abstractions/Model/ExportData.cs | 3 +- .../Service/IFileHttpService.cs | 7 ++ .../Service/IPageHttpService.cs | 2 +- .../CanvasModuleCollectionFetcher.cs | 15 ++-- .../CanvasServiceCollectionExtensions.cs | 6 +- Epsilon.Canvas/Epsilon.Canvas.csproj | 1 - Epsilon.Canvas/Service/FileHttpService.cs | 27 +++++++ Epsilon.Canvas/Service/PageHttpService.cs | 80 +++++-------------- Epsilon.Cli/Startup.cs | 12 ++- Epsilon/Epsilon.csproj | 1 - Epsilon/Export/ExportDataPackager.cs | 33 ++++++-- .../Export/Exporters/WordModuleExporter.cs | 66 ++++++++++++--- .../CoreServiceCollectionExtensions.cs | 2 + 15 files changed, 161 insertions(+), 98 deletions(-) create mode 100644 Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs create mode 100644 Epsilon.Canvas/Service/FileHttpService.cs diff --git a/Epsilon.Abstractions/Export/IExportDataPackager.cs b/Epsilon.Abstractions/Export/IExportDataPackager.cs index f4e0aafd..079d987e 100644 --- a/Epsilon.Abstractions/Export/IExportDataPackager.cs +++ b/Epsilon.Abstractions/Export/IExportDataPackager.cs @@ -5,5 +5,5 @@ namespace Epsilon.Abstractions.Export; public interface IExportDataPackager { - public Task<ExportData> GetExportData(IAsyncEnumerable<ModuleOutcomeResultCollection> data); + public Task<ExportData> GetExportData(); } \ No newline at end of file diff --git a/Epsilon.Abstractions/Http/HttpService.cs b/Epsilon.Abstractions/Http/HttpService.cs index c5643701..0c803fa2 100644 --- a/Epsilon.Abstractions/Http/HttpService.cs +++ b/Epsilon.Abstractions/Http/HttpService.cs @@ -4,5 +4,5 @@ public abstract class HttpService { protected HttpService(HttpClient client) => Client = client; - protected HttpClient Client { get; } + public HttpClient Client { get; } } \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/ExportData.cs b/Epsilon.Abstractions/Model/ExportData.cs index 51558856..fe1b7493 100644 --- a/Epsilon.Abstractions/Model/ExportData.cs +++ b/Epsilon.Abstractions/Model/ExportData.cs @@ -2,6 +2,7 @@ { public class ExportData { + public string PersonaHtml { get; set; } = string.Empty; public IEnumerable<CourseModule> CourseModules { get; set; } = Enumerable.Empty<CourseModule>(); } -} +} \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs new file mode 100644 index 00000000..f5dd1635 --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs @@ -0,0 +1,7 @@ +namespace Epsilon.Canvas.Abstractions.Service; + +public interface IFileHttpService +{ + Task<byte[]> GetFileByteArray(string url); + HttpClient Client { get; } +} \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs index 9c22d8cf..a9390d6c 100644 --- a/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IPageHttpService.cs @@ -4,7 +4,7 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IPageHttpService { - Task<Page?> GetPageByName(int courseId, string pageName); + Task<string> GetPageByName(int courseId, string pageName); Task<IEnumerable<Page>?> GetAll(int courseId, IEnumerable<string> include); } \ No newline at end of file diff --git a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs index 136469dd..b38db8ae 100644 --- a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs +++ b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs @@ -10,25 +10,22 @@ public class CanvasModuleCollectionFetcher : ICanvasModuleCollectionFetcher { private readonly IModuleHttpService _moduleService; private readonly IOutcomeHttpService _outcomeService; - private readonly IPageHttpService _pageService; public CanvasModuleCollectionFetcher( ILogger<CanvasModuleCollectionFetcher> logger, IModuleHttpService moduleService, - IOutcomeHttpService outcomeService, - IPageHttpService pageService + IOutcomeHttpService outcomeService ) { _moduleService = moduleService; _outcomeService = outcomeService; - _pageService = pageService; } - public async IAsyncEnumerable<ModuleOutcomeResultCollection> GetAll(int courseId, IEnumerable<string>? allowedModules) + public async IAsyncEnumerable<ModuleOutcomeResultCollection> GetAll(int courseId, + IEnumerable<string>? allowedModules) { - var pages = await _pageService.GetPageByName(courseId,"front_page"); - var response = await _outcomeService.GetResults(courseId, new[] { "outcomes", "alignments" }); - var modules = await _moduleService.GetAll(courseId, new[] { "items" }); + var response = await _outcomeService.GetResults(courseId, new[] {"outcomes", "alignments"}); + var modules = await _moduleService.GetAll(courseId, new[] {"items"}); Debug.Assert(response != null, nameof(response) + " != null"); Debug.Assert(modules != null, nameof(modules) + " != null"); @@ -45,7 +42,7 @@ public async IAsyncEnumerable<ModuleOutcomeResultCollection> GetAll(int courseId yield return new ModuleOutcomeResultCollection(module, new OutcomeResultCollection( response.OutcomeResults.Where(r => ids.Contains(r.Link.Alignment)), - response.Links with { Alignments = response.Links.Alignments.Where(a => ids.Contains(a.Id)) } + response.Links with {Alignments = response.Links.Alignments.Where(a => ids.Contains(a.Id))} )); } } diff --git a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs index 04989038..31b6664c 100644 --- a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs +++ b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs @@ -24,7 +24,8 @@ public static IServiceCollection AddCanvas(this IServiceCollection services, ICo var settings = provider.GetRequiredService<IOptions<CanvasSettings>>().Value; client.BaseAddress = settings.ApiUrl; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", settings.AccessToken); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", settings.AccessToken); }); services.AddHttpClient<IPaginatorHttpService, PaginatorHttpService>(CanvasHttpClient); @@ -33,9 +34,10 @@ public static IServiceCollection AddCanvas(this IServiceCollection services, ICo services.AddHttpClient<IOutcomeHttpService, OutcomeHttpService>(CanvasHttpClient); services.AddHttpClient<ISubmissionHttpService, SubmissionHttpService>(CanvasHttpClient); services.AddHttpClient<IPageHttpService, PageHttpService>(CanvasHttpClient); + services.AddHttpClient<IFileHttpService, FileHttpService>(CanvasHttpClient); services.AddScoped<ILinkHeaderConverter, LinkHeaderConverter>(); - + services.AddScoped<ICanvasModuleCollectionFetcher, CanvasModuleCollectionFetcher>(); return services; diff --git a/Epsilon.Canvas/Epsilon.Canvas.csproj b/Epsilon.Canvas/Epsilon.Canvas.csproj index 5077c19b..69096ea1 100644 --- a/Epsilon.Canvas/Epsilon.Canvas.csproj +++ b/Epsilon.Canvas/Epsilon.Canvas.csproj @@ -16,7 +16,6 @@ <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" /> - <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" /> </ItemGroup> diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs new file mode 100644 index 00000000..37ad63d1 --- /dev/null +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -0,0 +1,27 @@ +using System.Net; +using System.Net.Http.Json; +using Epsilon.Abstractions.Http; +using Epsilon.Canvas.Abstractions.Converter; +using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Service; +using HtmlAgilityPack; + +namespace Epsilon.Canvas.Service; + +public class FileHttpService : HttpService, IFileHttpService +{ + private readonly ILinkHeaderConverter _headerConverter; + + public FileHttpService(HttpClient client, ILinkHeaderConverter headerConverter) : base(client) + { + _headerConverter = headerConverter; + } + + public async Task<byte[]> GetFileByteArray(string url) + { + if (url == null) + throw new ArgumentNullException(nameof(url)); + + return await Client.GetByteArrayAsync(url); + } +} \ No newline at end of file diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index 92f566c5..d524d3a2 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -4,7 +4,6 @@ using Epsilon.Canvas.Abstractions.Converter; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; -using HtmlAgilityPack; namespace Epsilon.Canvas.Service; @@ -17,32 +16,22 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) _headerConverter = headerConverter; } - public async Task<Page?> GetPageByName(int courseId, string pageName) + public async Task<string?> GetPageByName(int courseId, string pageName) { var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); - var response = await Client.SendAsync(request); - - if(response.StatusCode == HttpStatusCode.NotFound) - { - throw new Exception("Page not found"); - } - - if(response.StatusCode == HttpStatusCode.Forbidden) - { - throw new Exception("Page forbidden"); - } - - if(response.StatusCode == HttpStatusCode.Unauthorized) - { - throw new Exception("Page unauthorized"); - } + var response = await Client.SendAsync(request); + + if (response.StatusCode == HttpStatusCode.NotFound) + throw new Exception("Not found"); + + if (response.StatusCode == HttpStatusCode.Forbidden) + throw new Exception("Forbidden"); + + if (response.StatusCode == HttpStatusCode.Unauthorized) + throw new Exception("Unauthorized"); if (response.StatusCode == HttpStatusCode.OK) - { - var page = await response.Content.ReadFromJsonAsync<Page>(); - this.GetPageImages(page); - return page; - } + return (await response.Content.ReadFromJsonAsync<Page>()).Body; return null; } @@ -51,48 +40,19 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) { var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); var response = await Client.SendAsync(request); - - if(response.StatusCode == HttpStatusCode.NotFound) - { + + if (response.StatusCode == HttpStatusCode.NotFound) throw new Exception("Not found"); - } - - if(response.StatusCode == HttpStatusCode.Forbidden) - { + + if (response.StatusCode == HttpStatusCode.Forbidden) throw new Exception("Forbidden"); - } - - if(response.StatusCode == HttpStatusCode.Unauthorized) - { + + if (response.StatusCode == HttpStatusCode.Unauthorized) throw new Exception("Unauthorized"); - } if (response.StatusCode == HttpStatusCode.OK) - { return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); - } - - return null; - } - - private async Task<string> GetPageImages(Page page) - { - var htmlDoc = new HtmlDocument(); - htmlDoc.LoadHtml(page.Body); - - foreach (var node in htmlDoc.DocumentNode.SelectNodes("//img")) - { - string imageSrc = htmlDoc.DocumentNode - .SelectNodes("//img") - .First() - .Attributes["src"].Value; - - byte[] imageBytes = await Client.GetByteArrayAsync(imageSrc); - string imageBase64 = Convert.ToBase64String(imageBytes); - node.SetAttributeValue("src", $"data:image/jpeg;base64,{imageBase64}"); - } - - return htmlDoc.DocumentNode.WriteTo(); + return null; } -} +} \ No newline at end of file diff --git a/Epsilon.Cli/Startup.cs b/Epsilon.Cli/Startup.cs index e026bc23..37abc68c 100644 --- a/Epsilon.Cli/Startup.cs +++ b/Epsilon.Cli/Startup.cs @@ -15,7 +15,6 @@ public class Startup : IHostedService private readonly IHostApplicationLifetime _lifetime; private readonly ExportOptions _exportOptions; private readonly CanvasSettings _canvasSettings; - private readonly ICanvasModuleCollectionFetcher _collectionFetcher; private readonly IModuleExporterCollection _exporterCollection; private readonly IExportDataPackager _exporterDataCollection; @@ -24,7 +23,6 @@ public Startup( IHostApplicationLifetime lifetime, IOptions<CanvasSettings> canvasSettings, IOptions<ExportOptions> exportSettings, - ICanvasModuleCollectionFetcher collectionFetcher, IModuleExporterCollection exporterCollection, IExportDataPackager exporterDataCollection) { @@ -32,7 +30,6 @@ public Startup( _canvasSettings = canvasSettings.Value; _exportOptions = exportSettings.Value; _lifetime = lifetime; - _collectionFetcher = collectionFetcher; _exporterCollection = exporterCollection; _exporterDataCollection = exporterDataCollection; } @@ -65,12 +62,11 @@ private async Task ExecuteAsync() return; } - var modules = _exportOptions.Modules?.Split(","); _logger.LogInformation("Targeting Canvas course: {CourseId}, at {Url}", _canvasSettings.CourseId, _canvasSettings.ApiUrl); _logger.LogInformation("Downloading results, this may take a few seconds..."); - var items = _collectionFetcher.GetAll(_canvasSettings.CourseId, modules); - var formattedItems = await _exporterDataCollection.GetExportData(items); + + var formattedItems = await _exporterDataCollection.GetExportData(); var formats = _exportOptions.Formats.Split(","); var exporters = _exporterCollection.DetermineExporters(formats).ToArray(); @@ -82,7 +78,9 @@ private async Task ExecuteAsync() _logger.LogInformation("Exporting to {Format} using {Exporter}...", format, exporter.GetType().Name); var stream = await exporter.Export(formattedItems, format); - await using var fileStream = new FileStream($"{_exportOptions.FormattedOutputName}.{exporter.FileExtension}", FileMode.Create, FileAccess.Write); + await using var fileStream = + new FileStream($"{_exportOptions.FormattedOutputName}.{exporter.FileExtension}", FileMode.Create, + FileAccess.Write); stream.Position = 0; // Reset position to zero to prepare for copy await stream.CopyToAsync(fileStream); diff --git a/Epsilon/Epsilon.csproj b/Epsilon/Epsilon.csproj index 56da7f5e..120f0714 100644 --- a/Epsilon/Epsilon.csproj +++ b/Epsilon/Epsilon.csproj @@ -17,7 +17,6 @@ <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" /> <PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="DocumentFormat.OpenXml" Version="2.18.0" /> - <PackageReference Include="System.Linq.Async" Version="6.0.1" /> </ItemGroup> </Project> diff --git a/Epsilon/Export/ExportDataPackager.cs b/Epsilon/Export/ExportDataPackager.cs index a9f94d7d..9e75afd7 100644 --- a/Epsilon/Export/ExportDataPackager.cs +++ b/Epsilon/Export/ExportDataPackager.cs @@ -1,19 +1,42 @@ using System.Diagnostics; using Epsilon.Abstractions.Export; using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Canvas; +using Epsilon.Canvas.Abstractions; +using Epsilon.Canvas.Abstractions.Service; +using Microsoft.Extensions.Options; namespace Epsilon.Export; public class ExportDataPackager : IExportDataPackager { - public async Task<ExportData> GetExportData(IAsyncEnumerable<ModuleOutcomeResultCollection> data) + private readonly ICanvasModuleCollectionFetcher _moduleCollectionFetcher; + private readonly IPageHttpService _pageService; + private readonly ExportOptions _exportOptions; + private readonly CanvasSettings _canvasSettings; + + public ExportDataPackager(ICanvasModuleCollectionFetcher moduleCollectionFetcher, IPageHttpService pageService, + IOptions<CanvasSettings> canvasSettings, IOptions<ExportOptions> exportOptions) + { + _moduleCollectionFetcher = moduleCollectionFetcher; + _pageService = pageService; + _exportOptions = exportOptions.Value; + _canvasSettings = canvasSettings.Value; + } + + public async Task<ExportData> GetExportData() { + var modules = _exportOptions.Modules?.Split(","); + var courseId = _canvasSettings.CourseId; + + var moduleOutcomes = _moduleCollectionFetcher.GetAll(courseId, modules); + var personaHtml = await _pageService.GetPageByName(courseId, "front_page"); + var output = new List<CourseModule>(); - await foreach (var item in data.Where(m => m.Collection.OutcomeResults.Any())) + await foreach (var item in moduleOutcomes.Where(m => m.Collection.OutcomeResults.Any())) { - var module = new CourseModule { Name = item.Module.Name }; + var module = new CourseModule {Name = item.Module.Name}; var links = item.Collection.Links; Debug.Assert(links != null, nameof(links) + " != null"); @@ -56,6 +79,6 @@ public async Task<ExportData> GetExportData(IAsyncEnumerable<ModuleOutcomeResult output.Add(module); } - return new ExportData { CourseModules = output }; + return new ExportData {CourseModules = output, PersonaHtml = personaHtml}; } } \ No newline at end of file diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index f9a36be3..fa50d377 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -4,18 +4,20 @@ using DocumentFormat.OpenXml.Wordprocessing; using Epsilon.Abstractions.Export; using Epsilon.Abstractions.Model; +using Epsilon.Canvas.Abstractions.Service; +using HtmlAgilityPack; namespace Epsilon.Export.Exporters; public class WordModuleExporter : ICanvasModuleExporter { private static readonly TableBorders s_defaultBorders = new( - new TopBorder { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3 }, - new BottomBorder { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3 }, - new LeftBorder { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3 }, - new RightBorder { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3 }, - new InsideHorizontalBorder { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 6 }, - new InsideVerticalBorder { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 6 } + new TopBorder {Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3}, + new BottomBorder {Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3}, + new LeftBorder {Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3}, + new RightBorder {Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 3}, + new InsideHorizontalBorder {Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 6}, + new InsideVerticalBorder {Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 6} ); private static readonly TableProperties s_defaultTableProperties = new(s_defaultBorders); @@ -26,10 +28,15 @@ public class WordModuleExporter : ICanvasModuleExporter CreateTextCell("Score") ); - public IEnumerable<string> Formats { get; } = new[] { "word", "docx" }; - + private readonly IFileHttpService _fileService; + public IEnumerable<string> Formats { get; } = new[] {"word", "docx"}; public string FileExtension => "docx"; + public WordModuleExporter(IFileHttpService fileService) + { + _fileService = fileService; + } + public async Task<Stream> Export(ExportData data, string format) { var stream = new MemoryStream(); @@ -42,6 +49,25 @@ public async Task<Stream> Export(ExportData data, string format) var cellValueBuilder = new StringBuilder(); var cellValueOutComeResultsBuilder = new StringBuilder(); + var altChunkId = "HomePage"; + + var personaHTML = new HtmlDocument(); + personaHTML.LoadHtml(data.PersonaHtml); + + var ms = new MemoryStream(new UTF8Encoding(true).GetPreamble() + .Concat(Encoding.UTF8.GetBytes($"<html>{personaHTML.Text}</html>")).ToArray()); + + var formatImportPart = + document.MainDocumentPart.AddAlternativeFormatImportPart( + AlternativeFormatImportPartType.Html, altChunkId); + + formatImportPart.FeedData(ms); + AltChunk altChunk = new AltChunk(); + altChunk.Id = altChunkId; + + body?.Append(altChunk); + body?.Append(new Paragraph(new Run(new Break() {Type = BreakValues.Page}))); + foreach (var module in data.CourseModules) { body?.Append(CreateText(module.Name)); @@ -86,6 +112,28 @@ public async Task<Stream> Export(ExportData data, string format) private static TableCell CreateTextCell(string text) => new( CreateText(text), - new TableCellProperties(new TableCellWidth { Type = TableWidthUnitValues.Auto }) + new TableCellProperties(new TableCellWidth {Type = TableWidthUnitValues.Auto}) ); + + private async Task ReplaceSrcWithBase64String(HtmlDocument htmlDoc) + { + if (htmlDoc == null) + throw new ArgumentNullException(nameof(htmlDoc)); + + foreach (var node in htmlDoc.DocumentNode.SelectNodes("//img")) + { + var imageSrc = node + .SelectNodes("//img") + .First() + .Attributes["src"].Value; + + if (imageSrc == null) + throw new ArgumentNullException(nameof(imageSrc)); + + var imageBytes = await _fileService.GetFileByteArray(imageSrc); + var imageBase64 = Convert.ToBase64String(imageBytes); + + node.SetAttributeValue("src", $"data:image/jpeg;base64,{imageBase64}"); + } + } } \ No newline at end of file diff --git a/Epsilon/Extensions/CoreServiceCollectionExtensions.cs b/Epsilon/Extensions/CoreServiceCollectionExtensions.cs index 63515668..493a4f34 100644 --- a/Epsilon/Extensions/CoreServiceCollectionExtensions.cs +++ b/Epsilon/Extensions/CoreServiceCollectionExtensions.cs @@ -1,5 +1,7 @@ using Epsilon.Abstractions.Export; using Epsilon.Canvas; +using Epsilon.Canvas.Abstractions.Service; +using Epsilon.Canvas.Service; using Epsilon.Export; using Epsilon.Export.Exporters; using Microsoft.Extensions.Configuration; From c6f351539334a377f462288c319708d7d56b4708 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 15:39:42 +0200 Subject: [PATCH 06/15] Added dispose --- Epsilon.Canvas/Service/PageHttpService.cs | 2 ++ Epsilon/Export/Exporters/WordModuleExporter.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index d524d3a2..f8b780bf 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -20,6 +20,7 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) { var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); var response = await Client.SendAsync(request); + request.Dispose(); if (response.StatusCode == HttpStatusCode.NotFound) throw new Exception("Not found"); @@ -40,6 +41,7 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) { var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); var response = await Client.SendAsync(request); + request.Dispose(); if (response.StatusCode == HttpStatusCode.NotFound) throw new Exception("Not found"); diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index fa50d377..8dad919b 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -54,6 +54,7 @@ public async Task<Stream> Export(ExportData data, string format) var personaHTML = new HtmlDocument(); personaHTML.LoadHtml(data.PersonaHtml); + var ms = new MemoryStream(new UTF8Encoding(true).GetPreamble() .Concat(Encoding.UTF8.GetBytes($"<html>{personaHTML.Text}</html>")).ToArray()); @@ -66,6 +67,7 @@ public async Task<Stream> Export(ExportData data, string format) altChunk.Id = altChunkId; body?.Append(altChunk); + ms.DisposeAsync(); body?.Append(new Paragraph(new Run(new Break() {Type = BreakValues.Page}))); foreach (var module in data.CourseModules) From e7c57c8dd43782069860013d21af420f4812b059 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 17:06:40 +0200 Subject: [PATCH 07/15] Removed redundant code and renamed method --- Epsilon.Canvas/Service/FileHttpService.cs | 8 +------ Epsilon.Canvas/Service/PageHttpService.cs | 23 +------------------ .../Export/Exporters/WordModuleExporter.cs | 5 +--- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs index 37ad63d1..9208a74d 100644 --- a/Epsilon.Canvas/Service/FileHttpService.cs +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -10,18 +10,12 @@ namespace Epsilon.Canvas.Service; public class FileHttpService : HttpService, IFileHttpService { - private readonly ILinkHeaderConverter _headerConverter; - - public FileHttpService(HttpClient client, ILinkHeaderConverter headerConverter) : base(client) + public FileHttpService(HttpClient client) : base(client) { - _headerConverter = headerConverter; } public async Task<byte[]> GetFileByteArray(string url) { - if (url == null) - throw new ArgumentNullException(nameof(url)); - return await Client.GetByteArrayAsync(url); } } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index f8b780bf..69dd1113 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -9,11 +9,8 @@ namespace Epsilon.Canvas.Service; public class PageHttpService : HttpService, IPageHttpService { - private readonly ILinkHeaderConverter _headerConverter; - - public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) : base(client) + public PageHttpService(HttpClient client) : base(client) { - _headerConverter = headerConverter; } public async Task<string?> GetPageByName(int courseId, string pageName) @@ -22,15 +19,6 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) var response = await Client.SendAsync(request); request.Dispose(); - if (response.StatusCode == HttpStatusCode.NotFound) - throw new Exception("Not found"); - - if (response.StatusCode == HttpStatusCode.Forbidden) - throw new Exception("Forbidden"); - - if (response.StatusCode == HttpStatusCode.Unauthorized) - throw new Exception("Unauthorized"); - if (response.StatusCode == HttpStatusCode.OK) return (await response.Content.ReadFromJsonAsync<Page>()).Body; @@ -43,15 +31,6 @@ public PageHttpService(HttpClient client, ILinkHeaderConverter headerConverter) var response = await Client.SendAsync(request); request.Dispose(); - if (response.StatusCode == HttpStatusCode.NotFound) - throw new Exception("Not found"); - - if (response.StatusCode == HttpStatusCode.Forbidden) - throw new Exception("Forbidden"); - - if (response.StatusCode == HttpStatusCode.Unauthorized) - throw new Exception("Unauthorized"); - if (response.StatusCode == HttpStatusCode.OK) return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 8dad919b..64f1689b 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -117,11 +117,8 @@ public async Task<Stream> Export(ExportData data, string format) new TableCellProperties(new TableCellWidth {Type = TableWidthUnitValues.Auto}) ); - private async Task ReplaceSrcWithBase64String(HtmlDocument htmlDoc) + private async Task ReplaceImageSrcWithBase64String(HtmlDocument htmlDoc) { - if (htmlDoc == null) - throw new ArgumentNullException(nameof(htmlDoc)); - foreach (var node in htmlDoc.DocumentNode.SelectNodes("//img")) { var imageSrc = node From 4e7e5e798ca87434f822117eb406026aae3826f6 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 17:24:55 +0200 Subject: [PATCH 08/15] Wrapped HttpRequestMessage in try catch block to ensure that the request gets disposed even when an error is thrown. --- Epsilon.Canvas/Service/FileHttpService.cs | 17 +++++++- Epsilon.Canvas/Service/PageHttpService.cs | 48 +++++++++++++++++------ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs index 9208a74d..1a17eb4e 100644 --- a/Epsilon.Canvas/Service/FileHttpService.cs +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -16,6 +16,21 @@ public FileHttpService(HttpClient client) : base(client) public async Task<byte[]> GetFileByteArray(string url) { - return await Client.GetByteArrayAsync(url); + var client = new HttpClient(); + var response = new byte[0]; + try + { + response = await Client.GetByteArrayAsync(url); + } + catch (Exception e) + { + throw new Exception($"Error in GetFileByteArray: {e.Message}"); + } + finally + { + client?.Dispose(); + } + + return response; } } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index 69dd1113..13d4a35f 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Http.Json; +using System.Runtime.InteropServices; using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Converter; using Epsilon.Canvas.Abstractions.Model; @@ -15,24 +16,47 @@ public PageHttpService(HttpClient client) : base(client) public async Task<string?> GetPageByName(int courseId, string pageName) { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); - var response = await Client.SendAsync(request); - request.Dispose(); - - if (response.StatusCode == HttpStatusCode.OK) - return (await response.Content.ReadFromJsonAsync<Page>()).Body; + var request = new HttpRequestMessage(); + try + { + request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); + var response = await Client.SendAsync(request); + request.Dispose(); + + if (response.StatusCode == HttpStatusCode.OK) + return (await response.Content.ReadFromJsonAsync<Page>()).Body; + } + catch (Exception e) + { + throw new Exception($"Error in GetPageByName: {e.Message}"); + } + finally + { + request?.Dispose(); + } return null; } public async Task<IEnumerable<Page>?> GetAll(int courseId, IEnumerable<string> include) { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); - var response = await Client.SendAsync(request); - request.Dispose(); - - if (response.StatusCode == HttpStatusCode.OK) - return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); + var request = new HttpRequestMessage(); + try + { + request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); + var response = await Client.SendAsync(request); + + if (response.StatusCode == HttpStatusCode.OK) + return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); + } + catch (Exception e) + { + throw new Exception($"Error in GetAll: {e.Message}"); + } + finally + { + request?.Dispose(); + } return null; } From 4e09649acaf9d7f8bb4cb9520e9b4b67cb1901c0 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 17:46:39 +0200 Subject: [PATCH 09/15] Changed HttpClient to protected, renamed HTMLUrl to HtmlUrl to match code convention, modified Httpservices and changed FileService to iEnumerable<byte>. --- Epsilon.Abstractions/Http/HttpService.cs | 2 +- Epsilon.Abstractions/Model/CoursePage.cs | 2 +- Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs | 3 +-- Epsilon.Canvas/Service/FileHttpService.cs | 2 +- Epsilon.Canvas/Service/PageHttpService.cs | 2 -- Epsilon/Export/Exporters/WordModuleExporter.cs | 2 +- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Epsilon.Abstractions/Http/HttpService.cs b/Epsilon.Abstractions/Http/HttpService.cs index 0c803fa2..c5643701 100644 --- a/Epsilon.Abstractions/Http/HttpService.cs +++ b/Epsilon.Abstractions/Http/HttpService.cs @@ -4,5 +4,5 @@ public abstract class HttpService { protected HttpService(HttpClient client) => Client = client; - public HttpClient Client { get; } + protected HttpClient Client { get; } } \ No newline at end of file diff --git a/Epsilon.Abstractions/Model/CoursePage.cs b/Epsilon.Abstractions/Model/CoursePage.cs index dc68fac2..50ab8728 100644 --- a/Epsilon.Abstractions/Model/CoursePage.cs +++ b/Epsilon.Abstractions/Model/CoursePage.cs @@ -11,7 +11,7 @@ public class CoursePage public string Published { get; set; } public string HideFromStudents { get; set; } public string FrontPage { get; set; } - public string HTMLUrl { get; set; } + public string HtmlUrl { get; set; } public string TodoDate { get; set; } public string PublishedAt { get; set; } public string UpdatedAt { get; set; } diff --git a/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs index f5dd1635..8afbe732 100644 --- a/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IFileHttpService.cs @@ -2,6 +2,5 @@ namespace Epsilon.Canvas.Abstractions.Service; public interface IFileHttpService { - Task<byte[]> GetFileByteArray(string url); - HttpClient Client { get; } + Task<IEnumerable<byte>?> GetFileByteArray(string url); } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs index 1a17eb4e..60ef8bfe 100644 --- a/Epsilon.Canvas/Service/FileHttpService.cs +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -14,7 +14,7 @@ public FileHttpService(HttpClient client) : base(client) { } - public async Task<byte[]> GetFileByteArray(string url) + public async Task<IEnumerable<byte>?> GetFileByteArray(string url) { var client = new HttpClient(); var response = new byte[0]; diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index 13d4a35f..0c6f8e2b 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -1,8 +1,6 @@ using System.Net; using System.Net.Http.Json; -using System.Runtime.InteropServices; using Epsilon.Abstractions.Http; -using Epsilon.Canvas.Abstractions.Converter; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 64f1689b..29c4bfbc 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -130,7 +130,7 @@ private async Task ReplaceImageSrcWithBase64String(HtmlDocument htmlDoc) throw new ArgumentNullException(nameof(imageSrc)); var imageBytes = await _fileService.GetFileByteArray(imageSrc); - var imageBase64 = Convert.ToBase64String(imageBytes); + var imageBase64 = Convert.ToBase64String(imageBytes.ToArray()); node.SetAttributeValue("src", $"data:image/jpeg;base64,{imageBase64}"); } From 7d0ae5661bec4c1dd8cd03650e8a3f3b4dd69ba7 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 18:03:59 +0200 Subject: [PATCH 10/15] Changed to using --- Epsilon.Canvas/Service/FileHttpService.cs | 13 +++----- Epsilon.Canvas/Service/PageHttpService.cs | 37 ++++++++++------------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs index 60ef8bfe..866a493c 100644 --- a/Epsilon.Canvas/Service/FileHttpService.cs +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -16,21 +16,16 @@ public FileHttpService(HttpClient client) : base(client) public async Task<IEnumerable<byte>?> GetFileByteArray(string url) { - var client = new HttpClient(); - var response = new byte[0]; try { - response = await Client.GetByteArrayAsync(url); + using var httpClient = new HttpClient(); + { + return await Client.GetByteArrayAsync(url); + } } catch (Exception e) { throw new Exception($"Error in GetFileByteArray: {e.Message}"); } - finally - { - client?.Dispose(); - } - - return response; } } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index 0c6f8e2b..c58d4761 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -14,47 +14,42 @@ public PageHttpService(HttpClient client) : base(client) public async Task<string?> GetPageByName(int courseId, string pageName) { - var request = new HttpRequestMessage(); try { - request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); - var response = await Client.SendAsync(request); - request.Dispose(); - - if (response.StatusCode == HttpStatusCode.OK) - return (await response.Content.ReadFromJsonAsync<Page>()).Body; + using var httpClient = new HttpClient(); + { + var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); + var response = await httpClient.SendAsync(request); + + if (response.StatusCode == HttpStatusCode.OK) + return (await response.Content.ReadFromJsonAsync<Page>()).Body; + } } catch (Exception e) { throw new Exception($"Error in GetPageByName: {e.Message}"); } - finally - { - request?.Dispose(); - } return null; } public async Task<IEnumerable<Page>?> GetAll(int courseId, IEnumerable<string> include) { - var request = new HttpRequestMessage(); try { - request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); - var response = await Client.SendAsync(request); - - if (response.StatusCode == HttpStatusCode.OK) - return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); + using var httpClient = new HttpClient(); + { + var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); + var response = await Client.SendAsync(request); + + if (response.StatusCode == HttpStatusCode.OK) + return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); + } } catch (Exception e) { throw new Exception($"Error in GetAll: {e.Message}"); } - finally - { - request?.Dispose(); - } return null; } From 061c6f8e90361db49965f10804fb39ab4fb27670 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 18:10:41 +0200 Subject: [PATCH 11/15] Change on previous commit --- Epsilon.Canvas/Service/FileHttpService.cs | 5 +---- Epsilon.Canvas/Service/PageHttpService.cs | 14 +++++--------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs index 866a493c..91c1d3b8 100644 --- a/Epsilon.Canvas/Service/FileHttpService.cs +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -18,10 +18,7 @@ public FileHttpService(HttpClient client) : base(client) { try { - using var httpClient = new HttpClient(); - { - return await Client.GetByteArrayAsync(url); - } + return await Client.GetByteArrayAsync(url); } catch (Exception e) { diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index c58d4761..ab21a82c 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -16,14 +16,11 @@ public PageHttpService(HttpClient client) : base(client) { try { - using var httpClient = new HttpClient(); - { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); - var response = await httpClient.SendAsync(request); + var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); + using var response = await Client.SendAsync(request); - if (response.StatusCode == HttpStatusCode.OK) - return (await response.Content.ReadFromJsonAsync<Page>()).Body; - } + if (response.StatusCode == HttpStatusCode.OK) + return (await response.Content.ReadFromJsonAsync<Page>()).Body; } catch (Exception e) { @@ -37,10 +34,9 @@ public PageHttpService(HttpClient client) : base(client) { try { - using var httpClient = new HttpClient(); { var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); - var response = await Client.SendAsync(request); + using var response = await Client.SendAsync(request); if (response.StatusCode == HttpStatusCode.OK) return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); From eca97d2a59f0ffb470c955df06c35f57c153cab4 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Mon, 27 Mar 2023 18:19:35 +0200 Subject: [PATCH 12/15] fix --- Epsilon.Canvas/Service/PageHttpService.cs | 4 ++-- Epsilon/Export/Exporters/WordModuleExporter.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index ab21a82c..8ea24d1a 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -16,7 +16,7 @@ public PageHttpService(HttpClient client) : base(client) { try { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); using var response = await Client.SendAsync(request); if (response.StatusCode == HttpStatusCode.OK) @@ -35,7 +35,7 @@ public PageHttpService(HttpClient client) : base(client) try { { - var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); using var response = await Client.SendAsync(request); if (response.StatusCode == HttpStatusCode.OK) diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 29c4bfbc..21f15538 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -55,7 +55,7 @@ public async Task<Stream> Export(ExportData data, string format) personaHTML.LoadHtml(data.PersonaHtml); - var ms = new MemoryStream(new UTF8Encoding(true).GetPreamble() + using var ms = new MemoryStream(new UTF8Encoding(true).GetPreamble() .Concat(Encoding.UTF8.GetBytes($"<html>{personaHTML.Text}</html>")).ToArray()); var formatImportPart = From a1fcadcef587f80b0a1af41ed8965728bec3b6bf Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Tue, 28 Mar 2023 17:23:43 +0200 Subject: [PATCH 13/15] Removed generic exception --- Epsilon.Canvas/Service/FileHttpService.cs | 9 +----- Epsilon.Canvas/Service/PageHttpService.cs | 36 +++++++---------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/Epsilon.Canvas/Service/FileHttpService.cs b/Epsilon.Canvas/Service/FileHttpService.cs index 91c1d3b8..121a70cc 100644 --- a/Epsilon.Canvas/Service/FileHttpService.cs +++ b/Epsilon.Canvas/Service/FileHttpService.cs @@ -16,13 +16,6 @@ public FileHttpService(HttpClient client) : base(client) public async Task<IEnumerable<byte>?> GetFileByteArray(string url) { - try - { - return await Client.GetByteArrayAsync(url); - } - catch (Exception e) - { - throw new Exception($"Error in GetFileByteArray: {e.Message}"); - } + return await Client.GetByteArrayAsync(url); } } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/PageHttpService.cs b/Epsilon.Canvas/Service/PageHttpService.cs index 8ea24d1a..5b2bfd0f 100644 --- a/Epsilon.Canvas/Service/PageHttpService.cs +++ b/Epsilon.Canvas/Service/PageHttpService.cs @@ -14,38 +14,22 @@ public PageHttpService(HttpClient client) : base(client) public async Task<string?> GetPageByName(int courseId, string pageName) { - try - { - using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); - using var response = await Client.SendAsync(request); - - if (response.StatusCode == HttpStatusCode.OK) - return (await response.Content.ReadFromJsonAsync<Page>()).Body; - } - catch (Exception e) - { - throw new Exception($"Error in GetPageByName: {e.Message}"); - } + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/{pageName}"); + using var response = await Client.SendAsync(request); + + if (response.StatusCode == HttpStatusCode.OK) + return (await response.Content.ReadFromJsonAsync<Page>()).Body; return null; } public async Task<IEnumerable<Page>?> GetAll(int courseId, IEnumerable<string> include) { - try - { - { - using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); - using var response = await Client.SendAsync(request); - - if (response.StatusCode == HttpStatusCode.OK) - return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); - } - } - catch (Exception e) - { - throw new Exception($"Error in GetAll: {e.Message}"); - } + using var request = new HttpRequestMessage(HttpMethod.Get, $"v1/courses/{courseId}/pages"); + using var response = await Client.SendAsync(request); + + if (response.StatusCode == HttpStatusCode.OK) + return await response.Content.ReadFromJsonAsync<IEnumerable<Page>>(); return null; } From 3bfa27fb92611cb94faaf30d86ccef6ee83fca1f Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Tue, 28 Mar 2023 18:55:36 +0200 Subject: [PATCH 14/15] fix --- Epsilon/Export/ExportDataPackager.cs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Epsilon/Export/ExportDataPackager.cs b/Epsilon/Export/ExportDataPackager.cs index 9e75afd7..6bad505c 100644 --- a/Epsilon/Export/ExportDataPackager.cs +++ b/Epsilon/Export/ExportDataPackager.cs @@ -2,7 +2,7 @@ using Epsilon.Abstractions.Export; using Epsilon.Abstractions.Model; using Epsilon.Canvas; -using Epsilon.Canvas.Abstractions; +using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; using Microsoft.Extensions.Options; @@ -10,31 +10,24 @@ namespace Epsilon.Export; public class ExportDataPackager : IExportDataPackager { - private readonly ICanvasModuleCollectionFetcher _moduleCollectionFetcher; private readonly IPageHttpService _pageService; - private readonly ExportOptions _exportOptions; private readonly CanvasSettings _canvasSettings; - public ExportDataPackager(ICanvasModuleCollectionFetcher moduleCollectionFetcher, IPageHttpService pageService, - IOptions<CanvasSettings> canvasSettings, IOptions<ExportOptions> exportOptions) + public ExportDataPackager(IPageHttpService pageService, + IOptions<CanvasSettings> canvasSettings) { - _moduleCollectionFetcher = moduleCollectionFetcher; _pageService = pageService; - _exportOptions = exportOptions.Value; _canvasSettings = canvasSettings.Value; } - public async Task<ExportData> GetExportData() + public async Task<ExportData> GetExportData(IAsyncEnumerable<ModuleOutcomeResultCollection> data) { - var modules = _exportOptions.Modules?.Split(","); var courseId = _canvasSettings.CourseId; - - var moduleOutcomes = _moduleCollectionFetcher.GetAll(courseId, modules); var personaHtml = await _pageService.GetPageByName(courseId, "front_page"); var output = new List<CourseModule>(); - await foreach (var item in moduleOutcomes.Where(m => m.Collection.OutcomeResults.Any())) + await foreach (var item in data.Where(m => m.Collection.OutcomeResults.Any())) { var module = new CourseModule {Name = item.Module.Name}; var links = item.Collection.Links; @@ -44,7 +37,7 @@ public async Task<ExportData> GetExportData() var alignments = links.AlignmentsDictionary; var outcomes = links.OutcomesDictionary; - var moduleKpis = new List<CourseOutcome>(); + var moduleOutcomes = new List<CourseOutcome>(); foreach (var (outcomeId, outcome) in outcomes) { @@ -65,7 +58,7 @@ public async Task<ExportData> GetExportData() }) .ToList(); - moduleKpis.Add(new CourseOutcome + moduleOutcomes.Add(new CourseOutcome { Name = outcome.Title, Description = outcome.ShortDescription(), @@ -74,7 +67,7 @@ public async Task<ExportData> GetExportData() } } - module.Kpis = moduleKpis; + module.Outcomes = moduleOutcomes; output.Add(module); } From 3659942facf4e55a6cbdd836321868323738c914 Mon Sep 17 00:00:00 2001 From: Koen Janssen <mail@koenjanssen.dev> Date: Tue, 28 Mar 2023 19:01:05 +0200 Subject: [PATCH 15/15] Added throw exception to suppress testing error. --- Epsilon/Export/ExportDataPackager.cs | 5 +++++ Epsilon/Export/Exporters/WordModuleExporter.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Epsilon/Export/ExportDataPackager.cs b/Epsilon/Export/ExportDataPackager.cs index 6bad505c..9aaca7b5 100644 --- a/Epsilon/Export/ExportDataPackager.cs +++ b/Epsilon/Export/ExportDataPackager.cs @@ -20,6 +20,11 @@ public ExportDataPackager(IPageHttpService pageService, _canvasSettings = canvasSettings.Value; } + public ExportDataPackager() + { + throw new NotImplementedException(); + } + public async Task<ExportData> GetExportData(IAsyncEnumerable<ModuleOutcomeResultCollection> data) { var courseId = _canvasSettings.CourseId; diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 1604800b..036c627a 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -37,6 +37,11 @@ public WordModuleExporter(IFileHttpService fileService) _fileService = fileService; } + public WordModuleExporter() + { + throw new NotImplementedException(); + } + public async Task<Stream> Export(ExportData data, string format) { var stream = new MemoryStream();