This commit is contained in:
666
server/Controllers/AttachmentController.cs
Normal file
666
server/Controllers/AttachmentController.cs
Normal file
@@ -0,0 +1,666 @@
|
||||
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.IO;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Util;
|
||||
using Sockeye.Biz;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
//FROM DOCS HERE:
|
||||
//https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
//https://github.com/aspnet/Docs/tree/74a44669d5e7039e2d4d2cb3f8b0c4ed742d1124/aspnetcore/mvc/models/file-uploads/sample/FileUploadSample
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attachment controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/attachment")]
|
||||
[Produces("application/json")]
|
||||
public class AttachmentController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AttachmentController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public AttachmentController(AyContext dbcontext, ILogger<AttachmentController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class UpdateAttachmentInfo
|
||||
{
|
||||
[Required]
|
||||
public uint Concurrency { get; set; }
|
||||
[Required]
|
||||
public string DisplayFileName { get; set; }
|
||||
public string Notes { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update FileAttachment
|
||||
/// (FileName and notes only)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="inObj"></param>
|
||||
/// <returns>list</returns>
|
||||
[Authorize]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutAttachment([FromRoute] long id, [FromBody] UpdateAttachmentInfo inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, dbObject.AttachToAType))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
string ChangeTextra = string.Empty;
|
||||
if (dbObject.DisplayFileName != inObj.DisplayFileName)
|
||||
{
|
||||
ChangeTextra = $"\"{dbObject.DisplayFileName}\" => \"{inObj.DisplayFileName}\"";
|
||||
}
|
||||
if (dbObject.Notes != inObj.Notes)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ChangeTextra))
|
||||
ChangeTextra += ", ";
|
||||
ChangeTextra += "Notes";
|
||||
}
|
||||
dbObject.DisplayFileName = inObj.DisplayFileName;
|
||||
dbObject.Notes = inObj.Notes;
|
||||
|
||||
|
||||
|
||||
//Set "original" value of concurrency token to input token
|
||||
//this will allow EF to check it out
|
||||
ct.Entry(dbObject).OriginalValues["Concurrency"] = inObj.Concurrency;
|
||||
|
||||
//Log event and save context
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentModified, ChangeTextra), ct);
|
||||
|
||||
|
||||
|
||||
//SEARCH INDEXING
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationIdFromContext.Id(HttpContext.Items), id, SockType.FileAttachment);
|
||||
SearchParams.AddText(inObj.Notes).AddText(inObj.DisplayFileName);
|
||||
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
|
||||
|
||||
//--------------
|
||||
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
|
||||
//Normallyh wouldn't return a whole list but in this case the UI demands it because of reactivity issues
|
||||
var ret = await GetFileListForObjectAsync(dbObject.AttachToAType, dbObject.AttachToObjectId);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get attachments for object type and id specified
|
||||
///
|
||||
/// Required Role: Read full object properties rights to object type specified
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>file attachment list for object</returns>
|
||||
[Authorize]
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList([FromQuery] SockType sockType, [FromQuery] long sockId)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Is this a customer user attempting to view a wo attachments??
|
||||
// var userType = UserTypeFromContext.Type(HttpContext.Items);
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, sockType))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
var ret = await GetFileListForObjectAsync(sockType, sockId);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get parent object type and id
|
||||
/// for specified attachment id
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpGet("parent/{id}")]
|
||||
public async Task<IActionResult> GetParent([FromRoute] long id)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var at = await ct.FileAttachment.AsNoTracking().Where(z => z.Id == id).FirstOrDefaultAsync();
|
||||
if (at == null)
|
||||
return NotFound();
|
||||
|
||||
|
||||
return Ok(ApiOkResponse.Response(new { id = at.AttachToObjectId, type = at.AttachToAType }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Upload attachment file
|
||||
/// Max 10GiB total
|
||||
/// Requires same Authorization roles as object that file is being attached to
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>NameValue list of filenames and attachment id's</returns>
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
[DisableFormValueModelBinding]
|
||||
[RequestSizeLimit(ServerBootConfig.MAX_ATTACHMENT_UPLOAD_BYTES)]
|
||||
public async Task<IActionResult> 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<NameIdItem>();
|
||||
object ret = null;
|
||||
SockTypeId attachToObject = null;
|
||||
try
|
||||
{
|
||||
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}"));
|
||||
|
||||
|
||||
|
||||
bool badRequest = false;
|
||||
string AttachToAType = string.Empty;
|
||||
string AttachToObjectId = string.Empty;
|
||||
string errorMessage = string.Empty;
|
||||
string Notes = string.Empty;
|
||||
long? OverrideUserId = null;
|
||||
List<UploadFileData> FileData = new List<UploadFileData>();
|
||||
|
||||
var uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);
|
||||
if (!string.IsNullOrWhiteSpace(uploadFormData.Error))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = uploadFormData.Error;
|
||||
}
|
||||
|
||||
if (!badRequest
|
||||
&& (!uploadFormData.FormFieldData.ContainsKey("FileData")
|
||||
|| !uploadFormData.FormFieldData.ContainsKey("AttachToAType")
|
||||
|| !uploadFormData.FormFieldData.ContainsKey("AttachToObjectId")))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "Missing one or more required FormFieldData values: AttachToAType, AttachToObjectId, FileData";
|
||||
}
|
||||
if (!badRequest)
|
||||
{
|
||||
AttachToAType = uploadFormData.FormFieldData["AttachToAType"].ToString();
|
||||
//for v8 migrate purposes
|
||||
if (uploadFormData.FormFieldData.ContainsKey("OverrideUserId"))
|
||||
OverrideUserId = long.Parse(uploadFormData.FormFieldData["OverrideUserId"].ToString());
|
||||
|
||||
AttachToObjectId = uploadFormData.FormFieldData["AttachToObjectId"].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<List<UploadFileData>>(uploadFormData.FormFieldData["FileData"].ToString());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(AttachToAType) || string.IsNullOrWhiteSpace(AttachToObjectId))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "AttachToAType and / or AttachToObjectId are empty and are required";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Get type and id object from post paramters
|
||||
|
||||
if (!badRequest)
|
||||
{
|
||||
attachToObject = new SockTypeId(AttachToAType, AttachToObjectId);
|
||||
if (attachToObject.IsEmpty)
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "AttachToAType and / or AttachToObjectId are not valid and are required";
|
||||
}
|
||||
}
|
||||
|
||||
//Is it an attachable type of object?
|
||||
if (!badRequest)
|
||||
{
|
||||
if (!attachToObject.IsCoreBizObject)
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = attachToObject.SockType.ToString() + " - AttachToAType does not support attachments";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//does attach to object exist?
|
||||
if (!badRequest)
|
||||
{
|
||||
//check if object exists
|
||||
if (!await BizObjectExistsInDatabase.ExistsAsync(attachToObject.SockType, attachToObject.ObjectId, ct))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "Invalid attach object";
|
||||
}
|
||||
else
|
||||
{
|
||||
// User needs modify rights to the object type in question
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, attachToObject.SockType))
|
||||
{
|
||||
//delete temp files
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (badRequest)
|
||||
{
|
||||
//delete temp files
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
//file too large is most likely issue so in that case return this localized properly
|
||||
if (errorMessage.Contains("413"))
|
||||
{
|
||||
var TransId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
return BadRequest(new ApiErrorResponse(
|
||||
ApiErrorCode.VALIDATION_LENGTH_EXCEEDED,
|
||||
null,
|
||||
"HTTP ERROR CODE 413 " + String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), FileUtil.GetBytesReadable(ServerBootConfig.MAX_ATTACHMENT_UPLOAD_BYTES))));
|
||||
}
|
||||
else//not too big, something else
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage));
|
||||
}
|
||||
|
||||
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).UtcDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (theDate == DateTime.MinValue)
|
||||
theDate = DateTime.UtcNow;
|
||||
|
||||
var v = await FileUtil.StoreFileAttachmentAsync(a.InitialUploadedPathName, a.MimeType, a.OriginalFileName, theDate, attachToObject, Notes, OverrideUserId ?? UserId, ct);
|
||||
|
||||
//EVENT LOG
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, attachToObject.ObjectId, attachToObject.SockType, SockEvent.AttachmentCreate, v.DisplayFileName), ct);
|
||||
|
||||
//SEARCH INDEXING
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationIdFromContext.Id(HttpContext.Items), v.Id, SockType.FileAttachment);
|
||||
SearchParams.AddText(v.Notes).AddText(v.DisplayFileName);
|
||||
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
|
||||
|
||||
}
|
||||
}
|
||||
ret = await GetFileListForObjectAsync(attachToObject.SockType, attachToObject.ObjectId);
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message));
|
||||
}
|
||||
|
||||
//Return the list of attachment ids and filenames
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Utility to delete files that were uploaded but couldn't be stored for some reason, called by Attach route
|
||||
// /// </summary>
|
||||
// /// <param name="uploadFormData"></param>
|
||||
// private static void DeleteTempFileUploadDueToBadRequest(ApiUploadProcessor.ApiUploadedFilesResult uploadFormData)
|
||||
// {
|
||||
// if (uploadFormData.UploadedFiles.Count > 0)
|
||||
// {
|
||||
// foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
// {
|
||||
// System.IO.File.Delete(a.InitialUploadedPathName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete Attachment
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[Authorize]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteAttachmentAsync([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, dbObject.AttachToAType))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//do the delete
|
||||
//this handles removing the file if there are no refs left and also the db record for the attachment
|
||||
await FileUtil.DeleteFileAttachmentAsync(dbObject, ct);
|
||||
|
||||
//Event log process delete
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDelete, dbObject.DisplayFileName), ct);
|
||||
|
||||
//Delete search index
|
||||
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, SockType.FileAttachment, ct);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch delete attachments
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>No content</returns>
|
||||
[HttpPost("batch-delete")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostBatchDelete([FromBody] List<long> idList)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
foreach (long id in idList)
|
||||
{
|
||||
var dbObject = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
continue;
|
||||
//do the delete
|
||||
//this handles removing the file if there are no refs left and also the db record for the attachment
|
||||
await FileUtil.DeleteFileAttachmentAsync(dbObject, ct);
|
||||
|
||||
//Event log process delete
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDelete, dbObject.DisplayFileName), ct);
|
||||
|
||||
//Delete search index
|
||||
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, SockType.FileAttachment, ct);
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
/// <summary>
|
||||
/// Batch move attachments
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>No content</returns>
|
||||
[HttpPost("batch-move")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostBatchMove([FromBody] dtoBatchMove dt)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await BizObjectExistsInDatabase.ExistsAsync(dt.ToType, dt.ToId, ct))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, "LT:ErrorAPI2010"));
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
foreach (long id in dt.IdList)
|
||||
{
|
||||
var dbObject = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
continue;
|
||||
|
||||
//do the move
|
||||
var msg = $"{dbObject.DisplayFileName} moved from {dbObject.AttachToAType}-{dbObject.AttachToObjectId} to {dt.ToType}-{dt.ToId} ";
|
||||
dbObject.AttachToObjectId = dt.ToId;
|
||||
dbObject.AttachToAType = dt.ToType;
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//Event log process move
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentModified, msg), ct);
|
||||
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
public class dtoBatchMove
|
||||
{
|
||||
public List<long> IdList { get; set; }
|
||||
public SockType ToType { get; set; }
|
||||
public long ToId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a file attachment
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("download/{id}")]
|
||||
public async Task<IActionResult> DownloadAsync([FromRoute] long id, [FromQuery] string t)
|
||||
{
|
||||
//https://dotnetcoretutorials.com/2017/03/12/uploading-files-asp-net-core/
|
||||
//https://stackoverflow.com/questions/45763149/asp-net-core-jwt-in-uri-query-parameter/45811270#45811270
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
|
||||
var DownloadUser = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct);
|
||||
if (DownloadUser == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
|
||||
//Ok, user has a valid download key and it's not expired yet so get the attachment record
|
||||
var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
//is this allowed?
|
||||
|
||||
if (!Authorized.HasReadFullRole(DownloadUser.Roles, dbObject.AttachToAType))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
//they are allowed, let's send the file
|
||||
string mimetype = dbObject.ContentType;
|
||||
var filePath = FileUtil.GetPermanentAttachmentFilePath(dbObject.StoredFileName);
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
{
|
||||
|
||||
//TODO: this should reset the validity
|
||||
|
||||
var errText = $"Physical file {dbObject.StoredFileName} not found despite attachment record, this file is missing";
|
||||
log.LogError(errText);
|
||||
await NotifyEventHelper.AddOpsProblemEvent($"File attachment issue: {errText}");
|
||||
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, errText));
|
||||
}
|
||||
|
||||
//Log
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(DownloadUser.Id, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDownload, dbObject.DisplayFileName), ct);
|
||||
|
||||
return PhysicalFile(filePath, mimetype, dbObject.DisplayFileName);
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
async private Task<object> GetFileListForObjectAsync(SockType sockType, long sockId)
|
||||
{
|
||||
var retList = new List<FileAttachmentListItem>();
|
||||
using (var cmd = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
await ct.Database.OpenConnectionAsync();
|
||||
cmd.CommandText = $@"select afileattachment.id, afileattachment.xmin as concurrency, displayfilename,contenttype,lastmodified, afileattachment.notes, size, auser.name as attachedbyuser from afileattachment
|
||||
left join auser on (afileattachment.attachedByUserId=auser.id)
|
||||
where attachtosockType={(int)sockType} and attachtoobjectid={sockId}
|
||||
order by displayfilename";
|
||||
|
||||
using (var dr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (dr.Read())
|
||||
{
|
||||
retList.Add(new FileAttachmentListItem()
|
||||
{
|
||||
Id = dr.GetInt64(0),
|
||||
Concurrency = (UInt32)dr.GetValue(1),
|
||||
DisplayFileName = dr.GetString(2),
|
||||
ContentType = dr.GetString(3),
|
||||
LastModified = dr.GetDateTime(4),
|
||||
Notes = dr.GetString(5),
|
||||
Size = dr.GetInt64(6),
|
||||
AttachedByUser = dr.GetString(7)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retList;
|
||||
}
|
||||
|
||||
private class FileAttachmentListItem
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public uint Concurrency { get; set; }
|
||||
public string DisplayFileName { get; set; }
|
||||
public string ContentType { get; set; }//mime type
|
||||
public DateTime LastModified { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string AttachedByUser { get; set; }
|
||||
public long Size { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger immediate AttachmentMaintenanceJob
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("maintenance")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostTriggerAttachmentMaintenanceJob()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var JobName = $"Attachment maintenance (demand) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = SockType.FileAttachment;
|
||||
j.JobType = JobType.AttachmentMaintenance;
|
||||
j.SubType = JobSubType.NotSet;
|
||||
j.Exclusive = true;
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
|
||||
Reference in New Issue
Block a user