diff --git a/server/AyaNova/Controllers/ReportController.cs b/server/AyaNova/Controllers/ReportController.cs
index 5affd3af..17547cb6 100644
--- a/server/AyaNova/Controllers/ReportController.cs
+++ b/server/AyaNova/Controllers/ReportController.cs
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
+using System.Linq;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
@@ -191,7 +192,7 @@ namespace AyaNova.Api.Controllers
/// Data required for report
/// From route path
///
- [HttpPost("object-report-data")]
+ [HttpPost("data")]
public async Task GetReportData([FromBody] ObjectReportDataParameter reportDataParam, ApiVersion apiVersion)
{
/*{
@@ -237,7 +238,7 @@ namespace AyaNova.Api.Controllers
///
/// report id and object id values for object type specified in report template
/// From route path
- ///
+ /// downloadable pdf name
[HttpPost("render")]
public async Task RenderReport([FromBody] RenderReportParameter reportParam, ApiVersion apiVersion)
{
@@ -253,15 +254,61 @@ namespace AyaNova.Api.Controllers
var API_URL = $"http://127.0.0.1:{httpConnectionFeature.LocalPort}/api/v8/";
var result = await biz.RenderReport(reportParam, API_URL);
- if (result == null)
+ if (string.IsNullOrWhiteSpace(result))
return BadRequest(new ApiErrorResponse(biz.Errors));
else
- return new FileContentResult(result.RenderedOutput, result.MimeType);
+ return Ok(ApiOkResponse.Response(result));
}
+ ///
+ /// Download a report file
+ ///
+ ///
+ /// download token
+ ///
+ [HttpGet("download/{fileName}")]
+ public async Task DownloadAsync([FromRoute] string fileName, [FromQuery] string t)
+ {
+ int nFailedAuthDelay = 3000;
+ if (!serverState.IsOpen)
+ return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
+ if (string.IsNullOrWhiteSpace(t))
+ {
+ await Task.Delay(nFailedAuthDelay);//DOS protection
+ return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
+ }
+ var DownloadUser = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.DlKey == t && z.Active == true);
+ if (DownloadUser == null)
+ {
+ await Task.Delay(nFailedAuthDelay);//DOS protection
+ return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
+ }
+ var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);
+ if (DownloadUser.DlKeyExpire < utcNow.DateTime)
+ {
+ await Task.Delay(nFailedAuthDelay);//DOS protection
+ return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
+ }
+ if (!Authorized.HasModifyRole(DownloadUser.Roles, AyaType.Backup))//not technically modify but treating as such as a backup is very sensitive data
+ {
+ await Task.Delay(nFailedAuthDelay);//DOS protection
+ return StatusCode(403, new ApiNotAuthorizedResponse());
+ }
+
+ if (!FileUtil.BackupFileExists(fileName))
+ {
+ await Task.Delay(nFailedAuthDelay);//fishing protection
+ return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
+ }
+ string mimetype = fileName.EndsWith("zip") ? "application/zip" : "application/octet-stream";
+ var utilityFilePath = FileUtil.GetFullPathForBackupFile(fileName);
+ await EventLogProcessor.LogEventToDatabaseAsync(new Event(DownloadUser.Id, 0, AyaType.NoType, AyaEvent.UtilityFileDownload, fileName), ct);
+ return PhysicalFile(utilityFilePath, mimetype, fileName);
+
+ }
// [HttpGet("render-test")]
// [AllowAnonymous]
@@ -406,7 +453,7 @@ namespace AyaNova.Api.Controllers
}
- [HttpPost("post-poc")]
+ [HttpPost("post-poc")]
public async Task PostProofOfConcept([FromBody] NameItem nameItem)
{
//https://test.helloayanova.com/api/v8/report/poc
diff --git a/server/AyaNova/biz/ReportBiz.cs b/server/AyaNova/biz/ReportBiz.cs
index 4f2fb8c9..d29a9e0d 100644
--- a/server/AyaNova/biz/ReportBiz.cs
+++ b/server/AyaNova/biz/ReportBiz.cs
@@ -281,12 +281,8 @@ namespace AyaNova.Biz
////////////////////////////////////////////////////////////////////////////////////////////////
//RENDER
//
- /*
- Both a route (for external calls) that returns a report and an internal biz object that is used by notification for the same purpose
- so the route just calls the biz object which handles processing, getting data, checking rights and then making the report and either attaching it to an email (maybe I do need that temp server folder after all)
- or return to route to return to Client end
- */
- public async Task RenderReport(RenderReportParameter reportParam, string apiUrl, AuthorizationRoles overrideRoles = AuthorizationRoles.NoRole)
+
+ public async Task RenderReport(RenderReportParameter reportParam, string apiUrl, AuthorizationRoles overrideRoles = AuthorizationRoles.NoRole)
{
var log = AyaNova.Util.ApplicationLogging.CreateLogger("ReportBiz::RenderReport");
@@ -369,15 +365,110 @@ namespace AyaNova.Biz
//If need the generated page content
//var pagecontent = await page.GetContentAsync();
+ string outputFileName = StringUtil.ReplaceLastOccurrence(FileUtil.NewRandomFileName,".","")+".pdf";
+ string outputFullPath= System.IO.Path.Combine(FileUtil.TemporaryFilesFolder,outputFileName);
+
//render to pdf and return
- var pdfBuffer = await page.PdfDataAsync();
+ await page.PdfAsync(outputFullPath);
log.LogDebug($"returning results now:");
- return new RenderedReport() { MimeType = "application/pdf", RenderedOutput = pdfBuffer };
+ return outputFileName;
}
}
-
+
+ // public async Task RenderReport(RenderReportParameter reportParam, string apiUrl, AuthorizationRoles overrideRoles = AuthorizationRoles.NoRole)
+ // {
+ // var log = AyaNova.Util.ApplicationLogging.CreateLogger("ReportBiz::RenderReport");
+
+ // //get report, vet security, see what we need before init in case of issue
+ // var report = await ct.Report.FirstOrDefaultAsync(z => z.Id == reportParam.ReportId);
+ // if (report == null)
+ // {
+ // AddError(ApiErrorCode.NOT_FOUND, "id");
+ // return null;
+ // }
+
+ // AuthorizationRoles effectiveRoles = CurrentUserRoles;
+ // if (overrideRoles != AuthorizationRoles.NoRole)
+ // effectiveRoles = overrideRoles;
+
+
+ // if (!AyaNova.Api.ControllerHelpers.Authorized.HasReadFullRole(effectiveRoles, report.ObjectType))
+ // {
+ // AddError(ApiErrorCode.NOT_AUTHORIZED, null, $"User not authorized for {report.ObjectType} type object");
+ // return null;
+ // }
+
+
+ // //Get data
+ // var ReportData = await GetReportData(new ObjectReportDataParameter() { ObjectType = report.ObjectType, SelectedRowIds = reportParam.SelectedRowIds, DataListKey = reportParam.DataListKey, ListView = reportParam.ListView });
+
+ // //initialization
+ // log.LogDebug("Initializing report system");
+ // var ReportJSFolderPath = Path.Combine(ServerBootConfig.AYANOVA_CONTENT_ROOT_PATH, "resource", "rpt");
+ // var lo = new LaunchOptions { Headless = true };
+ // bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
+ // if (!isWindows)
+ // {
+ // log.LogDebug($"Not Windows: setting executable path for chrome to expected '/usr/bin/chromium-browser'");
+ // lo.ExecutablePath = "/usr/bin/chromium-browser";//this is the default path for docker based alpine dist, but maybe not others, need to make a config setting likely
+ // lo.Args = new string[] { "--no-sandbox" };
+ // }
+ // else
+ // {
+ // log.LogDebug($"Windows: Calling browserFetcher download async now:");
+ // await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
+ // }
+
+
+ // log.LogDebug($"Launching headless Chrome now:");
+ // using (var browser = await Puppeteer.LaunchAsync(lo))
+ // using (var page = await browser.NewPageAsync())
+ // {
+ // log.LogDebug($"Preparing page: adding base reporting scripts to page");
+
+ // //Add Handlebars JS for compiling and presenting
+ // await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-hb.js") });
+
+ // //add Marked for markdown processing
+ // await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-md.js") });
+
+ // //add stock helpers
+ // await page.AddScriptTagAsync(new AddTagOptions() { Path = Path.Combine(ReportJSFolderPath, "ay-report.js") });
+
+ // //execute to add to handlebars
+ // await page.EvaluateExpressionAsync("ayRegisterHelpers();");
+
+ // log.LogDebug($"Preparing page: adding this report's scripts, style and templates to page");
+
+ // //TODO: CUSTOM HELPERS
+
+ // //compile and run handlebars template
+ // var compileScript = $"let reportData=ayPreRender({ReportData});Handlebars.compile({report.Template})(reportData);";
+ // var resultHTML = await page.EvaluateExpressionAsync(compileScript);
+
+ // //render report as HTML
+ // await page.SetContentAsync(resultHTML);
+
+ // //add style (after page or it won't work)
+ // if (!string.IsNullOrWhiteSpace(report.Style))
+ // {
+ // await page.AddStyleTagAsync(new AddTagOptions { Content = report.Style });
+ // }
+
+ // //If need the generated page content
+ // //var pagecontent = await page.GetContentAsync();
+
+ // //render to pdf and return
+ // var pdfBuffer = await page.PdfDataAsync();
+ // log.LogDebug($"returning results now:");
+ // return new RenderedReport() { MimeType = "application/pdf", RenderedOutput = pdfBuffer };
+
+ // }
+ // }
+
+
////////////////////////////////////////////////////////////////////////////////////////////////
//JOB / OPERATIONS
diff --git a/server/AyaNova/util/FileUtil.cs b/server/AyaNova/util/FileUtil.cs
index c3d53098..8cefbb16 100644
--- a/server/AyaNova/util/FileUtil.cs
+++ b/server/AyaNova/util/FileUtil.cs
@@ -598,7 +598,7 @@ namespace AyaNova.Util
#region General utilities
///
- /// Get a random file name
+ /// Get a random file name, no extension
///
///
internal static string NewRandomFileName
diff --git a/server/AyaNova/util/StringUtil.cs b/server/AyaNova/util/StringUtil.cs
index a2f43236..2e00c379 100644
--- a/server/AyaNova/util/StringUtil.cs
+++ b/server/AyaNova/util/StringUtil.cs
@@ -118,6 +118,18 @@ namespace AyaNova.Util
return str;
}
+ public static string ReplaceLastOccurrence(string source, string find, string replace)
+ {
+ if (source == null) { return source; }
+ int place = source.LastIndexOf(find);
+
+ if (place == -1)
+ return source;
+
+ string result = source.Remove(place, find.Length).Insert(place, replace);
+ return result;
+ }
+
}//eoc