diff --git a/.gitignore b/.gitignore index 5cab3f6d4..9bf03830c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /src/Microservices/Services.Storage/ClassifiedAds.Services.Storage.Api/Migrations/ /src/UIs/reactjs/.eslintcache .tye/ +.local-chromium/ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/src/Microservices/Common/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs b/src/Microservices/Common/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs new file mode 100644 index 000000000..94670be95 --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace ClassifiedAds.CrossCuttingConcerns.HtmlGenerator +{ + public interface IHtmlGenerator + { + Task GenerateAsync(string template, object model); + } +} diff --git a/src/Microservices/Common/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs b/src/Microservices/Common/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs new file mode 100644 index 000000000..bf2242984 --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.CrossCuttingConcerns.PdfConverter +{ + public interface IPdfConverter + { + Stream Convert(string html, PdfOptions pdfOptions = null); + + Task ConvertAsync(string html, PdfOptions pdfOptions = null); + } + + public class PdfOptions + { + + } +} diff --git a/src/Microservices/Common/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj b/src/Microservices/Common/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj index cac7e157e..7b907e007 100644 --- a/src/Microservices/Common/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj +++ b/src/Microservices/Common/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj @@ -30,6 +30,7 @@ + @@ -60,7 +61,9 @@ + + @@ -79,4 +82,15 @@ + + + PreserveNewest + libwkhtmltox.dll + + + PreserveNewest + libwkhtmltox.so + + + diff --git a/src/Microservices/Common/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs b/src/Microservices/Common/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs new file mode 100644 index 000000000..0d358f8de --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs @@ -0,0 +1,21 @@ +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using RazorLight; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.HtmlGenerators +{ + public class HtmlGenerator : IHtmlGenerator + { + private readonly IRazorLightEngine _razorLightEngine; + + public HtmlGenerator(IRazorLightEngine razorLightEngine) + { + _razorLightEngine = razorLightEngine; + } + + public Task GenerateAsync(string template, object model) + { + return _razorLightEngine.CompileRenderAsync(template, model); + } + } +} diff --git a/src/Microservices/Common/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs b/src/Microservices/Common/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs new file mode 100644 index 000000000..767908860 --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs @@ -0,0 +1,23 @@ +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using ClassifiedAds.Infrastructure.HtmlGenerators; +using RazorLight; +using System; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class HtmlGeneratorCollectionExtensions + { + public static IServiceCollection AddHtmlGenerator(this IServiceCollection services) + { + var engine = new RazorLightEngineBuilder() + .UseFileSystemProject(Environment.CurrentDirectory) + .UseMemoryCachingProvider() + .Build(); + + services.AddSingleton(engine); + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs new file mode 100644 index 000000000..ffd0ad58f --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs @@ -0,0 +1,51 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using DinkToPdf; +using DinkToPdf.Contracts; +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.PdfConverters.DinkToPdf +{ + public class DinkToPdfConverter : IPdfConverter + { + private readonly IConverter _converter; + + public DinkToPdfConverter(IConverter converter) + { + _converter = converter; + } + + public Stream Convert(string html, PdfOptions pdfOptions = null) + { + var doc = new HtmlToPdfDocument() + { + GlobalSettings = + { + ColorMode = ColorMode.Color, + Orientation = Orientation.Portrait, + PaperSize = PaperKind.A4, + Margins = new MarginSettings() { Top = 10, Bottom = 15, Left = 10, Right = 10 }, + }, + Objects = + { + new ObjectSettings() + { + PagesCount = true, + HtmlContent = html, + WebSettings = { DefaultEncoding = "utf-8", Background = true }, + HeaderSettings = { FontSize = 9, Right = "Page [page] of [toPage]", Line = true, Spacing = 2.812 }, + }, + }, + }; + + byte[] pdf = _converter.Convert(doc); + + return new MemoryStream(pdf); + } + + public Task ConvertAsync(string html, PdfOptions pdfOptions = null) + { + return Task.FromResult(Convert(html, pdfOptions)); + } + } +} diff --git a/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs new file mode 100644 index 000000000..ff031cf6c --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs @@ -0,0 +1,18 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using ClassifiedAds.Infrastructure.PdfConverters.DinkToPdf; +using DinkToPdf; +using DinkToPdf.Contracts; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class DinkToPdfConverterCollectionExtensions + { + public static IServiceCollection AddDinkToPdfConverter(this IServiceCollection services) + { + services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools())); + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs new file mode 100644 index 000000000..b7aa7f367 --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs @@ -0,0 +1,26 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using PuppeteerSharp; +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.PdfConverters.PuppeteerSharp +{ + public class PuppeteerSharpConverter : IPdfConverter + { + public Stream Convert(string html, CrossCuttingConcerns.PdfConverter.PdfOptions pdfOptions = null) + { + return ConvertAsync(html, pdfOptions).GetAwaiter().GetResult(); + } + + public async Task ConvertAsync(string html, CrossCuttingConcerns.PdfConverter.PdfOptions pdfOptions = null) + { + await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); + await using var page = await browser.NewPageAsync(); + await page.SetContentAsync(html); + return new MemoryStream(await page.PdfDataAsync(new global::PuppeteerSharp.PdfOptions + { + PrintBackground = true, + })); + } + } +} diff --git a/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs new file mode 100644 index 000000000..84f141e3f --- /dev/null +++ b/src/Microservices/Common/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs @@ -0,0 +1,19 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using ClassifiedAds.Infrastructure.PdfConverters.PuppeteerSharp; +using PuppeteerSharp; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class PuppeteerSharpConverterCollectionExtensions + { + public static IServiceCollection AddPuppeteerSharpPdfConverter(this IServiceCollection services) + { + var browserFetcher = new BrowserFetcher(); + browserFetcher.DownloadAsync().GetAwaiter().GetResult(); + + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll new file mode 100644 index 000000000..98a007bbc Binary files /dev/null and b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll differ diff --git a/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so new file mode 100644 index 000000000..eecc8834a Binary files /dev/null and b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so differ diff --git a/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll new file mode 100644 index 000000000..c5e529e21 Binary files /dev/null and b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll differ diff --git a/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so new file mode 100644 index 000000000..802f1dfee Binary files /dev/null and b/src/Microservices/Common/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so differ diff --git a/src/Microservices/Services.Identity/ClassifiedAds.Services.Identity.AuthServer/ClassifiedAds.Services.Identity.AuthServer.csproj b/src/Microservices/Services.Identity/ClassifiedAds.Services.Identity.AuthServer/ClassifiedAds.Services.Identity.AuthServer.csproj index 05dcb0fbe..1f0782d42 100644 --- a/src/Microservices/Services.Identity/ClassifiedAds.Services.Identity.AuthServer/ClassifiedAds.Services.Identity.AuthServer.csproj +++ b/src/Microservices/Services.Identity/ClassifiedAds.Services.Identity.AuthServer/ClassifiedAds.Services.Identity.AuthServer.csproj @@ -16,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/ClassifiedAds.Services.Product.Api.csproj b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/ClassifiedAds.Services.Product.Api.csproj index 254f165f7..7678adb48 100644 --- a/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/ClassifiedAds.Services.Product.Api.csproj +++ b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/ClassifiedAds.Services.Product.Api.csproj @@ -33,4 +33,15 @@ + + true + true + + + + + PreserveNewest + + + diff --git a/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Controllers/ProductsController.cs b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Controllers/ProductsController.cs index 187b32026..027441efd 100644 --- a/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Controllers/ProductsController.cs +++ b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Controllers/ProductsController.cs @@ -1,4 +1,6 @@ using ClassifiedAds.Application; +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; using ClassifiedAds.Infrastructure.Web.Authorization.Policies; using ClassifiedAds.Services.Product.Authorization.Policies.Products; using ClassifiedAds.Services.Product.Commands; @@ -12,7 +14,9 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net.Mime; using System.Threading.Tasks; namespace ClassifiedAds.Services.Product.Controllers @@ -25,11 +29,17 @@ public class ProductsController : ControllerBase { private readonly Dispatcher _dispatcher; private readonly ILogger _logger; + private readonly IHtmlGenerator _htmlGenerator; + private readonly IPdfConverter _pdfConverter; - public ProductsController(Dispatcher dispatcher, ILogger logger) + public ProductsController(Dispatcher dispatcher, ILogger logger, + IHtmlGenerator htmlGenerator, + IPdfConverter pdfConverter) { _dispatcher = dispatcher; _logger = logger; + _htmlGenerator = htmlGenerator; + _pdfConverter = pdfConverter; } [AuthorizePolicy(typeof(GetProductsPolicy))] @@ -132,5 +142,18 @@ public async Task>> GetAuditLogs(Guid return Ok(entries.OrderByDescending(x => x.CreatedDateTime)); } + + [HttpGet("exportaspdf")] + public async Task ExportAsPdf() + { + var products = await _dispatcher.DispatchAsync(new GetProductsQuery()); + var model = products.ToModels(); + + var template = Path.Combine(Environment.CurrentDirectory, $"Templates/ProductList.cshtml"); + var html = await _htmlGenerator.GenerateAsync(template, model); + var pdf = await _pdfConverter.ConvertAsync(html); + + return File(pdf, MediaTypeNames.Application.Octet, "Products.pdf"); + } } } \ No newline at end of file diff --git a/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Startup.cs b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Startup.cs index 64219be16..3f530e59b 100644 --- a/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Startup.cs +++ b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Startup.cs @@ -49,6 +49,9 @@ public void ConfigureServices(IServiceCollection services) services.AddDateTimeProvider(); services.AddApplicationServices(); + services.AddHtmlGenerator(); + services.AddDinkToPdfConverter(); + services.AddProductModule(AppSettings); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) diff --git a/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Templates/ProductList.cshtml b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Templates/ProductList.cshtml new file mode 100644 index 000000000..41551aed8 --- /dev/null +++ b/src/Microservices/Services.Product/ClassifiedAds.Services.Product.Api/Templates/ProductList.cshtml @@ -0,0 +1,51 @@ +@model IEnumerable + + + + + + Practical.CleanArchitecture + + + +
+ + + + + + + + + + @foreach (var product in Model) + { + + + + + + } + +
ProductCodeDescription
@product.Name@product.Code@product.Description
+
+ + diff --git a/src/ModularMonolith/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs b/src/ModularMonolith/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs new file mode 100644 index 000000000..94670be95 --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace ClassifiedAds.CrossCuttingConcerns.HtmlGenerator +{ + public interface IHtmlGenerator + { + Task GenerateAsync(string template, object model); + } +} diff --git a/src/ModularMonolith/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs b/src/ModularMonolith/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs new file mode 100644 index 000000000..bf2242984 --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.CrossCuttingConcerns.PdfConverter +{ + public interface IPdfConverter + { + Stream Convert(string html, PdfOptions pdfOptions = null); + + Task ConvertAsync(string html, PdfOptions pdfOptions = null); + } + + public class PdfOptions + { + + } +} diff --git a/src/ModularMonolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj b/src/ModularMonolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj index bf36c3d92..dd99d4dd5 100644 --- a/src/ModularMonolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj +++ b/src/ModularMonolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj @@ -30,6 +30,7 @@ + @@ -58,7 +59,9 @@ + + @@ -77,4 +80,15 @@ + + + PreserveNewest + libwkhtmltox.dll + + + PreserveNewest + libwkhtmltox.so + + + diff --git a/src/ModularMonolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs b/src/ModularMonolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs new file mode 100644 index 000000000..0d358f8de --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs @@ -0,0 +1,21 @@ +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using RazorLight; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.HtmlGenerators +{ + public class HtmlGenerator : IHtmlGenerator + { + private readonly IRazorLightEngine _razorLightEngine; + + public HtmlGenerator(IRazorLightEngine razorLightEngine) + { + _razorLightEngine = razorLightEngine; + } + + public Task GenerateAsync(string template, object model) + { + return _razorLightEngine.CompileRenderAsync(template, model); + } + } +} diff --git a/src/ModularMonolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs b/src/ModularMonolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs new file mode 100644 index 000000000..767908860 --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs @@ -0,0 +1,23 @@ +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using ClassifiedAds.Infrastructure.HtmlGenerators; +using RazorLight; +using System; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class HtmlGeneratorCollectionExtensions + { + public static IServiceCollection AddHtmlGenerator(this IServiceCollection services) + { + var engine = new RazorLightEngineBuilder() + .UseFileSystemProject(Environment.CurrentDirectory) + .UseMemoryCachingProvider() + .Build(); + + services.AddSingleton(engine); + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs new file mode 100644 index 000000000..ffd0ad58f --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs @@ -0,0 +1,51 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using DinkToPdf; +using DinkToPdf.Contracts; +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.PdfConverters.DinkToPdf +{ + public class DinkToPdfConverter : IPdfConverter + { + private readonly IConverter _converter; + + public DinkToPdfConverter(IConverter converter) + { + _converter = converter; + } + + public Stream Convert(string html, PdfOptions pdfOptions = null) + { + var doc = new HtmlToPdfDocument() + { + GlobalSettings = + { + ColorMode = ColorMode.Color, + Orientation = Orientation.Portrait, + PaperSize = PaperKind.A4, + Margins = new MarginSettings() { Top = 10, Bottom = 15, Left = 10, Right = 10 }, + }, + Objects = + { + new ObjectSettings() + { + PagesCount = true, + HtmlContent = html, + WebSettings = { DefaultEncoding = "utf-8", Background = true }, + HeaderSettings = { FontSize = 9, Right = "Page [page] of [toPage]", Line = true, Spacing = 2.812 }, + }, + }, + }; + + byte[] pdf = _converter.Convert(doc); + + return new MemoryStream(pdf); + } + + public Task ConvertAsync(string html, PdfOptions pdfOptions = null) + { + return Task.FromResult(Convert(html, pdfOptions)); + } + } +} diff --git a/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs new file mode 100644 index 000000000..ff031cf6c --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs @@ -0,0 +1,18 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using ClassifiedAds.Infrastructure.PdfConverters.DinkToPdf; +using DinkToPdf; +using DinkToPdf.Contracts; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class DinkToPdfConverterCollectionExtensions + { + public static IServiceCollection AddDinkToPdfConverter(this IServiceCollection services) + { + services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools())); + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs new file mode 100644 index 000000000..b7aa7f367 --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs @@ -0,0 +1,26 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using PuppeteerSharp; +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.PdfConverters.PuppeteerSharp +{ + public class PuppeteerSharpConverter : IPdfConverter + { + public Stream Convert(string html, CrossCuttingConcerns.PdfConverter.PdfOptions pdfOptions = null) + { + return ConvertAsync(html, pdfOptions).GetAwaiter().GetResult(); + } + + public async Task ConvertAsync(string html, CrossCuttingConcerns.PdfConverter.PdfOptions pdfOptions = null) + { + await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); + await using var page = await browser.NewPageAsync(); + await page.SetContentAsync(html); + return new MemoryStream(await page.PdfDataAsync(new global::PuppeteerSharp.PdfOptions + { + PrintBackground = true, + })); + } + } +} diff --git a/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs new file mode 100644 index 000000000..84f141e3f --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs @@ -0,0 +1,19 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using ClassifiedAds.Infrastructure.PdfConverters.PuppeteerSharp; +using PuppeteerSharp; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class PuppeteerSharpConverterCollectionExtensions + { + public static IServiceCollection AddPuppeteerSharpPdfConverter(this IServiceCollection services) + { + var browserFetcher = new BrowserFetcher(); + browserFetcher.DownloadAsync().GetAwaiter().GetResult(); + + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/ModularMonolith/ClassifiedAds.Modules.Product/Controllers/ProductsController.cs b/src/ModularMonolith/ClassifiedAds.Modules.Product/Controllers/ProductsController.cs index 057e0c400..6b261cbbe 100644 --- a/src/ModularMonolith/ClassifiedAds.Modules.Product/Controllers/ProductsController.cs +++ b/src/ModularMonolith/ClassifiedAds.Modules.Product/Controllers/ProductsController.cs @@ -1,4 +1,6 @@ using ClassifiedAds.Application; +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; using ClassifiedAds.Infrastructure.Web.Authorization.Policies; using ClassifiedAds.Modules.AuditLog.Contracts.DTOs; using ClassifiedAds.Modules.Product.Authorization.Policies.Products; @@ -12,7 +14,9 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net.Mime; using System.Threading.Tasks; namespace ClassifiedAds.Modules.Product.Controllers @@ -25,11 +29,17 @@ public class ProductsController : ControllerBase { private readonly Dispatcher _dispatcher; private readonly ILogger _logger; + private readonly IHtmlGenerator _htmlGenerator; + private readonly IPdfConverter _pdfConverter; - public ProductsController(Dispatcher dispatcher, ILogger logger) + public ProductsController(Dispatcher dispatcher, ILogger logger, + IHtmlGenerator htmlGenerator, + IPdfConverter pdfConverter) { _dispatcher = dispatcher; _logger = logger; + _htmlGenerator = htmlGenerator; + _pdfConverter = pdfConverter; } [AuthorizePolicy(typeof(GetProductsPolicy))] @@ -132,5 +142,18 @@ public async Task>> GetAuditLogs(Guid return Ok(entries.OrderByDescending(x => x.CreatedDateTime)); } + + [HttpGet("exportaspdf")] + public async Task ExportAsPdf() + { + var products = await _dispatcher.DispatchAsync(new GetProductsQuery()); + var model = products.ToModels(); + + var template = Path.Combine(Environment.CurrentDirectory, $"Templates/ProductList.cshtml"); + var html = await _htmlGenerator.GenerateAsync(template, model); + var pdf = await _pdfConverter.ConvertAsync(html); + + return File(pdf, MediaTypeNames.Application.Octet, "Products.pdf"); + } } } \ No newline at end of file diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj b/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj index 48c6ec133..ab0ebfbbf 100644 --- a/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj +++ b/src/ModularMonolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj @@ -30,4 +30,15 @@ + + true + true + + + + + PreserveNewest + + + diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs b/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs index 0923d1410..509797dc9 100644 --- a/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs +++ b/src/ModularMonolith/ClassifiedAds.WebAPI/Startup.cs @@ -135,6 +135,9 @@ public void ConfigureServices(IServiceCollection services) }) .AddApplicationServices(); + services.AddHtmlGenerator(); + services.AddDinkToPdfConverter(); + services.AddDataProtection() .PersistKeysToDbContext() .SetApplicationName("ClassifiedAds"); diff --git a/src/ModularMonolith/ClassifiedAds.WebAPI/Templates/ProductList.cshtml b/src/ModularMonolith/ClassifiedAds.WebAPI/Templates/ProductList.cshtml new file mode 100644 index 000000000..2d6a7e819 --- /dev/null +++ b/src/ModularMonolith/ClassifiedAds.WebAPI/Templates/ProductList.cshtml @@ -0,0 +1,51 @@ +@model IEnumerable + + + + + + Practical.CleanArchitecture + + + +
+ + + + + + + + + + @foreach (var product in Model) + { + + + + + + } + +
ProductCodeDescription
@product.Name@product.Code@product.Description
+
+ + diff --git a/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll new file mode 100644 index 000000000..98a007bbc Binary files /dev/null and b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll differ diff --git a/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so new file mode 100644 index 000000000..eecc8834a Binary files /dev/null and b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so differ diff --git a/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll new file mode 100644 index 000000000..c5e529e21 Binary files /dev/null and b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll differ diff --git a/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so new file mode 100644 index 000000000..802f1dfee Binary files /dev/null and b/src/ModularMonolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so differ diff --git a/src/Monolith/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs b/src/Monolith/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs new file mode 100644 index 000000000..94670be95 --- /dev/null +++ b/src/Monolith/ClassifiedAds.CrossCuttingConcerns/HtmlGenerator/IHtmlGenerator.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace ClassifiedAds.CrossCuttingConcerns.HtmlGenerator +{ + public interface IHtmlGenerator + { + Task GenerateAsync(string template, object model); + } +} diff --git a/src/Monolith/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs b/src/Monolith/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs new file mode 100644 index 000000000..bf2242984 --- /dev/null +++ b/src/Monolith/ClassifiedAds.CrossCuttingConcerns/PdfConverter/IPdfConverter.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.CrossCuttingConcerns.PdfConverter +{ + public interface IPdfConverter + { + Stream Convert(string html, PdfOptions pdfOptions = null); + + Task ConvertAsync(string html, PdfOptions pdfOptions = null); + } + + public class PdfOptions + { + + } +} diff --git a/src/Monolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj b/src/Monolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj index ef6de21ff..a85a57b05 100644 --- a/src/Monolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj +++ b/src/Monolith/ClassifiedAds.Infrastructure/ClassifiedAds.Infrastructure.csproj @@ -30,6 +30,7 @@ + @@ -58,7 +59,9 @@ + + @@ -77,4 +80,15 @@ + + + PreserveNewest + libwkhtmltox.dll + + + PreserveNewest + libwkhtmltox.so + + + diff --git a/src/Monolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs b/src/Monolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs new file mode 100644 index 000000000..0d358f8de --- /dev/null +++ b/src/Monolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGenerator.cs @@ -0,0 +1,21 @@ +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using RazorLight; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.HtmlGenerators +{ + public class HtmlGenerator : IHtmlGenerator + { + private readonly IRazorLightEngine _razorLightEngine; + + public HtmlGenerator(IRazorLightEngine razorLightEngine) + { + _razorLightEngine = razorLightEngine; + } + + public Task GenerateAsync(string template, object model) + { + return _razorLightEngine.CompileRenderAsync(template, model); + } + } +} diff --git a/src/Monolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs b/src/Monolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs new file mode 100644 index 000000000..767908860 --- /dev/null +++ b/src/Monolith/ClassifiedAds.Infrastructure/HtmlGenerators/HtmlGeneratorCollectionExtensions.cs @@ -0,0 +1,23 @@ +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using ClassifiedAds.Infrastructure.HtmlGenerators; +using RazorLight; +using System; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class HtmlGeneratorCollectionExtensions + { + public static IServiceCollection AddHtmlGenerator(this IServiceCollection services) + { + var engine = new RazorLightEngineBuilder() + .UseFileSystemProject(Environment.CurrentDirectory) + .UseMemoryCachingProvider() + .Build(); + + services.AddSingleton(engine); + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs new file mode 100644 index 000000000..ffd0ad58f --- /dev/null +++ b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverter.cs @@ -0,0 +1,51 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using DinkToPdf; +using DinkToPdf.Contracts; +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.PdfConverters.DinkToPdf +{ + public class DinkToPdfConverter : IPdfConverter + { + private readonly IConverter _converter; + + public DinkToPdfConverter(IConverter converter) + { + _converter = converter; + } + + public Stream Convert(string html, PdfOptions pdfOptions = null) + { + var doc = new HtmlToPdfDocument() + { + GlobalSettings = + { + ColorMode = ColorMode.Color, + Orientation = Orientation.Portrait, + PaperSize = PaperKind.A4, + Margins = new MarginSettings() { Top = 10, Bottom = 15, Left = 10, Right = 10 }, + }, + Objects = + { + new ObjectSettings() + { + PagesCount = true, + HtmlContent = html, + WebSettings = { DefaultEncoding = "utf-8", Background = true }, + HeaderSettings = { FontSize = 9, Right = "Page [page] of [toPage]", Line = true, Spacing = 2.812 }, + }, + }, + }; + + byte[] pdf = _converter.Convert(doc); + + return new MemoryStream(pdf); + } + + public Task ConvertAsync(string html, PdfOptions pdfOptions = null) + { + return Task.FromResult(Convert(html, pdfOptions)); + } + } +} diff --git a/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs new file mode 100644 index 000000000..ff031cf6c --- /dev/null +++ b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/DinkToPdf/DinkToPdfConverterCollectionExtensions.cs @@ -0,0 +1,18 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using ClassifiedAds.Infrastructure.PdfConverters.DinkToPdf; +using DinkToPdf; +using DinkToPdf.Contracts; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class DinkToPdfConverterCollectionExtensions + { + public static IServiceCollection AddDinkToPdfConverter(this IServiceCollection services) + { + services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools())); + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs new file mode 100644 index 000000000..b7aa7f367 --- /dev/null +++ b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverter.cs @@ -0,0 +1,26 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using PuppeteerSharp; +using System.IO; +using System.Threading.Tasks; + +namespace ClassifiedAds.Infrastructure.PdfConverters.PuppeteerSharp +{ + public class PuppeteerSharpConverter : IPdfConverter + { + public Stream Convert(string html, CrossCuttingConcerns.PdfConverter.PdfOptions pdfOptions = null) + { + return ConvertAsync(html, pdfOptions).GetAwaiter().GetResult(); + } + + public async Task ConvertAsync(string html, CrossCuttingConcerns.PdfConverter.PdfOptions pdfOptions = null) + { + await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true }); + await using var page = await browser.NewPageAsync(); + await page.SetContentAsync(html); + return new MemoryStream(await page.PdfDataAsync(new global::PuppeteerSharp.PdfOptions + { + PrintBackground = true, + })); + } + } +} diff --git a/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs new file mode 100644 index 000000000..84f141e3f --- /dev/null +++ b/src/Monolith/ClassifiedAds.Infrastructure/PdfConverters/PuppeteerSharp/PuppeteerSharpConverterCollectionExtensions.cs @@ -0,0 +1,19 @@ +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; +using ClassifiedAds.Infrastructure.PdfConverters.PuppeteerSharp; +using PuppeteerSharp; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class PuppeteerSharpConverterCollectionExtensions + { + public static IServiceCollection AddPuppeteerSharpPdfConverter(this IServiceCollection services) + { + var browserFetcher = new BrowserFetcher(); + browserFetcher.DownloadAsync().GetAwaiter().GetResult(); + + services.AddSingleton(); + + return services; + } + } +} diff --git a/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj b/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj index 3782175dd..bda55d03d 100644 --- a/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj +++ b/src/Monolith/ClassifiedAds.WebAPI/ClassifiedAds.WebAPI.csproj @@ -25,6 +25,17 @@ + + true + true + + + + + PreserveNewest + + + Always diff --git a/src/Monolith/ClassifiedAds.WebAPI/Controllers/ProductsController.cs b/src/Monolith/ClassifiedAds.WebAPI/Controllers/ProductsController.cs index b7b4a1d54..08ef5215d 100644 --- a/src/Monolith/ClassifiedAds.WebAPI/Controllers/ProductsController.cs +++ b/src/Monolith/ClassifiedAds.WebAPI/Controllers/ProductsController.cs @@ -4,6 +4,8 @@ using ClassifiedAds.Application.Products.Commands; using ClassifiedAds.Application.Products.DTOs; using ClassifiedAds.Application.Products.Queries; +using ClassifiedAds.CrossCuttingConcerns.HtmlGenerator; +using ClassifiedAds.CrossCuttingConcerns.PdfConverter; using ClassifiedAds.Domain.Entities; using ClassifiedAds.Infrastructure.Web.Authorization.Policies; using ClassifiedAds.WebAPI.Authorization.Policies.Products; @@ -15,7 +17,9 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net.Mime; using System.Threading.Tasks; namespace ClassifiedAds.WebAPI.Controllers @@ -28,11 +32,18 @@ public class ProductsController : ControllerBase { private readonly Dispatcher _dispatcher; private readonly ILogger _logger; + private readonly IHtmlGenerator _htmlGenerator; + private readonly IPdfConverter _pdfConverter; - public ProductsController(Dispatcher dispatcher, ILogger logger) + public ProductsController(Dispatcher dispatcher, + ILogger logger, + IHtmlGenerator htmlGenerator, + IPdfConverter pdfConverter) { _dispatcher = dispatcher; _logger = logger; + _htmlGenerator = htmlGenerator; + _pdfConverter = pdfConverter; } [AuthorizePolicy(typeof(GetProductsPolicy))] @@ -135,5 +146,18 @@ public async Task>> GetAuditLogs(Guid return Ok(entries.OrderByDescending(x => x.CreatedDateTime)); } + + [HttpGet("exportaspdf")] + public async Task ExportAsPdf() + { + var products = await _dispatcher.DispatchAsync(new GetProductsQuery()); + var model = products.ToModels(); + + var template = Path.Combine(Environment.CurrentDirectory, $"Templates/ProductList.cshtml"); + var html = await _htmlGenerator.GenerateAsync(template, model); + var pdf = await _pdfConverter.ConvertAsync(html); + + return File(pdf, MediaTypeNames.Application.Octet, "Products.pdf"); + } } } \ No newline at end of file diff --git a/src/Monolith/ClassifiedAds.WebAPI/Startup.cs b/src/Monolith/ClassifiedAds.WebAPI/Startup.cs index 8f412885d..86c3e6075 100644 --- a/src/Monolith/ClassifiedAds.WebAPI/Startup.cs +++ b/src/Monolith/ClassifiedAds.WebAPI/Startup.cs @@ -172,6 +172,8 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddStorageManager(AppSettings.Storage); + services.AddHtmlGenerator(); + services.AddDinkToPdfConverter(); services.AddMessageBusSender(AppSettings.MessageBroker); services.AddMessageBusSender(AppSettings.MessageBroker); diff --git a/src/Monolith/ClassifiedAds.WebAPI/Templates/ProductList.cshtml b/src/Monolith/ClassifiedAds.WebAPI/Templates/ProductList.cshtml new file mode 100644 index 000000000..02ccf4bad --- /dev/null +++ b/src/Monolith/ClassifiedAds.WebAPI/Templates/ProductList.cshtml @@ -0,0 +1,51 @@ +@model IEnumerable + + + + + + Practical.CleanArchitecture + + + +
+ + + + + + + + + + @foreach (var product in Model) + { + + + + + + } + +
ProductCodeDescription
@product.Name@product.Code@product.Description
+
+ + diff --git a/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll new file mode 100644 index 000000000..98a007bbc Binary files /dev/null and b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.dll differ diff --git a/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so new file mode 100644 index 000000000..eecc8834a Binary files /dev/null and b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x64.so differ diff --git a/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll new file mode 100644 index 000000000..c5e529e21 Binary files /dev/null and b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.dll differ diff --git a/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so new file mode 100644 index 000000000..802f1dfee Binary files /dev/null and b/src/Monolith/libs/libwkhtmltox/libwkhtmltox-0.12.4-x86.so differ