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.Extensions.Logging; using AyaNova.Models; using AyaNova.Api.ControllerHelpers; using AyaNova.Biz; using AyaNova.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.IO; using System.IO.Compression; using ChoETL; using System; 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/{format}")] public async Task RenderExport([FromRoute] string format, [FromBody] DataListSelectedRequest selectedRequest) { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); if (selectedRequest == null) return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "DataListSelectedRequest is required")); if (!Authorized.HasReadFullRole(HttpContext.Items, selectedRequest.AType)) 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'")); var UserId = UserIdFromContext.Id(HttpContext.Items); var UserRoles = UserRolesFromContext.Roles(HttpContext.Items); var UserTranslationId = UserTranslationIdFromContext.Id(HttpContext.Items); //Rehydrate id list if necessary if (selectedRequest.SelectedRowIds.Length == 0) selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList( selectedRequest, ct, UserRoles, log, UserId, UserTranslationId); log.LogDebug($"Instantiating biz object handler for {selectedRequest.AType}"); var biz = BizObjectFactory.GetBizObject(selectedRequest.AType, ct, UserId, UserRoles, UserTranslationId); log.LogDebug($"Fetching data for {selectedRequest.SelectedRowIds.Length} {selectedRequest.AType} items"); string baseFileName = FileUtil.StringToSafeFileName($"{selectedRequest.AType.ToString().ToLowerInvariant()}-{format}-{FileUtil.GetSafeDateFileName()}"); string outputSourceFileName = baseFileName + "." + format; string outputSourceFullPath = System.IO.Path.Combine(FileUtil.TemporaryFilesFolder, outputSourceFileName); string outputZipFullpath = System.IO.Path.Combine(FileUtil.TemporaryFilesFolder, baseFileName + ".zip"); log.LogDebug($"Calling render export data to file {outputZipFullpath}"); try { switch (format) { case "csv": using (var w = new ChoCSVWriter(outputSourceFullPath).WithFirstLineHeader()) { var dat = await ((IExportAbleObject)biz).GetExportData(selectedRequest, Guid.Empty);//todo: jobify w.Write(ToDynamicList(dat)); } break; case "json": using (StreamWriter file = System.IO.File.CreateText(outputSourceFullPath)) using (JsonTextWriter writer = new JsonTextWriter(file)) { var dat = await ((IExportAbleObject)biz).GetExportData(selectedRequest, Guid.Empty);//todo: jobify dat.WriteTo(writer); } break; } //zip it using (FileStream fs = new FileStream(outputZipFullpath, FileMode.Create)) using (ZipArchive arch = new ZipArchive(fs, ZipArchiveMode.Create)) { arch.CreateEntryFromFile(outputSourceFullPath, outputSourceFileName); } log.LogDebug($"Completed, returning results"); return Ok(ApiOkResponse.Response(baseFileName + ".zip")); } catch (ReportRenderTimeOutException) { log.LogInformation($"RenderExport timeout data list key: {selectedRequest.DataListKey}, record count:{selectedRequest.SelectedRowIds.LongLength}, user:{UserNameFromContext.Name(HttpContext.Items)} "); return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "timeout - select fewer records")); } } public static IList ToDynamicList(JArray data) { var dynamicData = new List(); var expConverter = new Newtonsoft.Json.Converters.ExpandoObjectConverter(); foreach (var dataItem in data) { dynamic obj = JsonConvert.DeserializeObject(dataItem.ToString(), expConverter); dynamicData.Add(obj); } return dynamicData; } /// /// 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/zip"); } //----------------------------------------- }//eoc }//eons