diff --git a/server/AyaNova/ControllerHelpers/ApiUploadProcessor.cs b/server/AyaNova/ControllerHelpers/ApiUploadProcessor.cs index bd3671a6..c80498d6 100644 --- a/server/AyaNova/ControllerHelpers/ApiUploadProcessor.cs +++ b/server/AyaNova/ControllerHelpers/ApiUploadProcessor.cs @@ -23,36 +23,16 @@ namespace AyaNova.Api.ControllerHelpers internal static class ApiUploadProcessor { - /// - /// Process uploaded attachment file - /// Will be treated as a temporary file for further processing into database - /// - /// - /// - internal static async Task ProcessAttachmentUploadAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) - { - return await ProcessUploadAsync(httpContext, true); - } - /// - /// Process uploaded utility file (backup) - /// Anything that will be stored in the backup folder as is - /// - /// - /// - internal static async Task ProcessUtilityFileUploadAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) - { - return await ProcessUploadAsync(httpContext, false); - } + /// /// handle upload /// - /// - /// + /// /// list of files and form field data (if present) - private static async Task ProcessUploadAsync(Microsoft.AspNetCore.Http.HttpContext httpContext, bool processAsAttachment) + internal static async Task ProcessUploadAsync(Microsoft.AspNetCore.Http.HttpContext httpContext) { ApiUploadedFilesResult result = new ApiUploadedFilesResult(); @@ -69,7 +49,6 @@ namespace AyaNova.Api.ControllerHelpers var section = await reader.ReadNextSectionAsync(); - while (section != null) { ContentDispositionHeaderValue contentDisposition; @@ -83,24 +62,13 @@ namespace AyaNova.Api.ControllerHelpers string filePathAndName = string.Empty; var CleanedUploadFileName = contentDisposition.FileName.Value.Replace("\"", ""); - if (processAsAttachment) - { - //get temp file path and temp file name - filePathAndName = FileUtil.NewRandomAttachmentFileName; - } - else - { - //store directly into the backup file folder - //NOTE: all utility files are always stored as lowercase to avoid recognition issues down the road - CleanedUploadFileName = CleanedUploadFileName.ToLowerInvariant(); - filePathAndName = FileUtil.GetFullPathForUtilityFile(CleanedUploadFileName); - } - + //get temp file path and temp file name + filePathAndName = FileUtil.NewRandomUserFilesFolderFileName; //save to disk using (var stream = new FileStream(filePathAndName, FileMode.Create)) { - await section.Body.CopyToAsync(stream); + await section.Body.CopyToAsync(stream); } result.UploadedFiles.Add(new UploadedFileInfo() { diff --git a/server/AyaNova/Controllers/AttachmentController.cs b/server/AyaNova/Controllers/AttachmentController.cs index a8a591eb..5da0efc0 100644 --- a/server/AyaNova/Controllers/AttachmentController.cs +++ b/server/AyaNova/Controllers/AttachmentController.cs @@ -165,11 +165,11 @@ namespace AyaNova.Api.Controllers //used to hold extra file data sent by client - public class fileData - { - public string name { get; set; } - public long lastModified { get; set; } - } + // public class UploadFileData + // { + // public string name { get; set; } + // public long lastModified { get; set; } + // } /// /// Upload attachment file @@ -197,14 +197,14 @@ namespace AyaNova.Api.Controllers if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "FileUploadAttempt", $"Expected a multipart request, but got {Request.ContentType}")); - var uploadFormData = await ApiUploadProcessor.ProcessAttachmentUploadAsync(HttpContext); + var uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); bool badRequest = false; string AttachToObjectType = string.Empty; string AttachToObjectId = string.Empty; string errorMessage = string.Empty; string Notes = string.Empty; - List FileData = new List(); + List FileData = new List(); if ( !uploadFormData.FormFieldData.ContainsKey("FileData") || @@ -222,7 +222,7 @@ namespace AyaNova.Api.Controllers Notes = uploadFormData.FormFieldData["Notes"].ToString(); //fileData in JSON stringify format which contains the actual last modified dates etc //"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]" - FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); + FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); if (string.IsNullOrWhiteSpace(AttachToObjectType) || string.IsNullOrWhiteSpace(AttachToObjectId)) { @@ -298,7 +298,7 @@ namespace AyaNova.Api.Controllers //Get the actual date from the separate filedata //this is because the lastModified date is always empty in the form data files DateTime theDate = DateTime.MinValue; - foreach (fileData f in FileData) + foreach (UploadFileData f in FileData) { if (f.name == a.OriginalFileName) { diff --git a/server/AyaNova/Controllers/TranslationController.cs b/server/AyaNova/Controllers/TranslationController.cs index f7ed7ff7..c28a52d5 100644 --- a/server/AyaNova/Controllers/TranslationController.cs +++ b/server/AyaNova/Controllers/TranslationController.cs @@ -15,6 +15,7 @@ using System; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System.Linq; +using AyaNova.Util; @@ -304,12 +305,11 @@ namespace AyaNova.Api.Controllers var asText = Newtonsoft.Json.JsonConvert.SerializeObject( o, Newtonsoft.Json.Formatting.None, - new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver(new string[] { "Concurrency", "Id","TranslationId" }) }); + new JsonSerializerSettings { ContractResolver = new ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "TranslationId" }) }); var bytes = System.Text.Encoding.UTF8.GetBytes(asText); var file = new FileContentResult(bytes, "application/octet-stream"); file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".json"; return file; - } @@ -336,6 +336,104 @@ namespace AyaNova.Api.Controllers + /// + /// Upload Translation export file + /// Max 15mb total + /// + /// Accepted + [Authorize] + [HttpPost("upload")] + [DisableFormValueModelBinding] + [RequestSizeLimit(15000000)]//currently export file is 200kb * 50 maximum at a time = 15mb https://github.com/aspnet/Announcements/issues/267 + 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)); + + // var returnList = new List(); + object ret = null; + AyaTypeId attachToObject = null; + try + { + if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "FileUploadAttempt", $"Expected a multipart request, but got {Request.ContentType}")); + + var uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext); + + bool badRequest = false; + string UploadObjectType = string.Empty; + string UploadObjectId = string.Empty; + string errorMessage = string.Empty; + string Notes = string.Empty; + List FileData = new List(); + + if ( + !uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required + { + badRequest = true; + errorMessage = "Missing required FormFieldData value: FileData"; + } + if (!badRequest) + { + if (uploadFormData.FormFieldData.ContainsKey("ObjectType")) + UploadObjectType = uploadFormData.FormFieldData["ObjectType"].ToString(); + if (uploadFormData.FormFieldData.ContainsKey("ObjectId")) + UploadObjectId = uploadFormData.FormFieldData["ObjectId"].ToString(); + if (uploadFormData.FormFieldData.ContainsKey("Notes")) + Notes = uploadFormData.FormFieldData["Notes"].ToString(); + //fileData in JSON stringify format which contains the actual last modified dates etc + //"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]" + FileData = Newtonsoft.Json.JsonConvert.DeserializeObject>(uploadFormData.FormFieldData["FileData"].ToString()); + + } + + + long UserId = UserIdFromContext.Id(HttpContext.Items); + + //We have our files and a confirmed AyObject, ready to attach and save permanently + if (uploadFormData.UploadedFiles.Count > 0) + { + + foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) + { + //Get the actual date from the separate filedata + //this is because the lastModified date is always empty in the form data files + DateTime theDate = DateTime.MinValue; + foreach (UploadFileData f in FileData) + { + if (f.name == a.OriginalFileName) + { + if (f.lastModified > 0) + { + theDate = DateTimeOffset.FromUnixTimeMilliseconds(f.lastModified).DateTime; + } + } + } + if (theDate == DateTime.MinValue) + theDate = DateTime.UtcNow; + + var v = await FileUtil.StoreFileAttachmentAsync(a.InitialUploadedPathName, a.MimeType, a.OriginalFileName, theDate, attachToObject, Notes, ct); + + //EVENT LOG + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, attachToObject.ObjectId, attachToObject.ObjectType, AyaEvent.AttachmentCreate, v.DisplayFileName), ct); + + //SEARCH INDEXING + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationIdFromContext.Id(HttpContext.Items), v.Id, AyaType.FileAttachment); + SearchParams.AddText(v.Notes).AddText(v.DisplayFileName); + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + } + } + } + catch (System.IO.InvalidDataException ex) + { + return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "FileUploadAttempt", ex.Message)); + } + + //Return the list of attachment ids and filenames + return Accepted(); + } diff --git a/server/AyaNova/models/dto/UploadFileData.cs b/server/AyaNova/models/dto/UploadFileData.cs new file mode 100644 index 00000000..38d69bb7 --- /dev/null +++ b/server/AyaNova/models/dto/UploadFileData.cs @@ -0,0 +1,8 @@ +namespace AyaNova.Models +{ + public class UploadFileData + { + public string name { get; set; } + public long lastModified { get; set; } + } +} diff --git a/server/AyaNova/util/FileUtil.cs b/server/AyaNova/util/FileUtil.cs index 5a135021..c33834fc 100644 --- a/server/AyaNova/util/FileUtil.cs +++ b/server/AyaNova/util/FileUtil.cs @@ -331,7 +331,7 @@ namespace AyaNova.Util /// Get a random file name with path to attachments folder /// /// - internal static string NewRandomAttachmentFileName + internal static string NewRandomUserFilesFolderFileName { get {