This commit is contained in:
76
server/AyaNova/Controllers/ApiRootController.cs
Normal file
76
server/AyaNova/Controllers/ApiRootController.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Biz;
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Meta controller class
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/")]
|
||||
public class ApiMetaController : Controller
|
||||
{
|
||||
|
||||
private readonly ILogger<ApiMetaController> _log;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public ApiMetaController(ILogger<ApiMetaController> logger)
|
||||
{
|
||||
_log = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AyaNova API documentation and manual
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public ContentResult Index()
|
||||
{
|
||||
|
||||
var resp = $@"<html lang=""en"">
|
||||
<head>
|
||||
<meta charset=""utf-8"">
|
||||
<meta name=""viewport"" content=""width=device-width, initial-scale=1, shrink-to-fit=no"">
|
||||
<title>AyaNova server</title>
|
||||
</head>
|
||||
<body >
|
||||
<div style=""text-align: center;"">
|
||||
<div style=""display: inline-block;text-align:left;"">
|
||||
<h1>{AyaNovaVersion.FullNameAndVersion}</h1>
|
||||
<a href=""/docs"">AyaNova manual</a><br/><br/>
|
||||
<a href=""/api-docs"">API explorer</a><br/><br/>
|
||||
<a href=""mailto:support@ayanova.com"">Email AyaNova support</a><br/><br/>
|
||||
<h4>{LocaleBiz.GetDefaultLocalizedText("HelpLicense").Result}</h4>
|
||||
<pre>{AyaNova.Core.License.LicenseInfo}</pre>
|
||||
<h4>Schema version</h4>
|
||||
<pre>{AySchema.currentSchema.ToString()}</pre>
|
||||
<h4>Server time</h4>
|
||||
<pre>{DateUtil.ServerDateTimeString(System.DateTime.UtcNow)}</pre>
|
||||
<pre>{TimeZoneInfo.Local.Id}</pre>
|
||||
<h4>Server logs</h4>
|
||||
<pre>{ServerBootConfig.AYANOVA_LOG_PATH}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
|
||||
return new ContentResult
|
||||
{
|
||||
ContentType = "text/html",
|
||||
StatusCode = 200,
|
||||
Content = resp
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
452
server/AyaNova/Controllers/AttachmentController.cs
Normal file
452
server/AyaNova/Controllers/AttachmentController.cs
Normal file
@@ -0,0 +1,452 @@
|
||||
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 AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.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>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class AttachmentController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AttachmentController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
//private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
||||
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//TODO: Centralize this code somewhere else, it's going to be needed for backup as well
|
||||
//consider the 1 hour is this legit depending on client
|
||||
|
||||
/// <summary>
|
||||
/// Get download token
|
||||
/// A download token is good for 1 hour from issue
|
||||
/// </summary>
|
||||
/// <returns>Current download token for user</returns>
|
||||
[HttpGet("DownloadToken")]
|
||||
public async Task<IActionResult> GetDownloadToken()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
long lUserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
var u = await ct.User.FirstOrDefaultAsync(a => a.Id == lUserId);
|
||||
if (u == null)
|
||||
return NotFound();
|
||||
else
|
||||
{
|
||||
|
||||
//Generate a download token and store it with the user account
|
||||
//users who are authenticated can get their token via download route
|
||||
Guid g = Guid.NewGuid();
|
||||
string dlkey = Convert.ToBase64String(g.ToByteArray());
|
||||
dlkey = dlkey.Replace("=", "");
|
||||
dlkey = dlkey.Replace("+", "");
|
||||
|
||||
//get expiry date for download token
|
||||
var exp = new DateTimeOffset(DateTime.Now.AddHours(1).ToUniversalTime(), TimeSpan.Zero);
|
||||
|
||||
u.DlKey = dlkey;
|
||||
u.DlKeyExpire = exp.DateTime;
|
||||
ct.User.Update(u);
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();//triggering concurrency exception here
|
||||
}
|
||||
catch (Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException)
|
||||
{
|
||||
log.LogInformation("Auth retry dlkey");
|
||||
};
|
||||
|
||||
return Ok(new ApiOkResponse(new { dlkey = u.DlKey, expires = u.DlKeyExpire }));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Upload attachment file
|
||||
///
|
||||
/// Required roles: Same roles as object that file is being attached to
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>NameValue list of filenames and attachment id's</returns>
|
||||
[HttpPost]
|
||||
[DisableFormValueModelBinding]
|
||||
[RequestSizeLimit(10737418241)]//10737418240 = 10gb https://github.com/aspnet/Announcements/issues/267
|
||||
public async Task<IActionResult> Upload()
|
||||
{
|
||||
//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(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
var returnList = new List<NameIdItem>();
|
||||
|
||||
|
||||
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.ProcessAttachmentUpload(HttpContext);
|
||||
|
||||
bool badRequest = false;
|
||||
string AttachToObjectType = string.Empty;
|
||||
string AttachToObjectId = string.Empty;
|
||||
string errorMessage = string.Empty;
|
||||
|
||||
if (!uploadFormData.FormFieldData.ContainsKey("AttachToObjectType") || !uploadFormData.FormFieldData.ContainsKey("AttachToObjectId"))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "AttachToObjectType and / or AttachToObjectId are missing and are required";
|
||||
}
|
||||
if (!badRequest)
|
||||
{
|
||||
AttachToObjectType = uploadFormData.FormFieldData["AttachToObjectType"].ToString();
|
||||
AttachToObjectId = uploadFormData.FormFieldData["AttachToObjectId"].ToString();
|
||||
if (string.IsNullOrWhiteSpace(AttachToObjectType) || string.IsNullOrWhiteSpace(AttachToObjectId))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "AttachToObjectType and / or AttachToObjectId are empty and are required";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Get type and id object from post paramters
|
||||
AyaTypeId attachToObject = null;
|
||||
if (!badRequest)
|
||||
{
|
||||
attachToObject = new AyaTypeId(AttachToObjectType, AttachToObjectId);
|
||||
if (attachToObject.IsEmpty)
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "AttachToObjectType and / or AttachToObjectId are not valid and are required";
|
||||
}
|
||||
}
|
||||
|
||||
//Is it an attachable type of object?
|
||||
if (!badRequest)
|
||||
{
|
||||
if (!attachToObject.IsAttachable)
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = attachToObject.ObjectType.ToString() + " - AttachToObjectType does not support attachments";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//does attach to object exist?
|
||||
if (!badRequest)
|
||||
{
|
||||
//check if object exists
|
||||
long attachToObjectOwnerId = attachToObject.OwnerId(ct);
|
||||
if (attachToObjectOwnerId == -1)
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "Invalid attach object";
|
||||
}
|
||||
else
|
||||
{
|
||||
// User needs modify rights to the object type in question
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, attachToObject.ObjectType, attachToObjectOwnerId))
|
||||
{
|
||||
//delete temp files
|
||||
DeleteTempFileUploadDueToBadRequest(uploadFormData);
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (badRequest)
|
||||
{
|
||||
//delete temp files
|
||||
DeleteTempFileUploadDueToBadRequest(uploadFormData);
|
||||
//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)
|
||||
{
|
||||
var v = FileUtil.storeFileAttachment(a.InitialUploadedPathName, a.MimeType, a.OriginalFileName, UserIdFromContext.Id(HttpContext.Items), attachToObject, ct);
|
||||
returnList.Add(new NameIdItem()
|
||||
{
|
||||
Name = v.DisplayFileName,
|
||||
Id = v.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "FileUploadAttempt", ex.Message));
|
||||
}
|
||||
|
||||
//Return the list of attachment ids and filenames
|
||||
return Ok(new ApiOkResponse(returnList));
|
||||
}
|
||||
|
||||
/// <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>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteAttachment([FromRoute] long id)
|
||||
{
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var dbObj = await ct.FileAttachment.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (dbObj == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
|
||||
if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, dbObj.AttachToObjectType, dbObj.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//do the delete
|
||||
//this handles removing the file if there are no refs left and also the db record for the attachment
|
||||
FileUtil.deleteFileAttachment(dbObj, ct);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Download a file attachment
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="dlkey"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("download/{id}")]
|
||||
public async Task<IActionResult> Download([FromRoute] long id, [FromQuery] string dlkey)
|
||||
{
|
||||
//copied from Rockfish
|
||||
//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(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dlkey))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
//get user by key, if not found then reject
|
||||
//If user dlkeyexp has not expired then return file
|
||||
var dlkeyUser = await ct.User.SingleOrDefaultAsync(m => m.DlKey == dlkey);
|
||||
if (dlkeyUser == null)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token not valid"));
|
||||
}
|
||||
|
||||
//Make sure the token provided is for the current user
|
||||
long lAuthenticatedUserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
if (lAuthenticatedUserId != dlkeyUser.Id)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token not valid"));
|
||||
}
|
||||
|
||||
|
||||
var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);
|
||||
if (dlkeyUser.DlKeyExpire < utcNow.DateTime)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "dlkey", "Download token has expired"));
|
||||
}
|
||||
|
||||
//Ok, user has a valid download key and it's not expired yet so get the attachment record
|
||||
var dbObj = await ct.FileAttachment.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (dbObj == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
//is this allowed?
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, dbObj.AttachToObjectType))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//they are allowed, let's send the file
|
||||
string mimetype = dbObj.ContentType;
|
||||
var filePath = FileUtil.GetPermanentAttachmentFilePath(dbObj.StoredFileName);
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
{
|
||||
//TODO: this should trigger some kind of notification to the ops people
|
||||
//and a red light on the dashboard
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, $"Physical file {dbObj.StoredFileName} not found despite attachment record, this file is missing"));
|
||||
}
|
||||
|
||||
return PhysicalFile(filePath, mimetype, dbObj.DisplayFileName);
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
|
||||
#region sample html form to work with this
|
||||
/*
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title></title>
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
|
||||
crossorigin="anonymous"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$("#upload").click(function (evt) {
|
||||
var fileUpload = $("#files").get(0);
|
||||
var files = fileUpload.files;
|
||||
var data = new FormData();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
data.append(files[i].name, files[i]);
|
||||
}
|
||||
|
||||
//attachment test
|
||||
data.append('AttachToObjectType','2');//object 2 is widget
|
||||
data.append('AttachToObjectId','200');//there should normally always be a widget with id 1
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "http://localhost:7575/api/v8.0/Attachment",
|
||||
headers: {
|
||||
Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIxNTIxNTY5ODE5IiwiZXhwIjoiMTUyNDE2MTgxOSIsImlzcyI6IkF5YU5vdmEiLCJpZCI6IjEifQ.4PzkzZNYK5mJkbfCHGQ2N2248atAvvcDgApoz65oIC0"
|
||||
},
|
||||
contentType: false,
|
||||
processData: false,
|
||||
data: data,
|
||||
success: function (message) {
|
||||
alert("upload successful!");
|
||||
console.log(message);
|
||||
},
|
||||
error: function (error) {
|
||||
console.log(error);
|
||||
alert("There was an error uploading files!");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="file" id="files" name="files" multiple />
|
||||
<!-- <input type="file" accept=".zip,application/zip" id="files" name="files" multiple /> -->
|
||||
<input type="button" id="upload" value="Upload file(s)" />
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
*/
|
||||
#endregion
|
||||
162
server/AyaNova/Controllers/AuthController.cs
Normal file
162
server/AyaNova/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using App.Metrics;
|
||||
|
||||
//required to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AuthController> log;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ApiServerState serverState;
|
||||
private readonly IMetrics metrics;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
/// <param name="Metrics"></param>
|
||||
public AuthController(AyContext context, ILogger<AuthController> logger, IConfiguration configuration, ApiServerState apiServerState, IMetrics Metrics)//these two are injected, see startup.cs
|
||||
{
|
||||
ct = context;
|
||||
log = logger;
|
||||
_configuration = configuration;
|
||||
serverState = apiServerState;
|
||||
metrics = Metrics;
|
||||
}
|
||||
|
||||
//AUTHENTICATE CREDS
|
||||
//RETURN JWT
|
||||
|
||||
/// <summary>
|
||||
/// Post credentials to receive a JSON web token
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This route is used to authenticate to the AyaNova API.
|
||||
/// Once you have a token you need to include it in all requests that require authentication like this:
|
||||
/// <code>Authorization: Bearer [TOKEN]</code>
|
||||
/// Note the space between Bearer and the token. Also, do not include the square brackets
|
||||
/// </remarks>
|
||||
/// <param name="creds"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostCreds([FromBody] AuthController.CredentialsParam creds) //if was a json body then //public JsonResult PostCreds([FromBody] string login, [FromBody] string password)
|
||||
{
|
||||
//a bit different as ops users can still login if the state is opsonly
|
||||
//so the only real barrier here would be a completely closed api
|
||||
if (!serverState.IsOpenOrOpsOnly)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
int nFailedAuthDelay = 10000;
|
||||
#if (DEBUG)
|
||||
nFailedAuthDelay = 1;
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(creds.Login) || string.IsNullOrWhiteSpace(creds.Password))
|
||||
{
|
||||
metrics.Measure.Meter.Mark(MetricsRegistry.FailedLoginMeter);
|
||||
//Make a failed pw wait
|
||||
await Task.Delay(nFailedAuthDelay);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
|
||||
//Multiple users are allowed the same password and login
|
||||
//Salt will differentiate them so get all users that match login, then try to match pw
|
||||
var users = await ct.User.AsNoTracking().Where(m => m.Login == creds.Login).ToListAsync();
|
||||
|
||||
foreach (User u in users)
|
||||
{
|
||||
string hashed = Hasher.hash(u.Salt, creds.Password);
|
||||
if (hashed == u.Password)
|
||||
{
|
||||
//Restrict auth due to server state?
|
||||
//If we're here the server state is not closed, but it might be ops only
|
||||
|
||||
//If the server is ops only then this user needs to be ops or else they are not allowed in
|
||||
if (serverState.IsOpsOnly &&
|
||||
!u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdminFull) &&
|
||||
!u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdminLimited))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//build the key (JWT set in startup.cs)
|
||||
byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.AYANOVA_JWT_SECRET);
|
||||
|
||||
//create a new datetime offset of now in utc time
|
||||
var iat = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);//timespan zero means zero time off utc / specifying this is a UTC datetime
|
||||
var exp = new DateTimeOffset(DateTime.Now.AddDays(30).ToUniversalTime(), TimeSpan.Zero);
|
||||
|
||||
var payload = new Dictionary<string, object>()
|
||||
{
|
||||
{ "iat", iat.ToUnixTimeSeconds().ToString() },
|
||||
{ "exp", exp.ToUnixTimeSeconds().ToString() },//in payload exp must be in unix epoch time per standard
|
||||
{ "iss", "AyaNova" },
|
||||
{ "id", u.Id.ToString() }
|
||||
};
|
||||
|
||||
|
||||
//NOTE: probably don't need Jose.JWT as am using Microsoft jwt stuff to validate routes so it should also be able to
|
||||
//issue tokens as well, but it looked cmplex and this works so unless need to remove in future keeping it.
|
||||
string token = Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256);
|
||||
|
||||
|
||||
log.LogInformation($"User number \"{u.Id}\" logged in from \"{Util.StringUtil.MaskIPAddress(HttpContext.Connection.RemoteIpAddress.ToString())}\" ok");
|
||||
metrics.Measure.Meter.Mark(MetricsRegistry.SuccessfulLoginMeter);
|
||||
return Ok(new ApiOkResponse(new
|
||||
{
|
||||
ok = 1,
|
||||
issued = iat,
|
||||
expires = exp,
|
||||
token = token,
|
||||
id = u.Id
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
//No users matched, it's a failed login
|
||||
//Make a failed pw wait
|
||||
metrics.Measure.Meter.Mark(MetricsRegistry.FailedLoginMeter);
|
||||
await Task.Delay(nFailedAuthDelay);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
public class CredentialsParam
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Login { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Password { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
83
server/AyaNova/Controllers/AyaTypeController.cs
Normal file
83
server/AyaNova/Controllers/AyaTypeController.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// AyaType list controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class AyaTypeController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AyaTypeController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public AyaTypeController(AyContext dbcontext, ILogger<AyaTypeController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get name value list of AyaNova business object types
|
||||
///
|
||||
/// Required roles: Any
|
||||
/// </summary>
|
||||
/// <returns>List</returns>
|
||||
[HttpGet]
|
||||
public ActionResult GetAyaTypes()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
List<NameIdItem> l = new List<NameIdItem>();
|
||||
var values = Enum.GetValues(typeof(AyaType));
|
||||
foreach (AyaType t in values)
|
||||
{
|
||||
|
||||
string name=t.ToString();
|
||||
if(t.HasAttribute(typeof(AttachableAttribute))){
|
||||
name+=" [Attachable]";
|
||||
}
|
||||
|
||||
if(t.HasAttribute(typeof(TaggableAttribute))){
|
||||
name+=" [Taggable]";
|
||||
}
|
||||
|
||||
l.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
}
|
||||
|
||||
|
||||
return Ok(new ApiOkResponse(l));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
203
server/AyaNova/Controllers/BackupController.cs
Normal file
203
server/AyaNova/Controllers/BackupController.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
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 AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Util;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//************************************************************************************************************** */
|
||||
//JUNE 19th 2018 LARGE FILE UPLOAD POSSIBLY NEW INFO HERE:
|
||||
//http://www.talkingdotnet.com/how-to-increase-file-upload-size-asp-net-core/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace AyaNova.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>
|
||||
/// Backup and restore controller for uploading or downloading backup files
|
||||
/// and triggering a restore from backup
|
||||
///
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class BackupController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<BackupController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public BackupController(AyContext dbcontext, ILogger<BackupController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO:
|
||||
A backup archive consists of a similar format to the v7 data dumper utility, json file per object in subdirectories corresponding to object type all in a zip archive
|
||||
|
||||
Route to trigger restore from selected file
|
||||
Route to force immediate backup
|
||||
Backup code in biz objects "IBackup" interface (which in turn is probably going to implement an IExport interface as it serves dual purpose
|
||||
of exporting data, or maybe that's special purpose custom objects for exporting like csv etc since there is likely a graph of data involved)
|
||||
- object is exported / backed up to json
|
||||
Restore code in biz objects "IRestore" interface
|
||||
- object(s) imported via restore and data given to them or file or whatever (See discource project)
|
||||
|
||||
*/
|
||||
|
||||
//TODO: Copy the code from ImportAyaNova7Controller upload method instead of this old crap
|
||||
|
||||
// /// <summary>
|
||||
// /// Upload AyaNova backup files
|
||||
// /// **Files of the same name will overwrite without warning**
|
||||
// /// Maximum 10gb
|
||||
// /// </summary>
|
||||
// /// <returns></returns>
|
||||
// [HttpPost("Upload")]
|
||||
// [DisableFormValueModelBinding]
|
||||
// [RequestSizeLimit(10737418241)]//10737418240 = 10gb https://github.com/aspnet/Announcements/issues/267
|
||||
// public async Task<IActionResult> Upload()
|
||||
// {
|
||||
// //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
|
||||
// try
|
||||
// {
|
||||
// if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
|
||||
// {
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "FileUploadAttempt", $"Expected a multipart request, but got {Request.ContentType}"));
|
||||
// }
|
||||
|
||||
// // Used to accumulate all the form url encoded key value pairs in the
|
||||
// // request.
|
||||
// var formAccumulator = new KeyValueAccumulator();
|
||||
// //string targetFilePath = null;
|
||||
|
||||
// var boundary = MultipartRequestHelper.GetBoundary(
|
||||
// MediaTypeHeaderValue.Parse(Request.ContentType),
|
||||
// _defaultFormOptions.MultipartBoundaryLengthLimit);
|
||||
// var reader = new MultipartReader(boundary, HttpContext.Request.Body);
|
||||
|
||||
// var section = await reader.ReadNextSectionAsync();
|
||||
|
||||
// while (section != null)
|
||||
// {
|
||||
// ContentDispositionHeaderValue contentDisposition;
|
||||
// var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
|
||||
|
||||
// if (hasContentDispositionHeader)
|
||||
// {
|
||||
// if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
|
||||
// {
|
||||
// //Save file
|
||||
// //as it's just a backup file there is no db involvement at all
|
||||
// FileUtil.storeBackupFile(section.Body, contentDisposition.FileName.Value.Replace("\"",""));
|
||||
|
||||
|
||||
// }
|
||||
// else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
|
||||
// {
|
||||
// // Content-Disposition: form-data; name="key"
|
||||
// //
|
||||
// // value
|
||||
|
||||
// // Do not limit the key name length here because the
|
||||
// // multipart headers length limit is already in effect.
|
||||
// var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
|
||||
// var encoding = GetEncoding(section);
|
||||
// using (var streamReader = new StreamReader(
|
||||
// section.Body,
|
||||
// encoding,
|
||||
// detectEncodingFromByteOrderMarks: true,
|
||||
// bufferSize: 1024,
|
||||
// leaveOpen: true))
|
||||
// {
|
||||
// // The value length limit is enforced by MultipartBodyLengthLimit
|
||||
// var value = await streamReader.ReadToEndAsync();
|
||||
// if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// value = String.Empty;
|
||||
// }
|
||||
// formAccumulator.Append(key.Value, value);
|
||||
|
||||
// if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
|
||||
// {
|
||||
// throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Drains any remaining section body that has not been consumed and
|
||||
// // reads the headers for the next section.
|
||||
// section = await reader.ReadNextSectionAsync();
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// }
|
||||
// catch (InvalidDataException ex)
|
||||
// {
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "FileUploadAttempt", ex.Message));
|
||||
// }
|
||||
|
||||
// return Ok();
|
||||
// }
|
||||
|
||||
|
||||
// private static Encoding GetEncoding(MultipartSection section)
|
||||
// {
|
||||
// MediaTypeHeaderValue mediaType;
|
||||
// var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
|
||||
// // UTF-7 is insecure and should not be honored. UTF-8 will succeed in
|
||||
// // most cases.
|
||||
// if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
|
||||
// {
|
||||
// return Encoding.UTF8;
|
||||
// }
|
||||
// return mediaType.Encoding;
|
||||
// }
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
283
server/AyaNova/Controllers/ImportAyaNova7Controller.cs
Normal file
283
server/AyaNova/Controllers/ImportAyaNova7Controller.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
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
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Import AyaNova 7 data controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ImportAyaNova7Controller : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ImportAyaNova7Controller> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ImportAyaNova7Controller(AyContext dbcontext, ILogger<ImportAyaNova7Controller> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Upload AyaNova 7 import file
|
||||
///
|
||||
/// Required roles: OpsAdminFull
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>NameValue list of filenames and id's</returns>
|
||||
[HttpPost]
|
||||
[DisableFormValueModelBinding]
|
||||
[RequestSizeLimit(10737418241)]//10737418240 = 10gb https://github.com/aspnet/Announcements/issues/267
|
||||
public async Task<IActionResult> 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.IsAuthorizedToCreate(HttpContext.Items, AyaType.AyaNova7Import))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
var returnList = new List<String>();
|
||||
|
||||
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(new ApiOkResponse(returnList));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete import file
|
||||
///
|
||||
/// Required roles: OpsAdminFull
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[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.IsAuthorizedToDelete(HttpContext.Items, AyaType.AyaNova7Import))
|
||||
{
|
||||
return StatusCode(401, 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();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get AyaNova 7 data dump uploaded files list
|
||||
///
|
||||
/// Required roles: OpsAdminFull
|
||||
///
|
||||
/// This list cannot be filtered or queried
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>List of uploaded data dump files</returns>
|
||||
[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.IsAuthorizedToRead(HttpContext.Items, AyaType.AyaNova7Import))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//dump file name example: ayanova.data.dump.XXX.zip
|
||||
List<string> l = FileUtil.UtilityFileList("ayanova.data.dump.*.zip");
|
||||
return Ok(new ApiOkResponse(l));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start import of previously uploaded import file
|
||||
///
|
||||
/// Required roles: OpsAdminFull
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpPost("startImport/{filename}")]
|
||||
public ActionResult StartImport([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
|
||||
// //TODO: 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.IsAuthorizedToCreate(HttpContext.Items, AyaType.AyaNova7Import))
|
||||
{
|
||||
return StatusCode(401, 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.OwnerId = UserIdFromContext.Id(HttpContext.Items);
|
||||
j.JobInfo = jobInfo.ToString();
|
||||
JobsBiz.AddJob(j, ct);
|
||||
return Accepted(new { JobId = j.GId });//202 accepted
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
134
server/AyaNova/Controllers/JobOperationsController.cs
Normal file
134
server/AyaNova/Controllers/JobOperationsController.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
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.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Tag controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class JobOperationsController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<JobOperationsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public JobOperationsController(AyContext dbcontext, ILogger<JobOperationsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Operations jobs list
|
||||
///
|
||||
/// Required roles: OpsAdminFull, OpsAdminLimited, BizAdminFull, BizAdminLimited
|
||||
///
|
||||
/// This list cannot be filtered or queried as there are typically not many jobs
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>List of operations jobs</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List()
|
||||
{
|
||||
//Open or opsOnly and user is opsadminfull or opsadminlimited
|
||||
if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.JobOperations))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
JobOperationsBiz biz = new JobOperationsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
List<JobOperationsFetchInfo> l = await biz.GetJobListAsync();
|
||||
return Ok(new ApiOkResponse(l));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Operations log for a job
|
||||
///
|
||||
/// Required roles: OpsAdminFull, OpsAdminLimited, BizAdminFull, BizAdminLimited
|
||||
///
|
||||
/// This list cannot be filtered or queried as there are typically not many jobs
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="gid"></param>
|
||||
/// <returns>A tag</returns>
|
||||
[HttpGet("logs/{gid}")]
|
||||
public async Task<IActionResult> GetLogs([FromRoute] Guid gid)
|
||||
{
|
||||
//Open or opsOnly and user is opsadminfull or opsadminlimited
|
||||
if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.JobOperations))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
JobOperationsBiz biz = new JobOperationsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
List<JobOperationsLogInfoItem> l = await biz.GetJobLogListAsync(gid);
|
||||
return Ok(new ApiOkResponse(l));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
197
server/AyaNova/Controllers/LicenseController.cs
Normal file
197
server/AyaNova/Controllers/LicenseController.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
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 System.ComponentModel.DataAnnotations;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// License route
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class LicenseController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<LicenseController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public LicenseController(AyContext dbcontext, ILogger<LicenseController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get License info
|
||||
///
|
||||
/// Required roles:
|
||||
/// AuthorizationRoles.BizAdminFull | AuthorizationRoles.OpsAdminFull |
|
||||
/// AuthorizationRoles.BizAdminLimited | AuthorizationRoles.OpsAdminLimited
|
||||
/// </summary>
|
||||
/// <returns>Information about the currently installed license in AyaNova</returns>
|
||||
[HttpGet()]
|
||||
public ActionResult GetLicenseInfo()
|
||||
{
|
||||
//Open or opsOnly and user is opsadminfull or opsadminlimited
|
||||
if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.License))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
var ret = AyaNova.Core.License.LicenseInfoAsJson;
|
||||
|
||||
return Ok(new ApiOkResponse(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fetch license
|
||||
///
|
||||
/// Posting to this route causes AyaNova to attempt to refresh it's license
|
||||
/// from the AyaNova license server
|
||||
///
|
||||
/// Required roles:
|
||||
/// AuthorizationRoles.BizAdminFull | AuthorizationRoles.OpsAdminFull
|
||||
/// </summary>
|
||||
/// <returns>On success returns information about the currently installed license in AyaNova</returns>
|
||||
[HttpPost]
|
||||
public ActionResult FetchLicense()
|
||||
{
|
||||
//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.IsAuthorizedToCreate(HttpContext.Items, AyaType.License))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AyaNova.Core.License.Fetch(serverState, ct, log);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception rootex = ex;
|
||||
while (rootex.InnerException != null)
|
||||
{
|
||||
rootex = rootex.InnerException;
|
||||
}
|
||||
|
||||
|
||||
if (rootex.Message.Contains("E1020"))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "LICENSE_KEY", rootex.Message));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
var ret = AyaNova.Core.License.LicenseInfoAsJson;
|
||||
|
||||
return Ok(new ApiOkResponse(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Request trial license
|
||||
///
|
||||
/// Posting to this route causes AyaNova to request a trial license key from the AyaNova license server
|
||||
/// Database must be empty and unlicensed or trial license
|
||||
///
|
||||
/// Required roles:
|
||||
/// [OpsFull, BizAdminFull]
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="requestData"></param>
|
||||
/// <returns>HTTP 204 No Content result code on success or fail code with explanation</returns>
|
||||
[HttpPost("trial")]
|
||||
public ActionResult RequestTrial([FromBody] dtoTrialRequestData requestData)
|
||||
{
|
||||
//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.IsAuthorizedToCreate(HttpContext.Items, AyaType.License))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
if (!AyaNova.Util.DbUtil.DBIsEmpty(ct, log))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Only an empty AyaNova database can request a trial key. Erase the database to proceed with a new trial."));
|
||||
}
|
||||
|
||||
if (!AyaNova.Core.License.ActiveKey.IsEmpty && !AyaNova.Core.License.ActiveKey.TrialLicense)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "There is an active registered license. Only an unlicensed or trial license database can request a trial key."));
|
||||
}
|
||||
|
||||
//Send the request to RockFish here (or at least start the job to do it in which case return Accepted instead of no content and update comment above)
|
||||
var ret = Core.License.RequestTrial(requestData.EmailAddress, requestData.RegisteredTo, log);
|
||||
|
||||
return Ok(new ApiOkResponse(ret));
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
public class dtoTrialRequestData
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string RegisteredTo { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required, System.ComponentModel.DataAnnotations.EmailAddress]
|
||||
public string EmailAddress { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
384
server/AyaNova/Controllers/LocaleController.cs
Normal file
384
server/AyaNova/Controllers/LocaleController.cs
Normal file
@@ -0,0 +1,384 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.JsonPatch;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
//DOCUMENTATING THE API
|
||||
//https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/recommended-tags-for-documentation-comments
|
||||
//https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments
|
||||
|
||||
/// <summary>
|
||||
/// Localized text controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class LocaleController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<LocaleController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public LocaleController(AyContext dbcontext, ILogger<LocaleController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Locale all values
|
||||
///
|
||||
/// Required roles: Any
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>A single Locale and it's values</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetLocale([FromRoute] long id)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
LocaleBiz biz = new LocaleBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
var o = await biz.GetAsync(id);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Locale pick list
|
||||
/// Required roles: Any
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Picklist in alphabetical order of all locales</returns>
|
||||
[HttpGet("PickList")]
|
||||
public async Task<IActionResult> LocalePickList()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
LocaleBiz biz = new LocaleBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
var l = await biz.GetPickListAsync();
|
||||
return Ok(new ApiOkResponse(l));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get subset of locale values
|
||||
/// Required roles: Any
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="inObj">LocaleSubsetParam object defining the locale Id and a list of keys required</param>
|
||||
/// <returns>A key value array of localized text values</returns>
|
||||
[HttpPost("SubSet")]
|
||||
public async Task<IActionResult> SubSet([FromBody] LocaleSubsetParam inObj)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
LocaleBiz biz = new LocaleBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
var l = await biz.GetSubset(inObj);
|
||||
return Ok(new ApiOkResponse(l));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Duplicates an existing locale with a new name
|
||||
///
|
||||
/// Required roles: OpsAdminFull | BizAdminFull
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="inObj">NameIdItem object containing source locale Id and new name</param>
|
||||
/// <returns>Error response or newly created locale</returns>
|
||||
[HttpPost("Duplicate")]
|
||||
public async Task<IActionResult> Duplicate([FromBody] NameIdItem inObj)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
LocaleBiz biz = new LocaleBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
var o = await biz.DuplicateAsync(inObj);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
//error return
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
else
|
||||
{
|
||||
//save and success return
|
||||
await ct.SaveChangesAsync();
|
||||
return CreatedAtAction("GetLocale", new { id = o.Id }, new ApiCreatedResponse(o));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Put (UpdateLocaleItemDisplayText)
|
||||
///
|
||||
/// Required roles: OpsAdminFull | BizAdminFull
|
||||
///
|
||||
/// Update a single key with new display text
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="inObj">NewText/Id/Concurrency token object. NewText is new display text, Id is LocaleItem Id, concurrency token is required</param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("UpdateLocaleItemDisplayText")]
|
||||
public async Task<IActionResult> PutLocalItemDisplyaText([FromBody] NewTextIdConcurrencyTokenItem inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var oFromDb = await ct.LocaleItem.SingleOrDefaultAsync(m => m.Id == inObj.Id);
|
||||
|
||||
if (oFromDb == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
//Now fetch locale for rights and to ensure not stock
|
||||
var oDbParent = await ct.Locale.SingleOrDefaultAsync(x => x.Id == oFromDb.LocaleId);
|
||||
if (oDbParent == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.Locale, oDbParent.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
LocaleBiz biz = new LocaleBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
if (!biz.PutLocaleItemDisplayText(oFromDb, inObj, oDbParent))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!biz.LocaleItemExists(inObj.Id))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
//exists but was changed by another user
|
||||
//I considered returning new and old record, but where would it end?
|
||||
//Better to let the client decide what to do than to send extra data that is not required
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(new { ConcurrencyToken = oFromDb.ConcurrencyToken }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put (UpdateLocaleName)
|
||||
///
|
||||
/// Required roles: OpsAdminFull | BizAdminFull
|
||||
///
|
||||
/// Update a locale to change the name (non-stock locales only)
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="inObj">NewText/Id/Concurrency token object. NewText is new locale name, Id is Locale Id, concurrency token is required</param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("UpdateLocaleName")]
|
||||
public async Task<IActionResult> PutLocaleName([FromBody] NewTextIdConcurrencyTokenItem inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var oFromDb = await ct.Locale.SingleOrDefaultAsync(m => m.Id == inObj.Id);
|
||||
|
||||
if (oFromDb == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.Locale, oFromDb.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
LocaleBiz biz = new LocaleBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
if (!biz.PutLocaleName(oFromDb, inObj))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!biz.LocaleExists(inObj.Id))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
//exists but was changed by another user
|
||||
//I considered returning new and old record, but where would it end?
|
||||
//Better to let the client decide what to do than to send extra data that is not required
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(new { ConcurrencyToken = oFromDb.ConcurrencyToken }));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete Locale
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteLocale([FromRoute] long id)
|
||||
{
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
|
||||
//Fetch locale and it's children
|
||||
//(fetch here so can return proper REST responses on failing basic validity)
|
||||
var dbObj = ct.Locale.Include(x => x.LocaleItems).SingleOrDefault(m => m.Id == id);
|
||||
if (dbObj == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, AyaType.Locale, dbObj.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
//Instantiate the business object handler
|
||||
LocaleBiz biz = new LocaleBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
if (!biz.Delete(dbObj))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//Delete children / attached objects
|
||||
// biz.DeleteChildren(dbObj);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// private bool LocaleExists(long id)
|
||||
// {
|
||||
// return ct.Locale.Any(e => e.Id == id);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
public class LocaleSubsetParam
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public long LocaleId { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public List<string> Keys { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
177
server/AyaNova/Controllers/LogFilesController.cs
Normal file
177
server/AyaNova/Controllers/LogFilesController.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System.Linq;
|
||||
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.Util;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Log files controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
//[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class LogFilesController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<LogFilesController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public LogFilesController(AyContext dbcontext, ILogger<LogFilesController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get server log
|
||||
///
|
||||
/// Required roles:
|
||||
/// OpsAdminFull | OpsAdminLimited
|
||||
/// </summary>
|
||||
/// <param name="logname"></param>
|
||||
/// <returns>A single log file in plain text</returns>
|
||||
[HttpGet("{logname}")]
|
||||
public ActionResult GetLog([FromRoute] string logname)
|
||||
{
|
||||
//Open or opsOnly and user is opsadminfull or opsadminlimited
|
||||
if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.LogFile))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//stream the file contents into a json object and return
|
||||
|
||||
//build the full path from the log file name and defined path
|
||||
var logFilePath = System.IO.Path.Combine(ServerBootConfig.AYANOVA_LOG_PATH, logname);
|
||||
//does file exist?
|
||||
if (!System.IO.File.Exists(logFilePath))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
// //read it and stream it back in a json object
|
||||
|
||||
|
||||
// Newtonsoft.Json.Linq.JObject o = Newtonsoft.Json.Linq.JObject.FromObject(new
|
||||
// {
|
||||
// log = new
|
||||
// {
|
||||
// name = logname,
|
||||
// log = System.IO.File.ReadAllText(logFilePath)
|
||||
// }
|
||||
// });
|
||||
|
||||
// return Ok(new ApiOkResponse(o));
|
||||
|
||||
return Content(System.IO.File.ReadAllText(logFilePath));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get list of operations logs
|
||||
///
|
||||
/// Required roles:
|
||||
/// OpsAdminFull | OpsAdminLimited
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet()]
|
||||
public ActionResult ListLogs()
|
||||
{
|
||||
//Open or opsOnly and user is opsadminfull or opsadminlimited
|
||||
if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.LogFile))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
|
||||
//Iterate all log files and build return
|
||||
var files = System.IO.Directory.GetFiles(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova*.txt");
|
||||
|
||||
Newtonsoft.Json.Linq.JObject o = Newtonsoft.Json.Linq.JObject.FromObject(new
|
||||
{
|
||||
logs =
|
||||
from f in files
|
||||
orderby f
|
||||
select new
|
||||
{
|
||||
logName = System.IO.Path.GetFileName(f)
|
||||
}
|
||||
});
|
||||
|
||||
// Newtonsoft.Json.Linq.JObject o = Newtonsoft.Json.Linq.JObject.FromObject(new
|
||||
// {
|
||||
// logs = new
|
||||
// {
|
||||
// licensedTo = ActiveKey.RegisteredTo,
|
||||
// registeredEmail = ActiveKey.FetchEmail,
|
||||
// trial = ActiveKey.Trial,
|
||||
// keySerial = ActiveKey.Id,
|
||||
// keySource = ActiveKey.Source,
|
||||
// created = ActiveKey.Created.ToString(),
|
||||
// features =
|
||||
// from f in files
|
||||
// orderby f
|
||||
// select new
|
||||
// {
|
||||
// logName = f
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
|
||||
return Ok(new ApiOkResponse(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
137
server/AyaNova/Controllers/MetricsController.cs
Normal file
137
server/AyaNova/Controllers/MetricsController.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System.IO;
|
||||
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 Newtonsoft.Json.Linq;
|
||||
using App.Metrics;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Log files controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Authorize]
|
||||
public class MetricsController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<LogFilesController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
private readonly IMetrics metrics;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
/// <param name="Metrics"></param>
|
||||
public MetricsController(AyContext dbcontext, ILogger<LogFilesController> logger, ApiServerState apiServerState, IMetrics Metrics)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
metrics = Metrics;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get metrics as text document
|
||||
///
|
||||
/// Required roles:
|
||||
/// OpsAdminFull | OpsAdminLimited
|
||||
/// </summary>
|
||||
/// <returns>Snapshot of metrics</returns>
|
||||
[HttpGet("TextSnapShot")]
|
||||
public async Task<IActionResult> GetMetrics()
|
||||
{
|
||||
//Open or opsOnly and user is opsadminfull or opsadminlimited
|
||||
if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Metrics))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
string sResult = await GetTheMetrics("plain");
|
||||
return Content(sResult);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get metrics as json object
|
||||
///
|
||||
/// Required roles:
|
||||
/// OpsAdminFull | OpsAdminLimited
|
||||
/// </summary>
|
||||
/// <returns>Snapshot of metrics</returns>
|
||||
[HttpGet("JsonSnapShot")]
|
||||
public async Task<IActionResult> GetJsonMetrics()
|
||||
{
|
||||
//Open or opsOnly and user is opsadminfull or opsadminlimited
|
||||
if (!serverState.IsOpenOrOpsOnly || (serverState.IsOpsOnly && !Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Metrics))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
string sResult = await GetTheMetrics("json");
|
||||
|
||||
//return Ok(new ApiOkResponse(new { metrics = sResult }));
|
||||
// /THIS IS NOT RETURNING VALID PARSEABLE JSON, FIX IT
|
||||
//IDEAS:
|
||||
//try parsing the result first then return it
|
||||
//
|
||||
JObject json = JObject.Parse(sResult);
|
||||
return Ok(new ApiOkResponse(json));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the metrics snapshot
|
||||
/// </summary>
|
||||
/// <param name="format">Either "json" for json format or "plain" for plaintext format</param>
|
||||
/// <returns></returns>
|
||||
private async Task<string> GetTheMetrics(string format)
|
||||
{
|
||||
var snapshot = metrics.Snapshot.Get();
|
||||
|
||||
var formatters = ((IMetricsRoot)metrics).OutputMetricsFormatters;
|
||||
string sResult = $"ERROR GETTING METRICS IN {format} FORMAT";
|
||||
|
||||
foreach (var formatter in formatters)
|
||||
{
|
||||
if (formatter.MediaType.Format == format)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
await formatter.WriteAsync(stream, snapshot);
|
||||
sResult = System.Text.Encoding.UTF8.GetString(stream.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sResult;
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
120
server/AyaNova/Controllers/ServerStateController.cs
Normal file
120
server/AyaNova/Controllers/ServerStateController.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Server state controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class ServerStateController : Controller
|
||||
{
|
||||
private readonly ILogger<ServerStateController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ServerStateController(ILogger<ServerStateController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get server state
|
||||
///
|
||||
/// Required roles:
|
||||
/// [NONE / authentication not required]
|
||||
/// </summary>
|
||||
/// <returns>Current server state (Closed, OpsOnly, Open)</returns>
|
||||
[HttpGet]
|
||||
public ActionResult Get()
|
||||
{
|
||||
return Ok(new ApiOkResponse(new ServerStateModel() { ServerState = serverState.GetState().ToString(), Reason = serverState.Reason }));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set server state
|
||||
///
|
||||
/// Required roles:
|
||||
/// [OpsFull, BizAdminFull]
|
||||
///
|
||||
/// Valid parameters:
|
||||
/// One of "Closed", "OpsOnly" or "Open"
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="state">{"NewState":"Closed"}</param>
|
||||
/// <returns>NoContent 204</returns>
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public ActionResult PostServerState([FromBody] ServerStateModel state)
|
||||
{
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.ServerState))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (serverState.IsSystemLocked)//no state change allowed when system locked, must correct the problem first
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
ApiServerState.ServerState desiredState;
|
||||
|
||||
if (!Enum.TryParse<ApiServerState.ServerState>(state.ServerState, true, out desiredState))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "Invalid state - must be one of \"Closing\", \"Closed\", \"OpsOnly\" or \"Open\""));
|
||||
}
|
||||
|
||||
log.LogInformation($"ServerState change request by user {UserNameFromContext.Name(HttpContext.Items)} from current state of \"{serverState.GetState().ToString()}\" to \"{desiredState.ToString()}\"");
|
||||
|
||||
serverState.SetState(desiredState, state.Reason);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parameter object
|
||||
/// </summary>
|
||||
public class ServerStateModel
|
||||
{
|
||||
/// <summary>
|
||||
/// One of "Closed", "OpsOnly" or "Open"
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Required]
|
||||
public string ServerState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reason for server state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string Reason { get; set; }
|
||||
}
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
366
server/AyaNova/Controllers/TagController.cs
Normal file
366
server/AyaNova/Controllers/TagController.cs
Normal file
@@ -0,0 +1,366 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.JsonPatch;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Tag controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class TagController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<TagController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public TagController(AyContext dbcontext, ILogger<TagController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Tag
|
||||
///
|
||||
/// Required roles:
|
||||
/// AnyOne
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>A tag</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetTag([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Tag))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagBiz biz = new TagBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
var o = await biz.GetAsync(id);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Tag pick list
|
||||
///
|
||||
/// Required roles: AnyRole
|
||||
///
|
||||
/// This endpoint queries the Name property of tags for
|
||||
/// items that **START WITH** the characters submitted in the
|
||||
/// "q" parameter
|
||||
///
|
||||
/// Unlike most other picklists, wildcard characters if found in the query will be escaped and be considered part of the search string
|
||||
/// Query is case insensitive as all tags are lowercase
|
||||
///
|
||||
/// Empty queries will return all tags
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Paged id/name collection of tags with paging data</returns>
|
||||
[HttpGet("PickList", Name = nameof(PickList))]
|
||||
public async Task<IActionResult> PickList([FromQuery] string q, [FromQuery] PagingOptions pagingOptions)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Tag))//Note: anyone can read a tag, but that might change in future so keeping this code in
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagBiz biz = new TagBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
ApiPagedResponse<NameIdItem> pr = await biz.GetPickListAsync(Url, nameof(PickList), pagingOptions, q);
|
||||
return Ok(new ApiOkWithPagingResponse<NameIdItem>(pr));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Post TAG
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, DispatchFull, InventoryFull, TechFull, AccountingFull
|
||||
/// </summary>
|
||||
/// <param name="inObj">String name of tag</param>
|
||||
/// <returns><see cref="Tag"/> object</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostTag([FromBody] NameItem inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
//If a user has change roles, or editOwnRoles then they can create, true is passed for isOwner since they are creating so by definition the owner
|
||||
if (!Authorized.IsAuthorizedToCreate(HttpContext.Items, AyaType.Tag))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagBiz biz = new TagBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
//Create and validate
|
||||
Tag o = await biz.CreateAsync(inObj.Name);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
//error return
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
else
|
||||
{
|
||||
//save and success return
|
||||
await ct.SaveChangesAsync();
|
||||
return CreatedAtAction("GetTag", new { id = o.Id }, new ApiCreatedResponse(o));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Put (update) Tag
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, DispatchFull, InventoryFull, TechFull, AccountingFull
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="oIn"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutTag([FromRoute] long id, [FromBody] Tag oIn)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var oFromDb = await ct.Tag.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (oFromDb == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.Tag, oFromDb.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagBiz biz = new TagBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
if (!biz.Put(oFromDb, oIn))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!TagExists(id))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
//exists but was changed by another user
|
||||
//I considered returning new and old record, but where would it end?
|
||||
//Better to let the client decide what to do than to send extra data that is not required
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(new { ConcurrencyToken = oFromDb.ConcurrencyToken }));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Patch (update) Tag
|
||||
/// Required roles: BizAdminFull, DispatchFull, InventoryFull, TechFull, AccountingFull
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="concurrencyToken"></param>
|
||||
/// <param name="objectPatch"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPatch("{id}/{concurrencyToken}")]
|
||||
public async Task<IActionResult> PatchTag([FromRoute] long id, [FromRoute] uint concurrencyToken, [FromBody]JsonPatchDocument<Tag> objectPatch)
|
||||
{
|
||||
//https://dotnetcoretutorials.com/2017/11/29/json-patch-asp-net-core/
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagBiz biz = new TagBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
|
||||
var oFromDb = await ct.Tag.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (oFromDb == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.Tag, oFromDb.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//patch and validate
|
||||
if (!biz.Patch(oFromDb, objectPatch, concurrencyToken))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!TagExists(id))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(new { ConcurrencyToken = oFromDb.ConcurrencyToken }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete Tag
|
||||
/// Required roles: BizAdminFull, DispatchFull, InventoryFull, TechFull, AccountingFull
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteTag([FromRoute] long id)
|
||||
{
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var dbObj = await ct.Tag.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (dbObj == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, AyaType.Tag, dbObj.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagBiz biz = new TagBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
if (!biz.Delete(dbObj))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool TagExists(long id)
|
||||
{
|
||||
return ct.Tag.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
258
server/AyaNova/Controllers/TagMapController.cs
Normal file
258
server/AyaNova/Controllers/TagMapController.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// TagMap controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class TagMapController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<TagMapController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public TagMapController(AyContext dbcontext, ILogger<TagMapController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get TagMap object
|
||||
///
|
||||
/// Required roles: Same roles as tagged object
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>A TagMap</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetTagMap([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.TagMap))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagMapBiz biz = new TagMapBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
var o = await biz.GetAsync(id);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
//Check rights to parent tagged object
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, o.TagToObjectType))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
return Ok(new ApiOkResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Post TagMap - Map a tag to an object / Id
|
||||
///
|
||||
/// Required roles: Same roles as tagged object
|
||||
/// </summary>
|
||||
/// <param name="inObj">TagMapInfo</param>
|
||||
/// <returns><see cref="TagMap"/> object</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostTagMap([FromBody] TagMapInfo inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
//If a user has change roles, or editOwnRoles then they can create, true is passed for isOwner since they are creating so by definition the owner
|
||||
if (!Authorized.IsAuthorizedToCreate(HttpContext.Items, AyaType.TagMap))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Rights to parent taggable object?
|
||||
if (!Authorized.IsAuthorizedToCreate(HttpContext.Items, inObj.TagToObjectType))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagMapBiz biz = new TagMapBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
//Create and validate
|
||||
TagMap o = await biz.CreateAsync(inObj);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
//error return
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
else
|
||||
{
|
||||
//save and success return
|
||||
await ct.SaveChangesAsync();
|
||||
return CreatedAtAction("GetTagMap", new { id = o.Id }, new ApiCreatedResponse(o));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete TagMap
|
||||
/// Required roles: Same roles as tagged object
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteTagMap([FromRoute] long id)
|
||||
{
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var dbObj = await ct.TagMap.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (dbObj == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, AyaType.TagMap, dbObj.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Rights to parent tagged object?
|
||||
if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, dbObj.TagToObjectType, dbObj.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagMapBiz biz = new TagMapBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
if (!biz.Delete(dbObj))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Tag pick list
|
||||
///
|
||||
/// Required roles: Follows parent (tagged object) roles
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Name / Id collection of tags on object</returns>
|
||||
[HttpGet("TagsOnObject")]
|
||||
public async Task<IActionResult> TagsOnObjectList([FromBody] TypeAndIdInfo inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Tag))//Note: anyone can read a tag, but that might change in future so keeping this code in
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
|
||||
//Check rights to parent tagged object
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, inObj.ObjectType))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
//Instantiate the business object handler
|
||||
TagMapBiz biz = new TagMapBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
|
||||
var l = await biz.GetTagsOnObjectListAsync(new AyaTypeId(inObj.ObjectType, inObj.ObjectId));
|
||||
return Ok(new ApiOkResponse(l));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private bool TagMapExists(long id)
|
||||
{
|
||||
return ct.TagMap.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}
|
||||
124
server/AyaNova/Controllers/TrialController.cs
Normal file
124
server/AyaNova/Controllers/TrialController.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///Test controller class used during development
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class TrialController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<WidgetController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public TrialController(AyContext dbcontext, ILogger<WidgetController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Seed a trial database with sample data.
|
||||
///
|
||||
/// You can control the size and scope of the seeded data with the passed in size value
|
||||
/// "Small" - a small one man shop dataset
|
||||
/// "Medium" - Local service company with multiple employees and departments dataset
|
||||
/// "Large" - Large corporate multi regional dataset
|
||||
/// </summary>
|
||||
/// <param name="size">Valid values are "Small", "Medium", "Large"</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("seed/{size}")]
|
||||
public ActionResult SeedTrialDatabase([FromRoute] string size)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
if (!AyaNova.Core.License.ActiveKey.TrialLicense)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Current license is not a trial license key. Only a trial can be seeded."));
|
||||
}
|
||||
|
||||
Seeder.SeedLevel seedLevel = Seeder.SeedLevel.SmallOneManShopTrialDataSet;
|
||||
switch (size.ToLowerInvariant())
|
||||
{
|
||||
case "small":
|
||||
seedLevel = Seeder.SeedLevel.SmallOneManShopTrialDataSet;
|
||||
break;
|
||||
case "medium":
|
||||
seedLevel = Seeder.SeedLevel.MediumLocalServiceCompanyTrialDataSet;
|
||||
break;
|
||||
case "large":
|
||||
seedLevel = Seeder.SeedLevel.LargeCorporateMultiRegionalTrialDataSet;
|
||||
break;
|
||||
default:
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "size", "Valid values are \"small\", \"medium\", \"large\""));
|
||||
}
|
||||
|
||||
|
||||
//Create the job here
|
||||
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
seedLevel = seedLevel
|
||||
// channel = new
|
||||
// {
|
||||
// title = "Star Wars",
|
||||
// link = "http://www.starwars.com",
|
||||
// description = "Star Wars blog.",
|
||||
// item =
|
||||
// from p in posts
|
||||
// orderby p.Title
|
||||
// select new
|
||||
// {
|
||||
// title = p.Title,
|
||||
// description = p.Description,
|
||||
// link = p.Link,
|
||||
// category = p.Categories
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = $"Seed test data (size={size})";
|
||||
j.JobType = JobType.SeedTestData;
|
||||
j.Exclusive=true;//don't run other jobs, this will erase the db
|
||||
j.JobInfo = o.ToString();
|
||||
JobsBiz.AddJob(j, ct);
|
||||
return Accepted(new { JobId = j.GId });//202 accepted
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
482
server/AyaNova/Controllers/WidgetController.cs
Normal file
482
server/AyaNova/Controllers/WidgetController.cs
Normal file
@@ -0,0 +1,482 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.JsonPatch;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
//DOCUMENTATING THE API
|
||||
//https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/recommended-tags-for-documentation-comments
|
||||
//https://github.com/domaindrivendev/Swashbuckle.AspNetCore#include-descriptions-from-xml-comments
|
||||
|
||||
/// <summary>
|
||||
/// Sample controller class used during development for testing purposes
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class WidgetController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<WidgetController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public WidgetController(AyContext dbcontext, ILogger<WidgetController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get widget
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull, BizAdminLimited, InventoryLimited, TechFull, TechLimited, Accounting
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>A single widget</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetWidget([FromRoute] long id)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Widget))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
WidgetBiz biz = new WidgetBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
var o = await biz.GetAsync(id);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get paged list of widgets
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull, BizAdminLimited, InventoryLimited, TechFull, TechLimited, Accounting
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Paged collection of widgets with paging data</returns>
|
||||
[HttpGet("List", Name = nameof(List))]//We MUST have a "Name" defined or we can't get the link for the pagination, non paged urls don't need a name
|
||||
public async Task<IActionResult> List([FromQuery] PagingOptions pagingOptions)
|
||||
{
|
||||
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Widget))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
WidgetBiz biz = new WidgetBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
ApiPagedResponse<Widget> pr = await biz.GetManyAsync(Url, nameof(List), pagingOptions);
|
||||
return Ok(new ApiOkWithPagingResponse<Widget>(pr));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get widget pick list
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull, BizAdminLimited, InventoryLimited, TechFull, TechLimited, Accounting
|
||||
///
|
||||
/// This list supports querying the Name property
|
||||
/// include a "q" parameter for string to search for
|
||||
/// use % for wildcards.
|
||||
///
|
||||
/// e.g. q=%Jones%
|
||||
///
|
||||
/// Query is case insensitive
|
||||
/// </summary>
|
||||
/// <returns>Paged id/name collection of widgets with paging data</returns>
|
||||
[HttpGet("PickList", Name = nameof(WidgetPickList))]
|
||||
public async Task<IActionResult> WidgetPickList([FromQuery] string q, [FromQuery] PagingOptions pagingOptions)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Widget))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
WidgetBiz biz = new WidgetBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
ApiPagedResponse<NameIdItem> pr = await biz.GetPickListAsync(Url, nameof(WidgetPickList), pagingOptions, q);
|
||||
return Ok(new ApiOkWithPagingResponse<NameIdItem>(pr));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Put (update) widget
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull
|
||||
/// TechFull (owned only)
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="inObj"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutWidget([FromRoute] long id, [FromBody] Widget inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var oFromDb = await ct.Widget.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (oFromDb == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.Widget, oFromDb.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
WidgetBiz biz = new WidgetBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
if (!biz.Put(oFromDb, inObj))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!WidgetExists(id))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
//exists but was changed by another user
|
||||
//I considered returning new and old record, but where would it end?
|
||||
//Better to let the client decide what to do than to send extra data that is not required
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(new { ConcurrencyToken = oFromDb.ConcurrencyToken }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Patch (update) widget
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull
|
||||
/// TechFull (owned only)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="concurrencyToken"></param>
|
||||
/// <param name="objectPatch"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPatch("{id}/{concurrencyToken}")]
|
||||
public async Task<IActionResult> PatchWidget([FromRoute] long id, [FromRoute] uint concurrencyToken, [FromBody]JsonPatchDocument<Widget> objectPatch)
|
||||
{
|
||||
//https://dotnetcoretutorials.com/2017/11/29/json-patch-asp-net-core/
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
WidgetBiz biz = new WidgetBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
|
||||
var oFromDb = await ct.Widget.SingleOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (oFromDb == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.Widget, oFromDb.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//patch and validate
|
||||
if (!biz.Patch(oFromDb, objectPatch, concurrencyToken))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!WidgetExists(id))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new ApiOkResponse(new { ConcurrencyToken = oFromDb.ConcurrencyToken }));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Post widget
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull, TechFull
|
||||
/// </summary>
|
||||
/// <param name="inObj"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostWidget([FromBody] Widget inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
//If a user has change roles, or editOwnRoles then they can create, true is passed for isOwner since they are creating so by definition the owner
|
||||
if (!Authorized.IsAuthorizedToCreate(HttpContext.Items, AyaType.Widget))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
WidgetBiz biz = new WidgetBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
//Create and validate
|
||||
Widget o = await biz.CreateAsync(inObj);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
//error return
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
else
|
||||
{
|
||||
//save and success return
|
||||
await ct.SaveChangesAsync();
|
||||
return CreatedAtAction("GetWidget", new { id = o.Id }, new ApiCreatedResponse(o));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete widget
|
||||
///
|
||||
/// Required roles:
|
||||
/// BizAdminFull, InventoryFull
|
||||
/// TechFull (owned only)
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteWidget([FromRoute] long id)
|
||||
{
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var dbObj = await ct.Widget.SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (dbObj == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToDelete(HttpContext.Items, AyaType.Widget, dbObj.OwnerId))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
WidgetBiz biz = new WidgetBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
if (!biz.Delete(dbObj))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//Delete children / attached objects
|
||||
biz.DeleteChildren(dbObj);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private bool WidgetExists(long id)
|
||||
{
|
||||
return ct.Widget.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get route that triggers exception for testing
|
||||
/// </summary>
|
||||
/// <returns>Nothing, triggers exception</returns>
|
||||
[HttpGet("exception")]
|
||||
public ActionResult GetException()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Widget))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
throw new System.NotSupportedException("Test exception from widget controller");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get route that triggers an alternate type of exception for testing
|
||||
/// </summary>
|
||||
/// <returns>Nothing, triggers exception</returns>
|
||||
[HttpGet("altexception")]
|
||||
public ActionResult GetAltException()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToRead(HttpContext.Items, AyaType.Widget))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
throw new System.ArgumentException("Test exception (ALT) from widget controller");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get route that submits a long running operation job for testing
|
||||
/// </summary>
|
||||
/// <returns>Nothing</returns>
|
||||
[HttpGet("TestWidgetJob")]
|
||||
public ActionResult TestWidgetJob()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!Authorized.IsAuthorizedToModify(HttpContext.Items, AyaType.JobOperations))
|
||||
{
|
||||
return StatusCode(401, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Create the job here
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = "TestWidgetJob";
|
||||
j.JobType = JobType.TestWidgetJob;
|
||||
JobsBiz.AddJob(j, ct);
|
||||
return Accepted(new { JobId = j.GId });//202 accepted
|
||||
}
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user