This commit is contained in:
520
server/Controllers/ApiRootController.cs
Normal file
520
server/Controllers/ApiRootController.cs
Normal file
@@ -0,0 +1,520 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using Sockeye.Util;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Linq;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Meta controller class
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/")]
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
public class ApiMetaController : ControllerBase
|
||||
{
|
||||
private readonly ApiServerState serverState;
|
||||
private readonly ILogger<ApiMetaController> _log;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ApiMetaController(ILogger<ApiMetaController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
_log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
private static string SupportUrl()
|
||||
{
|
||||
/*
|
||||
contactSupportUrl() {
|
||||
let dbId = encodeURIComponent(
|
||||
window.$gz.store.state.globalSettings.serverDbId
|
||||
);
|
||||
let company = encodeURIComponent(
|
||||
window.$gz.store.state.globalSettings.company
|
||||
);
|
||||
return `https://contact.ayanova.com/contact?dbid=${dbId}&company=${company}`;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
return $"https://contact.ayanova.com/contact";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Server landing page
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet]
|
||||
public ContentResult Index()
|
||||
{
|
||||
//https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays
|
||||
var errorBlock = string.Empty;
|
||||
if (serverState.IsSystemLocked)
|
||||
errorBlock = $@"<div class=""error""><h1>SERVER LOCKED</h1><h2>{serverState.Reason}</h2></div>";
|
||||
var resp = $@"<!DOCTYPE html>
|
||||
<html lang=""en"">
|
||||
<head>
|
||||
<meta charset=""utf-8"">
|
||||
<meta name=""viewport"" content=""width=device-width, initial-scale=1.0"">
|
||||
<title>Sockeye server</title>
|
||||
|
||||
<style type=""text/css"">
|
||||
body {{
|
||||
text-align: left;
|
||||
font-family: sans-serif;
|
||||
background-color: #282828;
|
||||
color: #ffb000;
|
||||
}}
|
||||
a {{
|
||||
color: #33ff33;
|
||||
text-decoration:none;
|
||||
}}
|
||||
.error{{
|
||||
color: #D8000C;
|
||||
padding-top:10px;
|
||||
padding-bottom:10px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{errorBlock}
|
||||
<div>
|
||||
<h1>{SockeyeVersion.FullNameAndVersion}</h1>
|
||||
<h2><a href=""/"" target=""_blank"">Sockeye App</a></h2>
|
||||
<h2><a href=""/docs"" target=""_blank"">User and technical guide</a></h2>
|
||||
<h2><a href=""{SupportUrl()}"" target=""_blank"">Contact technical support</a></h2>
|
||||
<h2><a href=""/api-docs"" target=""_blank"">API explorer for developers</a></h2>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>";
|
||||
System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(@"(?<=\s)\s+");
|
||||
resp = reg.Replace(resp, string.Empty).Replace("\n", "").Replace("\t", "");
|
||||
return new ContentResult
|
||||
{
|
||||
ContentType = "text/html",
|
||||
StatusCode = 200,
|
||||
Content = resp
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get API server info for ABOUT form Sockeye display
|
||||
/// </summary>
|
||||
/// <returns>API server info</returns>
|
||||
[HttpGet("server-info")]
|
||||
public ActionResult ServerInfo()
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
data = new
|
||||
{
|
||||
ServerVersion = SockeyeVersion.FullNameAndVersion,
|
||||
DBSchemaVersion = AySchema.currentSchema,
|
||||
ServerLocalTime = DateUtil.ServerDateTimeString(System.DateTime.UtcNow),
|
||||
ServerTimeZone = TimeZoneInfo.Local.Id,
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of types and roles required
|
||||
/// </summary>
|
||||
/// <returns>A list of SockType role rights</returns>
|
||||
[HttpGet("role-rights")]
|
||||
public ActionResult RoleRights()
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
data = new
|
||||
{
|
||||
SockTypes = BizRoles.roles.OrderBy(z => z.Key.ToString()).Select(z => new { SockType = z.Key.ToString(), Change = z.Value.Change.ToString(), ReadFullRecord = z.Value.ReadFullRecord.ToString(), Select = z.Value.Select.ToString() }).ToList()
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#if (DEBUG)
|
||||
/// <summary>
|
||||
/// Get build mode of server, used for automated testing purposes
|
||||
/// </summary>
|
||||
/// <returns>"DEBUG" or "RELEASE"</returns>
|
||||
[HttpGet("build-mode")]
|
||||
public ActionResult BuildMode()
|
||||
{
|
||||
return Ok(new { data = new { BuildMode = "DEBUG" } });
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Get build mode of server, used for automated testing purposes
|
||||
/// </summary>
|
||||
/// <returns>"DEBUG" or "RELEASE"</returns>
|
||||
[HttpGet("build-mode")]
|
||||
public ActionResult BuildMode()
|
||||
{
|
||||
return Ok(new { data = new { BuildMode = "RELEASE" } });
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#region sigtest script
|
||||
/*
|
||||
|
||||
<div style=""text-align: center;"">
|
||||
<hr/>
|
||||
<h2>SIGTEST - 1</h2>
|
||||
<h4>This is the signature header</h4>
|
||||
<canvas id=""sigpad"" width=""600"" height=""200"" style=""border: 1px dotted;touch-action: none;"">
|
||||
<p>
|
||||
Your browser does not support signing <br/>
|
||||
The following browsers are supported:<br/>
|
||||
IE 9.0 +, FIREFOX 3.0 +, SAFARI 3.0 +, CHROME 3.0 +, OPERA 10.0 +, IPAD 1.0 +, IPHONE
|
||||
1.0 +, ANDROID 1.0 +</p>
|
||||
</canvas>
|
||||
</div>
|
||||
{SigScript()}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private string SigScript(){
|
||||
return @"<script type=""text/javascript"">
|
||||
$(document).ready(function () {
|
||||
var canvas = document.getElementById(""sigpad"");
|
||||
///////////////////////////////////////////////
|
||||
// Prevent scrolling when touching the canvas
|
||||
//addEventListener('touchstart', FUNCTION, {passive: false});
|
||||
document.body.addEventListener(""touchstart"", function (e) {
|
||||
if (e.target == canvas) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, {passive: false});
|
||||
document.body.addEventListener(""touchend"", function (e) {
|
||||
if (e.target == canvas) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, {passive: false});
|
||||
document.body.addEventListener(""touchmove"", function (e) {
|
||||
if (e.target == canvas) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}, {passive: false});
|
||||
|
||||
///////////////////////////////////////////////
|
||||
|
||||
$('#sigpad').sigpad();
|
||||
});
|
||||
|
||||
(function ($) {
|
||||
$.fn.extend(
|
||||
{
|
||||
sigpad: function (options) {
|
||||
// Default options
|
||||
var defaults = {
|
||||
lineWidth: 3.0,
|
||||
lineCap: 'round',
|
||||
lineJoin: 'round',
|
||||
miterLimit: 10,
|
||||
strokeStyle: 'black',
|
||||
fillStyle: 'none',
|
||||
showClear: false,
|
||||
clearLabel: 'Clear',
|
||||
clearStyle: 'button'
|
||||
};
|
||||
|
||||
options = $.extend(defaults, options);
|
||||
|
||||
return this.each(function () {
|
||||
if (this.nodeName === 'CANVAS') {
|
||||
$(this).css('cursor', 'pointer');
|
||||
//$(this).attr('onclick', 'function onclick(event) { void 1; }');
|
||||
$(this).click('function onclick(event) { void 1; }');
|
||||
|
||||
if (this.getContext) {
|
||||
var canvas = this;
|
||||
var context = this.getContext('2d');
|
||||
var id = $(this).attr('id');
|
||||
|
||||
$(this).after('<div id=""' + id + '-controls"" style=""width:' + $(this).width() + 'px""></div>');
|
||||
|
||||
context.underInteractionEnabled = true;
|
||||
|
||||
// Overrides with passed options
|
||||
context.lineWidth = options.lineWidth;
|
||||
context.lineCap = options.lineCap;
|
||||
context.lineJoin = options.lineJoin;
|
||||
context.miterLimit = options.miterLimit;
|
||||
context.strokeStyle = options.strokeStyle;
|
||||
context.fillStyle = options.fillStyle;
|
||||
|
||||
|
||||
var data_input = id + '-data';
|
||||
$(this).after('<input type=""hidden"" id=""' + data_input + '"" name=""' + data_input + '"" />');
|
||||
|
||||
//case 1975
|
||||
//add hidden to form dirty tracking
|
||||
$('form').trigger('rescan.areYouSure');
|
||||
|
||||
// Defines all our tracking variables
|
||||
var drawing = false;
|
||||
var height = $('#' + id).height();
|
||||
var width = $('#' + id).width();
|
||||
var svg_path = '';
|
||||
var scrollLeft = 0;
|
||||
var scrollTop = 0;
|
||||
|
||||
// var offsetX = $(this).attr('offsetLeft');
|
||||
// var offsetY = $(this).attr('offsetTop');
|
||||
|
||||
var offsetX = 0;
|
||||
var offsetY = 0;
|
||||
|
||||
|
||||
var inside = false;
|
||||
var prevX = false;
|
||||
var prevY = false;
|
||||
var x = false;
|
||||
var y = false;
|
||||
|
||||
// Mouse events
|
||||
$(document).mousedown(function (e) { drawingStart(e); });
|
||||
$(document).mousemove(function (e) { drawingMove(e); });
|
||||
$(document).mouseup(function () { drawingStop(); });
|
||||
|
||||
// Touch events
|
||||
$(document).bind('touchstart', function (e) { drawingStart(e); });
|
||||
$(document).bind('touchmove', function (e) { drawingMove(e); });
|
||||
$(document).bind('touchend', function () { drawingStop(); });
|
||||
$(document).bind('touchcancel', function () { drawingStop(); });
|
||||
|
||||
// Adds the clear button / link
|
||||
if (options.showClear === true) {
|
||||
// var clear_tag = (options.clearStyle == 'link' ? 'div' : 'button');
|
||||
|
||||
// $('#' + id + '-controls').append('<' + clear_tag + ' id=""' + id + '-clear"" style=""float:left"">' + options.clearLabel + '</' + clear_tag + '><br style=""clear:both"" />');
|
||||
$('#' + id + '-controls').append('<div ' + ' id=""' + id + '-clear"" >' +
|
||||
'<span class=""btn btn-sm btn-danger icon-Delete sock-icon-large""></span></div>');
|
||||
|
||||
clear = true;
|
||||
}
|
||||
|
||||
// Clearing the canvas
|
||||
$('#' + id + '-clear').click(function (e) {
|
||||
context.save();
|
||||
context.beginPath();
|
||||
context.closePath();
|
||||
context.restore();
|
||||
context.clearRect(0, 0, $(canvas).width(), $(canvas).height());
|
||||
|
||||
$('#' + data_input).val('');
|
||||
});
|
||||
|
||||
function getTouch(e) {
|
||||
|
||||
//console.log(e);//3566
|
||||
// iPhone/iPad/iPod uses event.touches and not the passed event
|
||||
if (typeof (event) != ""undefined"" && typeof (event.touches) != ""undefined"") {
|
||||
e = event.touches.item(0);
|
||||
|
||||
scrollLeft = document.body.scrollLeft;
|
||||
scrollTop = document.body.scrollTop;
|
||||
}
|
||||
else {
|
||||
scrollLeft = $(document).scrollLeft();
|
||||
scrollTop = $(document).scrollTop();
|
||||
}
|
||||
|
||||
//console.log(""scrollLeft:"" + scrollLeft.toString());
|
||||
//console.log(""scrollTop:"" + scrollTop.toString());
|
||||
|
||||
// Tracks last position to handle dots (as opposed to lines)
|
||||
if (x != false) {
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
|
||||
// Calculates the X and Y values
|
||||
x = e.clientX - (offsetX - scrollLeft);
|
||||
y = e.clientY - (offsetY - scrollTop);
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function draw(type) {
|
||||
if (type != 'stop') {
|
||||
if (type == 'start') {
|
||||
inside = false;
|
||||
prevX = false;
|
||||
prevY = false;
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(x, y);
|
||||
|
||||
if (svg_path == '') {
|
||||
//timestamp and dimentions
|
||||
var currentDate = new Date();
|
||||
var captured = currentDate.getFullYear() + ':' + (currentDate.getMonth() + 1) + ':' + currentDate.getDate() + ':' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds();
|
||||
svg_path = '{version=1 width=' + width + ' height=' + height + ' captured=' + captured + '}';
|
||||
}
|
||||
|
||||
if (svg_path != '') {
|
||||
svg_path += 'X';
|
||||
}
|
||||
//svg_path = '{polyline points=""';
|
||||
}
|
||||
else {
|
||||
// If there's no previous increment since it's a .
|
||||
if (prevX == false) {
|
||||
x = x + 1;
|
||||
y = y + 1;
|
||||
}
|
||||
|
||||
context.lineTo(x, y);
|
||||
}
|
||||
|
||||
context.stroke();
|
||||
|
||||
if (svg_path.length > 0 && svg_path.substring(svg_path.length - 1) != '""') {
|
||||
svg_path = svg_path + ' ';
|
||||
}
|
||||
|
||||
svg_path = svg_path + x + ',' + y;
|
||||
|
||||
if ((x > 0 && x <= width) && (y > 0 && y <= height)) {
|
||||
inside = true;
|
||||
//console.log(""INSIDE"");
|
||||
} else {
|
||||
//console.log(""OUTSIDE X="" + x.toString() + "", Y="" + y.toString() + "", WIDTH="" + width.toString() + "", HEIGHT="" + height.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
draw('move');
|
||||
|
||||
if (inside == true) {
|
||||
// Closes the polyline (with style info) and adds the closing svg tag
|
||||
//svg_path = svg_path + '"" style=""fill:' + options.fillStyle + ';stroke:' + context.strokeStyle + ';stroke-width:' + context.lineWidth + '"" /}{/svg}';
|
||||
|
||||
var element = $('#' + data_input);
|
||||
|
||||
|
||||
|
||||
var svg_data = element.val();
|
||||
|
||||
// Adds the opening and closing SVG tags
|
||||
// if (svg_data == '')
|
||||
// {
|
||||
// svg_data = '{?xml version=""1.0"" standalone=""no""?}{!DOCTYPE svg PUBLIC ""-//W3C//DTD SVG 1.1//EN"" ""http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd""}{svg width=""' + width + '"" height=""' + height + '"" version=""1.1"" xmlns=""http://www.w3.org/2000/svg""}{/svg}';
|
||||
// }
|
||||
|
||||
// Appends the recorded path
|
||||
//element.val(svg_data.substring(0, svg_data.length - 6) + svg_path);
|
||||
element.val(svg_path);
|
||||
|
||||
//rescan hidden field form changed
|
||||
//case 1975
|
||||
$('form').trigger('checkform.areYouSure');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawingStart(e) {
|
||||
// console.log(""drawing start"");//3566
|
||||
setCanvasOffset();
|
||||
// Prevent the default action (scrolling) from occurring
|
||||
if (inside == true) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
drawing = true;
|
||||
|
||||
e = getTouch(e);
|
||||
|
||||
context.strokeStyle = $('#' + id + '-colors div.selected').css('backgroundColor');
|
||||
|
||||
draw('start');
|
||||
}
|
||||
|
||||
function drawingMove(e) {
|
||||
//console.log(""drawing move"");
|
||||
// Prevent the default action (scrolling) from occurring
|
||||
if (inside == true) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (drawing == true) {
|
||||
e = getTouch(e);
|
||||
|
||||
draw('move');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function drawingStop() {
|
||||
//console.log(""drawing STOP"");
|
||||
drawing = false;
|
||||
|
||||
// Draws one last line so we can draw dots (e.g. i)
|
||||
draw('stop');
|
||||
}
|
||||
|
||||
|
||||
//===========================
|
||||
|
||||
function setCanvasOffset() {
|
||||
canvasOffset = Offset(document.getElementById(id));
|
||||
offsetX = canvasOffset.left;
|
||||
offsetY = canvasOffset.top;
|
||||
}
|
||||
|
||||
function Offset(element) {
|
||||
if (element === undefined) return null;
|
||||
var obj = element.getBoundingClientRect();
|
||||
return {
|
||||
left: obj.left + window.pageXOffset,
|
||||
top: obj.top + window.pageYOffset
|
||||
};
|
||||
}
|
||||
|
||||
//===============
|
||||
|
||||
}
|
||||
// else {
|
||||
// alert('Your browser does not support the CANVAS element required for signing. The following browsers will work: IE 9.0+, FIREFOX 3.0+, SAFARI 3.0+, CHROME 3.0+, OPERA 10.0+, IPAD 1.0+, IPHONE 1.0+, ANDROID 1.0+');
|
||||
// }
|
||||
}
|
||||
else {
|
||||
alert('Not a CANVAS element');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
</script>";
|
||||
}
|
||||
*/
|
||||
#endregion
|
||||
666
server/Controllers/AttachmentController.cs
Normal file
666
server/Controllers/AttachmentController.cs
Normal file
@@ -0,0 +1,666 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.IO;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Util;
|
||||
using Sockeye.Biz;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
//FROM DOCS HERE:
|
||||
//https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
//https://github.com/aspnet/Docs/tree/74a44669d5e7039e2d4d2cb3f8b0c4ed742d1124/aspnetcore/mvc/models/file-uploads/sample/FileUploadSample
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Attachment controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/attachment")]
|
||||
[Produces("application/json")]
|
||||
public class AttachmentController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AttachmentController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public AttachmentController(AyContext dbcontext, ILogger<AttachmentController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class UpdateAttachmentInfo
|
||||
{
|
||||
[Required]
|
||||
public uint Concurrency { get; set; }
|
||||
[Required]
|
||||
public string DisplayFileName { get; set; }
|
||||
public string Notes { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update FileAttachment
|
||||
/// (FileName and notes only)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="inObj"></param>
|
||||
/// <returns>list</returns>
|
||||
[Authorize]
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutAttachment([FromRoute] long id, [FromBody] UpdateAttachmentInfo inObj)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, dbObject.AttachToAType))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
string ChangeTextra = string.Empty;
|
||||
if (dbObject.DisplayFileName != inObj.DisplayFileName)
|
||||
{
|
||||
ChangeTextra = $"\"{dbObject.DisplayFileName}\" => \"{inObj.DisplayFileName}\"";
|
||||
}
|
||||
if (dbObject.Notes != inObj.Notes)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ChangeTextra))
|
||||
ChangeTextra += ", ";
|
||||
ChangeTextra += "Notes";
|
||||
}
|
||||
dbObject.DisplayFileName = inObj.DisplayFileName;
|
||||
dbObject.Notes = inObj.Notes;
|
||||
|
||||
|
||||
|
||||
//Set "original" value of concurrency token to input token
|
||||
//this will allow EF to check it out
|
||||
ct.Entry(dbObject).OriginalValues["Concurrency"] = inObj.Concurrency;
|
||||
|
||||
//Log event and save context
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentModified, ChangeTextra), ct);
|
||||
|
||||
|
||||
|
||||
//SEARCH INDEXING
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationIdFromContext.Id(HttpContext.Items), id, SockType.FileAttachment);
|
||||
SearchParams.AddText(inObj.Notes).AddText(inObj.DisplayFileName);
|
||||
await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
|
||||
|
||||
//--------------
|
||||
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
|
||||
//Normallyh wouldn't return a whole list but in this case the UI demands it because of reactivity issues
|
||||
var ret = await GetFileListForObjectAsync(dbObject.AttachToAType, dbObject.AttachToObjectId);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get attachments for object type and id specified
|
||||
///
|
||||
/// Required Role: Read full object properties rights to object type specified
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>file attachment list for object</returns>
|
||||
[Authorize]
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList([FromQuery] SockType sockType, [FromQuery] long sockId)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Is this a customer user attempting to view a wo attachments??
|
||||
// var userType = UserTypeFromContext.Type(HttpContext.Items);
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, sockType))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
var ret = await GetFileListForObjectAsync(sockType, sockId);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get parent object type and id
|
||||
/// for specified attachment id
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize]
|
||||
[HttpGet("parent/{id}")]
|
||||
public async Task<IActionResult> GetParent([FromRoute] long id)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var at = await ct.FileAttachment.AsNoTracking().Where(z => z.Id == id).FirstOrDefaultAsync();
|
||||
if (at == null)
|
||||
return NotFound();
|
||||
|
||||
|
||||
return Ok(ApiOkResponse.Response(new { id = at.AttachToObjectId, type = at.AttachToAType }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Upload attachment file
|
||||
/// Max 10GiB total
|
||||
/// Requires same Authorization roles as object that file is being attached to
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>NameValue list of filenames and attachment id's</returns>
|
||||
[Authorize]
|
||||
[HttpPost]
|
||||
[DisableFormValueModelBinding]
|
||||
[RequestSizeLimit(ServerBootConfig.MAX_ATTACHMENT_UPLOAD_BYTES)]
|
||||
public async Task<IActionResult> UploadAsync()
|
||||
{
|
||||
//Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
// var returnList = new List<NameIdItem>();
|
||||
object ret = null;
|
||||
SockTypeId attachToObject = null;
|
||||
try
|
||||
{
|
||||
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}"));
|
||||
|
||||
|
||||
|
||||
bool badRequest = false;
|
||||
string AttachToAType = string.Empty;
|
||||
string AttachToObjectId = string.Empty;
|
||||
string errorMessage = string.Empty;
|
||||
string Notes = string.Empty;
|
||||
long? OverrideUserId = null;
|
||||
List<UploadFileData> FileData = new List<UploadFileData>();
|
||||
|
||||
var uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);
|
||||
if (!string.IsNullOrWhiteSpace(uploadFormData.Error))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = uploadFormData.Error;
|
||||
}
|
||||
|
||||
if (!badRequest
|
||||
&& (!uploadFormData.FormFieldData.ContainsKey("FileData")
|
||||
|| !uploadFormData.FormFieldData.ContainsKey("AttachToAType")
|
||||
|| !uploadFormData.FormFieldData.ContainsKey("AttachToObjectId")))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "Missing one or more required FormFieldData values: AttachToAType, AttachToObjectId, FileData";
|
||||
}
|
||||
if (!badRequest)
|
||||
{
|
||||
AttachToAType = uploadFormData.FormFieldData["AttachToAType"].ToString();
|
||||
//for v8 migrate purposes
|
||||
if (uploadFormData.FormFieldData.ContainsKey("OverrideUserId"))
|
||||
OverrideUserId = long.Parse(uploadFormData.FormFieldData["OverrideUserId"].ToString());
|
||||
|
||||
AttachToObjectId = uploadFormData.FormFieldData["AttachToObjectId"].ToString();
|
||||
if (uploadFormData.FormFieldData.ContainsKey("Notes"))
|
||||
Notes = uploadFormData.FormFieldData["Notes"].ToString();
|
||||
//fileData in JSON stringify format which contains the actual last modified dates etc
|
||||
//"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]"
|
||||
FileData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<UploadFileData>>(uploadFormData.FormFieldData["FileData"].ToString());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(AttachToAType) || string.IsNullOrWhiteSpace(AttachToObjectId))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "AttachToAType and / or AttachToObjectId are empty and are required";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Get type and id object from post paramters
|
||||
|
||||
if (!badRequest)
|
||||
{
|
||||
attachToObject = new SockTypeId(AttachToAType, AttachToObjectId);
|
||||
if (attachToObject.IsEmpty)
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "AttachToAType and / or AttachToObjectId are not valid and are required";
|
||||
}
|
||||
}
|
||||
|
||||
//Is it an attachable type of object?
|
||||
if (!badRequest)
|
||||
{
|
||||
if (!attachToObject.IsCoreBizObject)
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = attachToObject.SockType.ToString() + " - AttachToAType does not support attachments";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//does attach to object exist?
|
||||
if (!badRequest)
|
||||
{
|
||||
//check if object exists
|
||||
if (!await BizObjectExistsInDatabase.ExistsAsync(attachToObject.SockType, attachToObject.ObjectId, ct))
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "Invalid attach object";
|
||||
}
|
||||
else
|
||||
{
|
||||
// User needs modify rights to the object type in question
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, attachToObject.SockType))
|
||||
{
|
||||
//delete temp files
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (badRequest)
|
||||
{
|
||||
//delete temp files
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
//file too large is most likely issue so in that case return this localized properly
|
||||
if (errorMessage.Contains("413"))
|
||||
{
|
||||
var TransId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
return BadRequest(new ApiErrorResponse(
|
||||
ApiErrorCode.VALIDATION_LENGTH_EXCEEDED,
|
||||
null,
|
||||
"HTTP ERROR CODE 413 " + String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), FileUtil.GetBytesReadable(ServerBootConfig.MAX_ATTACHMENT_UPLOAD_BYTES))));
|
||||
}
|
||||
else//not too big, something else
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage));
|
||||
}
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
//We have our files and a confirmed AyObject, ready to attach and save permanently
|
||||
if (uploadFormData.UploadedFiles.Count > 0)
|
||||
{
|
||||
|
||||
foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
{
|
||||
//Get the actual date from the separate filedata
|
||||
//this is because the lastModified date is always empty in the form data files
|
||||
DateTime theDate = DateTime.MinValue;
|
||||
foreach (UploadFileData f in FileData)
|
||||
{
|
||||
if (f.name == a.OriginalFileName)
|
||||
{
|
||||
if (f.lastModified > 0)
|
||||
{
|
||||
theDate = DateTimeOffset.FromUnixTimeMilliseconds(f.lastModified).UtcDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (theDate == DateTime.MinValue)
|
||||
theDate = DateTime.UtcNow;
|
||||
|
||||
var v = await FileUtil.StoreFileAttachmentAsync(a.InitialUploadedPathName, a.MimeType, a.OriginalFileName, theDate, attachToObject, Notes, OverrideUserId ?? UserId, ct);
|
||||
|
||||
//EVENT LOG
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, attachToObject.ObjectId, attachToObject.SockType, SockEvent.AttachmentCreate, v.DisplayFileName), ct);
|
||||
|
||||
//SEARCH INDEXING
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationIdFromContext.Id(HttpContext.Items), v.Id, SockType.FileAttachment);
|
||||
SearchParams.AddText(v.Notes).AddText(v.DisplayFileName);
|
||||
await Search.ProcessNewObjectKeywordsAsync(SearchParams);
|
||||
|
||||
}
|
||||
}
|
||||
ret = await GetFileListForObjectAsync(attachToObject.SockType, attachToObject.ObjectId);
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message));
|
||||
}
|
||||
|
||||
//Return the list of attachment ids and filenames
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Utility to delete files that were uploaded but couldn't be stored for some reason, called by Attach route
|
||||
// /// </summary>
|
||||
// /// <param name="uploadFormData"></param>
|
||||
// private static void DeleteTempFileUploadDueToBadRequest(ApiUploadProcessor.ApiUploadedFilesResult uploadFormData)
|
||||
// {
|
||||
// if (uploadFormData.UploadedFiles.Count > 0)
|
||||
// {
|
||||
// foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
// {
|
||||
// System.IO.File.Delete(a.InitialUploadedPathName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete Attachment
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[Authorize]
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteAttachmentAsync([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, dbObject.AttachToAType))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//do the delete
|
||||
//this handles removing the file if there are no refs left and also the db record for the attachment
|
||||
await FileUtil.DeleteFileAttachmentAsync(dbObject, ct);
|
||||
|
||||
//Event log process delete
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDelete, dbObject.DisplayFileName), ct);
|
||||
|
||||
//Delete search index
|
||||
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, SockType.FileAttachment, ct);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch delete attachments
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>No content</returns>
|
||||
[HttpPost("batch-delete")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostBatchDelete([FromBody] List<long> idList)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
foreach (long id in idList)
|
||||
{
|
||||
var dbObject = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
continue;
|
||||
//do the delete
|
||||
//this handles removing the file if there are no refs left and also the db record for the attachment
|
||||
await FileUtil.DeleteFileAttachmentAsync(dbObject, ct);
|
||||
|
||||
//Event log process delete
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDelete, dbObject.DisplayFileName), ct);
|
||||
|
||||
//Delete search index
|
||||
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, SockType.FileAttachment, ct);
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
/// <summary>
|
||||
/// Batch move attachments
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>No content</returns>
|
||||
[HttpPost("batch-move")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostBatchMove([FromBody] dtoBatchMove dt)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await BizObjectExistsInDatabase.ExistsAsync(dt.ToType, dt.ToId, ct))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, "LT:ErrorAPI2010"));
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
foreach (long id in dt.IdList)
|
||||
{
|
||||
var dbObject = await ct.FileAttachment.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
continue;
|
||||
|
||||
//do the move
|
||||
var msg = $"{dbObject.DisplayFileName} moved from {dbObject.AttachToAType}-{dbObject.AttachToObjectId} to {dt.ToType}-{dt.ToId} ";
|
||||
dbObject.AttachToObjectId = dt.ToId;
|
||||
dbObject.AttachToAType = dt.ToType;
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//Event log process move
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentModified, msg), ct);
|
||||
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
public class dtoBatchMove
|
||||
{
|
||||
public List<long> IdList { get; set; }
|
||||
public SockType ToType { get; set; }
|
||||
public long ToId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a file attachment
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("download/{id}")]
|
||||
public async Task<IActionResult> DownloadAsync([FromRoute] long id, [FromQuery] string t)
|
||||
{
|
||||
//https://dotnetcoretutorials.com/2017/03/12/uploading-files-asp-net-core/
|
||||
//https://stackoverflow.com/questions/45763149/asp-net-core-jwt-in-uri-query-parameter/45811270#45811270
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
|
||||
var DownloadUser = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct);
|
||||
if (DownloadUser == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
|
||||
//Ok, user has a valid download key and it's not expired yet so get the attachment record
|
||||
var dbObject = await ct.FileAttachment.SingleOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
//is this allowed?
|
||||
|
||||
if (!Authorized.HasReadFullRole(DownloadUser.Roles, dbObject.AttachToAType))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
//they are allowed, let's send the file
|
||||
string mimetype = dbObject.ContentType;
|
||||
var filePath = FileUtil.GetPermanentAttachmentFilePath(dbObject.StoredFileName);
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
{
|
||||
|
||||
//TODO: this should reset the validity
|
||||
|
||||
var errText = $"Physical file {dbObject.StoredFileName} not found despite attachment record, this file is missing";
|
||||
log.LogError(errText);
|
||||
await NotifyEventHelper.AddOpsProblemEvent($"File attachment issue: {errText}");
|
||||
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, errText));
|
||||
}
|
||||
|
||||
//Log
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(DownloadUser.Id, dbObject.AttachToObjectId, dbObject.AttachToAType, SockEvent.AttachmentDownload, dbObject.DisplayFileName), ct);
|
||||
|
||||
return PhysicalFile(filePath, mimetype, dbObject.DisplayFileName);
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
async private Task<object> GetFileListForObjectAsync(SockType sockType, long sockId)
|
||||
{
|
||||
var retList = new List<FileAttachmentListItem>();
|
||||
using (var cmd = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
await ct.Database.OpenConnectionAsync();
|
||||
cmd.CommandText = $@"select afileattachment.id, afileattachment.xmin as concurrency, displayfilename,contenttype,lastmodified, afileattachment.notes, size, auser.name as attachedbyuser from afileattachment
|
||||
left join auser on (afileattachment.attachedByUserId=auser.id)
|
||||
where attachtosockType={(int)sockType} and attachtoobjectid={sockId}
|
||||
order by displayfilename";
|
||||
|
||||
using (var dr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (dr.Read())
|
||||
{
|
||||
retList.Add(new FileAttachmentListItem()
|
||||
{
|
||||
Id = dr.GetInt64(0),
|
||||
Concurrency = (UInt32)dr.GetValue(1),
|
||||
DisplayFileName = dr.GetString(2),
|
||||
ContentType = dr.GetString(3),
|
||||
LastModified = dr.GetDateTime(4),
|
||||
Notes = dr.GetString(5),
|
||||
Size = dr.GetInt64(6),
|
||||
AttachedByUser = dr.GetString(7)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retList;
|
||||
}
|
||||
|
||||
private class FileAttachmentListItem
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public uint Concurrency { get; set; }
|
||||
public string DisplayFileName { get; set; }
|
||||
public string ContentType { get; set; }//mime type
|
||||
public DateTime LastModified { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string AttachedByUser { get; set; }
|
||||
public long Size { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger immediate AttachmentMaintenanceJob
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("maintenance")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostTriggerAttachmentMaintenanceJob()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.FileAttachment))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var JobName = $"Attachment maintenance (demand) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = SockType.FileAttachment;
|
||||
j.JobType = JobType.AttachmentMaintenance;
|
||||
j.SubType = JobSubType.NotSet;
|
||||
j.Exclusive = true;
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
|
||||
|
||||
717
server/Controllers/AuthController.cs
Normal file
717
server/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,717 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using TwoFactorAuthNet;
|
||||
using QRCoder;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Util;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Sockeye.Biz;
|
||||
|
||||
//required to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/auth")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AuthController> log;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ApiServerState serverState;
|
||||
private const int JWT_LIFETIME_DAYS = 5;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public AuthController(AyContext context, ILogger<AuthController> logger, IConfiguration configuration, ApiServerState apiServerState)
|
||||
{
|
||||
ct = context;
|
||||
log = logger;
|
||||
_configuration = configuration;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
//AUTHENTICATE CREDS
|
||||
//RETURN JWT
|
||||
|
||||
/// <summary>
|
||||
/// Create credentials to receive a JSON web token
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This route is used to authenticate to the Sockeye 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]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> PostCreds([FromBody] AuthController.CredentialsParam creds) //if was a json body then //public JsonResult PostCreds([FromBody] string login, [FromBody] string password)
|
||||
{
|
||||
|
||||
//NOTE: lockout or other login impacting state is processed later in ReturnUserCredsOnSuccessfulAuthentication() because many of those states need to have exceptions once the user is known
|
||||
//or return alternate result of auth etc
|
||||
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(creds.Login) || string.IsNullOrWhiteSpace(creds.Password))
|
||||
{
|
||||
//Make a failed pw wait
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
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.Where(z => z.Login == creds.Login && z.Active == true && z.AllowLogin == true).ToListAsync();
|
||||
|
||||
foreach (User u in users)
|
||||
{
|
||||
string hashed = Hasher.hash(u.Salt, creds.Password);
|
||||
if (hashed == u.Password)
|
||||
{
|
||||
|
||||
|
||||
//TWO FACTOR ENABLED??
|
||||
//if 2fa enabled then need to validate it before sending token, so we're halfway there and need to send a 2fa prompt
|
||||
if (u.TwoFactorEnabled)
|
||||
{
|
||||
//Generate a temporary token to identify and verify this is the same user
|
||||
u.TempToken = Hasher.GenerateSalt().Replace("=", "").Replace("+", "");
|
||||
await ct.SaveChangesAsync();
|
||||
var UOpt = await ct.UserOptions.AsNoTracking().FirstAsync(z => z.UserId == u.Id);
|
||||
|
||||
List<string> TranslationKeysToFetch = new List<string> { "AuthTwoFactor", "AuthEnterPin", "AuthVerifyCode", "Cancel", "AuthPinInvalid" };
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, UOpt.TranslationId);
|
||||
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
AuthTwoFactor = LT["AuthTwoFactor"],
|
||||
AuthEnterPin = LT["AuthEnterPin"],
|
||||
AuthVerifyCode = LT["AuthVerifyCode"],
|
||||
AuthPinInvalid = LT["AuthPinInvalid"],
|
||||
Cancel = LT["Cancel"],
|
||||
tfa = true,
|
||||
tt = u.TempToken
|
||||
}));
|
||||
}
|
||||
|
||||
//Not 2fa, Valid password, user is authorized
|
||||
return await ReturnUserCredsOnSuccessfulAuthentication(u);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//No users matched, it's a failed login
|
||||
//Make a failed pw wait
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verify tfa code
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This route is used to authenticate to the Sockeye API via tfa code.
|
||||
/// 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="pin"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("tfa-authenticate")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> TfaAuthenticate([FromBody] TFAPinParam pin)
|
||||
{
|
||||
//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.IsClosed)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pin.Pin))
|
||||
{
|
||||
//Make a failed pw wait
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
//Match to temp token that would have been set by initial credentialed login for 2fa User
|
||||
var user = await ct.User.Where(z => z.TempToken == pin.TempToken && z.Active == true && z.AllowLogin==true && z.TwoFactorEnabled == true).FirstOrDefaultAsync();
|
||||
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
//Valid temp token, now check the pin code is right
|
||||
if (string.IsNullOrWhiteSpace(user.TotpSecret))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa not activated"));
|
||||
}
|
||||
|
||||
//ok, something to validate, let's validate it
|
||||
var tfa = new TwoFactorAuth("Sockeye");
|
||||
if (!tfa.VerifyCode(user.TotpSecret, pin.Pin.Replace(" ", "").Trim()))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
//User is valid and authenticated
|
||||
//clear temp token
|
||||
user.TempToken = string.Empty;
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
return await ReturnUserCredsOnSuccessfulAuthentication(user);
|
||||
}
|
||||
|
||||
//No users matched, it's a failed login
|
||||
//Make a failed pw wait
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//return creds and or process lockout handling here
|
||||
private async Task<IActionResult> ReturnUserCredsOnSuccessfulAuthentication(User u)
|
||||
{
|
||||
|
||||
bool licenseLockout = false;
|
||||
//check if server available to SuperUser account only (closed or migrate mode)
|
||||
//if it is it means we got here either because there is no license
|
||||
//and only *the* SuperUser account can login now or we're in migrate mode
|
||||
if (serverState.IsClosed )
|
||||
{
|
||||
//if not SuperUser account then boot closed
|
||||
//SuperUser account is always ID 1
|
||||
if (u.Id != 1)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
}
|
||||
|
||||
}
|
||||
//Restrict auth due to server state?
|
||||
//If we're here it's the superuser or 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 ((u.Id != 1) && serverState.IsOpsOnly &&
|
||||
!u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdmin) &&
|
||||
!u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdminRestricted))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
}
|
||||
|
||||
|
||||
//build the key (JWT set in startup.cs)
|
||||
byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.SOCKEYE_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
|
||||
|
||||
//###################################
|
||||
//Lifetime of jwt token
|
||||
//after this point the user will no longer be able to make requests without logging in again
|
||||
//and the client will automatically send them to the login screen
|
||||
//so this is auto logout after this time period
|
||||
|
||||
//security wise the length of time is not an issue how long this is because our system allows to revoke tokens as they are checked on every access
|
||||
//the adivce online is to make it short and use refresh tokens but that's not an issue with our system since we both issue and validate
|
||||
//the tokens ourselves
|
||||
|
||||
//The only down side is that an expired license at the server will not prevent people from continuing to work until their token expires
|
||||
//an expired license only stops a fresh login
|
||||
//so whatever this value is will allow people who haven't logged out to continue to work until it expires
|
||||
|
||||
//so this really only controls how long we allow them to work with an expired ayanova license which would be a rare occurence I suspect
|
||||
//so really to prevent fuckery for people 5 days seems fine meaning they won't need to sign in again all business week if they want to continue working
|
||||
var exp = new DateTimeOffset(DateTime.Now.AddDays(JWT_LIFETIME_DAYS).ToUniversalTime(), TimeSpan.Zero);
|
||||
|
||||
|
||||
|
||||
//=============== download token ===================
|
||||
//Generate a download token and store it with the user account
|
||||
//string DownloadToken = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
|
||||
string DownloadToken = Hasher.GenerateSalt();
|
||||
DownloadToken = DownloadToken.Replace("=", "");
|
||||
DownloadToken = DownloadToken.Replace("+", "");
|
||||
u.DlKey = DownloadToken;
|
||||
u.DlKeyExpire = exp.UtcDateTime;
|
||||
|
||||
//=======================================================
|
||||
|
||||
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", "rockfish.ayanova.com" },
|
||||
{ "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);
|
||||
|
||||
//save auth token to ensure single sign on only
|
||||
u.CurrentAuthToken = token;
|
||||
|
||||
u.LastLogin = DateTime.UtcNow;
|
||||
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//KEEP this, masked version of IP address
|
||||
//Not sure if this is necessary or not but if it turns out to be then make it a boot option
|
||||
// log.LogInformation($"User number \"{u.Id}\" logged in from \"{Util.StringUtil.MaskIPAddress(HttpContext.Connection.RemoteIpAddress.ToString())}\" ok");
|
||||
|
||||
log.LogInformation($"User \"{u.Name}\" logged in from \"{HttpContext.Connection.RemoteIpAddress.ToString()}\" ok");
|
||||
|
||||
|
||||
//return appropriate data for user type...
|
||||
if (u.UserType == UserType.Customer | u.UserType == UserType.HeadOffice)
|
||||
{
|
||||
//customer type has special rights restrictions for UI features so return them here so client UI can enable or disable
|
||||
var effectiveRights = await UserBiz.CustomerUserEffectiveRightsAsync(u.Id);
|
||||
//A non active Customer or Head Office record's contacts are also not allowed to login
|
||||
if (!effectiveRights.EntityActive)
|
||||
{
|
||||
log.LogInformation($"Customer contact user \"{u.Name}\" attempted login was denied due to inactive parent (Customer or HeadOffice)");
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
token = token,
|
||||
name = u.Name,
|
||||
usertype = u.UserType,
|
||||
roles = ((int)u.Roles).ToString(),
|
||||
dlt = DownloadToken,
|
||||
tfa = u.TwoFactorEnabled,
|
||||
CustomerRights = effectiveRights
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Non customer user
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
token = token,
|
||||
name = u.Name,
|
||||
usertype = u.UserType,
|
||||
roles = ((int)u.Roles).ToString(),
|
||||
dlt = DownloadToken,
|
||||
tfa = u.TwoFactorEnabled,
|
||||
l = licenseLockout
|
||||
}));
|
||||
}
|
||||
|
||||
//------------------------ /STANDARD BLOCK -------------------------
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Change Password
|
||||
/// </summary>
|
||||
/// <param name="changecreds"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("change-password")]
|
||||
public async Task<IActionResult> ChangePassword([FromBody] AuthController.ChangePasswordParam changecreds)
|
||||
{
|
||||
//Note: need to be authenticated to use this, only called from own user's UI
|
||||
//it still asks for old creds in case someone attempts to do this on another user's logged in session
|
||||
//Also it checks here that this is in fact the same user account calling this method as the user attempting to be modified
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(changecreds.OldPassword) || string.IsNullOrWhiteSpace(changecreds.LoginName))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(changecreds.NewPassword))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "NewPassword"));
|
||||
|
||||
if (changecreds.NewPassword != changecreds.ConfirmPassword)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "ConfirmPassword", "NewPassword does not match ConfirmPassword"));
|
||||
|
||||
//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(z => z.Login == changecreds.LoginName).ToListAsync();
|
||||
|
||||
foreach (User u in users)
|
||||
{
|
||||
string hashed = Hasher.hash(u.Salt, changecreds.OldPassword);
|
||||
if (hashed == u.Password)
|
||||
{
|
||||
|
||||
//If the user is inactive they may not login
|
||||
if (!u.Active || !u.AllowLogin)
|
||||
{
|
||||
//respond like bad creds so as not to leak information
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
//double check it's the currently logged in User's own User object only
|
||||
//otherwise it's feasible someone could change someone else's password through their own change password form with a mis-type or intentional hack
|
||||
if (u.Id != UserIdFromContext.Id(HttpContext.Items))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
//fetch and update user
|
||||
//Instantiate the business object handler
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
await biz.ChangePasswordAsync(u.Id, changecreds.NewPassword);
|
||||
|
||||
return NoContent();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//No users matched, it's a failed login
|
||||
//Make a failed pw wait
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Change Password via reset token
|
||||
/// </summary>
|
||||
/// <param name="resetcreds"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("reset-password")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> ResetPassword([FromBody] AuthController.ResetPasswordParam resetcreds)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(resetcreds.PasswordResetCode))
|
||||
{
|
||||
//Make a fail wait
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "PasswordResetCode", "Reset code is required"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(resetcreds.Password))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "Password", "Password is required"));
|
||||
}
|
||||
|
||||
//look for user with this reset code
|
||||
var user = await ct.User.AsNoTracking().Where(z => z.PasswordResetCode == resetcreds.PasswordResetCode).FirstOrDefaultAsync();
|
||||
if (user == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "PasswordResetCode", "Reset code not valid"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(user.PasswordResetCode) || user.PasswordResetCodeExpire == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "PasswordResetCode", "Reset code not valid"));
|
||||
}
|
||||
|
||||
//vet the expiry
|
||||
var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);
|
||||
if (user.PasswordResetCodeExpire < utcNow.DateTime)
|
||||
{//if reset code expired before now
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "PasswordResetCodeExpire", "Reset code has expired"));
|
||||
}
|
||||
//Ok, were in, it's all good, accept the new password and update the user record
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
await biz.ChangePasswordAsync(user.Id, resetcreds.Password);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate time limited password reset code for User
|
||||
/// and email link to them so they can set their password
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="id">User id</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>New concurrency code</returns>
|
||||
[HttpPost("request-reset-password/{id}")]
|
||||
public async Task<IActionResult> SendPasswordResetCode([FromRoute] long id, ApiVersion apiVersion)
|
||||
{
|
||||
//Note: this is not allowed for an anonymous users because it's only intended for now to work for staff user's who will send the request on behalf of the User
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
uint res = await biz.SendPasswordResetCode(id);
|
||||
if (res == 0)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
else
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
concurrency = res
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generate TOTP secret and return for use in auth app
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>Authentication app activation code</returns>
|
||||
[HttpGet("totp")]
|
||||
public async Task<IActionResult> GenerateAndSendTOTP(ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//get user and save the secret
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId);
|
||||
if (u == null)//should never happen but ?
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
//this is to stop someone from messing up someone's login accidentally or maliciously by simply hitting the route logged in as them
|
||||
if (u.TwoFactorEnabled)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa already enabled"));
|
||||
|
||||
var tfa = new TwoFactorAuth("Sockeye");
|
||||
u.TotpSecret = tfa.CreateSecret(160);
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
//otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
|
||||
//this format tested and works with Google, Microsoft Authy, Duo authenticators
|
||||
string payload = $"otpauth://totp/Sockeye:{u.Name}?secret={u.TotpSecret}&issuer=Sockeye&algorithm=SHA1&digits=6&period=30";//NOTE: the 30 here is seconds the totp code is allowed to be used before a new one is required
|
||||
|
||||
QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||
QRCodeData qrCodeData = qrGenerator.CreateQrCode(payload, QRCodeGenerator.ECCLevel.Q);
|
||||
// Base64QRCode qrCode = new Base64QRCode(qrCodeData);
|
||||
// string qrCodeImageAsBase64 = qrCode.GetGraphic(3);
|
||||
|
||||
PngByteQRCode qpng = new PngByteQRCode(qrCodeData);
|
||||
string qrCodeImageAsBase64 = Convert.ToBase64String(qpng.GetGraphic(3));
|
||||
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
s = u.TotpSecret,
|
||||
qr = qrCodeImageAsBase64
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Confirm 2fa ready to use
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pin">Auth app 6 digit passcode</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>OK on success</returns>
|
||||
[HttpPost("totp-validate")]
|
||||
public async Task<IActionResult> ValidateTOTP([FromBody] TFAPinParam pin, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//get user
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId);
|
||||
if (u == null)//should never happen but ?
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (u.TwoFactorEnabled)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa already enabled"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(u.TotpSecret))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa activation code not requested yet (missed a step?)"));
|
||||
}
|
||||
|
||||
//ok, something to validate, let's validate it
|
||||
var tfa = new TwoFactorAuth("Sockeye");
|
||||
var ret = tfa.VerifyCode(u.TotpSecret, pin.Pin.Replace(" ", "").Trim());
|
||||
if (ret == true)
|
||||
{
|
||||
//enable 2fa on user account
|
||||
u.TwoFactorEnabled = true;
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);
|
||||
}
|
||||
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
ok = ret
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Disable (turn off) 2fa for user account
|
||||
/// (For other user id requires full privileges)
|
||||
/// </summary>
|
||||
/// <param name="id">User id</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>OK on success</returns>
|
||||
[HttpPost("totp-disable/{id}")]
|
||||
public async Task<IActionResult> DisableTOTP([FromRoute] long id, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
if (id != UserId) //doing it on behalf of someone else
|
||||
{
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.User))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
var u = await ct.User.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (u == null)//should never happen but ?
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
u.TotpSecret = null;
|
||||
u.TempToken = null;
|
||||
u.TwoFactorEnabled = false;
|
||||
await ct.SaveChangesAsync();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
//generate an internal JWT key for reporting purposes used by corejobnotify to send reports as manager account
|
||||
internal static string GenRpt(long overrideLanguageId)
|
||||
{
|
||||
byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.SOCKEYE_JWT_SECRET);
|
||||
var iat = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);
|
||||
var exp = new DateTimeOffset(DateTime.Now.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT).ToUniversalTime(), TimeSpan.Zero);
|
||||
var payload = new Dictionary<string, object>()
|
||||
{
|
||||
{ "exp", exp.ToUnixTimeSeconds().ToString() },//in payload exp must be in unix epoch time per standard
|
||||
{ "iss", "rockfish.ayanova.com" },
|
||||
{ "id", "1"},
|
||||
{ "rpl",overrideLanguageId.ToString() }
|
||||
};
|
||||
return Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256);
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
public class CredentialsParam
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Login { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Password { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class ChangePasswordParam
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string LoginName { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string OldPassword { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string NewPassword { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class ResetPasswordParam
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string PasswordResetCode { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
public class TFAPinParam
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Pin { get; set; }
|
||||
public string TempToken { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
69
server/Controllers/AuthorizationRolesController.cs
Normal file
69
server/Controllers/AuthorizationRolesController.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Enum pick list controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/authorization-roles")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class AuthorizationRolesController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AuthorizationRolesController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public AuthorizationRolesController(AyContext dbcontext, ILogger<AuthorizationRolesController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get roles
|
||||
/// </summary>
|
||||
/// <param name="AsJson">Return as compact JSON format</param>
|
||||
/// <returns>Dictionary list of Sockeye object types and their authorization role rights in Sockeye</returns>
|
||||
[HttpGet("list")]
|
||||
public ActionResult GetRoles([FromQuery] bool AsJson = false)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//as json for client end of things
|
||||
if (AsJson)
|
||||
return Ok(ApiOkResponse.Response(Newtonsoft.Json.JsonConvert.SerializeObject(BizRoles.roles, Newtonsoft.Json.Formatting.None)));
|
||||
else
|
||||
return Ok(ApiOkResponse.Response(BizRoles.roles));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
160
server/Controllers/BackupController.cs
Normal file
160
server/Controllers/BackupController.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Threading.Tasks;
|
||||
using Sockeye.Util;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Backup
|
||||
///
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/backup")]
|
||||
[Produces("application/json")]
|
||||
public class BackupController : ControllerBase
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
//DANGER: MUST ADD AUTHORIZE ATTRIBUTE TO ANY NEW ROUTES
|
||||
//[Authorize]
|
||||
|
||||
/// <summary>
|
||||
/// Trigger immediate system backup
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("backup-now")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostBackupNow()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Backup))//technically maybe this could be wider open, but for now keeping as locked down
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var JobName = $"LT:BackupNow LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = SockType.NoType;
|
||||
j.JobType = JobType.Backup;
|
||||
j.SubType = JobSubType.NotSet;
|
||||
j.Exclusive = true;
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get status of backup
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("status")]
|
||||
[Authorize]
|
||||
public ActionResult BackupStatus()
|
||||
{
|
||||
//Need size and more info
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Backup))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
return Ok(ApiOkResponse.Response(FileUtil.BackupStatusReport()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a backup file
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("download/{fileName}")]
|
||||
public async Task<IActionResult> DownloadAsync([FromRoute] string fileName, [FromQuery] string t)
|
||||
{
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
var DownloadUser = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct);
|
||||
if (DownloadUser == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
if (!Authorized.HasModifyRole(DownloadUser.Roles, SockType.Backup))//not technically modify but treating as such as a backup is very sensitive data
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!FileUtil.BackupFileExists(fileName))
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
string mimetype = fileName.EndsWith("zip") ? "application/zip" : "application/octet-stream";
|
||||
var utilityFilePath = FileUtil.GetFullPathForBackupFile(fileName);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(DownloadUser.Id, 0, SockType.NoType, SockEvent.UtilityFileDownload, fileName), ct);
|
||||
return PhysicalFile(utilityFilePath, mimetype, fileName);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete Backup
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{name}")]
|
||||
[Authorize]
|
||||
public ActionResult DeleteBackupFile([FromRoute] string name)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.Backup))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
FileUtil.EraseBackupFile(name);
|
||||
//never errors only no content
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
//DANGER: MUST ADD AUTHORIZE ATTRIBUTE TO ANY NEW ROUTES
|
||||
//[Authorize]
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
233
server/Controllers/CustomerController.cs
Normal file
233
server/Controllers/CustomerController.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
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.Linq;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/customer")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class CustomerController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<CustomerController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public CustomerController(AyContext dbcontext, ILogger<CustomerController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Customer
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostCustomer([FromBody] Customer newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
Customer o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(CustomerController.GetCustomer), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Customer
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Customer</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetCustomer([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Customer
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutCustomer([FromBody] Customer updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Customer
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteCustomer([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Alert notes for this customer
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Alert notes or null</returns>
|
||||
[HttpGet("alert/{id}")]
|
||||
public async Task<IActionResult> GetCustomerAlert([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
return Ok(ApiOkResponse.Response(await ct.Customer.AsNoTracking().Where(x => x.Id == id).Select(x => x.AlertNotes).FirstOrDefaultAsync()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get addresses of interest for Customer id provided
|
||||
/// (postal, physical, headoffice postal if billheadoffice=true)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Multiple addresses</returns>
|
||||
[HttpGet("address/{id}")]
|
||||
public async Task<IActionResult> GetCustomerBillToAddress([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var cust = await ct.Customer.AsNoTracking().Where(x => x.Id == id).FirstOrDefaultAsync();
|
||||
if (cust == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
HeadOffice head = null;
|
||||
if (cust.BillHeadOffice == true && cust.HeadOfficeId != null)
|
||||
head = await ct.HeadOffice.AsNoTracking().Where(x => x.Id == cust.HeadOfficeId).FirstOrDefaultAsync();
|
||||
|
||||
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
customerpost = new PostalAddressRecord(cust.Name, cust.PostAddress, cust.PostCity, cust.PostRegion, cust.PostCountry, cust.PostCode),
|
||||
customerphys = new AddressRecord(cust.Name, cust.Address, cust.City, cust.Region, cust.Country, cust.AddressPostal, cust.Latitude, cust.Longitude),
|
||||
headofficepost = (head != null ? new PostalAddressRecord(head.Name, head.PostAddress, head.PostCity, head.PostRegion, head.PostCountry, head.PostCode) : new PostalAddressRecord("", "", "", "", "", ""))
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get list for accounting integrations
|
||||
/// </summary>
|
||||
/// <returns>NameIdActive list</returns>
|
||||
[HttpGet("accounting-list")]
|
||||
public async Task<IActionResult> GetAccountingList()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
CustomerBiz biz = CustomerBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetNameIdActiveItemsAsync();
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Get service (physical) address for this customer
|
||||
// /// </summary>
|
||||
// /// <param name="id"></param>
|
||||
// /// <returns>Service address</returns>
|
||||
// [HttpGet("service-address/{id}")]
|
||||
// public async Task<IActionResult> GetCustomerServiceAddress([FromRoute] long id)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// var cust = await ct.Customer.AsNoTracking().Where(x => x.Id == id).FirstOrDefaultAsync();
|
||||
// if (cust == null)
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
// return Ok(ApiOkResponse.Response(new
|
||||
// {
|
||||
// customer = new AddressRecord(cust.Address, cust.City, cust.Region, cust.Country, cust.Latitude, cust.Longitude)
|
||||
// }));
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
137
server/Controllers/CustomerNoteController.cs
Normal file
137
server/Controllers/CustomerNoteController.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/customer-note")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class CustomerNoteController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<CustomerNoteController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public CustomerNoteController(AyContext dbcontext, ILogger<CustomerNoteController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create CustomerNote
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostCustomerNote([FromBody] CustomerNote newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerNote o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(CustomerNoteController.GetCustomerNote), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get CustomerNote
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>CustomerNote</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetCustomerNote([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update CustomerNote
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutCustomerNote([FromBody] CustomerNote updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete CustomerNote
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteCustomerNote([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerNoteBiz biz = CustomerNoteBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
206
server/Controllers/CustomerNotifySubscriptionController.cs
Normal file
206
server/Controllers/CustomerNotifySubscriptionController.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/customer-notify-subscription")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class CustomerNotifySubscriptionController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<CustomerNotifySubscriptionController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public CustomerNotifySubscriptionController(AyContext dbcontext, ILogger<CustomerNotifySubscriptionController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create CustomerNotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostCustomerNotifySubscription([FromBody] CustomerNotifySubscription newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerNotifySubscription o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(CustomerNotifySubscriptionController.GetCustomerNotifySubscription), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get CustomerNotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>CustomerNotifySubscription</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetCustomerNotifySubscription([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update CustomerNotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutCustomerNotifySubscription([FromBody] CustomerNotifySubscription updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete CustomerNotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteCustomerNotifySubscription([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
CustomerNotifySubscriptionBiz biz = CustomerNotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get a list of which customers will potentially receive a notification based on tags
|
||||
/// </summary>
|
||||
/// <param name="customerTags"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("who")]
|
||||
public async Task<IActionResult> GetWho([FromBody] List<string> customerTags, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, SockType.CustomerNotifySubscription))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
string custTagsWhere = DataList.DataListSqlFilterCriteriaBuilder.TagFilterToSqlCriteriaHelper("acustomer.tags", customerTags, false);
|
||||
|
||||
|
||||
List<CustomerNotifySubscriptionWhoRecord> ret = new List<CustomerNotifySubscriptionWhoRecord>();
|
||||
using (var cmd = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
await ct.Database.OpenConnectionAsync();
|
||||
cmd.CommandText = $"select id, name, COALESCE(emailaddress,'') from acustomer where active=true {custTagsWhere} order by name";
|
||||
using (var dr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (dr.Read())
|
||||
{
|
||||
ret.Add(new CustomerNotifySubscriptionWhoRecord(dr.GetInt64(0), dr.GetString(1), dr.GetString(2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WHERE ARRAY['red','green'::varchar(255)] <@ tags
|
||||
//array1.All(i => array2.Contains(i)) array1 <@ array2
|
||||
//https://www.npgsql.org/efcore/mapping/array.html
|
||||
// var ret = await ct.Customer.Where(z => z.Active == true && (customerTags.All(i => z.Tags.Contains(i)))).Select(c => new CustomerNotifySubscriptionWhoRecord(c.Id, c.Name, c.EmailAddress)).ToListAsync();
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
private record CustomerNotifySubscriptionWhoRecord(long Id, string Name, string EmailAddress);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Subscription list
|
||||
/// </summary>
|
||||
/// <returns>User's notification subscription list </returns>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.CustomerNotifySubscription))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
var subs = await ct.CustomerNotifySubscription.AsNoTracking().OrderBy(z => z.Id).ToListAsync();
|
||||
|
||||
List<CustomerNotifySubscriptionRecord> ret = new List<CustomerNotifySubscriptionRecord>();
|
||||
foreach (var s in subs)
|
||||
{
|
||||
ret.Add(new CustomerNotifySubscriptionRecord(s.Id, s.EventType, s.SockType, s.CustomerTags, s.Tags, "na-status", s.AgeValue, s.DecValue));
|
||||
}
|
||||
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
private record CustomerNotifySubscriptionRecord(
|
||||
long id, NotifyEventType eventType, SockType SockType,
|
||||
List<string> customerTags, List<string> tags, string status, TimeSpan ageValue, decimal decValue
|
||||
);
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
124
server/Controllers/DashboardViewController.cs
Normal file
124
server/Controllers/DashboardViewController.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/dashboard-view")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class DashboardViewController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<DashboardViewController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public DashboardViewController(AyContext dbcontext, ILogger<DashboardViewController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get DashboardView object for current User
|
||||
/// There is always one for each user
|
||||
/// </summary>
|
||||
/// <returns>Dashboard view</returns>
|
||||
[HttpGet()]
|
||||
public async Task<IActionResult> GetDashboardView()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
DashboardViewBiz biz = DashboardViewBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
//user always has full access to their own dashboard view and can only access their own through api so no need to check
|
||||
// if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var o = await biz.GetAsync();
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update logged in User's Dashboard view
|
||||
/// </summary>
|
||||
/// <param name="theView"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut()]
|
||||
public async Task<IActionResult> PutDashboardView([FromBody] string theView)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
DashboardViewBiz biz = DashboardViewBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var o = await biz.GetAsync();
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
//user always has full access to their own dashboard view and can only access their own through api so no need to check
|
||||
// if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
try
|
||||
{
|
||||
if (!await biz.PutAsync(o, theView))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!await biz.ExistsAsync())
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
else
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
136
server/Controllers/DataListColumnViewController.cs
Normal file
136
server/Controllers/DataListColumnViewController.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/data-list-column-view")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class DataListColumnViewController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<DataListColumnViewController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public DataListColumnViewController(AyContext dbcontext, ILogger<DataListColumnViewController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get DataListColumnView for current user
|
||||
/// </summary>
|
||||
/// <param name="listKey"></param>
|
||||
/// <returns>DataListColumnView</returns>
|
||||
[HttpGet("{listKey}")]
|
||||
public async Task<IActionResult> GetDataListColumnView([FromRoute] string listKey)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(biz.UserId, listKey, true);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Replace DataListColumnView
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ReplaceDataListColumnView([FromBody] DataListColumnView newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
DataListColumnView o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reset DataListColumnView to factory defaults
|
||||
/// </summary>
|
||||
/// <param name="listKey"></param>
|
||||
/// <returns>Default DataListColumnView</returns>
|
||||
[HttpDelete("{listKey}")]
|
||||
public async Task<IActionResult> ResetDataListColumnView([FromRoute] string listKey)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext);
|
||||
var o = await biz.DeleteAsync(biz.UserId, listKey);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update sort order for user's CoulumnView for DataList key specified
|
||||
/// </summary>
|
||||
/// <param name="sortRequest">e.g.{"listKey":"CustomerDataList","sortBy":["CustomerPhone1","CustomerEmail"],"sortDesc":[false,false]}</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("sort")]
|
||||
public async Task<IActionResult> SetSort([FromBody] DataListSortRequest sortRequest, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
DataListColumnViewBiz biz = DataListColumnViewBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if(!await biz.SetSort(sortRequest))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return Ok();
|
||||
}
|
||||
public record SortRequest(string ListKey, string[] sortBy, bool[] sortDesc);
|
||||
//{"listKey":"CustomerDataList","sortBy":["CustomerPhone1","CustomerEmail"],"sortDesc":[false,false]}
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
187
server/Controllers/DataListController.cs
Normal file
187
server/Controllers/DataListController.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.DataList;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using EnumsNET;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/data-list")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class DataListController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<DataListController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public DataListController(AyContext dbcontext, ILogger<DataListController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get list of data for selection / viewing
|
||||
///
|
||||
/// Authorization varies list by list, will return 403 - Not Authorized if user has insufficient role
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="tableRequest">List key, Paging, filtering and sorting options</param>
|
||||
/// <returns>Collection with paging data</returns>
|
||||
// [HttpPost("List", Name = nameof(List))]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> List([FromBody] DataListTableRequest tableRequest)
|
||||
{
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
if (!serverState.IsOpen && UserId != 1)//bypass for superuser to view list of Users to fix license issues
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (tableRequest.Limit == null || tableRequest.Limit < 1)
|
||||
{
|
||||
tableRequest.Limit = DataListTableProcessingOptions.DefaultLimit;
|
||||
}
|
||||
if (tableRequest.Offset == null)
|
||||
{
|
||||
tableRequest.Offset = 0;
|
||||
}
|
||||
|
||||
|
||||
var UserRoles = UserRolesFromContext.Roles(HttpContext.Items);
|
||||
|
||||
var UType = UserTypeFromContext.Type(HttpContext.Items);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
DataListColumnViewBiz viewbiz = DataListColumnViewBiz.GetBiz(ct, HttpContext);
|
||||
var SavedView = await viewbiz.GetAsync(UserId, tableRequest.DataListKey, true);
|
||||
|
||||
DataListSavedFilter SavedFilter = null;
|
||||
if (tableRequest.FilterId != 0)
|
||||
{
|
||||
DataListSavedFilterBiz filterbiz = DataListSavedFilterBiz.GetBiz(ct, HttpContext);
|
||||
SavedFilter = await filterbiz.GetAsync(tableRequest.FilterId);
|
||||
}
|
||||
var DataList = DataListFactory.GetAyaDataList(tableRequest.DataListKey, UserTranslationIdFromContext.Id(HttpContext.Items));
|
||||
if (DataList == null)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "DataListKey", $"DataList \"{tableRequest.DataListKey}\" specified does not exist"));
|
||||
|
||||
|
||||
|
||||
//check rights
|
||||
if (!UserRoles.HasAnyFlags(DataList.AllowedRoles))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
//IF user is a customer type check if they are allowed to view this datalist
|
||||
//and build the data list internal 'client' criteria
|
||||
if (UType == UserType.Customer || UType == UserType.HeadOffice)
|
||||
if (!await HandleCustomerTypeUserDataListRequest(UserId, tableRequest))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
//hydrate the saved view and filter
|
||||
DataListTableProcessingOptions dataListTableOptions = new DataListTableProcessingOptions(tableRequest, DataList, SavedView, SavedFilter, UserId, UserRoles);
|
||||
DataListReturnData r = await DataListFetcher.GetResponseAsync(ct, dataListTableOptions, DataList, UserRoles, log, UserId);
|
||||
return Ok(r);
|
||||
}
|
||||
catch (System.UnauthorizedAccessException)
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
catch (System.ArgumentOutOfRangeException e)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, e.Message));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> HandleCustomerTypeUserDataListRequest(long currentUserId, DataListTableRequest tableRequest)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
return false;
|
||||
// //Is this list allowed for a customer user and also enabled in global settings
|
||||
// switch (tableRequest.DataListKey)
|
||||
// {
|
||||
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// //Build client criteria if user is of correct type
|
||||
// var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == currentUserId).Select(x => new { x.UserType, x.CustomerId, x.HeadOfficeId }).SingleOrDefaultAsync();
|
||||
// switch (UserInfo.UserType)
|
||||
// {
|
||||
// case UserType.Customer:
|
||||
// if (UserInfo.CustomerId == null || UserInfo.CustomerId == 0) return false;
|
||||
// tableRequest.ClientCriteria = $"{UserInfo.CustomerId},{(int)SockType.Customer}";
|
||||
// break;
|
||||
// case UserType.HeadOffice:
|
||||
// if (UserInfo.HeadOfficeId == null || UserInfo.HeadOfficeId == 0) return false;
|
||||
// tableRequest.ClientCriteria = $"{UserInfo.HeadOfficeId},{(int)SockType.HeadOffice}";
|
||||
// break;
|
||||
// default://other user type
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of all DataList keys available
|
||||
/// </summary>
|
||||
/// <returns>List of strings</returns>
|
||||
[HttpGet("listkeys")]
|
||||
public ActionResult GetDataListKeys()
|
||||
{
|
||||
//NOTE: not used by Sockeye Client, convenience method for developers api usage
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
return Ok(ApiOkResponse.Response(DataListFactory.GetListOfAllDataListKeyNames()));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List of all fields for data list key specified
|
||||
/// </summary>
|
||||
/// <returns>List of DataListFieldDefinition</returns>
|
||||
[HttpGet("listfields")]
|
||||
public ActionResult GetDataListFields([FromQuery] string DataListKey)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
var DataList = DataListFactory.GetAyaDataList(DataListKey, UserTranslationIdFromContext.Id(HttpContext.Items));
|
||||
//was the name not found as a list?
|
||||
if (DataList == null)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, $"DataList \"{DataListKey}\" specified does not exist"));
|
||||
}
|
||||
|
||||
var ExternalOnly = DataList.FieldDefinitions.Where(z => z.IsMeta == false);
|
||||
return Ok(ApiOkResponse.Response(ExternalOnly));
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
189
server/Controllers/DataListSavedFilterController.cs
Normal file
189
server/Controllers/DataListSavedFilterController.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/data-list-filter")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class DataListSavedFilterController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<DataListSavedFilterController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public DataListSavedFilterController(AyContext dbcontext, ILogger<DataListSavedFilterController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get full DataListSavedFilter object
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>A single DataListSavedFilter</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetDataListSavedFilter([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get DataListSavedFilter list
|
||||
/// </summary>
|
||||
/// <returns>List of public or owned data list views listKey provided</returns>
|
||||
[HttpGet("list", Name = nameof(DataListSavedFilterList))]
|
||||
public async Task<IActionResult> DataListSavedFilterList([FromQuery] string ListKey)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var l = await biz.GetViewListAsync(ListKey);
|
||||
return Ok(ApiOkResponse.Response(l));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update DataListSavedFilter
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutDataListSavedFilter([FromBody] DataListSavedFilter updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create DataListSavedFilter
|
||||
/// </summary>
|
||||
/// <param name="inObj"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostDataListSavedFilter([FromBody] DataListSavedFilter inObj, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Default filters can never be created from outside
|
||||
//they are only ever created from inside so a post with a default=true is always invalid
|
||||
if (inObj.DefaultFilter == true)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "default", "Default filters can only be created internally"));
|
||||
}
|
||||
|
||||
//Create and validate
|
||||
DataListSavedFilter o = await biz.CreateAsync(inObj);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(DataListSavedFilterController.GetDataListSavedFilter), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete DataListSavedFilter
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteDataListSavedFilter([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
DataListSavedFilterBiz biz = DataListSavedFilterBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
if (!await biz.DeleteAsync(o))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
585
server/Controllers/EnumListController.cs
Normal file
585
server/Controllers/EnumListController.cs
Normal file
@@ -0,0 +1,585 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Util;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Enum list controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/enum-list")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class EnumListController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<EnumListController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public EnumListController(AyContext dbcontext, ILogger<EnumListController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get name value Translated display value list of Sockeye enumerated types for list specified
|
||||
/// </summary>
|
||||
/// <param name="enumkey">The key name of the enumerated type</param>
|
||||
/// <returns>List</returns>
|
||||
[HttpGet("list/{enumkey}")]
|
||||
public async Task<IActionResult> GetList([FromRoute] string enumkey)
|
||||
{
|
||||
if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
var ret = await GetEnumList(enumkey, UserTranslationIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get all possible enumerated values list key names
|
||||
/// </summary>
|
||||
/// <returns>List of Sockeye enumerated type list key names that can be fetched from the GetList Route</returns>
|
||||
[HttpGet("listkeys")]
|
||||
public ActionResult GetTypesList()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
List<KeyValuePair<string, string>> ret = new List<KeyValuePair<string, string>>();
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(UserType).ToString()), "Sockeye user account types"));
|
||||
ret.Add(new KeyValuePair<string, string>("insideusertype", "Sockeye user account types for staff / contractors"));
|
||||
ret.Add(new KeyValuePair<string, string>("outsideusertype", "Sockeye user account types for customer / headoffice users"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()), "Sockeye user account role types"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(SockType).ToString()), "All Sockeye object types untranslated"));
|
||||
ret.Add(new KeyValuePair<string, string>("alltranslated", "All Sockeye object types translated"));
|
||||
ret.Add(new KeyValuePair<string, string>("coreall", "All Core Sockeye business object types"));
|
||||
ret.Add(new KeyValuePair<string, string>("coreview", "All Core Sockeye business object types current user is allowed to view"));
|
||||
ret.Add(new KeyValuePair<string, string>("coreedit", "All Core Sockeye business object types current user is allowed to edit"));
|
||||
ret.Add(new KeyValuePair<string, string>("reportable", "All Sockeye business object types that are reportable"));
|
||||
ret.Add(new KeyValuePair<string, string>("importable", "All Sockeye business object types that are importable"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(UiFieldDataType).ToString()), "Types of data used in Sockeye for display and formatting UI purposes"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()), "Notification event types"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(NotifyDeliveryMethod).ToString()), "Notification delivery methods"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(NotifyMailSecurity).ToString()), "Notification SMTP mail server security method"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(SockEvent).ToString()), "Event log object change types"));
|
||||
ret.Add(new KeyValuePair<string, string>(StringUtil.TrimTypeName(typeof(SockDaysOfWeek).ToString()), "Days of the week"));
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static async Task<List<NameIdItem>> GetEnumList(string enumKey, long translationId, AuthorizationRoles userRoles)
|
||||
{
|
||||
|
||||
List<string> TranslationKeysToFetch = new List<string>();
|
||||
|
||||
List<NameIdItem> ReturnList = new List<NameIdItem>();
|
||||
|
||||
var keyNameInLowerCase = enumKey.ToLowerInvariant();
|
||||
|
||||
|
||||
if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(UiFieldDataType).ToString()).ToLowerInvariant())
|
||||
{
|
||||
//Iterate the enum and get the values
|
||||
Type t = typeof(UiFieldDataType);
|
||||
Enum.GetName(t, UiFieldDataType.NoType);
|
||||
foreach (var dt in Enum.GetValues(t))
|
||||
{
|
||||
ReturnList.Add(new NameIdItem() { Name = Enum.GetName(t, dt), Id = (int)dt });
|
||||
}
|
||||
|
||||
}
|
||||
else if (keyNameInLowerCase == "coreall")
|
||||
{
|
||||
//core biz objects for UI facing purposes such as search form limit to object type etc
|
||||
var values = Enum.GetValues(typeof(SockType));
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
if (t.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
{
|
||||
TranslationKeysToFetch.Add(t.ToString());
|
||||
}
|
||||
}
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
if (t.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
{
|
||||
var tName = t.ToString();
|
||||
string name = string.Empty;
|
||||
if (LT.ContainsKey(tName))
|
||||
{
|
||||
name = LT[tName];
|
||||
}
|
||||
else
|
||||
{
|
||||
name = tName;
|
||||
}
|
||||
ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (keyNameInLowerCase == "reportable")
|
||||
{
|
||||
//reportable biz objects for report designer selection
|
||||
var values = Enum.GetValues(typeof(SockType));
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
if (t.HasAttribute(typeof(ReportableBizObjectAttribute)))
|
||||
{
|
||||
TranslationKeysToFetch.Add(t.ToString());
|
||||
}
|
||||
}
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
if (t.HasAttribute(typeof(ReportableBizObjectAttribute)))
|
||||
{
|
||||
var tName = t.ToString();
|
||||
string name = string.Empty;
|
||||
if (LT.ContainsKey(tName))
|
||||
{
|
||||
name = LT[tName];
|
||||
}
|
||||
else
|
||||
{
|
||||
name = tName;
|
||||
}
|
||||
ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (keyNameInLowerCase == "importable")
|
||||
{
|
||||
//importable biz objects for administration -> import selection
|
||||
var values = Enum.GetValues(typeof(SockType));
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
if (t.HasAttribute(typeof(ImportableBizObjectAttribute)))
|
||||
{
|
||||
|
||||
var nameToFetch = t.ToString();
|
||||
|
||||
TranslationKeysToFetch.Add(nameToFetch);
|
||||
}
|
||||
}
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
if (t.HasAttribute(typeof(ImportableBizObjectAttribute)))
|
||||
{
|
||||
var tName = t.ToString();
|
||||
|
||||
string name = string.Empty;
|
||||
if (LT.ContainsKey(tName))
|
||||
{
|
||||
name = LT[tName];
|
||||
}
|
||||
else
|
||||
{
|
||||
name = tName;
|
||||
}
|
||||
ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (keyNameInLowerCase == "coreview")//all core objects user can read
|
||||
{
|
||||
|
||||
//core biz objects for UI facing purposes
|
||||
var rawvalues = Enum.GetValues(typeof(SockType));
|
||||
|
||||
List<SockType> allowedValues = new List<SockType>();
|
||||
foreach (SockType t in rawvalues)
|
||||
{
|
||||
if (Authorized.HasReadFullRole(userRoles, t))
|
||||
allowedValues.Add(t);
|
||||
}
|
||||
|
||||
foreach (SockType t in allowedValues)
|
||||
{
|
||||
if (t.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
{
|
||||
TranslationKeysToFetch.Add(t.ToString());
|
||||
}
|
||||
}
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
foreach (SockType t in allowedValues)
|
||||
{
|
||||
if (t.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
{
|
||||
var tName = t.ToString();
|
||||
string name = string.Empty;
|
||||
if (LT.ContainsKey(tName))
|
||||
{
|
||||
name = LT[tName];
|
||||
}
|
||||
else
|
||||
{
|
||||
name = tName;
|
||||
}
|
||||
ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (keyNameInLowerCase == "coreedit")//all core objects current user can edit
|
||||
{
|
||||
//core biz objects for UI facing purposes
|
||||
var rawvalues = Enum.GetValues(typeof(SockType));
|
||||
|
||||
List<SockType> allowedValues = new List<SockType>();
|
||||
foreach (SockType t in rawvalues)
|
||||
{
|
||||
if (Authorized.HasModifyRole(userRoles, t))
|
||||
allowedValues.Add(t);
|
||||
}
|
||||
|
||||
foreach (SockType t in allowedValues)
|
||||
{
|
||||
if (t.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
{
|
||||
TranslationKeysToFetch.Add(t.ToString());
|
||||
}
|
||||
}
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
foreach (SockType t in allowedValues)
|
||||
{
|
||||
if (t.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
{
|
||||
var tName = t.ToString();
|
||||
string name = string.Empty;
|
||||
if (LT.ContainsKey(tName))
|
||||
{
|
||||
name = LT[tName];
|
||||
}
|
||||
else
|
||||
{
|
||||
name = tName;
|
||||
}
|
||||
ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (keyNameInLowerCase == "alltranslated")//all socktype objects with translations
|
||||
{
|
||||
//all types for search results and history UI
|
||||
var rawvalues = Enum.GetValues(typeof(SockType));
|
||||
foreach (SockType t in rawvalues)
|
||||
{
|
||||
TranslationKeysToFetch.Add(t.ToString());
|
||||
}
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
foreach (SockType t in rawvalues)
|
||||
{
|
||||
var tName = t.ToString();
|
||||
string name = string.Empty;
|
||||
if (LT.ContainsKey(tName))
|
||||
{
|
||||
name = LT[tName];
|
||||
}
|
||||
else
|
||||
{
|
||||
name = tName;
|
||||
}
|
||||
ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
|
||||
}
|
||||
}
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(SockType).ToString()).ToLowerInvariant())
|
||||
{
|
||||
//All types, used by search form and possibly others
|
||||
|
||||
var values = Enum.GetValues(typeof(SockType));
|
||||
|
||||
foreach (SockType t in values)
|
||||
TranslationKeysToFetch.Add(t.ToString());
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
string tName = t.ToString();
|
||||
string name = string.Empty;
|
||||
if (LT.ContainsKey(tName))
|
||||
{
|
||||
name = LT[tName];
|
||||
}
|
||||
else
|
||||
{
|
||||
name = tName;
|
||||
}
|
||||
ReturnList.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
}
|
||||
}
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(UserType).ToString()).ToLowerInvariant())
|
||||
{
|
||||
|
||||
TranslationKeysToFetch.Add("UserTypeService");
|
||||
TranslationKeysToFetch.Add("UserTypeNotService");
|
||||
TranslationKeysToFetch.Add("UserTypeCustomer");
|
||||
TranslationKeysToFetch.Add("UserTypeHeadOffice");
|
||||
TranslationKeysToFetch.Add("UserTypeServiceContractor");
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeService"], Id = (long)UserType.Service });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeNotService"], Id = (long)UserType.NotService });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeCustomer"], Id = (long)UserType.Customer });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeHeadOffice"], Id = (long)UserType.HeadOffice });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeServiceContractor"], Id = (long)UserType.ServiceContractor });
|
||||
}
|
||||
else if (keyNameInLowerCase == "insideusertype")
|
||||
{
|
||||
|
||||
TranslationKeysToFetch.Add("UserTypeService");
|
||||
TranslationKeysToFetch.Add("UserTypeNotService");
|
||||
TranslationKeysToFetch.Add("UserTypeServiceContractor");
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeService"], Id = (long)UserType.Service });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeNotService"], Id = (long)UserType.NotService });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeServiceContractor"], Id = (long)UserType.ServiceContractor });
|
||||
}
|
||||
else if (keyNameInLowerCase == "outsideusertype")
|
||||
{
|
||||
|
||||
|
||||
TranslationKeysToFetch.Add("UserTypeCustomer");
|
||||
TranslationKeysToFetch.Add("UserTypeHeadOffice");
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeCustomer"], Id = (long)UserType.Customer });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["UserTypeHeadOffice"], Id = (long)UserType.HeadOffice });
|
||||
}
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(SockEvent).ToString()).ToLowerInvariant())
|
||||
{
|
||||
TranslationKeysToFetch.Add("EventDeleted");
|
||||
TranslationKeysToFetch.Add("EventCreated");
|
||||
TranslationKeysToFetch.Add("EventRetrieved");
|
||||
TranslationKeysToFetch.Add("EventModified");
|
||||
TranslationKeysToFetch.Add("EventAttachmentCreate");
|
||||
TranslationKeysToFetch.Add("EventAttachmentDelete");
|
||||
TranslationKeysToFetch.Add("EventAttachmentDownload");
|
||||
TranslationKeysToFetch.Add("EventLicenseFetch");
|
||||
TranslationKeysToFetch.Add("EventLicenseTrialRequest");
|
||||
TranslationKeysToFetch.Add("EventServerStateChange");
|
||||
TranslationKeysToFetch.Add("EventSeedDatabase");
|
||||
TranslationKeysToFetch.Add("EventAttachmentModified");
|
||||
TranslationKeysToFetch.Add("AdminEraseDatabase");
|
||||
TranslationKeysToFetch.Add("EventResetSerial");
|
||||
TranslationKeysToFetch.Add("EventUtilityFileDownload");
|
||||
TranslationKeysToFetch.Add("NotifyEventDirectSMTPMessage");
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventDeleted"], Id = (long)SockEvent.Deleted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventCreated"], Id = (long)SockEvent.Created });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventRetrieved"], Id = (long)SockEvent.Retrieved });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventModified"], Id = (long)SockEvent.Modified });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentCreate"], Id = (long)SockEvent.AttachmentCreate });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentDelete"], Id = (long)SockEvent.AttachmentDelete });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentDownload"], Id = (long)SockEvent.AttachmentDownload });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventLicenseFetch"], Id = (long)SockEvent.LicenseFetch });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventLicenseTrialRequest"], Id = (long)SockEvent.LicenseTrialRequest });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventServerStateChange"], Id = (long)SockEvent.ServerStateChange });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventSeedDatabase"], Id = (long)SockEvent.SeedDatabase });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventAttachmentModified"], Id = (long)SockEvent.AttachmentModified });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AdminEraseDatabase"], Id = (long)SockEvent.EraseAllData });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventResetSerial"], Id = (long)SockEvent.ResetSerial });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["EventUtilityFileDownload"], Id = (long)SockEvent.UtilityFileDownload });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventDirectSMTPMessage"], Id = (long)SockEvent.DirectSMTP });
|
||||
}
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()).ToLowerInvariant())
|
||||
{
|
||||
|
||||
// TranslationKeysToFetch.Add("AuthorizationRoleNoRole");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleBizAdminRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleBizAdmin");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleServiceRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleService");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleInventoryRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleInventory");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleAccounting");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleTechRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleTech");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleSubContractorRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleSubContractor");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleCustomerRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleCustomer");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleOpsAdminRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleOpsAdmin");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleSalesRestricted");
|
||||
TranslationKeysToFetch.Add("AuthorizationRoleSales");
|
||||
// TranslationKeysToFetch.Add("AuthorizationRoleAll");
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
// ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleNoRole"], Id = (long)AuthorizationRoles.NoRole });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleBizAdminRestricted"], Id = (long)AuthorizationRoles.BizAdminRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleBizAdmin"], Id = (long)AuthorizationRoles.BizAdmin });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleServiceRestricted"], Id = (long)AuthorizationRoles.ServiceRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleService"], Id = (long)AuthorizationRoles.Service });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleInventoryRestricted"], Id = (long)AuthorizationRoles.InventoryRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleInventory"], Id = (long)AuthorizationRoles.Inventory });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleAccounting"], Id = (long)AuthorizationRoles.Accounting });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleTechRestricted"], Id = (long)AuthorizationRoles.TechRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleTech"], Id = (long)AuthorizationRoles.Tech });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSubContractorRestricted"], Id = (long)AuthorizationRoles.SubContractorRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSubContractor"], Id = (long)AuthorizationRoles.SubContractor });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleCustomerRestricted"], Id = (long)AuthorizationRoles.CustomerRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleCustomer"], Id = (long)AuthorizationRoles.Customer });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleOpsAdminRestricted"], Id = (long)AuthorizationRoles.OpsAdminRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleOpsAdmin"], Id = (long)AuthorizationRoles.OpsAdmin });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSalesRestricted"], Id = (long)AuthorizationRoles.SalesRestricted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleSales"], Id = (long)AuthorizationRoles.Sales });
|
||||
// ReturnList.Add(new NameIdItem() { Name = LT["AuthorizationRoleAll"], Id = (long)AuthorizationRoles.All });
|
||||
|
||||
}
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()).ToLowerInvariant())
|
||||
{
|
||||
TranslationKeysToFetch.Add("NotifyEventObjectDeleted");
|
||||
TranslationKeysToFetch.Add("NotifyEventObjectCreated");
|
||||
TranslationKeysToFetch.Add("NotifyEventObjectModified");
|
||||
TranslationKeysToFetch.Add("NotifyEventWorkorderStatusChange");
|
||||
TranslationKeysToFetch.Add("NotifyEventContractExpiring");
|
||||
TranslationKeysToFetch.Add("NotifyEventCSRAccepted");
|
||||
TranslationKeysToFetch.Add("NotifyEventCSRRejected");
|
||||
TranslationKeysToFetch.Add("NotifyEventWorkorderCompleted");
|
||||
TranslationKeysToFetch.Add("NotifyEventQuoteStatusChange");
|
||||
TranslationKeysToFetch.Add("NotifyEventQuoteStatusAge");
|
||||
TranslationKeysToFetch.Add("NotifyEventObjectAge");
|
||||
TranslationKeysToFetch.Add("NotifyEventServiceBankDepleted");
|
||||
TranslationKeysToFetch.Add("NotifyEventReminderImminent");
|
||||
TranslationKeysToFetch.Add("NotifyEventScheduledOnWorkorder");
|
||||
TranslationKeysToFetch.Add("NotifyEventScheduledOnWorkorderImminent");
|
||||
TranslationKeysToFetch.Add("NotifyEventWorkorderCompletedStatusOverdue");
|
||||
TranslationKeysToFetch.Add("NotifyEventOutsideServiceOverdue");
|
||||
TranslationKeysToFetch.Add("NotifyEventOutsideServiceReceived");
|
||||
TranslationKeysToFetch.Add("NotifyEventPartRequestReceived");
|
||||
TranslationKeysToFetch.Add("NotifyEventNotifyHealthCheck");
|
||||
TranslationKeysToFetch.Add("NotifyEventBackupStatus");
|
||||
TranslationKeysToFetch.Add("NotifyEventCustomerServiceImminent");
|
||||
TranslationKeysToFetch.Add("NotifyEventPMStopGeneratingDateReached");
|
||||
TranslationKeysToFetch.Add("NotifyEventWorkorderTotalExceedsThreshold");
|
||||
TranslationKeysToFetch.Add("NotifyEventWorkorderStatusAge");
|
||||
TranslationKeysToFetch.Add("NotifyEventUnitWarrantyExpiry");
|
||||
TranslationKeysToFetch.Add("NotifyEventUnitMeterReadingMultipleExceeded");
|
||||
TranslationKeysToFetch.Add("NotifyEventServerOperationsProblem");
|
||||
TranslationKeysToFetch.Add("NotifyEventGeneralNotification");
|
||||
//TranslationKeysToFetch.Add("NotifyEventCopyOfCustomerNotification");
|
||||
TranslationKeysToFetch.Add("NotifyEventWorkorderCreatedForCustomer");
|
||||
TranslationKeysToFetch.Add("NotifyEventPMGenerationFailed");
|
||||
TranslationKeysToFetch.Add("NotifyEventPMInsufficientInventory");
|
||||
TranslationKeysToFetch.Add("NotifyEventReviewImminent");
|
||||
TranslationKeysToFetch.Add("NotifyEventDirectSMTPMessage");
|
||||
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectDeleted"], Id = (long)NotifyEventType.ObjectDeleted });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectCreated"], Id = (long)NotifyEventType.ObjectCreated });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectModified"], Id = (long)NotifyEventType.ObjectModified });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventObjectAge"], Id = (long)NotifyEventType.ObjectAge });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventReminderImminent"], Id = (long)NotifyEventType.ReminderImminent });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventReviewImminent"], Id = (long)NotifyEventType.ReviewImminent });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventNotifyHealthCheck"], Id = (long)NotifyEventType.NotifyHealthCheck });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventBackupStatus"], Id = (long)NotifyEventType.BackupStatus });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventServerOperationsProblem"], Id = (long)NotifyEventType.ServerOperationsProblem });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventGeneralNotification"], Id = (long)NotifyEventType.GeneralNotification });
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyEventDirectSMTPMessage"], Id = (long)NotifyEventType.DirectSMTPMessage });
|
||||
|
||||
}
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(NotifyDeliveryMethod).ToString()).ToLowerInvariant())
|
||||
{
|
||||
TranslationKeysToFetch.Add("NotifyDeliveryMethodApp");
|
||||
TranslationKeysToFetch.Add("NotifyDeliveryMethodSMTP");
|
||||
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyDeliveryMethodApp"], Id = (long)NotifyDeliveryMethod.App });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyDeliveryMethodSMTP"], Id = (long)NotifyDeliveryMethod.SMTP });
|
||||
}
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(NotifyMailSecurity).ToString()).ToLowerInvariant())
|
||||
{
|
||||
TranslationKeysToFetch.Add("NotifyMailSecurityNone");
|
||||
TranslationKeysToFetch.Add("NotifyMailSecuritySSLTLS");
|
||||
TranslationKeysToFetch.Add("NotifyMailSecurityStartTls");
|
||||
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyMailSecurityNone"], Id = (long)NotifyMailSecurity.None });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyMailSecuritySSLTLS"], Id = (long)NotifyMailSecurity.SSLTLS });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["NotifyMailSecurityStartTls"], Id = (long)NotifyMailSecurity.StartTls });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
else if (keyNameInLowerCase == StringUtil.TrimTypeName(typeof(SockDaysOfWeek).ToString()).ToLowerInvariant())
|
||||
{
|
||||
|
||||
TranslationKeysToFetch.Add("DayMonday");
|
||||
TranslationKeysToFetch.Add("DayTuesday");
|
||||
TranslationKeysToFetch.Add("DayWednesday");
|
||||
TranslationKeysToFetch.Add("DayThursday");
|
||||
TranslationKeysToFetch.Add("DayFriday");
|
||||
TranslationKeysToFetch.Add("DaySaturday");
|
||||
TranslationKeysToFetch.Add("DaySunday");
|
||||
|
||||
|
||||
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, translationId);
|
||||
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["DayMonday"], Id = (long)SockDaysOfWeek.Monday });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["DayTuesday"], Id = (long)SockDaysOfWeek.Tuesday });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["DayWednesday"], Id = (long)SockDaysOfWeek.Wednesday });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["DayThursday"], Id = (long)SockDaysOfWeek.Thursday });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["DayFriday"], Id = (long)SockDaysOfWeek.Friday });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["DaySaturday"], Id = (long)SockDaysOfWeek.Saturday });
|
||||
ReturnList.Add(new NameIdItem() { Name = LT["DaySunday"], Id = (long)SockDaysOfWeek.Sunday });
|
||||
|
||||
}
|
||||
//#################################################################################################################
|
||||
//################### NEW HERE DO NOT FORGET TO ADD TO LISTS AVAILABLE ABOVE AS WELL ##############################
|
||||
//#################################################################################################################
|
||||
else
|
||||
{
|
||||
ReturnList.Add(new NameIdItem() { Name = $"Unknown enum type list key value {enumKey}", Id = 0 });
|
||||
}
|
||||
|
||||
return ReturnList;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
196
server/Controllers/EventLogController.cs
Normal file
196
server/Controllers/EventLogController.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Log files controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/event-log")]
|
||||
[Authorize]
|
||||
public class EventLogController : ControllerBase
|
||||
{
|
||||
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 EventLogController(AyContext dbcontext, ILogger<LogFilesController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get event log for object and range specified
|
||||
///
|
||||
/// Required Role: Read full object properties rights to object type specified
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Event log entry list for object</returns>
|
||||
[HttpGet("objectlog")]
|
||||
public async Task<IActionResult> GetObjectLog([FromQuery] EventLogOptions opt)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, opt.SockType))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var ret = await EventLogProcessor.GetLogForObjectAsync(opt, UserTranslationIdFromContext.Id(HttpContext.Items), ct);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get event log entries for a specified user and range
|
||||
///
|
||||
/// Required Role: Read rights to User object or User's own data
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Event log for user</returns>
|
||||
[HttpGet("userlog")]
|
||||
public async Task<IActionResult> GetUserLog([FromQuery] UserEventLogOptions opt)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
//If not authorized to read a user and also not the current user asking for their own log then NO LOG FOR YOU!
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.User) && opt.UserId != UserId)
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
var ret = await EventLogProcessor.GetLogForUserAsync(opt, UserTranslationIdFromContext.Id(HttpContext.Items), ct);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// V7 export replace log entry
|
||||
/// (internal use only)
|
||||
/// </summary>
|
||||
/// <param name="inObj"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[HttpPost("v7")]
|
||||
public async Task<IActionResult> PostV7Modify([FromBody] V7Event inObj, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
await EventLogProcessor.V7_Modify_LogAsync(inObj, ct);
|
||||
return NoContent();
|
||||
}
|
||||
public sealed class V7Event
|
||||
{
|
||||
public SockType SockType { get; set; }
|
||||
public long AyId { get; set; }
|
||||
public long Creator { get; set; }
|
||||
public long Modifier { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime Modified { get; set; }
|
||||
|
||||
}
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
public sealed class EventLogOptions
|
||||
{
|
||||
[FromQuery]
|
||||
public SockType SockType { get; set; }
|
||||
[FromQuery]
|
||||
public long AyId { get; set; }
|
||||
[FromQuery]
|
||||
public int? Offset { get; set; }
|
||||
[FromQuery]
|
||||
public int? Limit { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UserEventLogOptions
|
||||
{
|
||||
|
||||
[FromQuery]
|
||||
public long UserId { get; set; }
|
||||
[FromQuery]
|
||||
public int? Offset { get; set; }
|
||||
[FromQuery]
|
||||
public int? Limit { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ObjectEventLog
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public ObjectEventLogItem[] Events { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ObjectEventLogItem
|
||||
{
|
||||
//DateTime, UserId, Event, Textra
|
||||
public DateTime Date { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public SockEvent Event { get; set; }
|
||||
public string Textra { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UserEventLog
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public UserEventLogItem[] Events { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UserEventLogItem
|
||||
{
|
||||
//DateTime, SockType, ObjectId, Event, Textra
|
||||
public DateTime Date { get; set; }
|
||||
public SockType SockType { get; set; }
|
||||
public long ObjectId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public SockEvent Event { get; set; }
|
||||
public string Textra { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
160
server/Controllers/ExportController.cs
Normal file
160
server/Controllers/ExportController.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Util;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.IO;
|
||||
using System;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/export")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ExportController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ExportController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ExportController(AyContext dbcontext, ILogger<ExportController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Export to file
|
||||
/// </summary>
|
||||
/// <param name="selectedRequest"></param>
|
||||
/// <returns>downloadable export file name</returns>
|
||||
[HttpPost("render")]
|
||||
public async Task<IActionResult> RenderExport([FromBody] DataListSelectedRequest selectedRequest)
|
||||
{
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (selectedRequest == null)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "DataListSelectedRequest is required"));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, selectedRequest.SockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
var UserRoles = UserRolesFromContext.Roles(HttpContext.Items);
|
||||
var UserTranslationId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
//Rehydrate id list if necessary
|
||||
if (selectedRequest.SelectedRowIds.Length == 0)
|
||||
selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(
|
||||
selectedRequest,
|
||||
ct,
|
||||
UserRoles,
|
||||
log,
|
||||
UserId,
|
||||
UserTranslationId);
|
||||
|
||||
|
||||
log.LogDebug($"Instantiating biz object handler for {selectedRequest.SockType}");
|
||||
var biz = BizObjectFactory.GetBizObject(selectedRequest.SockType, ct, UserId, UserRoles, UserTranslationId);
|
||||
log.LogDebug($"Fetching data for {selectedRequest.SelectedRowIds.Length} {selectedRequest.SockType} items");
|
||||
string baseFileName = FileUtil.StringToSafeFileName($"{selectedRequest.SockType.ToString().ToLowerInvariant()}-{FileUtil.GetSafeDateFileName()}");
|
||||
string outputSourceFileName = baseFileName + ".json";
|
||||
string outputSourceFullPath = System.IO.Path.Combine(FileUtil.TemporaryFilesFolder, outputSourceFileName);
|
||||
|
||||
log.LogDebug($"Calling render export data to file {outputSourceFullPath}");
|
||||
try
|
||||
{
|
||||
using (StreamWriter file = System.IO.File.CreateText(outputSourceFullPath))
|
||||
using (JsonTextWriter writer = new JsonTextWriter(file))
|
||||
{
|
||||
var dat = await ((IExportAbleObject)biz).GetExportData(selectedRequest, Guid.Empty);//todo: jobify
|
||||
dat.WriteTo(writer);
|
||||
}
|
||||
|
||||
log.LogDebug($"Completed, returning results");
|
||||
return Ok(ApiOkResponse.Response(outputSourceFileName));
|
||||
}
|
||||
catch (ReportRenderTimeOutException)
|
||||
{
|
||||
log.LogInformation($"RenderExport timeout data list key: {selectedRequest.DataListKey}, record count:{selectedRequest.SelectedRowIds.LongLength}, user:{UserNameFromContext.Name(HttpContext.Items)} ");
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "timeout - select fewer records"));
|
||||
}
|
||||
}
|
||||
|
||||
public static IList<dynamic> ToDynamicList(JArray data)
|
||||
{
|
||||
var dynamicData = new List<dynamic>();
|
||||
var expConverter = new Newtonsoft.Json.Converters.ExpandoObjectConverter();
|
||||
|
||||
foreach (var dataItem in data)
|
||||
{
|
||||
dynamic obj = JsonConvert.DeserializeObject<System.Dynamic.ExpandoObject>(dataItem.ToString(), expConverter);
|
||||
dynamicData.Add(obj);
|
||||
}
|
||||
return dynamicData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a rendered Export
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("download/{fileName}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> DownloadAsync([FromRoute] string fileName, [FromQuery] string t)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null)
|
||||
{
|
||||
await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
if (!FileUtil.TemporaryFileExists(fileName))
|
||||
{
|
||||
await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
var FilePath = FileUtil.GetFullPathForTemporaryFile(fileName);
|
||||
|
||||
//including the file name triggers save automatically "attachment" rather than viewing it "inline"
|
||||
return PhysicalFile(FilePath, "application/json", fileName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
221
server/Controllers/FormCustomController.cs
Normal file
221
server/Controllers/FormCustomController.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/form-custom")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class FormCustomController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<FormCustomController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public FormCustomController(AyContext dbcontext, ILogger<FormCustomController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get form customizations for Client form display
|
||||
/// </summary>
|
||||
/// <param name="formkey">The official form key used by Sockeye</param>
|
||||
/// <param name="concurrency">A prior concurrency token used to check if there are any changes without using up bandwidth sending unnecessary data</param>
|
||||
/// <returns>A single FormCustom or nothing and a header 304 not modified</returns>
|
||||
[HttpGet("{formkey}")]
|
||||
public async Task<IActionResult> GetFormCustom([FromRoute] string formkey, [FromQuery] uint? concurrency)
|
||||
{
|
||||
if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
//Just have to be authenticated for this one
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
|
||||
var o = await biz.GetAsync(formkey);
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
//If concurrency token specified then check if ours is newer
|
||||
if (concurrency != null)
|
||||
{
|
||||
if (o.Concurrency == concurrency)
|
||||
{
|
||||
//returns a code 304 (NOT MODIFIED)
|
||||
return StatusCode(304);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get available types allowed for Custom fields
|
||||
/// Used to build UI for customizing a form
|
||||
/// These values are a subset of the AyaDataTypes values
|
||||
/// which can be fetched from the EnumList route
|
||||
/// </summary>
|
||||
/// <returns>A list of valid values for custom field types</returns>
|
||||
[HttpGet("availablecustomtypes")]
|
||||
public ActionResult GetAvailableCustomTypes()
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FormCustom))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
return Ok(ApiOkResponse.Response(CustomFieldType.ValidCustomFieldTypes));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of all customizable form keys
|
||||
/// Used to build UI for customizing a form
|
||||
/// </summary>
|
||||
/// <returns>A list of string formKey values valid for customization</returns>
|
||||
[HttpGet("availablecustomizableformkeys")]
|
||||
public ActionResult GetAvailableCustomizableFormKeys()
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FormCustom))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
return Ok(ApiOkResponse.Response(FormFieldOptionalCustomizableReference.FormFieldKeys));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update FormCustom
|
||||
/// </summary>
|
||||
/// <param name="formkey"></param>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("{formkey}")]
|
||||
public async Task<IActionResult> PutFormCustom([FromRoute] string formkey, [FromBody] FormCustom updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
FormCustomBiz biz = FormCustomBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
// var o = await biz.GetAsync(formkey);
|
||||
// if (o == null)
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
|
||||
// try
|
||||
// {
|
||||
// if (!await biz.PutAsync(o, inObj))
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// }
|
||||
// catch (DbUpdateConcurrencyException)
|
||||
// {
|
||||
// if (!await biz.ExistsAsync(formkey))
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
// else
|
||||
// return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
// }
|
||||
// return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get FormKey from id of FormCustom
|
||||
/// </summary>
|
||||
/// <returns>A formKey string if id found</returns>
|
||||
[HttpGet("form-key/{id}")]
|
||||
public async Task<IActionResult> GetFormKeyFromId(long id)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.FormCustom))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var fc = await ct.FormCustom.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (fc == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(fc.FormKey));
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
67
server/Controllers/FormFieldsDefinitionsController.cs
Normal file
67
server/Controllers/FormFieldsDefinitionsController.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/form-field-reference")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class FormFieldsDefinitionsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<FormFieldsDefinitionsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public FormFieldsDefinitionsController(AyContext dbcontext, ILogger<FormFieldsDefinitionsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get field reference list for Form specified
|
||||
/// Used at UI for customizing forms
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns>List of form fields and their properties</returns>
|
||||
[HttpGet("{key}")]
|
||||
public ActionResult GetFormFields([FromRoute] string key)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (FormFieldOptionalCustomizableReference.IsValidFormFieldKey(key))
|
||||
{
|
||||
return Ok(ApiOkResponse.Response(FormFieldOptionalCustomizableReference.FormFieldReferenceList(key)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
135
server/Controllers/FormUserOptionsController.cs
Normal file
135
server/Controllers/FormUserOptionsController.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/form-user-options")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class FormUserOptionsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<FormUserOptionsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public FormUserOptionsController(AyContext dbcontext, ILogger<FormUserOptionsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create or Replace FormUserOptions
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostFormUserOptions([FromBody] FormUserOptions newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
FormUserOptions o = await biz.UpsertAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(FormUserOptionsController.GetFormUserOptions), new { formKey = o.FormKey, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get FormUserOptions
|
||||
/// </summary>
|
||||
/// <param name="formKey"></param>
|
||||
/// <returns>FormUserOptions</returns>
|
||||
[HttpGet("{formKey}")]
|
||||
public async Task<IActionResult> GetFormUserOptions([FromRoute] string formKey)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(formKey);
|
||||
//note: this route unique in that it's expected that a formUserOptions object may not exist so just return null as client end expects
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Update FormUserOptions
|
||||
// /// </summary>
|
||||
// /// <param name="updatedObject"></param>
|
||||
// /// <returns></returns>
|
||||
// [HttpPut]
|
||||
// public async Task<IActionResult> PutFormUserOptions([FromBody] FormUserOptions updatedObject)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext);
|
||||
// if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// var o = await biz.PutAsync(updatedObject);
|
||||
// if (o == null)
|
||||
// {
|
||||
// if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
// return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
// else
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// }
|
||||
// return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Delete FormUserOptions
|
||||
/// </summary>
|
||||
/// <param name="formKey"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{formKey}")]
|
||||
public async Task<IActionResult> DeleteFormUserOptions([FromRoute] string formKey)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
FormUserOptionsBiz biz = FormUserOptionsBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(formKey))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
128
server/Controllers/GlobalBizSettingsController.cs
Normal file
128
server/Controllers/GlobalBizSettingsController.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/global-biz-setting")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class GlobalBizSettingsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<GlobalBizSettingsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public GlobalBizSettingsController(AyContext dbcontext, ILogger<GlobalBizSettingsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get GlobalBizSettings
|
||||
/// </summary>
|
||||
/// <returns>Global settings object</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetGlobalBizSettings()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
GlobalBizSettingsBiz biz = GlobalBizSettingsBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var o = await biz.GetAsync();
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// PUT Global biz settings
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns>New concurrency token</returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> ReplaceGlobalBizSettings([FromBody] GlobalBizSettings updatedObject)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
GlobalBizSettingsBiz biz = GlobalBizSettingsBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Client app relevant GlobalBizSettings
|
||||
/// </summary>
|
||||
/// <returns>Global settings object</returns>
|
||||
[HttpGet("client")]
|
||||
public ActionResult GetClientGlobalBizSettings()
|
||||
{
|
||||
//## NOTE: these are settings that the Client needs to see for standard operations
|
||||
//NOT the settings that the user changes in the global settings form which is fetched above
|
||||
//so do not include anything here unless the client needs it
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
//Exception for SuperUser account to handle licensing issues
|
||||
if (UserIdFromContext.Id(HttpContext.Items) != 1)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
}
|
||||
|
||||
|
||||
|
||||
var ret = new
|
||||
{
|
||||
//Actual global settings:
|
||||
FilterCaseSensitive = Sockeye.Util.ServerGlobalBizSettings.Cache.FilterCaseSensitive,
|
||||
Company = "GZTW"
|
||||
};
|
||||
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
114
server/Controllers/GlobalOpsBackupSettingsController.cs
Normal file
114
server/Controllers/GlobalOpsBackupSettingsController.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/global-ops-backup-setting")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class GlobalOpsBackupSettingsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<GlobalOpsBackupSettingsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public GlobalOpsBackupSettingsController(AyContext dbcontext, ILogger<GlobalOpsBackupSettingsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get GlobalOpsBackupSettings
|
||||
/// </summary>
|
||||
/// <returns>Global ops backup settings object</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetGlobalOpsBackupSettings()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
GlobalOpsBackupSettingsBiz biz = GlobalOpsBackupSettingsBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var o = await biz.GetAsync();
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// PUT Global ops backup settings
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns>New concurrency token</returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> ReplaceGlobalOpsBackupSettings([FromBody] GlobalOpsBackupSettings updatedObject)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
GlobalOpsBackupSettingsBiz biz = GlobalOpsBackupSettingsBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Get Client app relevant GlobalOpsBackupSettings
|
||||
// /// </summary>
|
||||
// /// <returns>Global ops backup settings object</returns>
|
||||
// [HttpGet("client")]
|
||||
// public async Task<ActionResult> GetClientGlobalOpsBackupSettings()
|
||||
// {
|
||||
// //NOTE: currently this looks like a dupe of get above and it is
|
||||
// //but it's kept here in case want to return a subset of the settings only to client users
|
||||
// //where some internal server only stuff might not be desired to send to user
|
||||
|
||||
// if (serverState.IsClosed)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// GlobalOpsBackupSettingsBiz biz = GlobalOpsBackupSettingsBiz.GetBiz(ct, HttpContext);
|
||||
// //this route is available to Ops role user only
|
||||
// if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// var o = await biz.GetAsync();
|
||||
// if (o == null)
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
// return Ok(ApiOkResponse.Response(o));
|
||||
// }
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
137
server/Controllers/GlobalOpsNotificationSettingsController.cs
Normal file
137
server/Controllers/GlobalOpsNotificationSettingsController.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/global-ops-notification-setting")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class GlobalOpsNotificationSettingsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<GlobalOpsNotificationSettingsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public GlobalOpsNotificationSettingsController(AyContext dbcontext, ILogger<GlobalOpsNotificationSettingsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get GlobalOpsNotificationSettings
|
||||
/// </summary>
|
||||
/// <returns>Global ops Notification settings object</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetGlobalOpsNotificationSettings()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
GlobalOpsNotificationSettingsBiz biz = GlobalOpsNotificationSettingsBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var o = await biz.GetAsync();
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// PUT Global ops Notification settings
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns>New concurrency token</returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> ReplaceGlobalOpsNotificationSettings([FromBody] GlobalOpsNotificationSettings updatedObject)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
GlobalOpsNotificationSettingsBiz biz = GlobalOpsNotificationSettingsBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test SMTP mail delivery
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>Status result</returns>
|
||||
[HttpPost("test-smtp-settings/{toAddress}")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> PostTestSmtpDelivery([FromRoute] string toAddress)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.OpsNotificationSettings))//technically maybe this could be wider open, but for now keeping as locked down
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var res = await CoreJobNotify.TestSMTPDelivery(toAddress);
|
||||
if(res!="ok"){
|
||||
return StatusCode(500, new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, null, res));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(res));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Get Client app relevant GlobalOpsNotificationSettings
|
||||
// /// </summary>
|
||||
// /// <returns>Global ops Notification settings object</returns>
|
||||
// [HttpGet("client")]
|
||||
// public async Task<ActionResult> GetClientGlobalOpsNotificationSettings()
|
||||
// {
|
||||
// //NOTE: currently this looks like a dupe of get above and it is
|
||||
// //but it's kept here in case want to return a subset of the settings only to client users
|
||||
// //where some internal server only stuff might not be desired to send to user
|
||||
|
||||
// if (serverState.IsClosed)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// GlobalOpsNotificationSettingsBiz biz = GlobalOpsNotificationSettingsBiz.GetBiz(ct, HttpContext);
|
||||
// //this route is available to Ops role user only
|
||||
// if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// var o = await biz.GetAsync();
|
||||
// if (o == null)
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
// return Ok(ApiOkResponse.Response(o));
|
||||
// }
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
159
server/Controllers/HeadOfficeController.cs
Normal file
159
server/Controllers/HeadOfficeController.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/head-office")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class HeadOfficeController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<HeadOfficeController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public HeadOfficeController(AyContext dbcontext, ILogger<HeadOfficeController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create HeadOffice
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostHeadOffice([FromBody] HeadOffice newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
HeadOffice o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(HeadOfficeController.GetHeadOffice), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Duplicate HeadOffice
|
||||
// /// (Wiki and Attachments are not duplicated)
|
||||
// /// </summary>
|
||||
// /// <param name="id">Source object id</param>
|
||||
// /// <param name="apiVersion">From route path</param>
|
||||
// /// <returns>HeadOffice</returns>
|
||||
// [HttpPost("duplicate/{id}")]
|
||||
// public async Task<IActionResult> DuplicateHeadOffice([FromRoute] long id, ApiVersion apiVersion)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext);
|
||||
// if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// HeadOffice o = await biz.DuplicateAsync(id);
|
||||
// if (o == null)
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// else
|
||||
// return CreatedAtAction(nameof(HeadOfficeController.GetHeadOffice), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Get HeadOffice
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>HeadOffice</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetHeadOffice([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update HeadOffice
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutHeadOffice([FromBody] HeadOffice updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete HeadOffice
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteHeadOffice([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
HeadOfficeBiz biz = HeadOfficeBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
43
server/Controllers/HealthController.cs
Normal file
43
server/Controllers/HealthController.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/health")]
|
||||
[Produces("text/plain")]
|
||||
[Authorize]
|
||||
public class HealthController : ControllerBase
|
||||
{
|
||||
private readonly HealthCheckService _healthCheckService;
|
||||
public HealthController(HealthCheckService healthCheckService)
|
||||
{
|
||||
_healthCheckService = healthCheckService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Health (verify server AND database connection)
|
||||
/// Note: for server monitoring or automation / orchestration use such as Docker prefer the mirror of this route at the server root: [api_server_url]/health
|
||||
/// as it avoids API versioning issues
|
||||
/// i.e. Docker: HEALTHCHECK CMD curl --fail http://localhost:5000/health || exit
|
||||
/// </summary>
|
||||
/// <remarks>Provides an indication about the health of the API</remarks>
|
||||
/// <response code="200">API is healthy</response>
|
||||
/// <response code="503">API is unhealthy or in degraded state</response>
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[SwaggerOperation(OperationId = "Health_Get")]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
var report = await _healthCheckService.CheckHealthAsync();
|
||||
Response.Headers.Add("Cache-Control", "no-store, no-cache");
|
||||
return report.Status == HealthStatus.Healthy ? Ok(report.Status.ToString()) : StatusCode(503, report.Status.ToString());
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
193
server/Controllers/ImportController.cs
Normal file
193
server/Controllers/ImportController.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/import")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ImportController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ImportController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ImportController(AyContext dbcontext, ILogger<ImportController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Import / Update JSON data to indicated object type
|
||||
/// </summary>
|
||||
/// <param name="importData"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostImportData([FromBody] AyImportData importData, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, importData.SockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
List<string> ImportResult = new List<string>();
|
||||
ImportResult.Add("Import results\n");
|
||||
log.LogDebug($"Instantiating biz object handler for {importData.SockType}");
|
||||
var biz = BizObjectFactory.GetBizObject(importData.SockType, ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
if (!(biz is IImportAbleObject))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Import not supported for {importData.SockType} objects"));
|
||||
ImportResult.AddRange(await ((IImportAbleObject)biz).ImportData(importData));
|
||||
|
||||
return Ok(ApiOkResponse.Response(ImportResult));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Upload and import file
|
||||
// /// Max 100MiB total
|
||||
// /// </summary>
|
||||
// /// <returns>Results</returns>
|
||||
// [Authorize]
|
||||
// [HttpPost("upload")]
|
||||
// [DisableFormValueModelBinding]
|
||||
// [RequestSizeLimit(Sockeye.Util.ServerBootConfig.MAX_IMPORT_FILE_UPLOAD_BYTES)]
|
||||
// public async Task<IActionResult> UploadAsync()
|
||||
// {
|
||||
// //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
// //This route is ONLY available to users with full rights to Global object
|
||||
// if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Global))
|
||||
// {
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// }
|
||||
|
||||
// // SockTypeId attachToObject = null;
|
||||
// ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null;
|
||||
// List<string> ImportResult = new List<string>();
|
||||
// ImportResult.Add("Import results\n");
|
||||
// try
|
||||
// {
|
||||
// if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}"));
|
||||
|
||||
// //Save uploads to disk under temporary file names until we decide how to handle them
|
||||
// // uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);xx
|
||||
|
||||
|
||||
// string UploadAType = string.Empty;
|
||||
|
||||
// string errorMessage = string.Empty;
|
||||
// string Notes = string.Empty;
|
||||
// List<UploadFileData> FileData = new List<UploadFileData>();
|
||||
|
||||
// //Save uploads to disk under temporary file names until we decide how to handle them
|
||||
// uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);
|
||||
// if (!string.IsNullOrWhiteSpace(uploadFormData.Error))
|
||||
// {
|
||||
// errorMessage = uploadFormData.Error;
|
||||
// //delete temp files
|
||||
// ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
// //file too large is most likely issue so in that case return this localized properly
|
||||
// if (errorMessage.Contains("413"))
|
||||
// {
|
||||
// var TransId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
// return BadRequest(new ApiErrorResponse(
|
||||
// ApiErrorCode.VALIDATION_LENGTH_EXCEEDED,
|
||||
// null,
|
||||
// String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), Sockeye.Util.FileUtil.GetBytesReadable(Sockeye.Util.ServerBootConfig.MAX_IMPORT_FILE_UPLOAD_BYTES))));
|
||||
// }
|
||||
// else//not too big, something else
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage));
|
||||
// }
|
||||
|
||||
// if (!uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: FileData"));
|
||||
|
||||
|
||||
// if (uploadFormData.FormFieldData.ContainsKey("SockType"))
|
||||
// UploadAType = uploadFormData.FormFieldData["SockType"].ToString();
|
||||
// else
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: SockType"));
|
||||
|
||||
// //fileData in JSON stringify format which contains the actual last modified dates etc
|
||||
// //"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]"
|
||||
// FileData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<UploadFileData>>(uploadFormData.FormFieldData["FileData"].ToString());
|
||||
|
||||
|
||||
|
||||
// //Instantiate the business object handler
|
||||
// SockType TheType = System.Enum.Parse<SockType>(UploadAType, true);
|
||||
// log.LogDebug($"Instantiating biz object handler for {TheType}");
|
||||
// var biz = BizObjectFactory.GetBizObject(TheType, ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
// if (!(biz is IImportAbleObject))
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Import not supported for {TheType} objects"));
|
||||
|
||||
// //We have our files now can parse and insert into db
|
||||
// if (uploadFormData.UploadedFiles.Count > 0)
|
||||
// {
|
||||
// //deserialize each file and import
|
||||
// foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
// {
|
||||
// JArray ja = JArray.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName));
|
||||
// ImportResult.AddRange(await ((IImportAbleObject)biz).ImportData(ja));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (System.IO.InvalidDataException ex)
|
||||
// {
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message));
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// //delete all the files temporarily uploaded and return bad request
|
||||
|
||||
// ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
// }
|
||||
|
||||
// return Ok(ApiOkResponse.Response(ImportResult));
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
246
server/Controllers/IntegrationController.cs
Normal file
246
server/Controllers/IntegrationController.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/integration")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class IntegrationController : ControllerBase
|
||||
{
|
||||
/*
|
||||
todo: needs routes for logging and fetching log to view, also mapping collection stuff perhaps??
|
||||
|
||||
*/
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<IntegrationController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public IntegrationController(AyContext dbcontext, ILogger<IntegrationController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Integration
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostIntegration([FromBody] Integration newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
Integration o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(IntegrationController.GetIntegration), new { integrationAppId = o.IntegrationAppId, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Integration
|
||||
/// </summary>
|
||||
/// <param name="integrationAppId"></param>
|
||||
/// <returns>Integration</returns>
|
||||
[HttpGet("{integrationAppId}")]
|
||||
public async Task<IActionResult> GetIntegration([FromRoute] Guid integrationAppId)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(integrationAppId, true);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Integration by DB Id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Integration</returns>
|
||||
[HttpGet("by-dbid/{id}")]
|
||||
public async Task<IActionResult> GetIntegrationByDbId([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id, true);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update Integration to db and return it
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns>Entire integration object (differs from most object routes which only return concurrency value)</returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutIntegration([FromBody] Integration updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(o)); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Integration
|
||||
/// </summary>
|
||||
/// <param name="integrationAppId"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{integrationAppId}")]
|
||||
public async Task<IActionResult> DeleteIntegration([FromRoute] Guid integrationAppId)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(integrationAppId))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Integration
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("by-dbid/{id}")]
|
||||
public async Task<IActionResult> DeleteIntegrationByDbId([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check Integration existance
|
||||
/// </summary>
|
||||
/// <param name="integrationAppId"></param>
|
||||
/// <returns>Integration</returns>
|
||||
[HttpGet("exists/{integrationAppId}")]
|
||||
public async Task<IActionResult> GetIntegrationExistance([FromRoute] Guid integrationAppId)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
return Ok(ApiOkResponse.Response(await biz.ExistsByIntegrationAppIdAsync(integrationAppId)));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create Integration log entry
|
||||
/// </summary>
|
||||
/// <param name="logItem">id=Integration internal Id (not IntegrationAppId value), name = status text to log</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>NoContent if ok otherwise BadRequest and an error object</returns>
|
||||
[HttpPost("log")]
|
||||
public async Task<IActionResult> PostIntegrationLog([FromBody] NameIdItem logItem, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
bool bResult = await biz.LogAsync(logItem);
|
||||
if (bResult == false)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Integration log for id of integration specified
|
||||
/// </summary>
|
||||
/// <returns>All log entries available for integration id</returns>
|
||||
[HttpGet("log/{id}")]
|
||||
public async Task<IActionResult> GetAllLogs([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
return Ok(ApiOkResponse.Response(await biz.GetLogAsync(id)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
281
server/Controllers/JobOperationsController.cs
Normal file
281
server/Controllers/JobOperationsController.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// ServerJob controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/job-operations")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class JobOperationsController : ControllerBase
|
||||
{
|
||||
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
|
||||
/// </summary>
|
||||
/// <returns>List of operations jobs</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob))
|
||||
{
|
||||
return StatusCode(403, 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(ApiOkResponse.Response(l));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get current job status for a job
|
||||
/// </summary>
|
||||
/// <param name="gid"></param>
|
||||
/// <returns>A single job's current status</returns>
|
||||
[HttpGet("status/{gid}")]
|
||||
public async Task<IActionResult> GetJobStatus([FromRoute] Guid gid)
|
||||
{
|
||||
//this is called from things that might be running and have server temporarily locked down (e.g. seeding)
|
||||
//so it should never return an error on closed
|
||||
// if (serverState.IsClosed)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//This is called by the UI to monitor any operation that triggers a job so it really should be available to any logged in user
|
||||
// if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob))
|
||||
// {
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// }
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
return Ok(ApiOkResponse.Response(await JobsBiz.GetJobStatusAsync(gid)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current job status and progress for a job
|
||||
/// </summary>
|
||||
/// <param name="gid"></param>
|
||||
/// <returns>A single job's current status and progress</returns>
|
||||
[HttpGet("progress/{gid}")]
|
||||
public async Task<IActionResult> GetJobProgress([FromRoute] Guid gid)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
return Ok(ApiOkResponse.Response(await JobsBiz.GetJobProgressAsync(gid)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Operations log for a job
|
||||
/// </summary>
|
||||
/// <param name="gid"></param>
|
||||
/// <returns>A single job's log</returns>
|
||||
[HttpGet("logs/{gid}")]
|
||||
public async Task<IActionResult> GetLogs([FromRoute] Guid gid)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
|
||||
//## NOTE: deliberately do *not* check for authorization as this is called by any batch operation users may submit via extensions
|
||||
//and the user would need the exact Guid to view a job so not likely they will fish for it in a nefarious way
|
||||
// if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob))
|
||||
// {
|
||||
// return StatusCode(403, 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(ApiOkResponse.Response(l));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Operations log for all jobs
|
||||
/// </summary>
|
||||
|
||||
/// <returns>Log for all jobs in system</returns>
|
||||
[HttpGet("logs/all-jobs")]
|
||||
public async Task<IActionResult> GetAllLogs()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerJob))
|
||||
{
|
||||
return StatusCode(403, 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.GetAllJobsLogsListAsync();
|
||||
return Ok(ApiOkResponse.Response(l));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a test job that simulates a (30 second) long running operation for testing and ops confirmation
|
||||
/// </summary>
|
||||
/// <returns>Job id</returns>
|
||||
[HttpPost("test-job")]
|
||||
public async Task<IActionResult> TestWidgetJob()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.ServerJob))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = "TestJob";
|
||||
j.JobType = JobType.TestJob;
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, $"{j.JobType} {j.Name}"), ct);
|
||||
return Accepted(new { JobId = j.GId });//202 accepted
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// EXTENSION BATCH JOBS
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Batch DELETE list of object id's specified
|
||||
/// </summary>
|
||||
/// <param name="selectedRequest"></param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("batch-delete")]
|
||||
public async Task<IActionResult> BatchDeleteObjects([FromBody] DataListSelectedRequest selectedRequest)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (selectedRequest == null)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "DataListSelectedRequest is required"));
|
||||
|
||||
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, selectedRequest.SockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
//Rehydrate id list if necessary
|
||||
if (selectedRequest.SelectedRowIds.Length == 0)
|
||||
selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(
|
||||
selectedRequest,
|
||||
ct,
|
||||
UserRolesFromContext.Roles(HttpContext.Items),
|
||||
log,
|
||||
UserIdFromContext.Id(HttpContext.Items),
|
||||
UserTranslationIdFromContext.Id(HttpContext.Items));
|
||||
|
||||
var JobName = $"LT:BatchDeleteJob - LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
idList = selectedRequest.SelectedRowIds
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = selectedRequest.SockType;
|
||||
j.JobType = JobType.BatchCoreObjectOperation;
|
||||
j.SubType = JobSubType.Delete;
|
||||
j.Exclusive = false;
|
||||
j.JobInfo = o.ToString();
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Request cancellation of Job. Not all jobs can be cancelled.
|
||||
/// </summary>
|
||||
/// <param name="gid"></param>
|
||||
/// <returns>Accepted</returns>
|
||||
[HttpPost("request-cancel")]
|
||||
public async Task<IActionResult> RequestCancelJob([FromBody] Guid gid)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
await JobsBiz.RequestCancelAsync(gid);
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
85
server/Controllers/KPIController.cs
Normal file
85
server/Controllers/KPIController.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.KPI;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/kpi")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class KPIController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<KPIController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public KPIController(AyContext dbcontext, ILogger<KPIController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get KPI data
|
||||
/// </summary>
|
||||
/// <param name="options">Parameters for pick list see api docs for details </param>
|
||||
/// <returns>Filtered list</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostList([FromBody] KPIRequestOptions options)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
//UserTypeFromContext.Type(HttpContext.Items)
|
||||
return Ok(await KPIFetcher.GetResponseAsync(ct, options, UserRolesFromContext.Roles(HttpContext.Items), log, UserIdFromContext.Id(HttpContext.Items)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//SAVE FOR LATER IF WANT TO RETURN LIST OF ALL KPI'S (likely something to do with report editing??)
|
||||
// /// <summary>
|
||||
// /// List of all DataList keys available
|
||||
// /// </summary>
|
||||
// /// <returns>List of strings</returns>
|
||||
// [HttpGet("listkeys")]
|
||||
// public ActionResult GetDataListKeys()
|
||||
// {
|
||||
// //NOTE: not used by Sockeye Client, convenience method for developers api usage
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
// return Ok(ApiOkResponse.Response(DataListFactory.GetListOfAllDataListKeyNames()));
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
163
server/Controllers/LogFilesController.cs
Normal file
163
server/Controllers/LogFilesController.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Util;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Log files controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/log-file")]
|
||||
//[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class LogFilesController : ControllerBase
|
||||
{
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="logname"></param>
|
||||
/// <returns>A single log file in plain text</returns>
|
||||
[HttpGet("{logname}")]
|
||||
public ActionResult GetLog([FromRoute] string logname)
|
||||
{
|
||||
//NOTE: this route deliberately open even when server closed as a troubleshooting measure
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.LogFile))
|
||||
{
|
||||
return StatusCode(403, 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.SOCKEYE_LOG_PATH, logname);
|
||||
//does file exist?
|
||||
if (!System.IO.File.Exists(logFilePath))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
|
||||
//Log
|
||||
|
||||
//nlog update now locks file for writing so needs to be opened this way
|
||||
FileStreamOptions fso = new FileStreamOptions();
|
||||
fso.Access = FileAccess.Read;
|
||||
fso.Mode = FileMode.Open;
|
||||
fso.Share = FileShare.ReadWrite;
|
||||
using (StreamReader sr = new StreamReader(logFilePath, fso))
|
||||
{
|
||||
return Content(sr.ReadToEnd());
|
||||
}
|
||||
//return Content(System.IO.File.ReadAllText(logFilePath));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get list of operations logs
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet()]
|
||||
public ActionResult ListLogs()
|
||||
{
|
||||
//NOTE: this route deliberately open even when server closed as a troubleshooting measure
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.LogFile))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
|
||||
//Iterate all log files and build return
|
||||
var files = System.IO.Directory.GetFiles(ServerBootConfig.SOCKEYE_LOG_PATH, "log-sockeye*.txt");
|
||||
|
||||
var ret = files.Where(z => !z.EndsWith("ayanova.txt")).OrderByDescending(z => z).Select(z => System.IO.Path.GetFileName(z)).ToList();
|
||||
ret.Insert(0, "log-sockeye.txt");
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Download log
|
||||
/// </summary>
|
||||
/// <param name="logname"></param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns>A single log file</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("download/{logname}")]
|
||||
public async Task<IActionResult> DownloadLog([FromRoute] string logname, [FromQuery] string t)
|
||||
{
|
||||
//NOTE: this route deliberately open even when server closed as a troubleshooting measure
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var user = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct);
|
||||
if (user == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
if (!Authorized.HasReadFullRole(user.Roles, SockType.LogFile))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
var logFilePath = System.IO.Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, logname);
|
||||
if (!System.IO.File.Exists(logFilePath))
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
//Note: this works with new Nlog file locking for write so it must be opening it as read and shareable
|
||||
return PhysicalFile(logFilePath, "text/plain", $"{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}-{logname}");
|
||||
}
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
222
server/Controllers/LogoController.cs
Normal file
222
server/Controllers/LogoController.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Util;
|
||||
using System.IO;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Logo controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/logo")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class LogoController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<LogoController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
// private const int MAXIMUM_LOGO_SIZE = 512000;//We really don't want it too big or it will slow the fuck down for everything
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public LogoController(AyContext dbcontext, ILogger<LogoController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Logo
|
||||
/// </summary>
|
||||
/// <param name="size">One of "small", "medium", "large"</param>
|
||||
/// <returns>A single Logo and it's values</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("{size}")]
|
||||
public async Task<IActionResult> DownloadLogo([FromRoute] string size)
|
||||
{
|
||||
//allowing this because it messes up the login form needlessly
|
||||
// if (serverState.IsClosed)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(size))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "size", "Size is required and must be one of 'small', 'medium' or 'large'"));
|
||||
|
||||
size = size.ToLowerInvariant();
|
||||
if (size != "small" && size != "medium" && size != "large")
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "size", "Size parameter must be one of 'small', 'medium' or 'large'"));
|
||||
|
||||
var logo = await ct.Logo.SingleOrDefaultAsync();
|
||||
if (logo == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
|
||||
switch (size)
|
||||
{
|
||||
case "small":
|
||||
if (logo.Small == null)
|
||||
return NotFound();
|
||||
return new FileStreamResult(new MemoryStream(logo.Small), Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse(logo.SmallType));
|
||||
|
||||
case "medium":
|
||||
if (logo.Medium == null)
|
||||
return NotFound();
|
||||
return new FileStreamResult(new MemoryStream(logo.Medium), Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse(logo.MediumType));
|
||||
|
||||
case "large":
|
||||
if (logo.Large == null)
|
||||
return NotFound();
|
||||
return new FileStreamResult(new MemoryStream(logo.Large), Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse(logo.LargeType));
|
||||
}
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Upload Logo
|
||||
/// Max 500 KiB total (512000 bytes)
|
||||
/// Must have full rights to Global object
|
||||
/// </summary>
|
||||
/// <param name="size">One of "small", "medium", "large"</param>
|
||||
/// <returns>Accepted</returns>
|
||||
[Authorize]
|
||||
[HttpPost("{size}")]
|
||||
//[DisableFormValueModelBinding]
|
||||
[RequestSizeLimit(ServerBootConfig.MAX_LOGO_UPLOAD_BYTES)]
|
||||
public async Task<IActionResult> UploadAsync([FromRoute] string size)
|
||||
{
|
||||
|
||||
//https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-small-files-with-buffered-model-binding-to-a-database
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Global))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(size))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "size", "Size is required and must be one of 'small', 'medium' or 'large'"));
|
||||
|
||||
size = size.ToLowerInvariant();
|
||||
if (size != "small" && size != "medium" && size != "large")
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "size", "Size parameter must be one of 'small', 'medium' or 'large'"));
|
||||
|
||||
//get the one and only logo object
|
||||
var logo = await ct.Logo.FirstOrDefaultAsync();
|
||||
if (logo == null)
|
||||
{
|
||||
logo = new Logo();
|
||||
ct.Logo.Add(logo);
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var file = Request.Form.Files[0];
|
||||
|
||||
//var file=files[0];
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await file.CopyToAsync(memoryStream);
|
||||
if (memoryStream.Length > ServerBootConfig.MAX_LOGO_UPLOAD_BYTES)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, null, $"Logo files must be smaller than {ServerBootConfig.MAX_LOGO_UPLOAD_BYTES} maximum"));
|
||||
switch (size)
|
||||
{
|
||||
case "small":
|
||||
logo.Small = memoryStream.ToArray();
|
||||
logo.SmallType = file.ContentType;
|
||||
break;
|
||||
case "medium":
|
||||
logo.Medium = memoryStream.ToArray();
|
||||
logo.MediumType = file.ContentType;
|
||||
break;
|
||||
case "large":
|
||||
logo.Large = memoryStream.ToArray();
|
||||
logo.LargeType = file.ContentType;
|
||||
break;
|
||||
|
||||
}
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete logo
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[Authorize]
|
||||
[HttpDelete("{size}")]
|
||||
public async Task<IActionResult> DeleteLogo([FromRoute] string size)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Global))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (string.IsNullOrWhiteSpace(size))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "size", "Size is required and must be one of 'small', 'medium' or 'large'"));
|
||||
|
||||
size = size.ToLowerInvariant();
|
||||
if (size != "small" && size != "medium" && size != "large")
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "size", "Size parameter must be one of 'small', 'medium' or 'large'"));
|
||||
|
||||
//get the one and only logo object
|
||||
var logo = await ct.Logo.FirstOrDefaultAsync();
|
||||
if (logo != null)
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case "small":
|
||||
logo.Small = null;
|
||||
logo.SmallType = string.Empty;
|
||||
break;
|
||||
case "medium":
|
||||
logo.Medium = null;
|
||||
logo.MediumType = string.Empty;
|
||||
break;
|
||||
case "large":
|
||||
logo.Large = null;
|
||||
logo.LargeType = string.Empty;
|
||||
break;
|
||||
|
||||
}
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
228
server/Controllers/MemoController.cs
Normal file
228
server/Controllers/MemoController.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
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.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/memo")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class MemoController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<MemoController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public MemoController(AyContext dbcontext, ILogger<MemoController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Memo
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostMemo([FromBody] SendMemo newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var RouteUserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
//v8 migrate hacky workaround to allow specifying toid
|
||||
//-7 to id means it's migrating from v7 so treat as a single object
|
||||
|
||||
|
||||
if (newObject.Users.Count == 1 && newObject.Users[0] == -7 && RouteUserId == 1)
|
||||
{
|
||||
Memo newMemo = new Memo();
|
||||
Sockeye.Util.CopyObject.Copy(newObject.Memo, newMemo);
|
||||
// newMemo.ToId = newObject.Memo.ToId;
|
||||
// newMemo.FromId = newObject.Memo.FromId;
|
||||
Memo o = await biz.CreateAsync(newMemo);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return Ok(ApiOkResponse.Response(new { Id = o.Id }));//v8 migrate needs to id number to fixup the log post migrate
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
foreach (long lUserId in newObject.Users)
|
||||
{
|
||||
Memo newMemo = new Memo();
|
||||
Sockeye.Util.CopyObject.Copy(newObject.Memo, newMemo);
|
||||
newMemo.ToId = lUserId;
|
||||
newMemo.FromId = RouteUserId;
|
||||
Memo o = await biz.CreateAsync(newMemo);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
//return nothing but ok
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SendMemo
|
||||
{
|
||||
public SendMemo()
|
||||
{
|
||||
Users = new List<long>();
|
||||
}
|
||||
[Required]
|
||||
public Memo Memo { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<long> Users { get; set; }
|
||||
}
|
||||
//------------
|
||||
|
||||
|
||||
//NO DUPLICATING MEMOS
|
||||
// /// <summary>
|
||||
// /// Duplicate Memo
|
||||
// /// (Wiki and Attachments are not duplicated)
|
||||
// /// </summary>
|
||||
// /// <param name="id">Source object id</param>
|
||||
// /// <param name="apiVersion">From route path</param>
|
||||
// /// <returns>Memo</returns>
|
||||
// [HttpPost("duplicate/{id}")]
|
||||
// public async Task<IActionResult> DuplicateMemo([FromRoute] long id, ApiVersion apiVersion)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext);
|
||||
// if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// Memo o = await biz.DuplicateAsync(id);
|
||||
// if (o == null)
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// else
|
||||
// return CreatedAtAction(nameof(MemoController.GetMemo), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Get Memo
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Memo</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetMemo([FromRoute] long id)
|
||||
{
|
||||
//NOTE: In this case always getting own memo only
|
||||
//also it's always just for read only purposes so it should include from user name
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
var fromUser = await ct.User.AsNoTracking().SingleOrDefaultAsync(z => z.Id == o.FromId);
|
||||
var from = "??";
|
||||
if (fromUser != null) from = fromUser.Name;
|
||||
var ret = new
|
||||
{
|
||||
Id = o.Id,
|
||||
Name = o.Name,
|
||||
Notes = o.Notes,
|
||||
Wiki = o.Wiki,
|
||||
CustomFields = o.CustomFields,
|
||||
Tags = o.Tags,
|
||||
Viewed = o.Viewed,
|
||||
Replied = o.Replied,
|
||||
FromId = o.FromId,
|
||||
ToId = o.ToId,
|
||||
Sent = o.Sent,
|
||||
FromName = from
|
||||
};
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
//NO UPDATING MEMOS
|
||||
// /// <summary>
|
||||
// /// Update Memo
|
||||
// /// </summary>
|
||||
// /// <param name="updatedObject"></param>
|
||||
// /// <returns></returns>
|
||||
// [HttpPut]
|
||||
// public async Task<IActionResult> PutMemo([FromBody] Memo updatedObject)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext);
|
||||
// if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// var o = await biz.PutAsync(updatedObject);
|
||||
// if (o == null)
|
||||
// {
|
||||
// if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
// return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
// else
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// }
|
||||
// return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Delete Memo
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteMemo([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
MemoBiz biz = MemoBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
68
server/Controllers/NameController.cs
Normal file
68
server/Controllers/NameController.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/name")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class NameController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<NameController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public NameController(AyContext dbcontext, ILogger<NameController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Name of business object
|
||||
/// not all business objects have names some may return '-' or simply the type name
|
||||
/// if that is the case
|
||||
/// </summary>
|
||||
/// <param name="aType">SockType</param>
|
||||
/// <param name="id">Non zero id, if zero returns type name</param>
|
||||
/// <returns>Name</returns>
|
||||
[HttpGet("{aType}/{id}")]
|
||||
public ActionResult GetName([FromRoute] SockType aType, [FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasSelectRole(HttpContext.Items, aType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (id == 0)
|
||||
return Ok(ApiOkResponse.Response(aType.ToString()));
|
||||
|
||||
return Ok(ApiOkResponse.Response(BizObjectNameFetcherDirect.Name(aType, id,UserTranslationIdFromContext.Id(HttpContext.Items), ct)));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
353
server/Controllers/NotifyController.cs
Normal file
353
server/Controllers/NotifyController.cs
Normal file
@@ -0,0 +1,353 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Sockeye.Util;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/notify")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class NotifyController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<NotifyController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public NotifyController(AyContext dbcontext, ILogger<NotifyController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-login route to confirm server is available
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("hello")]
|
||||
public async Task<IActionResult> GetPreLoginPing()
|
||||
{
|
||||
|
||||
//confirm if there are logo's to show as well
|
||||
var logo = await ct.Logo.AsNoTracking().SingleOrDefaultAsync();
|
||||
|
||||
bool HasLargeLogo = false;
|
||||
bool HasMediumLogo = false;
|
||||
bool HasSmallLogo = false;
|
||||
if (logo != null)
|
||||
{
|
||||
if (logo.Small != null) HasSmallLogo = true;
|
||||
if (logo.Medium != null) HasMediumLogo = true;
|
||||
if (logo.Large != null) HasLargeLogo = true;
|
||||
}
|
||||
|
||||
|
||||
return Ok(ApiOkResponse.Response(
|
||||
new { ll = HasLargeLogo, ml = HasMediumLogo, sl = HasSmallLogo }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get count of new notifications waiting
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("new-count")]
|
||||
public async Task<IActionResult> GetNewCount()
|
||||
{
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
if (serverState.IsClosed && UserId != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
return Ok(ApiOkResponse.Response(await ct.InAppNotification.CountAsync(z => z.UserId == UserId && z.Fetched == false)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all in-app notifications
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("app-notifications")]
|
||||
public async Task<IActionResult> GetAppNotifications()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
var ret = await ct.InAppNotification.AsNoTracking().Where(z => z.UserId == UserId).OrderByDescending(z => z.Created).ToListAsync();
|
||||
await ct.Database.ExecuteSqlInterpolatedAsync($"update ainappnotification set fetched={true} where userid = {UserId}");
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete app Notification
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteAppNotification([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
var n = await ct.InAppNotification.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (n == null)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id"));
|
||||
if (n.UserId != UserId)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, null, "Can't delete notification for another user"));
|
||||
ct.InAppNotification.Remove(n);
|
||||
await ct.SaveChangesAsync();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Notify Event object list from queue
|
||||
/// </summary>
|
||||
/// <returns>Notify Event objects awaiting delivery</returns>
|
||||
[HttpGet("queue")]
|
||||
public async Task<IActionResult> GetQueue()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.OpsNotificationSettings))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var ret = new List<NotifyEventQueueItem>();
|
||||
var NotifyEvents = await ct.NotifyEvent.AsNoTracking().ToListAsync();
|
||||
foreach (NotifyEvent ne in NotifyEvents)
|
||||
{
|
||||
var UserInfo = await ct.User.AsNoTracking().Where(x => x.Id == ne.UserId).Select(x => new { Active = x.Active, Name = x.Name }).FirstOrDefaultAsync();
|
||||
var Subscription = await ct.NotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == ne.NotifySubscriptionId);
|
||||
|
||||
ret.Add(new NotifyEventQueueItem(ne.Id, ne.Created, ne.EventDate, (ne.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice), ne.UserId, UserInfo.Name, ne.EventType, ne.SockType, ne.Name));
|
||||
}
|
||||
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
public record NotifyEventQueueItem(long Id, DateTime Created, DateTime EventDate, DateTime DeliverAfter, long UserId, string User, NotifyEventType EventType, SockType SockType, string Name);
|
||||
|
||||
/// <summary>
|
||||
/// Delete pending notification event
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("notify-event/{id}")]
|
||||
public async Task<IActionResult> DeleteNotifyEvent([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.OpsNotificationSettings))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
var n = await ct.NotifyEvent.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (n == null)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "id"));
|
||||
ct.NotifyEvent.Remove(n);
|
||||
await ct.SaveChangesAsync();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Send direct message notification to selected users
|
||||
/// </summary>
|
||||
/// <returns>NoContent on success or error</returns>
|
||||
[HttpPost("direct-message")]
|
||||
public async Task<IActionResult> SendNotifyDirectMessage([FromBody] NotifyDirectMessage notifyDirectMessage)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
|
||||
foreach (long l in notifyDirectMessage.Users)
|
||||
{
|
||||
if (l != 0)
|
||||
await NotifyEventHelper.AddGeneralNotifyEvent(
|
||||
NotifyEventType.GeneralNotification, notifyDirectMessage.Message, UserNameFromContext.Name(HttpContext.Items), null, l
|
||||
);
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
public class NotifyDirectMessage
|
||||
{
|
||||
public NotifyDirectMessage()
|
||||
{
|
||||
Users = new List<long>();
|
||||
}
|
||||
[Required]
|
||||
public string Message { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<long> Users { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send direct SMTP message notification so single object / address
|
||||
/// Server notification settings must be set and active
|
||||
/// Currently supported types are Customer, HeadOffice, Vendor, User
|
||||
/// WARNING: be careful using this method; high volume emailing or spam-like behavior
|
||||
/// could result in a ban or block of your mail account or mail server or domain
|
||||
/// Use of this method is logged to Sockeye event log on successful attempted delivery
|
||||
/// </summary>
|
||||
/// <returns>Accepted on success or error</returns>
|
||||
[HttpPost("direct-smtp")]
|
||||
public async Task<IActionResult> SendNotifySmtpDirectMessage([FromBody] NotifyDirectSMTP notifyDirectSMTP)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, null, "Email notifications are set to OFF at server, unable to send 'on request' type SMTP notification"));
|
||||
|
||||
//for now I'm allowing any authenticated user to call this route intentionally as the use case is for our own internal messaging from the Customer form only or for API users who want to send
|
||||
//email when rendering a report (case 4310).
|
||||
|
||||
//validate incoming data
|
||||
if (string.IsNullOrWhiteSpace(notifyDirectSMTP.Subject))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "Subject", "Subject field is required"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(notifyDirectSMTP.TextBody) && string.IsNullOrWhiteSpace(notifyDirectSMTP.HTMLBody))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, null, "TextBody or HTMLBody field is required"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(notifyDirectSMTP.ToAddress))
|
||||
{
|
||||
//We need to fetch the address from the object type and id
|
||||
//if no id then can skip the rest here
|
||||
if (notifyDirectSMTP.ObjectId == 0)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ObjectId", "No address or object id specified, no where to send this"));
|
||||
}
|
||||
//get the address
|
||||
switch (notifyDirectSMTP.SockType)
|
||||
{
|
||||
case SockType.NoType:
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "ToAddress", "No address or object type and id specified no where to send this"));
|
||||
case SockType.Customer:
|
||||
{
|
||||
var o = await ct.Customer.AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.EmailAddress, x.Active }).FirstOrDefaultAsync();
|
||||
if (o == null)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId"));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(o.EmailAddress))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"Customer {o.Name} doesn't have an email address no where to send this"));
|
||||
if (o.Active == false)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"Customer {o.Name} is not active, only active customers can be emailed directly"));
|
||||
notifyDirectSMTP.ToAddress = o.EmailAddress;
|
||||
}
|
||||
break;
|
||||
case SockType.HeadOffice:
|
||||
{
|
||||
var o = await ct.HeadOffice.AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.EmailAddress, x.Active }).FirstOrDefaultAsync();
|
||||
if (o == null)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId"));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(o.EmailAddress))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"HeadOffice {o.Name} doesn't have an email address no where to send this"));
|
||||
if (o.Active == false)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"HeadOffice {o.Name} is not active, only active head offices can be emailed directly"));
|
||||
notifyDirectSMTP.ToAddress = o.EmailAddress;
|
||||
}
|
||||
break;
|
||||
|
||||
case SockType.User:
|
||||
{
|
||||
var o = await ct.User.Include(z => z.UserOptions).AsNoTracking().Where(x => x.Id == notifyDirectSMTP.ObjectId).Select(x => new { x.Name, x.UserOptions.EmailAddress, x.Active }).FirstOrDefaultAsync();
|
||||
if (o == null)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "ObjectId"));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(o.EmailAddress))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_MISSING_PROPERTY, "EmailAddress", $"Vendor {o.Name} doesn't have an email address no where to send this"));
|
||||
if (o.Active == false)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "Active", $"Vendor {o.Name} is not active, only active Vendors can be emailed directly"));
|
||||
notifyDirectSMTP.ToAddress = o.EmailAddress;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "SockType", "Specified Type not supported for 'on request' smtp"));
|
||||
|
||||
}
|
||||
}
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer;
|
||||
try
|
||||
{
|
||||
await m.SendEmailAsync(notifyDirectSMTP.ToAddress, notifyDirectSMTP.Subject, notifyDirectSMTP.TextBody, ServerGlobalOpsSettingsCache.Notify, null, null, notifyDirectSMTP.HTMLBody);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, notifyDirectSMTP.ObjectId, notifyDirectSMTP.SockType, SockEvent.DirectSMTP, $"\"{notifyDirectSMTP.Subject}\"->{notifyDirectSMTP.ToAddress}"), ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyEventHelper.AddOpsProblemEvent("SMTP direct message failed", ex);
|
||||
return StatusCode(500, new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, null, ExceptionUtil.ExtractAllExceptionMessages(ex)));
|
||||
}
|
||||
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
public class NotifyDirectSMTP
|
||||
{
|
||||
public NotifyDirectSMTP()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public long ObjectId { get; set; } = 0;
|
||||
public SockType SockType { get; set; } = SockType.NoType;
|
||||
public string ToAddress { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public string TextBody { get; set; }
|
||||
public string HTMLBody { get; set; }
|
||||
}
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
187
server/Controllers/NotifySubscriptionController.cs
Normal file
187
server/Controllers/NotifySubscriptionController.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/notify-subscription")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class NotifySubscriptionController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<NotifySubscriptionController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public NotifySubscriptionController(AyContext dbcontext, ILogger<NotifySubscriptionController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create NotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostNotifySubscription([FromBody] NotifySubscription newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
NotifySubscription o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(NotifySubscriptionController.GetNotifySubscription), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Duplicate NotifySubscription
|
||||
// /// </summary>
|
||||
// /// <param name="id">Source object id</param>
|
||||
// /// <param name="apiVersion">From route path</param>
|
||||
// /// <returns>NotifySubscription</returns>
|
||||
// [HttpPost("duplicate/{id}")]
|
||||
// public async Task<IActionResult> DuplicateNotifySubscription([FromRoute] long id, ApiVersion apiVersion)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
// if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// NotifySubscription o = await biz.DuplicateAsync(id);
|
||||
// if (o == null)
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// else
|
||||
// return CreatedAtAction(nameof(NotifySubscriptionController.GetNotifySubscription), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Get NotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NotifySubscription</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetNotifySubscription([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update NotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutNotifySubscription([FromBody] NotifySubscription updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete NotifySubscription
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteNotifySubscription([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
NotifySubscriptionBiz biz = NotifySubscriptionBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Subscription list
|
||||
/// </summary>
|
||||
/// <returns>User's notification subscription list </returns>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetQueue()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//NOTE: in future if getting list for another user should just duplicate this method but add the parameter for user id
|
||||
//and checking of rights
|
||||
long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
var subs = await ct.NotifySubscription.AsNoTracking().Where(z => z.UserId == UserId).OrderBy(z=>z.Id).ToListAsync();
|
||||
|
||||
List<NotifySubscriptionRecord> ret = new List<NotifySubscriptionRecord>();
|
||||
foreach (var s in subs)
|
||||
{
|
||||
ret.Add(new NotifySubscriptionRecord(s.Id, s.UserId, s.EventType, s.SockType, s.DeliveryMethod, s.DeliveryAddress, s.Tags, "na-status", s.AgeValue, s.DecValue));
|
||||
}
|
||||
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
private record NotifySubscriptionRecord(
|
||||
long id, long userid, NotifyEventType eventType, SockType SockType,
|
||||
NotifyDeliveryMethod deliveryMethod, string deliveryAddress, List<string> tags, string status, TimeSpan ageValue, decimal decValue
|
||||
);
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
284
server/Controllers/PickListController.cs
Normal file
284
server/Controllers/PickListController.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.PickList;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/pick-list")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class PickListController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<PickListController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public PickListController(AyContext dbcontext, ILogger<PickListController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get picklist of all Active objects of type specified and filtered by query specified
|
||||
/// NOTE: Query is valid only if:
|
||||
/// it is an empty string indicating not filtered just selected
|
||||
/// if not an empty string, it has at most two space separated strings and one of them is a special TAG specific query that starts with two consecutive periods
|
||||
/// i.e. "some" is valid (single query on all templated fields)
|
||||
/// "..zon some" is valid (all tags like zon and all template fields like some)
|
||||
/// "zon some" is NOT valid (missing TAGS indicator), "..zone some re" is NOT valid (too many strings)
|
||||
/// Note that this list is capped automatically to return no more than 100 results
|
||||
/// </summary>
|
||||
/// <param name="pickListParams">Parameters for pick list see api docs for details </param>
|
||||
/// <returns>Filtered list</returns>
|
||||
[HttpPost("list")]
|
||||
public async Task<IActionResult> PostList([FromBody] PickListOptions pickListParams)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//NOTE: these sequence of calls are a little different than other objects due to the nature of rights and stuff with picklists being different
|
||||
|
||||
var PickList = PickListFactory.GetAyaPickList(pickListParams.SockType);
|
||||
|
||||
//was the name not found as a pick list?
|
||||
if (PickList == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
//RIGHTS - NOTE: uniquely to other routes this one checks the actual picklist defined roles itself
|
||||
if (!Authorized.HasAnyRole(HttpContext.Items, PickList.AllowedRoles))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
//Instantiate the business object handler
|
||||
PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
|
||||
|
||||
|
||||
//handle HeadOffice only restricted variants
|
||||
if (pickListParams.ListVariant == "ho")
|
||||
{
|
||||
//add a variant for the current user's head office id in place of ho
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
var UType = UserTypeFromContext.Type(HttpContext.Items);
|
||||
if (UType != UserType.HeadOffice)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var HoId = await ct.User.AsNoTracking().Where(x => x.Id == UserId).Select(x => x.HeadOfficeId).SingleOrDefaultAsync();
|
||||
if (HoId == null || HoId == 0)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
pickListParams.ListVariant = $"{HoId},{(int)SockType.HeadOffice}";
|
||||
|
||||
}
|
||||
|
||||
var o = await biz.GetPickListAsync(PickList, pickListParams.Query, pickListParams.Inactive, pickListParams.PreselectedIds.ToArray(), pickListParams.ListVariant, log, pickListParams.Template);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a single item's name display in PickList templated format
|
||||
/// </summary>
|
||||
/// <param name="pickListSingleParams"></param>
|
||||
/// <returns>One display string or an empty string if not found or invalid</returns>
|
||||
[HttpPost("single")]
|
||||
public async Task<IActionResult> PostSingle([FromBody] PickListSingleOptions pickListSingleParams)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (!Authorized.HasSelectRole(HttpContext.Items, pickListSingleParams.SockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
//Instantiate the business object handler
|
||||
PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var o = await biz.GetTemplatedNameAsync(pickListSingleParams.SockType, pickListSingleParams.Id, pickListSingleParams.ListVariant, log, pickListSingleParams.Template);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get PickListTemplate
|
||||
/// </summary>
|
||||
/// <param name="sockType"></param>
|
||||
/// <returns>The current effective template, either a customized one or the default</returns>
|
||||
[HttpGet("template/{sockType}")]
|
||||
public async Task<IActionResult> GetPickListTemplate([FromRoute] SockType sockType)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var o = await biz.GetAsync(sockType);
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List of all PickList templates
|
||||
/// </summary>
|
||||
/// <returns>List of strings</returns>
|
||||
[HttpGet("template/list")]
|
||||
public ActionResult GetTemplateList()
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
long TranslationId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
var o = biz.GetListOfAllPickListTypes(TranslationId);
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// POST (replace) Pick List template
|
||||
/// (note: in this case the Id is the SockType numerical value as there is only one template per type)
|
||||
/// </summary>
|
||||
/// <param name="template"></param>
|
||||
/// <returns></returns>
|
||||
// [HttpPost("Template/{sockType}")]
|
||||
[HttpPost("template")]
|
||||
public async Task<IActionResult> ReplacePickListTemplate([FromBody] PickListTemplate template)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
// var o = await biz.GetAsync(sockType, false);
|
||||
// if (o == null)
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
try
|
||||
{
|
||||
if (!await biz.ReplaceAsync(template))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete customized template
|
||||
/// (revert to default)
|
||||
/// </summary>
|
||||
/// <param name="sockType"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpDelete("template/{sockType}")]
|
||||
public async Task<IActionResult> DeletePickListTemplate([FromRoute] SockType sockType)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//Instantiate the business object handler
|
||||
PickListBiz biz = PickListBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!await biz.DeleteAsync(sockType))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// List of all fields for pick list SockType specified
|
||||
/// </summary>
|
||||
/// <returns>List of fields available for template</returns>
|
||||
[HttpGet("template/listfields/{sockType}")]
|
||||
public ActionResult GetPickListFields([FromRoute] SockType sockType)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
var PickList = PickListFactory.GetAyaPickList(sockType);
|
||||
//type might not be supported
|
||||
if (PickList == null)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, null, $"PickList for type \"{sockType.ToString()}\" not supported"));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(PickList.ColumnDefinitions));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
164
server/Controllers/ReminderController.cs
Normal file
164
server/Controllers/ReminderController.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/reminder")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ReminderController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ReminderController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ReminderController(AyContext dbcontext, ILogger<ReminderController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Reminder
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostReminder([FromBody] Reminder newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
Reminder o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(ReminderController.GetReminder), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Reminder
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Reminder</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetReminder([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Reminder
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutReminder([FromBody] Reminder updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Reminder
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteReminder([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Reminder schedule "more" info
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Information to display in schedule when selected for more info</returns>
|
||||
[HttpGet("sched-info/{id}")]
|
||||
public async Task<IActionResult> GetScheduleInfoView([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
o.Color,
|
||||
o.Name,
|
||||
o.Notes,
|
||||
o.StartDate,
|
||||
o.StopDate
|
||||
}));
|
||||
}
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
425
server/Controllers/ReportController.cs
Normal file
425
server/Controllers/ReportController.cs
Normal file
@@ -0,0 +1,425 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Util;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/report")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ReportController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ReportController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ReportController(AyContext dbcontext, ILogger<ReportController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create Report
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostReport([FromBody] Report newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
Report o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(ReportController.GetReport), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Report
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Report</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetReport([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Report
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutReport([FromBody] Report updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Report
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteReport([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Report list for object
|
||||
/// </summary>
|
||||
/// <param name="aType">Type of object</param>
|
||||
/// <returns>Name / id report list of allowed reports for role of requester</returns>
|
||||
[HttpGet("list/{aType}")]
|
||||
public async Task<IActionResult> GetReportList([FromRoute] SockType aType)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
//extra check if they have rights to the type of object in question, this nips it in the bud before they even get to the fetch data stage later
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, aType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetReportListAsync(aType);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get a limited amount of sample data from id list in format used by report designer
|
||||
/// </summary>
|
||||
/// <param name="selectedRequest">Data required for report</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("data")]
|
||||
public async Task<IActionResult> GetReportData([FromBody] DataListSelectedRequest selectedRequest, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
//cap the data returned
|
||||
selectedRequest.ReportDesignerSample = true;
|
||||
JArray reportData;
|
||||
try
|
||||
{
|
||||
reportData = await biz.GetReportDataForReportDesigner(selectedRequest);
|
||||
if (reportData == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return Ok(ApiOkResponse.Response(reportData));
|
||||
}
|
||||
catch (ReportRenderTimeOutException)
|
||||
{
|
||||
log.LogInformation($"GetReportData timeout data list key: {selectedRequest.DataListKey}, record count:{selectedRequest.SelectedRowIds.LongLength}, user:{UserNameFromContext.Name(HttpContext.Items)} ");
|
||||
//note: this route is called by the report designer to get a limited subset of records so we should never see this error but including it for completeness
|
||||
//report designer should show this as a general error
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "timeout - select fewer records"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start Render Report job
|
||||
/// </summary>
|
||||
/// <param name="reportRequest">report id and object id values for object type specified in report template</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("render-job")]
|
||||
public async Task<IActionResult> RequestRenderReport([FromBody] DataListReportRequest reportRequest, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var httpConnectionFeature = HttpContext.Features.Get<IHttpConnectionFeature>();
|
||||
var API_URL = $"http://127.0.0.1:{httpConnectionFeature.LocalPort}/api/{SockeyeVersion.CurrentApiVersion}/";
|
||||
var result = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT), API_URL, UserNameFromContext.Name(HttpContext.Items));
|
||||
if (result == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return Accepted(new { JobId = result });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt cancel render job
|
||||
/// </summary>
|
||||
/// <param name="gid"></param>
|
||||
/// <returns>nothing</returns>
|
||||
[HttpPost("request-cancel/{gid}")]
|
||||
public async Task<IActionResult> RequestCancelJob([FromRoute] Guid gid)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
log.LogDebug($"request-cancel called for report rendering job id {gid}");
|
||||
await biz.CancelJob(gid);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Download a rendered report
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("download/{fileName}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> DownloadAsync([FromRoute] string fileName, [FromQuery] string t)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null)
|
||||
{
|
||||
await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
if (!FileUtil.TemporaryFileExists(fileName))
|
||||
{
|
||||
await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//fishing protection
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
var FilePath = FileUtil.GetFullPathForTemporaryFile(fileName);
|
||||
return PhysicalFile(FilePath, "application/pdf");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Download report template
|
||||
/// </summary>
|
||||
/// <param name="id">Report id</param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns>A single report template as a file</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("export/{id}")]
|
||||
public async Task<IActionResult> DownloadTemplate([FromRoute] long id, [FromQuery] string t)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null)
|
||||
{
|
||||
await Task.Delay(ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
var o = await ct.Report.SingleOrDefaultAsync(z => z.Id == id);
|
||||
//turn into correct format and then send as file
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
var asText = Newtonsoft.Json.JsonConvert.SerializeObject(
|
||||
o,
|
||||
Newtonsoft.Json.Formatting.None,
|
||||
new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id" }) });
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(asText);
|
||||
var file = new FileContentResult(bytes, "application/octet-stream");
|
||||
file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".ayrt";
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Upload Reprot template export file
|
||||
/// Max 15mb total
|
||||
/// </summary>
|
||||
/// <returns>Accepted</returns>
|
||||
[Authorize]
|
||||
[HttpPost("upload")]
|
||||
[DisableFormValueModelBinding]
|
||||
[RequestSizeLimit(Sockeye.Util.ServerBootConfig.MAX_REPORT_TEMPLATE_UPLOAD_BYTES)]
|
||||
public async Task<IActionResult> UploadAsync()
|
||||
{
|
||||
//Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
|
||||
// SockTypeId attachToObject = null;
|
||||
ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null;
|
||||
try
|
||||
{
|
||||
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}"));
|
||||
|
||||
//Save uploads to disk under temporary file names until we decide how to handle them
|
||||
// uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);
|
||||
|
||||
//Save uploads to disk under temporary file names until we decide how to handle them
|
||||
uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);
|
||||
if (!string.IsNullOrWhiteSpace(uploadFormData.Error))
|
||||
{
|
||||
|
||||
//delete temp files
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
//file too large is most likely issue so in that case return this localized properly
|
||||
if (uploadFormData.Error.Contains("413"))
|
||||
{
|
||||
var TransId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
return BadRequest(new ApiErrorResponse(
|
||||
ApiErrorCode.VALIDATION_LENGTH_EXCEEDED,
|
||||
null,
|
||||
String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), Sockeye.Util.FileUtil.GetBytesReadable(Sockeye.Util.ServerBootConfig.MAX_REPORT_TEMPLATE_UPLOAD_BYTES))));
|
||||
}
|
||||
else//not too big, something else
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, uploadFormData.Error));
|
||||
}
|
||||
|
||||
List<UploadFileData> FileData = new List<UploadFileData>();
|
||||
|
||||
if (!uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Missing required FormFieldData value: FileData"));
|
||||
}
|
||||
|
||||
//fileData in JSON stringify format
|
||||
FileData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<UploadFileData>>(uploadFormData.FormFieldData["FileData"].ToString());
|
||||
|
||||
//Instantiate the business object handler
|
||||
ReportBiz biz = ReportBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
|
||||
//We have our files now can parse and insert into db
|
||||
if (uploadFormData.UploadedFiles.Count > 0)
|
||||
{
|
||||
//deserialize each file and import
|
||||
foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
{
|
||||
JObject o = JObject.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName));
|
||||
if (!await biz.ImportAsync(o))
|
||||
{
|
||||
//delete all the files temporarily uploaded and return bad request
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.IO.InvalidDataException ex)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message));
|
||||
}
|
||||
finally
|
||||
{
|
||||
//delete all the files temporarily uploaded and return bad request
|
||||
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
}
|
||||
|
||||
//Return the list of attachment ids and filenames
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
// private static void DeleteTempUploadFile(ApiUploadProcessor.ApiUploadedFilesResult uploadFormData)
|
||||
// {
|
||||
// if (uploadFormData.UploadedFiles.Count > 0)
|
||||
// {
|
||||
// foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
// {
|
||||
// System.IO.File.Delete(a.InitialUploadedPathName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//-----------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
168
server/Controllers/ReviewController.cs
Normal file
168
server/Controllers/ReviewController.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/review")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ReviewController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ReviewController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ReviewController(AyContext dbcontext, ILogger<ReviewController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Review
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostReview([FromBody] Review newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
Review o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(ReviewController.GetReview), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Review
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Review</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetReview([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Review
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutReview([FromBody] Review updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency, ReviewObjectViz=o.ReviewObjectViz })); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Review
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteReview([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Review schedule "more" info
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Information to display in schedule when selected for more info</returns>
|
||||
[HttpGet("sched-info/{id}")]
|
||||
public async Task<IActionResult> GetScheduleInfoView([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
await biz.PopulateVizFields(o);
|
||||
return Ok(ApiOkResponse.Response(new
|
||||
{
|
||||
o.Name,
|
||||
o.Notes,
|
||||
o.ReviewDate,
|
||||
o.CompletedDate,
|
||||
o.CompletionNotes,
|
||||
o.OverDue,
|
||||
o.ReviewObjectViz,
|
||||
o.SockType,
|
||||
o.ObjectId
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
404
server/Controllers/ScheduleController.cs
Normal file
404
server/Controllers/ScheduleController.cs
Normal file
@@ -0,0 +1,404 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Util;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/schedule")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ScheduleController : ControllerBase
|
||||
{
|
||||
private const string WHITE_HEXA = "#FFFFFFFF";
|
||||
private const string BLACK_HEXA = "#000000FF";
|
||||
private const string GRAY_NEUTRAL_HEXA = "#CACACAFF";
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ScheduleController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ScheduleController(AyContext dbcontext, ILogger<ScheduleController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get service management schedule for parameters specified
|
||||
/// time zone UTC offset in minutes is required to be passed in
|
||||
/// timestamps returned are in Unix Epoch milliseconds converted for local time display
|
||||
/// </summary>
|
||||
/// <param name="p">Service schedule parameters</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("svc")]
|
||||
public async Task<IActionResult> PostServiceSchedule([FromBody] ServiceScheduleParams p, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
List<ServiceScheduleListItem> r = new List<ServiceScheduleListItem>();
|
||||
|
||||
//Note: query will return records that fall within viewed range even if they start or end outside of it
|
||||
//However in month view (only, rest are as is) we can see up to 6 days before or after the month so in the interest of filling those voids:
|
||||
//Adjust query dates to encompass actual potential view range
|
||||
DateTime ViewStart = p.Start;
|
||||
DateTime ViewEnd = p.End;
|
||||
//this covers the largest possible window that could display due to nearly a week of the last or next month showing
|
||||
if (p.View == ScheduleView.Month)
|
||||
{
|
||||
ViewStart = p.Start.AddDays(-6);
|
||||
ViewEnd = p.End.AddDays(6);
|
||||
}
|
||||
|
||||
|
||||
//Tags to Users
|
||||
List<NameIdItem> Users = null;
|
||||
|
||||
if (p.Tags.Count == 0)
|
||||
Users = await ct.User.AsNoTracking()
|
||||
.Where(x => x.Active == true && (x.UserType == UserType.ServiceContractor || x.UserType == UserType.Service))
|
||||
.OrderBy(x => x.Name)
|
||||
.Select(x => new NameIdItem { Name = x.Name, Id = x.Id })
|
||||
.ToListAsync();
|
||||
else
|
||||
{
|
||||
Users = new List<NameIdItem>();
|
||||
//add users that match any of the tags, to match they must have at least one of the tags
|
||||
//iterate available users
|
||||
var availableUsers = await ct.User.AsNoTracking().Where(x => x.Active == true && (x.UserType == UserType.ServiceContractor || x.UserType == UserType.Service)).OrderBy(x => x.Name).Select(x => new { x.Name, x.Id, x.Tags }).ToListAsync();
|
||||
//if user has any of the tags in the list then include them
|
||||
foreach (var u in availableUsers)
|
||||
{
|
||||
//any of the inclusive tags in contact tags?
|
||||
if (p.Tags.Intersect(u.Tags).Any())
|
||||
Users.Add(new NameIdItem { Name = u.Name, Id = u.Id });
|
||||
}
|
||||
}
|
||||
List<long?> userIdList = Users.Select(x => x.Id as long?).ToList();
|
||||
userIdList.Add(null);
|
||||
|
||||
var HasUnAssigned = r.Any(x => x.UserId == 0);
|
||||
return Ok(ApiOkResponse.Response(new { items = r, users = Users, hasUnassigned = HasUnAssigned }));
|
||||
}
|
||||
|
||||
public class ServiceScheduleParams
|
||||
{
|
||||
[Required]
|
||||
public ScheduleView View { get; set; }
|
||||
[Required]
|
||||
public DateTime Start { get; set; }
|
||||
[Required]
|
||||
public DateTime End { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<string> Tags { get; set; }
|
||||
[Required]
|
||||
public bool Dark { get; set; }//indicate if Client is set to dark mode or not, used for colorless types to display as black or white
|
||||
}
|
||||
|
||||
|
||||
|
||||
//###############################################################
|
||||
//USER - svc-schedule-user
|
||||
//###############################################################
|
||||
|
||||
// /// <summary>
|
||||
// /// Get User schedule for parameters specified
|
||||
// /// This is called when drilling down into specific user from service schedule form and is not the personal schedule
|
||||
// /// time zone UTC offset in minutes is required to be passed in
|
||||
// /// timestamps returned are in Unix Epoch milliseconds converted for local time display
|
||||
// /// </summary>
|
||||
// /// <param name="p">User schedule parameters</param>
|
||||
// /// <param name="apiVersion">From route path</param>
|
||||
// /// <returns></returns>
|
||||
// [HttpPost("user")]
|
||||
// public async Task<IActionResult> PostUserSchedule([FromBody] PersonalScheduleParams p, ApiVersion apiVersion)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
// List<PersonalScheduleListItem> r = new List<PersonalScheduleListItem>();
|
||||
|
||||
|
||||
|
||||
// //Note: query will return records that fall within viewed range even if they start or end outside of it
|
||||
// //However in month view (only, rest are as is) we can see up to 6 days before or after the month so in the interest of filling those voids:
|
||||
// //Adjust query dates to encompass actual potential view range
|
||||
// DateTime ViewStart = p.Start;
|
||||
// DateTime ViewEnd = p.End;
|
||||
// //this covers the largest possible window that could display due to nearly a week of the last or next month showing
|
||||
// if (p.View == ScheduleView.Month)
|
||||
// {
|
||||
// ViewStart = p.Start.AddDays(-6);
|
||||
// ViewEnd = p.End.AddDays(6);
|
||||
// }
|
||||
|
||||
// long? actualUserId = p.UserId == 0 ? null : p.UserId;
|
||||
|
||||
// return Ok(ApiOkResponse.Response(r));
|
||||
// }
|
||||
|
||||
|
||||
//###############################################################
|
||||
//PERSONAL
|
||||
//###############################################################
|
||||
|
||||
/// <summary>
|
||||
/// Get personal schedule for parameters specified
|
||||
/// time zone UTC offset in minutes is required to be passed in
|
||||
/// timestamps returned are in Unix Epoch milliseconds converted for local time display
|
||||
/// </summary>
|
||||
/// <param name="p">Personal schedule parameters</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("personal")]
|
||||
public async Task<IActionResult> PostPersonalSchedule([FromBody] PersonalScheduleParams p, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
List<PersonalScheduleListItem> r = new List<PersonalScheduleListItem>();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
var UType = UserTypeFromContext.Type(HttpContext.Items);
|
||||
|
||||
|
||||
|
||||
//Note: query will return records that fall within viewed range even if they start or end outside of it
|
||||
//However in month view (only, rest are as is) we can see up to 6 days before or after the month so in the interest of filling those voids:
|
||||
//Adjust query dates to encompass actual potential view range
|
||||
DateTime ViewStart = p.Start;
|
||||
DateTime ViewEnd = p.End;
|
||||
//this covers the largest possible window that could display due to nearly a week of the last or next month showing
|
||||
if (p.View == ScheduleView.Month)
|
||||
{
|
||||
ViewStart = p.Start.AddDays(-6);
|
||||
ViewEnd = p.End.AddDays(6);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//REMINDERS
|
||||
if (p.Reminders)
|
||||
{
|
||||
r.AddRange(await ct.Reminder.Where(x => x.UserId == UserId && ViewStart <= x.StopDate && x.StartDate <= ViewEnd).Select(x => MakeReminderSchedItem(x, p)).ToListAsync());
|
||||
}
|
||||
|
||||
//REVIEWS
|
||||
if (p.Reviews)
|
||||
{
|
||||
r.AddRange(await ct.Review.Where(x => x.UserId == UserId && ViewStart <= x.ReviewDate && x.ReviewDate <= ViewEnd).Select(x => MakeReviewSchedItem(x, p)).ToListAsync());
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(r));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust a schedule item's start / end timestamp
|
||||
/// </summary>
|
||||
/// <param name="ad">Adjustment parameters parameters</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>Error or OK response</returns>
|
||||
[HttpPost("adjust")]
|
||||
public async Task<IActionResult> AdjustSchedule([FromBody] ScheduleItemAdjustParams ad, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
switch (ad.Type)
|
||||
{
|
||||
|
||||
case SockType.Reminder:
|
||||
{
|
||||
ReminderBiz biz = ReminderBiz.GetBiz(ct, HttpContext);
|
||||
var o = await biz.PutNewScheduleTimeAsync(ad);
|
||||
if (o == false)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SockType.Review:
|
||||
{
|
||||
ReviewBiz biz = ReviewBiz.GetBiz(ct, HttpContext);
|
||||
var o = await biz.PutNewScheduleTimeAsync(ad);
|
||||
if (o == false)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "Type", "Type not supported for adjustment"));
|
||||
}
|
||||
|
||||
//a-ok response
|
||||
return Ok(ApiOkResponse.Response(true));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//#### UTILITY METHODS ##############
|
||||
|
||||
|
||||
|
||||
private static PersonalScheduleListItem MakeReminderSchedItem(Reminder v, PersonalScheduleParams p)
|
||||
{
|
||||
var s = new PersonalScheduleListItem();
|
||||
s.Id = v.Id;
|
||||
s.Color = v.Color;
|
||||
s.TextColor = TextColor(v.Color);
|
||||
s.Start = (DateTime)v.StartDate;
|
||||
s.End = (DateTime)v.StopDate;
|
||||
s.Type = SockType.Reminder;
|
||||
s.Name = v.Name;
|
||||
s.Editable = true;//personal reminders are always editable
|
||||
return s;
|
||||
}
|
||||
|
||||
private static PersonalScheduleListItem MakeReviewSchedItem(Review v, PersonalScheduleParams p)
|
||||
{
|
||||
var s = new PersonalScheduleListItem();
|
||||
s.Id = v.Id;
|
||||
s.Color = p.Dark ? WHITE_HEXA : BLACK_HEXA;
|
||||
s.TextColor = p.Dark ? "black" : "white";
|
||||
s.Start = (DateTime)v.ReviewDate;
|
||||
s.End = (DateTime)v.ReviewDate.AddMinutes(30);//just something to show in schedule as not supporting all day or unscheduled type stuff
|
||||
s.Type = SockType.Review;
|
||||
s.Name = v.Name;
|
||||
s.Editable = v.CompletedDate == null;//not completed yet so can still be changed
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static string TextColor(string hexcolor)
|
||||
{
|
||||
//Note: we use HEXA format which is 8 hex digits
|
||||
//this here works even though it's considering as 6 digits because in hexA the last two
|
||||
//digits are the opacity which this can ignore
|
||||
if (string.IsNullOrWhiteSpace(hexcolor) || hexcolor.Length < 6) return GRAY_NEUTRAL_HEXA;//gray neutral
|
||||
hexcolor = hexcolor.Replace("#", "");
|
||||
var r = StringUtil.HexToInt(hexcolor.Substring(0, 2));
|
||||
var g = StringUtil.HexToInt(hexcolor.Substring(2, 2));
|
||||
var b = StringUtil.HexToInt(hexcolor.Substring(4, 2));
|
||||
var yiq = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
//return yiq >= 128 ? WHITE_HEXA : BLACK_HEXA;
|
||||
return yiq >= 128 ? "black" : "white";//<---NOTE: this MUST be a named color due to how the style is applied at client
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public enum ScheduleView : int
|
||||
{
|
||||
Day = 1,
|
||||
Week = 2,
|
||||
Month = 3,
|
||||
Day4 = 4,
|
||||
Category = 5
|
||||
}
|
||||
|
||||
public class PersonalScheduleParams
|
||||
{
|
||||
[Required]
|
||||
public ScheduleView View { get; set; }
|
||||
[Required]
|
||||
public DateTime Start { get; set; }
|
||||
[Required]
|
||||
public DateTime End { get; set; }
|
||||
|
||||
[Required]
|
||||
public bool Reviews { get; set; }
|
||||
[Required]
|
||||
public bool Reminders { get; set; }
|
||||
[Required]
|
||||
public bool Dark { get; set; }//indicate if Client is set to dark mode or not, used for colorless types to display as black or white
|
||||
[Required]
|
||||
public long UserId { get; set; }//required due to dual use from home-schedule and svc-schedule-user if it's a 0 zero then it's actually meant to be null not assigned userid
|
||||
}
|
||||
|
||||
public class PersonalScheduleListItem
|
||||
{
|
||||
//Never be null dates in here even though source records might be have null dates because they are not queried for and
|
||||
//can't be displayed on a calendar anyway
|
||||
//user can simply filter a data table by null dates to see them
|
||||
//we shouldn't have allowed null dates in the first place in v7 but here we are :)
|
||||
public DateTime Start { get; set; }
|
||||
public DateTime End { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Color { get; set; }
|
||||
public string TextColor { get; set; }
|
||||
public SockType Type { get; set; }
|
||||
public long Id { get; set; }
|
||||
public bool Editable { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceScheduleListItem
|
||||
{
|
||||
//Never be null dates in here even though source records might be have null dates because they are not queried for and
|
||||
//can't be displayed on a calendar anyway
|
||||
//user can simply filter a data table by null dates to see them
|
||||
//we shouldn't have allowed null dates in the first place in v7 but here we are :)
|
||||
public DateTime Start { get; set; }
|
||||
public DateTime End { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Color { get; set; }
|
||||
public string TextColor { get; set; }
|
||||
public SockType Type { get; set; }
|
||||
public long Id { get; set; }
|
||||
public bool Editable { get; set; }
|
||||
public long UserId { get; set; }
|
||||
}
|
||||
//------------
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
145
server/Controllers/SearchController.cs
Normal file
145
server/Controllers/SearchController.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Search
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/search")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class SearchController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<SearchController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public SearchController(AyContext dbcontext, ILogger<SearchController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create search parameters
|
||||
/// MaxResults defaults to 500
|
||||
/// MaxResults = 0 returns all results
|
||||
/// </summary>
|
||||
/// <param name="searchParams"></param>
|
||||
/// <returns>SearchResult list</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostSearch([FromBody] Search.SearchRequestParameters searchParams)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Do the search
|
||||
var SearchResults = await Search.DoSearchAsync(
|
||||
ct,
|
||||
UserTranslationIdFromContext.Id(HttpContext.Items),
|
||||
UserRolesFromContext.Roles(HttpContext.Items),
|
||||
UserIdFromContext.Id(HttpContext.Items),
|
||||
searchParams
|
||||
);
|
||||
|
||||
return Ok(ApiOkResponse.Response(SearchResults));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get search result summary
|
||||
/// </summary>
|
||||
/// <param name="sockType"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="phrase"></param>
|
||||
/// <param name="max"></param>
|
||||
/// <returns>A search result excerpt of object</returns>
|
||||
[HttpGet("info/{sockType}/{id}")]
|
||||
public async Task<IActionResult> GetInfo([FromRoute] SockType sockType, [FromRoute] long id, [FromQuery] string phrase, [FromQuery] int max = 80)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, sockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (id == 0)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "id", "id can't be zero"));
|
||||
|
||||
|
||||
var res = await Search.GetInfoAsync(UserTranslationIdFromContext.Id(HttpContext.Items),
|
||||
UserRolesFromContext.Roles(HttpContext.Items), UserIdFromContext.Id(HttpContext.Items), phrase, max, sockType, id, ct);
|
||||
|
||||
return Ok(ApiOkResponse.Response(res));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Get the top level ancestor of provided type and id
|
||||
// /// (e.g. find the WorkOrder principle for a WorkOrderItemPart object descendant)
|
||||
// /// </summary>
|
||||
// /// <param name="sockType"></param>
|
||||
// /// <param name="id"></param>
|
||||
// /// <returns>A type and id of ancestor</returns>
|
||||
// [HttpGet("ancestor/{sockType}/{id}")]
|
||||
// public async Task<IActionResult> GetAncestor([FromRoute] SockType sockType, [FromRoute] long id)
|
||||
// {
|
||||
// if (serverState.IsClosed)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
// //since this is for opening an entire object it's appropriate to check if they have any role first
|
||||
// if (!Authorized.HasAnyRole(HttpContext.Items, sockType))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// if (id == 0)
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "id can't be zero"));
|
||||
|
||||
// switch (sockType)
|
||||
// {
|
||||
|
||||
|
||||
|
||||
// default:
|
||||
// return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "Only types with ancestors are valid"));
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
361
server/Controllers/ServerMetricsController.cs
Normal file
361
server/Controllers/ServerMetricsController.cs
Normal file
@@ -0,0 +1,361 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
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.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
//using StackExchange.Profiling;
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Server metrics
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/server-metric")]
|
||||
[Authorize]
|
||||
public class ServerMetricsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<LogFilesController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
private const int DEFAULT_MAX_RECORDS = 400;
|
||||
private const long MB = (1024 * 1024);
|
||||
private const long KB = 1024;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public ServerMetricsController(AyContext dbcontext, ILogger<LogFilesController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
// #if (DEBUG)
|
||||
// [HttpGet("collect")]
|
||||
// [AllowAnonymous]
|
||||
// public ActionResult GetCollect()
|
||||
// {
|
||||
// GC.Collect();
|
||||
// GC.WaitForPendingFinalizers();
|
||||
// GC.Collect();
|
||||
|
||||
// return Ok();
|
||||
// }
|
||||
|
||||
|
||||
// [HttpGet("hammer")]
|
||||
// [AllowAnonymous]
|
||||
// public async Task<IActionResult> GetHammer()
|
||||
// {
|
||||
// //test allocation and cleanup
|
||||
// for (int x = 0; x < 100000; x++)
|
||||
// {
|
||||
// using (AyContext ct = ServiceProviderProvider.DBContext)
|
||||
// var v=await ct.Widget.Where(z=>z.Serial<100).ToListAsync();
|
||||
// // int i = await ct.Database.ExecuteSqlRawAsync($"select * from aglobalbizsettings");
|
||||
// }
|
||||
|
||||
// return Ok();
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// #endif
|
||||
|
||||
/// <summary>
|
||||
/// Get Memory and CPU server metrics for time period specified
|
||||
/// </summary>
|
||||
/// <param name="tsStart">Start timestamp UTC</param>
|
||||
/// <param name="tsEnd">End timestamp UTC</param>
|
||||
/// <param name="maxRecords">Optional maximum records to return (downsampled). There is a 400 record maximum fixed default</param>
|
||||
/// <returns>Snapshot of metrics</returns>
|
||||
[HttpGet("memcpu")]
|
||||
public async Task<IActionResult> GetMemCPUMetrics([FromQuery, Required] DateTime? tsStart, [FromQuery, Required] DateTime? tsEnd, [FromQuery] int? maxRecords)
|
||||
{
|
||||
//Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerMetrics))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
maxRecords ??= DEFAULT_MAX_RECORDS;
|
||||
|
||||
List<MetricMM> MinuteMetrics = new List<MetricMM>();
|
||||
//touniversal is because the parameters are converted to local time here
|
||||
//but then sent to the query as local time as well and not universal time which is what it should be
|
||||
MinuteMetrics = await ct.MetricMM.AsNoTracking().Where(z => z.t >= ((DateTime)tsStart).ToUniversalTime() && z.t <= ((DateTime)tsEnd).ToUniversalTime()).OrderBy(z => z.t).ToListAsync();
|
||||
|
||||
|
||||
var dsCPU = MinuteMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.CPU)).ToList();
|
||||
dsCPU = Util.DataUtil.LargestTriangleThreeBuckets(dsCPU, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsAllocated = MinuteMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.Allocated)).ToList();
|
||||
dsAllocated = Util.DataUtil.LargestTriangleThreeBuckets(dsAllocated, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsWorkingSet = MinuteMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.WorkingSet)).ToList();
|
||||
dsWorkingSet = Util.DataUtil.LargestTriangleThreeBuckets(dsWorkingSet, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsPrivateBytes = MinuteMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.PrivateBytes)).ToList();
|
||||
dsPrivateBytes = Util.DataUtil.LargestTriangleThreeBuckets(dsPrivateBytes, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var ret = new
|
||||
{
|
||||
cpu = dsCPU.Select(z => new MetricDouble(DateTime.FromOADate(z.Item1), z.Item2)).ToArray(),
|
||||
allocated = dsAllocated.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(),
|
||||
workingSet = dsWorkingSet.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(),
|
||||
privateBytes = dsPrivateBytes.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray()
|
||||
};
|
||||
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerMetrics, SockEvent.Retrieved), ct);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get storage server metrics for time period specified
|
||||
/// </summary>
|
||||
/// <param name="tsStart">Start timestamp UTC</param>
|
||||
/// <param name="tsEnd">End timestamp UTC</param>
|
||||
/// <param name="maxRecords">Optional maximum records to return (downsampled). There is a 400 record maximum fixed default</param>
|
||||
/// <returns>Snapshot of metrics</returns>
|
||||
[HttpGet("storage")]
|
||||
public async Task<IActionResult> GetStorageMetrics([FromQuery, Required] DateTime? tsStart, [FromQuery, Required] DateTime? tsEnd, [FromQuery] int? maxRecords)
|
||||
{
|
||||
//Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerMetrics))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
maxRecords ??= DEFAULT_MAX_RECORDS;
|
||||
|
||||
List<MetricDD> DailyMetrics = new List<MetricDD>();
|
||||
//touniversal is because the parameters are converted to local time here
|
||||
//but then sent to the query as local time as well and not universal time which is what it should be
|
||||
DailyMetrics = await ct.MetricDD.AsNoTracking().Where(z => z.t >= ((DateTime)tsStart).ToUniversalTime() && z.t <= ((DateTime)tsEnd).ToUniversalTime()).OrderBy(z => z.t).ToListAsync();
|
||||
|
||||
|
||||
var dsAttachmentFileCount = DailyMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.AttachmentFileCount)).ToList();
|
||||
dsAttachmentFileCount = Util.DataUtil.LargestTriangleThreeBuckets(dsAttachmentFileCount, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsAttachmentFileSize = DailyMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.AttachmentFileSize)).ToList();
|
||||
dsAttachmentFileSize = Util.DataUtil.LargestTriangleThreeBuckets(dsAttachmentFileSize, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsAttachmentFilesAvailableSpace = DailyMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.AttachmentFilesAvailableSpace)).ToList();
|
||||
dsAttachmentFilesAvailableSpace = Util.DataUtil.LargestTriangleThreeBuckets(dsAttachmentFilesAvailableSpace, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsUtilityFileCount = DailyMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.UtilityFileCount)).ToList();
|
||||
dsUtilityFileCount = Util.DataUtil.LargestTriangleThreeBuckets(dsUtilityFileCount, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsUtilityFileSize = DailyMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.UtilityFileSize)).ToList();
|
||||
dsUtilityFileSize = Util.DataUtil.LargestTriangleThreeBuckets(dsUtilityFileSize, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var dsUtilityFilesAvailableSpace = DailyMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.UtilityFilesAvailableSpace)).ToList();
|
||||
dsUtilityFilesAvailableSpace = Util.DataUtil.LargestTriangleThreeBuckets(dsUtilityFilesAvailableSpace, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
var ret = new
|
||||
{
|
||||
attachmentFileCount = dsAttachmentFileCount.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2)).ToArray(),
|
||||
attachmentFileSize = dsAttachmentFileSize.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(),
|
||||
attachmentFilesAvailableSpace = dsAttachmentFilesAvailableSpace.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(),
|
||||
utilityFileCount = dsUtilityFileCount.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2)).ToArray(),
|
||||
utilityFileSize = dsUtilityFileSize.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray(),
|
||||
utilityFilesAvailableSpace = dsUtilityFilesAvailableSpace.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray()
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerMetrics, SockEvent.Retrieved), ct);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get database related metrics for time period specified
|
||||
/// </summary>
|
||||
/// <param name="tsStart">Start timestamp UTC</param>
|
||||
/// <param name="tsEnd">End timestamp UTC</param>
|
||||
/// <param name="maxRecords">Optional maximum records to return (downsampled). There is a 400 record maximum fixed default</param>
|
||||
/// <returns>Snapshot of metrics</returns>
|
||||
[HttpGet("db")]
|
||||
public async Task<IActionResult> GetDBMetrics([FromQuery, Required] DateTime? tsStart, [FromQuery, Required] DateTime? tsEnd, [FromQuery] int? maxRecords)
|
||||
{
|
||||
//Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerMetrics))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
maxRecords ??= DEFAULT_MAX_RECORDS;
|
||||
|
||||
|
||||
//############ DB SIZE TIME SERIES MB
|
||||
List<MetricDD> DBMetrics = new List<MetricDD>();
|
||||
//touniversal is because the parameters are converted to local time here
|
||||
//but then sent to the query as local time as well and not universal time which is what it should be
|
||||
DBMetrics = await ct.MetricDD.AsNoTracking().Where(z => z.t >= ((DateTime)tsStart).ToUniversalTime() && z.t <= ((DateTime)tsEnd).ToUniversalTime()).OrderBy(z => z.t).ToListAsync();
|
||||
var dsDBTotalSize = DBMetrics.Select(z => new Tuple<double, double>(z.t.ToOADate(), z.DBTotalSize)).ToList();
|
||||
dsDBTotalSize = Util.DataUtil.LargestTriangleThreeBuckets(dsDBTotalSize, (int)maxRecords) as List<Tuple<double, double>>;
|
||||
|
||||
|
||||
|
||||
//############# TOP TABLES KB
|
||||
int AllTableCount=0;
|
||||
List<MetricNameLongValue> TopTables = new List<MetricNameLongValue>();
|
||||
using (var command = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
/*
|
||||
SELECT table_name, pg_total_relation_size(table_name) AS total_size
|
||||
FROM ( SELECT ( table_schema || '.' || table_name )
|
||||
AS table_name FROM information_schema.tables
|
||||
where table_schema not in('pg_catalog','information_schema'))
|
||||
AS all_tables
|
||||
ORDER BY total_size DESC
|
||||
*/
|
||||
|
||||
var cmd = @"SELECT
|
||||
table_name,
|
||||
pg_total_relation_size(table_name) AS total_size
|
||||
FROM (
|
||||
SELECT ('""' || table_schema || '"".""' || table_name || '""') AS table_name
|
||||
FROM information_schema.tables
|
||||
where table_schema not in('pg_catalog','information_schema')
|
||||
) AS all_tables
|
||||
ORDER BY total_size DESC";
|
||||
command.CommandText = cmd;
|
||||
|
||||
ct.Database.OpenConnection();
|
||||
using (var dr = command.ExecuteReader())
|
||||
{
|
||||
if (dr.HasRows)
|
||||
{
|
||||
while (dr.Read())
|
||||
{
|
||||
AllTableCount++;
|
||||
long tableSize = dr.GetInt64(1);
|
||||
string tableName = dr.GetString(0);
|
||||
tableName = tableName.Replace("\"", "").Replace("public.a", "");
|
||||
if (tableSize > 0)
|
||||
{
|
||||
tableSize = tableSize / KB;
|
||||
}
|
||||
TopTables.Add(new MetricNameLongValue() { name = tableName, value = tableSize });
|
||||
}
|
||||
}
|
||||
ct.Database.CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
//trim out tables less than 1kb (the math above sets anything less than 1kb to zero)
|
||||
TopTables = TopTables.Where(z => z.value > 48).ToList();//NOTE: empty tables seem to all be 48kb so that's why this is here
|
||||
int OtherTableCount=AllTableCount-TopTables.Count();
|
||||
|
||||
long DBTotalSize = 0;
|
||||
using (var command = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
command.CommandText = "select pg_database_size(current_database());";
|
||||
ct.Database.OpenConnection();
|
||||
using (var dr = command.ExecuteReader())
|
||||
{
|
||||
if (dr.HasRows)
|
||||
{
|
||||
DBTotalSize = dr.Read() ? (dr.GetInt64(0) / KB) : 0;
|
||||
}
|
||||
ct.Database.CloseConnection();
|
||||
}
|
||||
}
|
||||
|
||||
long ttSize = 0;
|
||||
foreach (MetricNameLongValue tt in TopTables)
|
||||
{
|
||||
ttSize += tt.value;
|
||||
}
|
||||
// TopTables.Add(new MetricNameLongValue() { name = $"{OtherTableCount} others", value = DBTotalSize - ttSize });
|
||||
|
||||
var ret = new
|
||||
{
|
||||
TopTables = TopTables.OrderByDescending(z => z.value).ToList(),
|
||||
totalSize = dsDBTotalSize.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray()
|
||||
};
|
||||
|
||||
|
||||
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerMetrics, SockEvent.Retrieved), ct);
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
public class MetricLong
|
||||
{
|
||||
public DateTime x { get; set; }
|
||||
public long y { get; set; }
|
||||
public MetricLong(DateTime px, double py)
|
||||
{
|
||||
x = px;
|
||||
y = (long)py;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class MetricInt
|
||||
{
|
||||
public DateTime x { get; set; }
|
||||
public int y { get; set; }
|
||||
public MetricInt(DateTime px, double py)
|
||||
{
|
||||
x = px;
|
||||
y = (int)py;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class MetricDouble
|
||||
{
|
||||
public DateTime x { get; set; }
|
||||
public double y { get; set; }
|
||||
public MetricDouble(DateTime px, double py)
|
||||
{
|
||||
x = px;
|
||||
y = py;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class MetricNameLongValue
|
||||
{
|
||||
public string name { get; set; }
|
||||
public long value { get; set; }
|
||||
}
|
||||
//----------
|
||||
|
||||
}
|
||||
}
|
||||
363
server/Controllers/ServerStateController.cs
Normal file
363
server/Controllers/ServerStateController.cs
Normal file
@@ -0,0 +1,363 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Models;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Sockeye.Util;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Server state controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/server-state")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class ServerStateController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<ServerStateController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
private readonly IHostApplicationLifetime _appLifetime;
|
||||
|
||||
|
||||
public ServerStateController(ILogger<ServerStateController> logger, ApiServerState apiServerState, AyContext dbcontext, IHostApplicationLifetime appLifetime)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
_appLifetime = appLifetime;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get server state
|
||||
/// </summary>
|
||||
/// <returns>Current server state (Closed, MigrateMode, OpsOnly, Open)</returns>
|
||||
[HttpGet]
|
||||
public ActionResult Get()
|
||||
{
|
||||
//any logged in user can get the state
|
||||
return Ok(ApiOkResponse.Response(new ServerStateModel() { ServerState = serverState.GetState().ToString(), Reason = serverState.Reason }));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set server state
|
||||
/// Valid parameters:
|
||||
/// One of "OpsOnly", "MigrateMode" or "Open"
|
||||
/// </summary>
|
||||
/// <param name="state">{"serverState":"Open"}</param>
|
||||
/// <returns>New server state</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostServerState([FromBody] ServerStateModel state)
|
||||
{
|
||||
if (serverState.IsClosed)//no state change allowed when closed
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.ServerState))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
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 \"OpsOnly\", \"MigrateMode\" or \"Open\""));
|
||||
|
||||
//don't allow a server to be set to closed, that's for internal ops only
|
||||
if (desiredState == ApiServerState.ServerState.Closed)
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, "Invalid state - must be one of \"OpsOnly\", \"MigrateMode\" or \"Open\""));
|
||||
|
||||
var TransId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
log.LogInformation($"ServerState change request by user {UserNameFromContext.Name(HttpContext.Items)} from current state of \"{serverState.GetState().ToString()}\" to \"{desiredState.ToString()} {state.Reason}\"");
|
||||
|
||||
|
||||
|
||||
//Add a message if user didn't enter one so other users know why they can't login
|
||||
if (string.IsNullOrWhiteSpace(state.Reason))
|
||||
{
|
||||
switch (desiredState)
|
||||
{
|
||||
case ApiServerState.ServerState.Open:
|
||||
break;
|
||||
case ApiServerState.ServerState.OpsOnly:
|
||||
state.Reason += await TranslationBiz.GetTranslationStaticAsync("ServerStateLoginRestricted", TransId, ct) + ":\n" + await TranslationBiz.GetTranslationStaticAsync("ServerStateOps", TransId, ct);
|
||||
break;
|
||||
case ApiServerState.ServerState.Closed:
|
||||
state.Reason += await TranslationBiz.GetTranslationStaticAsync("ServerStateLoginRestricted", TransId, ct) + ":\n" + await TranslationBiz.GetTranslationStaticAsync("ErrorAPI2000", TransId, ct);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (desiredState != ApiServerState.ServerState.OpsOnly)
|
||||
state.Reason = await TranslationBiz.GetTranslationStaticAsync("ServerStateLoginRestricted", TransId, ct) + ":\n" + state.Reason;
|
||||
}
|
||||
|
||||
serverState.SetState(desiredState, state.Reason);
|
||||
|
||||
//Log
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerState, SockEvent.ServerStateChange, $"{state.ServerState}-{state.Reason}"), ct);
|
||||
|
||||
return Ok(ApiOkResponse.Response(new ServerStateModel() { ServerState = serverState.GetState().ToString(), Reason = serverState.Reason }));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parameter object
|
||||
/// </summary>
|
||||
public class ServerStateModel
|
||||
{
|
||||
/// <summary>
|
||||
/// "OpsOnly", "MigrateMode" or "Open"
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Required]
|
||||
public string ServerState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reason for server state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string Reason { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Controlled server shut down
|
||||
/// </summary>
|
||||
/// <returns>Accepted</returns>
|
||||
[HttpPost("shutdown")]
|
||||
public ActionResult PostShutdown()
|
||||
{
|
||||
if (serverState.IsClosed)//no state change allowed when closed
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.ServerState))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
log.LogInformation($"### Server shut down requested by user {UserNameFromContext.Name(HttpContext.Items)}, triggering shut down event now...");
|
||||
|
||||
_appLifetime.StopApplication();//Note: this should also trigger graceful shutdown of JOBS as well
|
||||
return Accepted();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get server configuration
|
||||
/// </summary>
|
||||
/// <returns>Active server configuration</returns>
|
||||
[HttpGet("active-configuration")]
|
||||
public ActionResult GetActiveConfiguration()
|
||||
{
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerState))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
return Ok(ApiOkResponse.Response(
|
||||
new
|
||||
{
|
||||
SOCKEYE_DEFAULT_TRANSLATION = ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION,
|
||||
SOCKEYE_USE_URLS = ServerBootConfig.SOCKEYE_USE_URLS,
|
||||
SOCKEYE_DB_CONNECTION = DbUtil.PasswordRedactedConnectionString(ServerBootConfig.SOCKEYE_DB_CONNECTION),
|
||||
SOCKEYE_REPORT_RENDERING_TIMEOUT = ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT,
|
||||
SOCKEYE_ATTACHMENT_FILES_PATH = ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH,
|
||||
SOCKEYE_BACKUP_FILES_PATH = ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH,
|
||||
SOCKEYE_TEMP_FILES_PATH = ServerBootConfig.SOCKEYE_TEMP_FILES_PATH,
|
||||
SOCKEYE_BACKUP_PG_DUMP_PATH = ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH,
|
||||
SOCKEYE_LOG_PATH = ServerBootConfig.SOCKEYE_LOG_PATH,
|
||||
SOCKEYE_LOG_LEVEL = ServerBootConfig.SOCKEYE_LOG_LEVEL,
|
||||
SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG = ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG,
|
||||
serverinfo = ServerBootConfig.BOOT_DIAGNOSTIC_INFO,
|
||||
dbserverinfo = ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO
|
||||
}));
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Download all logs and server configuration setting in one zip package for technical support purposes
|
||||
// /// </summary>
|
||||
// /// <param name="t">download token</param>
|
||||
// /// <returns>A single zip file</returns>
|
||||
// [AllowAnonymous]
|
||||
// [HttpGet("tech-support-info")]
|
||||
// public async Task<IActionResult> DownloadSupportInfo([FromQuery] string t)
|
||||
// {
|
||||
// //NOTE: this route deliberately open even when server closed as a troubleshooting measure
|
||||
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
// var user = await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct);
|
||||
// if (user == null)
|
||||
// {
|
||||
// await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
// return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
// }
|
||||
|
||||
// if (!Authorized.HasReadFullRole(user.Roles, SockType.LogFile) || !Authorized.HasReadFullRole(user.Roles, SockType.ServerState))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
// System.Text.StringBuilder sbRet = new System.Text.StringBuilder();
|
||||
// sbRet.AppendLine("#########################################################");
|
||||
// sbRet.AppendLine("SERVER CONFIGURATION");
|
||||
// sbRet.AppendLine($"SOCKEYE_USE_URLS: {ServerBootConfig.SOCKEYE_USE_URLS}");
|
||||
// sbRet.AppendLine($"SOCKEYE_DB_CONNECTION: {DbUtil.PasswordRedactedConnectionString(ServerBootConfig.SOCKEYE_DB_CONNECTION)}");
|
||||
// sbRet.AppendLine($"SOCKEYE_REPORT_RENDERING_TIMEOUT: {ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT}");
|
||||
// sbRet.AppendLine($"SOCKEYE_DEFAULT_TRANSLATION: {ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION}");
|
||||
// sbRet.AppendLine($"SOCKEYE_ATTACHMENT_FILES_PATH: {ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH}");
|
||||
// sbRet.AppendLine($"SOCKEYE_BACKUP_FILES_PATH: {ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH}");
|
||||
// sbRet.AppendLine($"SOCKEYE_TEMP_FILES_PATH: {ServerBootConfig.SOCKEYE_TEMP_FILES_PATH}");
|
||||
// sbRet.AppendLine($"SOCKEYE_BACKUP_PG_DUMP_PATH: {ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH}");
|
||||
// sbRet.AppendLine($"SOCKEYE_LOG_PATH: {ServerBootConfig.SOCKEYE_LOG_PATH}");
|
||||
// sbRet.AppendLine($"SOCKEYE_LOG_LEVEL: {ServerBootConfig.SOCKEYE_LOG_LEVEL}");
|
||||
// sbRet.AppendLine($"SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG: {ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG}");
|
||||
|
||||
// sbRet.AppendLine("#########################################################");
|
||||
// sbRet.AppendLine("SERVER DIAGNOSTICS");
|
||||
// foreach (var di in ServerBootConfig.BOOT_DIAGNOSTIC_INFO)
|
||||
// {
|
||||
// sbRet.AppendLine($"{di.Key}: {di.Value}");
|
||||
// }
|
||||
|
||||
// sbRet.AppendLine("#########################################################");
|
||||
// sbRet.AppendLine("DB SERVER DIAGNOSTICS");
|
||||
// foreach (var di in ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO)
|
||||
// {
|
||||
// sbRet.AppendLine($"{di.Key}: {di.Value}");
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// //System.IO.Compression.ZipFile.CreateFromDirectory(ServerBootConfig.SOCKEYE_LOG_PATH, AttachmentsBackupFile);
|
||||
|
||||
|
||||
// var files = System.IO.Directory.GetFiles(ServerBootConfig.SOCKEYE_LOG_PATH, "*.txt").OrderBy(z => new FileInfo(z).LastWriteTime).Select(z => System.IO.Path.GetFileName(z)).ToList();
|
||||
|
||||
// //Directory.GetFiles(@“C:\RPA\Vector Reports”,“IW28*”).OrderByAscending(d => new FileInfo(d).GetLastWriteTime)
|
||||
|
||||
// sbRet.AppendLine("#########################################################");
|
||||
// sbRet.AppendLine($"SERVER LOGS");
|
||||
|
||||
// foreach (string logfilename in files)
|
||||
// {
|
||||
// var logFilePath = System.IO.Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, logfilename);
|
||||
// FileStreamOptions fso = new FileStreamOptions();
|
||||
// fso.Access = FileAccess.Read;
|
||||
// fso.Mode = FileMode.Open;
|
||||
// fso.Share = FileShare.ReadWrite;
|
||||
|
||||
// using (StreamReader sr = new StreamReader(logFilePath, fso))
|
||||
// {
|
||||
// sbRet.AppendLine(sr.ReadToEnd());
|
||||
// }
|
||||
|
||||
// }
|
||||
// sbRet.AppendLine("#########################################################");
|
||||
// return Content(sbRet.ToString());
|
||||
|
||||
|
||||
// }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fetch all logs and server configuration setting for technical support purposes
|
||||
/// </summary>
|
||||
/// <returns>text result</returns>
|
||||
[HttpGet("tech-support-info")]
|
||||
public ActionResult GetTechSupportInfo()
|
||||
{
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.LogFile) || !Authorized.HasReadFullRole(HttpContext.Items, SockType.ServerState))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
|
||||
System.Text.StringBuilder sbRet = new System.Text.StringBuilder();
|
||||
sbRet.AppendLine("#########################################################");
|
||||
sbRet.AppendLine("SERVER CONFIGURATION");
|
||||
sbRet.AppendLine($"SOCKEYE_USE_URLS: {ServerBootConfig.SOCKEYE_USE_URLS}");
|
||||
sbRet.AppendLine($"SOCKEYE_DB_CONNECTION: {DbUtil.PasswordRedactedConnectionString(ServerBootConfig.SOCKEYE_DB_CONNECTION)}");
|
||||
sbRet.AppendLine($"SOCKEYE_REPORT_RENDERING_TIMEOUT: {ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT}");
|
||||
sbRet.AppendLine($"SOCKEYE_DEFAULT_TRANSLATION: {ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION}");
|
||||
sbRet.AppendLine($"SOCKEYE_ATTACHMENT_FILES_PATH: {ServerBootConfig.SOCKEYE_ATTACHMENT_FILES_PATH}");
|
||||
sbRet.AppendLine($"SOCKEYE_BACKUP_FILES_PATH: {ServerBootConfig.SOCKEYE_BACKUP_FILES_PATH}");
|
||||
sbRet.AppendLine($"SOCKEYE_TEMP_FILES_PATH: {ServerBootConfig.SOCKEYE_TEMP_FILES_PATH}");
|
||||
sbRet.AppendLine($"SOCKEYE_BACKUP_PG_DUMP_PATH: {ServerBootConfig.SOCKEYE_BACKUP_PG_DUMP_PATH}");
|
||||
sbRet.AppendLine($"SOCKEYE_LOG_PATH: {ServerBootConfig.SOCKEYE_LOG_PATH}");
|
||||
sbRet.AppendLine($"SOCKEYE_LOG_LEVEL: {ServerBootConfig.SOCKEYE_LOG_LEVEL}");
|
||||
sbRet.AppendLine($"SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG: {ServerBootConfig.SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG}");
|
||||
|
||||
sbRet.AppendLine("#########################################################");
|
||||
sbRet.AppendLine("SERVER DIAGNOSTICS");
|
||||
sbRet.AppendLine($"Version: {SockeyeVersion.FullNameAndVersion}");
|
||||
sbRet.AppendLine($"Schema level: {AySchema.currentSchema}");
|
||||
sbRet.AppendLine($"Server current time: {DateUtil.ServerDateTimeString(System.DateTime.UtcNow)}");
|
||||
|
||||
|
||||
foreach (var di in ServerBootConfig.BOOT_DIAGNOSTIC_INFO)
|
||||
{
|
||||
sbRet.AppendLine($"{di.Key}: {di.Value}");
|
||||
}
|
||||
|
||||
sbRet.AppendLine("#########################################################");
|
||||
sbRet.AppendLine("DB SERVER DIAGNOSTICS");
|
||||
foreach (var di in ServerBootConfig.DBSERVER_DIAGNOSTIC_INFO)
|
||||
{
|
||||
sbRet.AppendLine($"{di.Key}: {di.Value}");
|
||||
}
|
||||
|
||||
|
||||
var files = System.IO.Directory.GetFiles(ServerBootConfig.SOCKEYE_LOG_PATH, "*.txt").OrderBy(z => new FileInfo(z).LastWriteTime).Select(z => System.IO.Path.GetFileName(z)).ToList();
|
||||
|
||||
|
||||
sbRet.AppendLine("#########################################################");
|
||||
sbRet.AppendLine($"ALL SERVER LOGS");
|
||||
|
||||
foreach (string logfilename in files)
|
||||
{
|
||||
var logFilePath = System.IO.Path.Combine(ServerBootConfig.SOCKEYE_LOG_PATH, logfilename);
|
||||
FileStreamOptions fso = new FileStreamOptions();
|
||||
fso.Access = FileAccess.Read;
|
||||
fso.Mode = FileMode.Open;
|
||||
fso.Share = FileShare.ReadWrite;
|
||||
|
||||
using (StreamReader sr = new StreamReader(logFilePath, fso))
|
||||
{
|
||||
sbRet.AppendLine(sr.ReadToEnd());
|
||||
}
|
||||
|
||||
}
|
||||
sbRet.AppendLine("#########################################################");
|
||||
|
||||
return Ok(ApiOkResponse.Response(sbRet.ToString()));
|
||||
|
||||
|
||||
}
|
||||
|
||||
//------------
|
||||
|
||||
}
|
||||
}
|
||||
370
server/Controllers/TagController.cs
Normal file
370
server/Controllers/TagController.cs
Normal file
@@ -0,0 +1,370 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/tag-list")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class TagController : ControllerBase
|
||||
{
|
||||
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 list
|
||||
/// </summary>
|
||||
/// <param name="query">The query to filter the returned list by</param>
|
||||
/// <returns>Filtered list (maximum 25 items are returned for any query)</returns>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetList([FromQuery] string query)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
return Ok(ApiOkResponse.Response(await TagBiz.TagListFilteredAsync(ct, query)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get tag cloud list
|
||||
/// </summary>
|
||||
/// <param name="query">The query to filter the returned list by</param>
|
||||
/// <returns>List</returns>
|
||||
[HttpGet("cloudlist")]
|
||||
public async Task<IActionResult> GetCloudList([FromQuery] string query)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
return Ok(ApiOkResponse.Response(await TagBiz.CloudListFilteredAsync(ct, query)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
//BATCH OPS
|
||||
//
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Batch add tags to list of object id's specified
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="selectedRequest"></param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("batch-add/{tag}")]
|
||||
public async Task<IActionResult> BatchAdd([FromRoute] string tag, [FromBody] DataListSelectedRequest selectedRequest)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (!selectedRequest.SockType.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type"));
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, selectedRequest.SockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
tag = TagBiz.NormalizeTag(tag);
|
||||
if (string.IsNullOrWhiteSpace(tag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag required"));
|
||||
|
||||
//Rehydrate id list if necessary
|
||||
if (selectedRequest.SelectedRowIds.Length == 0)
|
||||
selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(
|
||||
selectedRequest,
|
||||
ct,
|
||||
UserRolesFromContext.Roles(HttpContext.Items),
|
||||
log,
|
||||
UserIdFromContext.Id(HttpContext.Items),
|
||||
UserTranslationIdFromContext.Id(HttpContext.Items));
|
||||
|
||||
var JobName = $"LT:BatchJob LT:Add LT:Tag \"{tag}\" LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
idList = selectedRequest.SelectedRowIds,
|
||||
tag = tag
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = selectedRequest.SockType;
|
||||
j.JobType = JobType.BatchCoreObjectOperation;
|
||||
j.SubType = JobSubType.TagAdd;
|
||||
j.Exclusive = false;
|
||||
j.JobInfo = o.ToString();
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch add tags to all objects of type specified
|
||||
/// </summary>
|
||||
/// <param name="sockType"></param>
|
||||
/// <param name="tag"></param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("batch-add-any/{sockType}/{tag}")]
|
||||
public async Task<IActionResult> BatchAddAny([FromRoute] SockType sockType, [FromRoute] string tag)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!sockType.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type"));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, sockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
tag = TagBiz.NormalizeTag(tag);
|
||||
if (string.IsNullOrWhiteSpace(tag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag"));
|
||||
|
||||
var JobName = $"LT:BatchJob LT:Add LT:Tag \"{tag}\" LT:{sockType.ToString()} (LT:GridRowFilterDropDownAllItem) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
tag = tag
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = sockType;
|
||||
j.JobType = JobType.BatchCoreObjectOperation;
|
||||
j.SubType = JobSubType.TagAddAny;
|
||||
j.Exclusive = false;
|
||||
j.JobInfo = o.ToString();
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch remove tags to list of object id's specified
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="selectedRequest"></param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("batch-remove/{tag}")]
|
||||
public async Task<IActionResult> BatchRemove([FromRoute] string tag, [FromBody] DataListSelectedRequest selectedRequest)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (!selectedRequest.SockType.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type"));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, selectedRequest.SockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
tag = TagBiz.NormalizeTag(tag);
|
||||
if (string.IsNullOrWhiteSpace(tag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag"));
|
||||
|
||||
//Rehydrate id list if necessary
|
||||
if (selectedRequest.SelectedRowIds.Length == 0)
|
||||
selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(
|
||||
selectedRequest,
|
||||
ct,
|
||||
UserRolesFromContext.Roles(HttpContext.Items),
|
||||
log,
|
||||
UserIdFromContext.Id(HttpContext.Items),
|
||||
UserTranslationIdFromContext.Id(HttpContext.Items));
|
||||
|
||||
var JobName = $"LT:BatchJob LT:Remove LT:Tag \"{tag}\" LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
idList = selectedRequest.SelectedRowIds,
|
||||
tag = tag
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = selectedRequest.SockType;
|
||||
j.JobType = JobType.BatchCoreObjectOperation;
|
||||
j.SubType = JobSubType.TagRemove;
|
||||
j.Exclusive = false;
|
||||
j.JobInfo = o.ToString();
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Batch remove tags to all objects of type specified
|
||||
/// </summary>
|
||||
/// <param name="sockType"></param>
|
||||
/// <param name="tag"></param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("batch-remove-any/{sockType}/{tag}")]
|
||||
public async Task<IActionResult> BatchRemoveAny([FromRoute] SockType sockType, [FromRoute] string tag)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!sockType.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type"));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, sockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
tag = TagBiz.NormalizeTag(tag);
|
||||
if (string.IsNullOrWhiteSpace(tag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "tag"));
|
||||
|
||||
var JobName = $"LT:BatchJob LT:Remove LT:Tag \"{tag}\" LT:{sockType.ToString()} (LT:GridRowFilterDropDownAllItem) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
tag = tag
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = sockType;
|
||||
j.SubType = JobSubType.TagRemoveAny;
|
||||
j.JobType = JobType.BatchCoreObjectOperation;
|
||||
j.Exclusive = false;
|
||||
j.JobInfo = o.ToString();
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Batch replace tags to list of object id's specified
|
||||
/// </summary>
|
||||
/// <param name="fromTag"></param>
|
||||
/// <param name="toTag"></param>
|
||||
/// <param name="selectedRequest"></param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("batch-replace/{fromTag}")]
|
||||
public async Task<IActionResult> BatchReplace([FromRoute] string fromTag, [FromQuery] string toTag, [FromBody] DataListSelectedRequest selectedRequest)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (!selectedRequest.SockType.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type"));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, selectedRequest.SockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
fromTag = TagBiz.NormalizeTag(fromTag);
|
||||
if (string.IsNullOrWhiteSpace(fromTag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "fromTag"));
|
||||
toTag = TagBiz.NormalizeTag(toTag);
|
||||
if (string.IsNullOrWhiteSpace(toTag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "toTag"));
|
||||
|
||||
//Rehydrate id list if necessary
|
||||
if (selectedRequest.SelectedRowIds.Length == 0)
|
||||
selectedRequest.SelectedRowIds = await DataListSelectedProcessingOptions.RehydrateIdList(
|
||||
selectedRequest,
|
||||
ct,
|
||||
UserRolesFromContext.Roles(HttpContext.Items),
|
||||
log,
|
||||
UserIdFromContext.Id(HttpContext.Items),
|
||||
UserTranslationIdFromContext.Id(HttpContext.Items));
|
||||
|
||||
var JobName = $"LT:BatchJob LT:Replace LT:Tag \"{fromTag}\" -> LT:Tag \"{toTag}\" LT:{selectedRequest.SockType} ({selectedRequest.SelectedRowIds.LongLength}) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
idList = selectedRequest.SelectedRowIds,
|
||||
tag = fromTag,
|
||||
toTag = toTag
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.SockType = selectedRequest.SockType;
|
||||
j.Name = JobName;
|
||||
j.JobType = JobType.BatchCoreObjectOperation;
|
||||
j.SubType = JobSubType.TagReplace;
|
||||
j.Exclusive = false;
|
||||
j.JobInfo = o.ToString();
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch replace tags to all objects of type specified
|
||||
/// </summary>
|
||||
/// <param name="sockType"></param>
|
||||
/// <param name="fromTag"></param>
|
||||
/// <param name="toTag"></param>
|
||||
/// <returns>Job Id</returns>
|
||||
[HttpPost("batch-replace-any/{sockType}/{fromTag}")]
|
||||
public async Task<IActionResult> BatchReplaceAny([FromRoute] SockType sockType, [FromRoute] string fromTag, [FromQuery] string toTag)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
if (!sockType.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Not a taggable object type"));
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, sockType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
fromTag = TagBiz.NormalizeTag(fromTag);
|
||||
if (string.IsNullOrWhiteSpace(fromTag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "fromTag"));
|
||||
|
||||
toTag = TagBiz.NormalizeTag(toTag);
|
||||
if (string.IsNullOrWhiteSpace(toTag))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, null, "toTag"));
|
||||
|
||||
var JobName = $"LT:BatchJob LT:Replace LT:Tag \"{fromTag}\" -> LT:Tag \"{toTag}\" LT:{sockType.ToString()} (LT:GridRowFilterDropDownAllItem) LT:User {UserNameFromContext.Name(HttpContext.Items)}";
|
||||
JObject o = JObject.FromObject(new
|
||||
{
|
||||
tag = fromTag,
|
||||
toTag = toTag
|
||||
});
|
||||
|
||||
OpsJob j = new OpsJob();
|
||||
j.Name = JobName;
|
||||
j.SockType = sockType;
|
||||
j.JobType = JobType.BatchCoreObjectOperation;
|
||||
j.SubType = JobSubType.TagReplaceAny;
|
||||
j.Exclusive = false;
|
||||
j.JobInfo = o.ToString();
|
||||
await JobsBiz.AddJobAsync(j);
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, SockType.ServerJob, SockEvent.Created, JobName), ct);
|
||||
return Accepted(new { JobId = j.GId });
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
569
server/Controllers/TranslationController.cs
Normal file
569
server/Controllers/TranslationController.cs
Normal file
@@ -0,0 +1,569 @@
|
||||
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.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
||||
|
||||
namespace Sockeye.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>
|
||||
/// Translation controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/translation")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class TranslationController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<TranslationController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public TranslationController(AyContext dbcontext, ILogger<TranslationController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Translation all values
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>A single Translation and it's values</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetTranslation([FromRoute] long id)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var o = await biz.GetAsync(id);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update Translation
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutTranslation([FromBody] Translation updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Translations list
|
||||
/// </summary>
|
||||
/// <returns>List in alphabetical order of all Translations</returns>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> TranslationList()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var l = await biz.GetTranslationListAsync();
|
||||
return Ok(ApiOkResponse.Response(l));
|
||||
}
|
||||
|
||||
|
||||
#if (DEBUG)
|
||||
/// <summary>
|
||||
/// Get a coverage report of translation keys used versus unused
|
||||
/// </summary>
|
||||
/// <returns>Report of all unique translation keys requested since last server reboot</returns>
|
||||
[HttpGet("translationkeycoverage")]
|
||||
public async Task<IActionResult> TranslationKeyCoverage()
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var l = await biz.TranslationKeyCoverageAsync();
|
||||
return Ok(ApiOkResponse.Response(l));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get subset of translation values
|
||||
/// </summary>
|
||||
/// <param name="inObj">List of translation key strings</param>
|
||||
/// <returns>A key value array of translation text values</returns>
|
||||
[HttpPost("subset")]
|
||||
public async Task<IActionResult> SubSet([FromBody] List<string> inObj)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
{
|
||||
//Exception for SuperUser account to handle licensing issues
|
||||
if (UserIdFromContext.Id(HttpContext.Items) != 1)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
|
||||
//Instantiate the business object handler
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
var l = await biz.GetSubsetAsync(inObj);
|
||||
return Ok(ApiOkResponse.Response(l));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get subset of translation values for specific translation Id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="inObj">List of translation key strings</param>
|
||||
/// <returns>A key value array of translation text values</returns>
|
||||
[HttpPost("subset/{id}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> SubSet([FromRoute] long id, [FromBody] List<string> inObj)
|
||||
{
|
||||
//## NOTE: This route is ONLY used at present for the reset password form at the client
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
var l = await TranslationBiz.GetSpecifiedTranslationSubsetStaticAsync(inObj, id);
|
||||
return Ok(ApiOkResponse.Response(l));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate
|
||||
/// </summary>
|
||||
/// <param name="id">Source object id</param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns>Duplicate</returns>
|
||||
[HttpPost("duplicate/{id}")]
|
||||
public async Task<IActionResult> DuplicateTranslation([FromRoute] long id, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
Translation o = await biz.DuplicateAsync(id);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(TranslationController.GetTranslation), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete Translation
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Ok</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteTranslation([FromRoute] long id)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
|
||||
//Fetch translation and it's children
|
||||
//(fetch here so can return proper REST responses on failing basic validity)
|
||||
var dbObject = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == id);
|
||||
if (dbObject == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.Translation))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
|
||||
//Instantiate the business object handler
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
if (!await biz.DeleteAsync(dbObject))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Translation all values
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="t">download token</param>
|
||||
/// <returns>A single Translation and it's values</returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("download/{id}")]
|
||||
public async Task<IActionResult> DownloadTranslation([FromRoute] long id, [FromQuery] string t)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
if (await UserBiz.ValidateDownloadTokenAndReturnUserAsync(t, ct) == null)
|
||||
{
|
||||
await Task.Delay(Sockeye.Util.ServerBootConfig.FAILED_AUTH_DELAY);//DOS protection
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
var o = await ct.Translation.Include(z => z.TranslationItems).SingleOrDefaultAsync(z => z.Id == id);
|
||||
//turn into correct format and then send as file
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
var asText = Newtonsoft.Json.JsonConvert.SerializeObject(
|
||||
o,
|
||||
Newtonsoft.Json.Formatting.None,
|
||||
new JsonSerializerSettings { ContractResolver = new Sockeye.Util.JsonUtil.ShouldSerializeContractResolver(new string[] { "Concurrency", "Id", "TranslationId" }) });
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(asText);
|
||||
var file = new FileContentResult(bytes, "application/octet-stream");
|
||||
file.FileDownloadName = Util.FileUtil.StringToSafeFileName(o.Name) + ".json";
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Upload Translation export file
|
||||
/// Max 15mb total
|
||||
/// </summary>
|
||||
/// <returns>Accepted</returns>
|
||||
[Authorize]
|
||||
[HttpPost("upload")]
|
||||
[DisableFormValueModelBinding]
|
||||
[RequestSizeLimit(Sockeye.Util.ServerBootConfig.MAX_TRANSLATION_UPLOAD_BYTES)]
|
||||
public async Task<IActionResult> UploadAsync()
|
||||
{
|
||||
//Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
|
||||
// SockTypeId attachToObject = null;
|
||||
ApiUploadProcessor.ApiUploadedFilesResult uploadFormData = null;
|
||||
try
|
||||
{
|
||||
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, $"Expected a multipart request, but got {Request.ContentType}"));
|
||||
|
||||
|
||||
bool badRequest = false;
|
||||
string UploadAType = string.Empty;
|
||||
string UploadObjectId = string.Empty;
|
||||
string errorMessage = string.Empty;
|
||||
string Notes = string.Empty;
|
||||
List<UploadFileData> FileData = new List<UploadFileData>();
|
||||
|
||||
//Save uploads to disk under temporary file names until we decide how to handle them
|
||||
uploadFormData = await ApiUploadProcessor.ProcessUploadAsync(HttpContext);
|
||||
if (!string.IsNullOrWhiteSpace(uploadFormData.Error))
|
||||
{
|
||||
errorMessage = uploadFormData.Error;
|
||||
//delete temp files
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
//file too large is most likely issue so in that case return this localized properly
|
||||
if (errorMessage.Contains("413"))
|
||||
{
|
||||
var TransId = UserTranslationIdFromContext.Id(HttpContext.Items);
|
||||
return BadRequest(new ApiErrorResponse(
|
||||
ApiErrorCode.VALIDATION_LENGTH_EXCEEDED,
|
||||
null,
|
||||
String.Format(await TranslationBiz.GetTranslationStaticAsync("AyaFileFileTooLarge", TransId, ct), Sockeye.Util.FileUtil.GetBytesReadable(Sockeye.Util.ServerBootConfig.MAX_TRANSLATION_UPLOAD_BYTES))));
|
||||
}
|
||||
else//not too big, something else
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, null, errorMessage));
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
!uploadFormData.FormFieldData.ContainsKey("FileData"))//only filedata is required
|
||||
{
|
||||
badRequest = true;
|
||||
errorMessage = "Missing required FormFieldData value: FileData";
|
||||
}
|
||||
if (!badRequest)
|
||||
{
|
||||
if (uploadFormData.FormFieldData.ContainsKey("SockType"))
|
||||
UploadAType = uploadFormData.FormFieldData["SockType"].ToString();
|
||||
if (uploadFormData.FormFieldData.ContainsKey("ObjectId"))
|
||||
UploadObjectId = uploadFormData.FormFieldData["ObjectId"].ToString();
|
||||
if (uploadFormData.FormFieldData.ContainsKey("Notes"))
|
||||
Notes = uploadFormData.FormFieldData["Notes"].ToString();
|
||||
//fileData in JSON stringify format which contains the actual last modified dates etc
|
||||
//"[{\"name\":\"Client.csv\",\"lastModified\":1582822079618},{\"name\":\"wmi4fu06nrs41.jpg\",\"lastModified\":1586900220990}]"
|
||||
FileData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<UploadFileData>>(uploadFormData.FormFieldData["FileData"].ToString());
|
||||
|
||||
}
|
||||
|
||||
|
||||
// long UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
//Instantiate the business object handler
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
|
||||
//We have our files now can parse and insert into db
|
||||
if (uploadFormData.UploadedFiles.Count > 0)
|
||||
{
|
||||
//deserialize each file and import
|
||||
foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
{
|
||||
JObject o = JObject.Parse(System.IO.File.ReadAllText(a.InitialUploadedPathName));
|
||||
if (!await biz.ImportAsync(o))
|
||||
{
|
||||
//delete all the files temporarily uploaded and return bad request
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.IO.InvalidDataException ex)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, ex.Message));
|
||||
}
|
||||
finally
|
||||
{
|
||||
//delete all the files temporarily uploaded and return bad request
|
||||
|
||||
ApiUploadProcessor.DeleteTempUploadFile(uploadFormData);
|
||||
}
|
||||
|
||||
//Return nothing
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Put (UpdateTranslationItemDisplayText)
|
||||
// /// Update a single key with new display text
|
||||
// /// </summary>
|
||||
// /// <param name="inObj">NewText/Id/Concurrency token object. NewText is new display text, Id is TranslationItem Id, concurrency token is required</param>
|
||||
// /// <returns></returns>
|
||||
// [HttpPut("updatetranslationitemdisplaytext")]
|
||||
// public async Task<IActionResult> PutTranslationItemDisplayText([FromBody] NewTextIdConcurrencyTokenItem inObj)
|
||||
// {
|
||||
// if (serverState.IsClosed)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
// if (!ModelState.IsValid)
|
||||
// {
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// }
|
||||
|
||||
// var oFromDb = await ct.TranslationItem.SingleOrDefaultAsync(z => z.Id == inObj.Id);
|
||||
|
||||
// if (oFromDb == null)
|
||||
// {
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
// }
|
||||
|
||||
// //Now fetch translation for rights and to ensure not stock
|
||||
// var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId);
|
||||
// if (oDbParent == null)
|
||||
// {
|
||||
// return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
// }
|
||||
|
||||
// if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Translation))
|
||||
// {
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// }
|
||||
|
||||
// //Instantiate the business object handler
|
||||
// TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
// try
|
||||
// {
|
||||
// if (!await biz.PutTranslationItemDisplayTextAsync(oFromDb, inObj, oDbParent))
|
||||
// {
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// }
|
||||
// }
|
||||
// catch (DbUpdateConcurrencyException)
|
||||
// {
|
||||
// if (!await biz.TranslationItemExistsAsync(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(ApiOkResponse.Response(new { Concurrency = oFromDb.Concurrency }));
|
||||
// }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Put (UpdateTranslationItemDisplayText)
|
||||
/// Update a list of items with new display text
|
||||
/// </summary>
|
||||
/// <param name="inObj">Array of NewText/Id/Concurrency token objects. NewText is new display text, Id is TranslationItem Id, concurrency token is required</param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("updatetranslationitemsdisplaytext")]
|
||||
public async Task<IActionResult> PutTranslationItemsDisplayText([FromBody] List<NewTextIdConcurrencyTokenItem> inObj)
|
||||
{
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var oFromDb = await ct.TranslationItem.AsNoTracking().SingleOrDefaultAsync(z => z.Id == inObj[0].Id);
|
||||
|
||||
if (oFromDb == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
//Now fetch translation for rights and to ensure not stock
|
||||
var oDbParent = await ct.Translation.SingleOrDefaultAsync(z => z.Id == oFromDb.TranslationId);
|
||||
if (oDbParent == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.Translation))
|
||||
{
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
TranslationBiz biz = TranslationBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
try
|
||||
{
|
||||
if (!await biz.PutTranslationItemsDisplayTextAsync(inObj, oDbParent))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
|
||||
//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 NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#if (DEBUG)
|
||||
public class TranslationCoverageInfo
|
||||
{
|
||||
public List<string> RequestedKeys { get; set; }
|
||||
public int RequestedKeyCount { get; set; }
|
||||
public List<string> NotRequestedKeys { get; set; }
|
||||
public int NotRequestedKeyCount { get; set; }
|
||||
|
||||
public TranslationCoverageInfo()
|
||||
{
|
||||
RequestedKeys = new List<string>();
|
||||
NotRequestedKeys = new List<string>();
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
385
server/Controllers/UserController.cs
Normal file
385
server/Controllers/UserController.cs
Normal file
@@ -0,0 +1,385 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// User
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/user")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<UserController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public UserController(AyContext dbcontext, ILogger<UserController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get User
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>A single User</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetUser([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
//Also used for Contacts (customer type user or ho type user)
|
||||
//by users with no User right so further biz rule required depending on usertype
|
||||
//this is just phase 1
|
||||
bool AllowedOutsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer);
|
||||
bool AllowedInsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.User);
|
||||
|
||||
if (!AllowedOutsideUser && !AllowedInsideUser)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetForPublicAsync(id);
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
|
||||
if (o.IsOutsideUser && !AllowedOutsideUser)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!o.IsOutsideUser && !AllowedInsideUser)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update User
|
||||
/// (Login and / or Password are not changed if set to null / omitted)
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutUser([FromBody] User updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
//Also used for Contacts (customer type user or ho type user)
|
||||
//by users with no User right so further biz rule required depending on usertype
|
||||
//this is just phase 1
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.User) && !Authorized.HasModifyRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create User
|
||||
/// </summary>
|
||||
/// <param name="inObj"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostUser([FromBody] User inObj, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
//Instantiate the business object handler
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
//Also used for Contacts (customer type user or ho type user)
|
||||
//by users with no User right so further biz rule required depending on usertype
|
||||
//this is just phase 1
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, SockType.User) && !Authorized.HasCreateRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
|
||||
//Create and validate
|
||||
dtUser o = await biz.CreateAsync(inObj);
|
||||
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
{
|
||||
|
||||
//return success and link
|
||||
//NOTE: this is a USER object so we don't want to return some key fields for security reasons
|
||||
//which is why the object is "cleaned" before return
|
||||
return CreatedAtAction(nameof(UserController.GetUser), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Duplicate User
|
||||
// /// (Wiki and Attachments are not duplicated)
|
||||
// /// </summary>
|
||||
// /// <param name="id">Source object id</param>
|
||||
// /// <param name="apiVersion">From route path</param>
|
||||
// /// <returns>User</returns>
|
||||
// [HttpPost("duplicate/{id}")]
|
||||
// public async Task<IActionResult> DuplicateUser([FromRoute] long id, ApiVersion apiVersion)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
|
||||
// //Also used for Contacts (customer type user or ho type user)
|
||||
// //by users with no User right so further biz rule required depending on usertype
|
||||
// //this is just phase 1
|
||||
// if (!Authorized.HasCreateRole(HttpContext.Items, SockType.User) && !Authorized.HasCreateRole(HttpContext.Items, SockType.Customer))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// User o = await biz.DuplicateAsync(id);
|
||||
// if (o == null)
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// else
|
||||
// return CreatedAtAction(nameof(UserController.GetUser), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
// }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Delete User
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteUser([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
|
||||
|
||||
//Also used for Contacts (customer type user or ho type user)
|
||||
//by users with no User right so further biz rule required depending on usertype
|
||||
//this is just phase 1
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, SockType.User) && !Authorized.HasDeleteRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get list of Users
|
||||
/// (rights to User object required)
|
||||
/// </summary>
|
||||
/// <returns>All "inside" Users (except Customer and HeadOffice type)</returns>
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetInsideUserList()
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.User))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
|
||||
var ret = await ct.User.Where(z => z.UserType != UserType.Customer && z.UserType != UserType.HeadOffice).Select(z => new dtUser
|
||||
{
|
||||
Id = z.Id,
|
||||
Active = z.Active,
|
||||
Name = z.Name,
|
||||
Roles = z.Roles,
|
||||
UserType = z.UserType,
|
||||
EmployeeNumber = z.EmployeeNumber,
|
||||
LastLogin = z.LastLogin
|
||||
|
||||
}).ToListAsync();
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Get list of Customer / Head office Users
|
||||
// /// (Rights to Customer object required)
|
||||
// /// </summary>
|
||||
// /// <returns>All "outside" Users (No staff or contractors)</returns>
|
||||
// [HttpGet("outlist")]
|
||||
// public async Task<IActionResult> GetOutsideUserList()
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
// if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer))
|
||||
// return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
// var ret = await ct.User.Include(c => c.Customer).Include(h => h.HeadOffice).Include(o => o.UserOptions).Where(z => z.UserType == UserType.Customer || z.UserType == UserType.HeadOffice).Select(z => new
|
||||
// {
|
||||
// Id = z.Id,
|
||||
// Active = z.Active,
|
||||
// Name = z.Name,
|
||||
// UserType = z.UserType,
|
||||
// LastLogin = z.LastLogin,
|
||||
// EmailAddress = z.UserOptions.EmailAddress,
|
||||
// Phone1 = z.UserOptions.Phone1,
|
||||
// Phone2 = z.UserOptions.Phone2,
|
||||
// Phone3 = z.UserOptions.Phone3,
|
||||
// Organization = z.HeadOffice.Name ?? z.Customer.Name
|
||||
|
||||
// }).ToListAsync();
|
||||
// return Ok(ApiOkResponse.Response(ret));
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Get list of Customer Contact Users
|
||||
/// (Rights to Customer object required)
|
||||
/// </summary>
|
||||
/// <returns>Customer contact users</returns>
|
||||
[HttpGet("customer-contacts/{customerId}")]
|
||||
public async Task<IActionResult> GetCustomerContactList(long customerId)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
var ret = await ct.User.Include(o => o.UserOptions).Where(z => z.UserType == UserType.Customer && z.CustomerId == customerId).Select(z => new
|
||||
{
|
||||
Id = z.Id,
|
||||
Active = z.Active,
|
||||
Name = z.Name,
|
||||
UserType = z.UserType,
|
||||
LastLogin = z.LastLogin,
|
||||
EmailAddress = z.UserOptions.EmailAddress,
|
||||
Phone1 = z.UserOptions.Phone1,
|
||||
Phone2 = z.UserOptions.Phone2,
|
||||
Phone3 = z.UserOptions.Phone3
|
||||
|
||||
}).ToListAsync();
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of HeadOffice Contact Users
|
||||
/// (Rights to HeadOffice object required)
|
||||
/// </summary>
|
||||
/// <returns>HeadOffice contact users</returns>
|
||||
[HttpGet("head-office-contacts/{headofficeId}")]
|
||||
public async Task<IActionResult> GetHeadOfficeContactList(long headofficeId)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, SockType.HeadOffice))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
var ret = await ct.User.Include(o => o.UserOptions).Where(z => z.UserType == UserType.HeadOffice && z.HeadOfficeId == headofficeId).Select(z => new
|
||||
{
|
||||
Id = z.Id,
|
||||
Active = z.Active,
|
||||
Name = z.Name,
|
||||
UserType = z.UserType,
|
||||
LastLogin = z.LastLogin,
|
||||
EmailAddress = z.UserOptions.EmailAddress,
|
||||
Phone1 = z.UserOptions.Phone1,
|
||||
Phone2 = z.UserOptions.Phone2,
|
||||
Phone3 = z.UserOptions.Phone3
|
||||
|
||||
}).ToListAsync();
|
||||
return Ok(ApiOkResponse.Response(ret));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fetch user type (inside meaning staff or subcontractor or outside meaning customer or headoffice type user)
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>All "inside" Users (except Customer and HeadOffice type)</returns>
|
||||
[HttpGet("inside-type/{id}")]
|
||||
public async Task<IActionResult> GetInsideStatus(long id)
|
||||
{
|
||||
//This method is used by the Client UI to determine the correct edit form to show
|
||||
if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!Authorized.HasSelectRole(HttpContext.Items, SockType.User))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var u = await ct.User.FirstOrDefaultAsync(z => z.Id == id);
|
||||
if (u == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(u.UserType != UserType.Customer && u.UserType != UserType.HeadOffice));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fetch super user status
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns>true or false</returns>
|
||||
[HttpGet("amsu")]
|
||||
public ActionResult GetAMSU()
|
||||
{
|
||||
//This is used by v8 migrate so it doesn't need to decode web tokens
|
||||
if (serverState.IsClosed)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
return Ok(ApiOkResponse.Response(UserIdFromContext.Id(HttpContext.Items)==1));
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
206
server/Controllers/UserOptionsController.cs
Normal file
206
server/Controllers/UserOptionsController.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
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 Sockeye.Models;
|
||||
using Sockeye.Api.ControllerHelpers;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.Controllers
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// UserOptions
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/user-option")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class UserOptionsController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<UserOptionsController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public UserOptionsController(AyContext dbcontext, ILogger<UserOptionsController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get full UserOptions object
|
||||
/// </summary>
|
||||
/// <param name="id">UserId</param>
|
||||
/// <returns>A single UserOptions</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetUserOptions([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
{
|
||||
//Exception for SuperUser account to handle licensing issues
|
||||
if (UserIdFromContext.Id(HttpContext.Items) != 1)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
//Different than normal here: a user is *always* allowed to retrieve their own user options object
|
||||
if (id != UserId)
|
||||
{
|
||||
//Not users own options so need to check just as for User object as could be a Contact
|
||||
|
||||
//Also used for Contacts (customer type user or ho type user)
|
||||
//by users with no User right so further biz rule required depending on usertype
|
||||
//this is just phase 1
|
||||
bool AllowedOutsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.Customer);
|
||||
bool AllowedInsideUser = Authorized.HasReadFullRole(HttpContext.Items, SockType.User);
|
||||
|
||||
if (!AllowedOutsideUser && !AllowedInsideUser)
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
UserOptionsBiz biz = new UserOptionsBiz(ct, UserId, UserRolesFromContext.Roles(HttpContext.Items));
|
||||
var o = await biz.GetAsync(id);
|
||||
if (o == null)
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Creating a user creates a user options so no need for create ever
|
||||
// /// <summary>
|
||||
// /// Create UserOptions
|
||||
// /// </summary>
|
||||
// /// <param name="newObject"></param>
|
||||
// /// <param name="apiVersion">From route path</param>
|
||||
// /// <returns></returns>
|
||||
// [HttpPost]
|
||||
// public async Task<IActionResult> PostUserOptions([FromBody] UserOptions newObject, ApiVersion apiVersion)
|
||||
// {
|
||||
// if (!serverState.IsOpen)
|
||||
// return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
// // var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
// // //preclearance
|
||||
// // //the biz object will further check
|
||||
// // if (newObject.Id != UserId)
|
||||
// // {
|
||||
// // //Also used for Contacts (customer type user or ho type user)
|
||||
// // //by users with no User right so further biz rule required depending on usertype
|
||||
// // //this is just phase 1
|
||||
// // if (!Authorized.HasCreateRole(HttpContext.Items, SockType.User) && !Authorized.HasCreateRole(HttpContext.Items, SockType.Customer))
|
||||
// // return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
// // }
|
||||
// UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(new ApiErrorResponse(ModelState));
|
||||
// UserOptions o = await biz.CreateAsync(newObject);
|
||||
// if (o == null)
|
||||
// return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
// else
|
||||
// return CreatedAtAction(nameof(UserOptionsController.GetUserOptions), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
// }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update UserOptions
|
||||
/// </summary>
|
||||
/// <param name="id">User id</param>
|
||||
/// <param name="inObj"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> PutUserOptions([FromRoute] long id, [FromBody] UserOptions inObj)
|
||||
{
|
||||
if (serverState.IsClosed && UserIdFromContext.Id(HttpContext.Items) != 1)//bypass for superuser to fix fundamental problems
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
|
||||
var UserId = UserIdFromContext.Id(HttpContext.Items);
|
||||
|
||||
var o = await ct.UserOptions.SingleOrDefaultAsync(z => z.UserId == id);
|
||||
|
||||
if (o == null)
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
|
||||
//preclearance
|
||||
//the biz object will further check
|
||||
if (id != UserId)
|
||||
{
|
||||
//Also used for Contacts (customer type user or ho type user)
|
||||
//by users with no User right so further biz rule required depending on usertype
|
||||
//this is just phase 1
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, SockType.User) && !Authorized.HasModifyRole(HttpContext.Items, SockType.Customer))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
|
||||
}
|
||||
|
||||
//Instantiate the business object handler
|
||||
UserOptionsBiz biz = new UserOptionsBiz(ct, UserIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items));
|
||||
|
||||
try
|
||||
{
|
||||
if (!await biz.PutAsync(o, inObj))
|
||||
{
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!UserOptionsExists(id))
|
||||
{
|
||||
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
//exists but was changed by another user
|
||||
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool UserOptionsExists(long id)
|
||||
{
|
||||
//NOTE: checks by UserId, NOT by Id as in most other objects
|
||||
return ct.UserOptions.Any(z => z.UserId == id);
|
||||
}
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
Reference in New Issue
Block a user