using System; 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 Microsoft.EntityFrameworkCore; using System.Globalization; using System.IO; using System.Text; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Net.Http.Headers; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using AyaNova.Models; using AyaNova.Api.ControllerHelpers; using AyaNova.Util; using AyaNova.Biz; namespace AyaNova.Api.Controllers { /// /// Import AyaNova 7 data controller /// [ApiController] [ApiVersion("8.0")] [Route("api/v{version:apiVersion}/[controller]")] [Produces("application/json")] [Authorize] public class ImportAyaNova7Controller : ControllerBase { private readonly AyContext ct; private readonly ILogger log; private readonly ApiServerState serverState; /// /// /// /// /// /// public ImportAyaNova7Controller(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) { ct = dbcontext; log = logger; serverState = apiServerState; } /// /// Upload AyaNova 7 import file /// /// NameValue list of filenames and id's [HttpPost] [DisableFormValueModelBinding] [RequestSizeLimit(10737418241)]//10737418240 = 10gb https://github.com/aspnet/Announcements/issues/267 public async Task Upload() { //Open or opsOnly and user is opsadminfull if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull))) { return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); } if (!Authorized.HasCreateRole(HttpContext.Items, AyaType.AyaNova7Import)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } var returnList = new List(); 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.ProcessUtilityFileUpload(HttpContext); bool badRequest = false; string errorMessage = string.Empty; //are these the right files? if (uploadFormData.UploadedFiles.Count > 0) { foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) { //should look like this: ayanova.data.dump.2018-04-2--12-30-57.zip string lwr = a.OriginalFileName.ToLowerInvariant(); if (!(lwr.StartsWith("ayanova.data.dump") && lwr.EndsWith(".zip"))) { badRequest = true; errorMessage = $"File uploaded \"{lwr}\" does not appear to be an AyaNova 7 data dump file. The name should start with \"ayanova.data.dump\" have a date in the middle and end with \".zip\". Upload process is terminated without saving."; } } } if (badRequest) { //delete temp files if (uploadFormData.UploadedFiles.Count > 0) { foreach (UploadedFileInfo a in uploadFormData.UploadedFiles) { System.IO.File.Delete(a.InitialUploadedPathName); } } //return bad request return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage)); } //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) { returnList.Add(a.OriginalFileName); } } } catch (InvalidDataException ex) { return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "FileUploadAttempt", ex.Message)); } //Return the list of attachment ids and filenames return Ok(ApiOkResponse.Response(returnList, true)); } /// /// Delete import file /// /// /// Ok [HttpDelete("{filename}")] public ActionResult Delete([FromRoute] string filename) { //Open or opsOnly and user is opsadminfull if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull))) { return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); } if (!ModelState.IsValid) { return BadRequest(new ApiErrorResponse(ModelState)); } if (!Authorized.HasDeleteRole(HttpContext.Items, AyaType.AyaNova7Import)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } //do the delete //this handles removing the file if there are no efs left and also the db record for the attachment FileUtil.DeleteUtilityFile(filename); return NoContent(); } /// /// Get AyaNova 7 data dump uploaded files list /// /// List of uploaded data dump files [HttpGet] public ActionResult List() { //Open or opsOnly and user is opsadminfull if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull))) { return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); } if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.AyaNova7Import)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } if (!ModelState.IsValid) { return BadRequest(new ApiErrorResponse(ModelState)); } //dump file name example: ayanova.data.dump.XXX.zip List l = FileUtil.UtilityFileList("ayanova.data.dump.*.zip"); return Ok(ApiOkResponse.Response(l, true)); } /// /// *ERASE DATABASE and start import of previously uploaded import file /// **This will permanently erase all current data in database without further warning as the first step in the import process** /// /// /// Ok [HttpPost("EraseDatabaseAndStartImport/{filename}")] public ActionResult EraseDatabaseAndStartImport([FromRoute] string filename) { //Open or opsOnly and user is opsadminfull if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull))) { return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); } if (!ModelState.IsValid) { return BadRequest(new ApiErrorResponse(ModelState)); } //UPDATE: I think it should be ok so commenting this out for now pending something coming up in testing // //LOOKAT: I decided not to allow trial to import v7 data. // //This was a snap decision, I didn't think about it much other than // //I'm concerned right now as of April 17 2018 during development that // //a trial user will import their old AyaNova data and then ... well somehow continue to use it I guess, // //maybe it's a non-issue as a trial will only work so long anyway // #if (!DEBUG) // if (AyaNova.Core.License.LicenseIsTrial) // { // return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Current license is a trial license key. Only a licensed database can be used with import.")); // } // #endif //Create, in that they are creating new data in AyaNova if (!Authorized.HasCreateRole(HttpContext.Items, AyaType.AyaNova7Import)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } //does the file even exist? if (!FileUtil.UtilityFileExists(filename)) { return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "filename", "File not found, ensure the name via the GET route endpoint list of previously uploaded import files")); } //Create the job here dynamic jobInfo = new JObject(); jobInfo.ImportFileName = filename; OpsJob j = new OpsJob(); j.Name = $"Import AyaNova7 data (import file \"{filename}\""; j.JobType = JobType.ImportV7Data; //j.O wnerId = UserIdFromContext.Id(HttpContext.Items); j.JobInfo = jobInfo.ToString(); JobsBiz.AddJob(j, ct); return Accepted(new { JobId = j.GId });//202 accepted } //////////////////////////////////////////////////////////////////////////////////// }//eoc }//eons