using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; using AyaNova.Models; using AyaNova.Api.ControllerHelpers; using AyaNova.Biz; using AyaNova.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace AyaNova.Api.Controllers { [ApiController] [ApiVersion("8.0")] [Route("api/v{version:apiVersion}/export")] [Produces("application/json")] [Authorize] public class ExportController : ControllerBase { private readonly AyContext ct; private readonly ILogger log; private readonly ApiServerState serverState; /// /// ctor /// /// /// /// public ExportController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) { ct = dbcontext; log = logger; serverState = apiServerState; } /// /// Export to file /// /// Valid values are: "csv","json" /// /// downloadable export file name [HttpPost("render")] public async Task RenderExport([FromRoute] string format, [FromBody] DataListSelection dataListSelection) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); if (dataListSelection.IsEmpty) return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "DataListSelection is required")); if (!dataListSelection.ObjectType.HasAttribute(typeof(CoreBizObjectAttribute))) return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type")); if (!Authorized.HasReadFullRole(HttpContext.Items, dataListSelection.ObjectType)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (string.IsNullOrWhiteSpace(format)) return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "format required")); if (format != "csv" && format != "json") return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "format not valid, must be 'csv' or 'json'")); await dataListSelection.RehydrateIdList(ct, UserRolesFromContext.Roles(HttpContext.Items), log); if (dataListSelection.SelectedRowIds.Length == 0) return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "List of ids")); log.LogDebug($"Instantiating biz object handler for {dataListSelection.ObjectType}"); var biz = BizObjectFactory.GetBizObject(dataListSelection.ObjectType, ct); log.LogDebug($"Fetching data for {dataListSelection.SelectedRowIds.Length} {dataListSelection.ObjectType} items"); var TheData = await ((IExportAbleObject)biz).GetExportData(dataListSelection.SelectedRowIds); string outputFileName = StringUtil.ReplaceLastOccurrence(FileUtil.NewRandomFileName, ".", "") + ".pdf"; string outputFullPath = System.IO.Path.Combine(FileUtil.TemporaryFilesFolder, outputFileName); log.LogDebug($"Calling render export data to file"); //TODO: RENDER DATA TO FILE HERE await page.PdfAsync(outputFullPath, PdfOptions); log.LogDebug($"Completed, returning results"); return Ok(ApiOkResponse.Response(outputFileName)); // var httpConnectionFeature = HttpContext.Features.Get(); // var API_URL = $"http://127.0.0.1:{httpConnectionFeature.LocalPort}/api/v8/"; // try // { // var result = await biz.RenderExport(ExportParam, API_URL); // if (string.IsNullOrWhiteSpace(result)) // return BadRequest(new ApiErrorResponse(biz.Errors)); // else // return Ok(ApiOkResponse.Response(result)); // } // catch (System.Exception ex) // { // //The Javascript evaluation stack trace can be in the message making it long and internalized, // //however the info is useful as it can indicate exactly which function failed etc so sending it all back is best // return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message)); // } } /// /// Download a rendered Export /// /// /// download token /// [HttpGet("download/{fileName}")] [AllowAnonymous] public async Task DownloadAsync([FromRoute] string fileName, [FromQuery] string t) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null) { await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); } if (!FileUtil.TemporaryFileExists(fileName)) { await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); } var FilePath = FileUtil.GetFullPathForTemporaryFile(fileName); return PhysicalFile(FilePath, "application/pdf"); } /// /// Download Export template /// /// Export id /// download token /// A single Export template as a file [AllowAnonymous] [HttpGet("export/{id}")] public async Task DownloadTemplate([FromRoute] long id, [FromQuery] string t) { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null) { await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); } var o = await ct.Export.SingleOrDefaultAsync(z => z.Id == id); //turn into correct format and then send as file if (o == null) { return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); } var asText = Newtonsoft.Json.JsonConvert.SerializeObject( o, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { ContractResolver = new AyaNova.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id" }) }); var bytes = System.Text.Encoding.UTF8.GetBytes(asText); var file = new FileContentResult(bytes, "application/octet-stream"); file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".ayrt"; return file; } /// /// Upload Reprot template export file /// Max 15mb total /// /// Accepted [Authorize] [HttpPost("upload")] [DisableFormValueModelBinding] [RequestSizeLimit(15000000)]//currently the largest v7 export for a Export template is 828kb, I'm guessing 15mb is more than enough public async Task UploadAsync() { //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); // AyaTypeId attachToObject = null; ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null; try { if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}")); //Save uploads to disk under temporary file names until we decide how to handle them uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); List FileData = new List(); if (!uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required { return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: FileData")); } //fileData in JSON stringify format FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); //Instantiate the business object handler ExportBiz biz = ExportBiz.GetBiz(ct, HttpContext); //We have our files now can parse and insert into db if (uploadFormData.UploadedFiles.Count > 0) { //deserialize each file and import foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) { JObject o = JObject.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName)); if (!await biz.ImportAsync(o)) { //delete all the files temporarily uploaded and return bad request DeleteTempUploadFile(uploadFormData); return BadRequest(new ApiErrorResponse(biz.Errors)); } } } } catch (System.IO.InvalidDataException ex) { return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message)); } finally { //delete all the files temporarily uploaded and return bad request DeleteTempUploadFile(uploadFormData); } //Return the list of attachment ids and filenames return Accepted(); } private static void DeleteTempUploadFile(ApiUploadProcessor.ApiUploadedFilesResult uploadFormData) { if (uploadFormData.UploadedFiles.Count > 0) { foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) { System.IO.File.Delete(a.InitialUploadedPathName); } } } //----------------------------------------- }//eoc }//eons