This commit is contained in:
54
server/ConfigureSwaggerOptions.cs
Normal file
54
server/ConfigureSwaggerOptions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace Sockeye
|
||||
{
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the Swagger generation options.
|
||||
/// </summary>
|
||||
/// <remarks>This allows API versioning to define a Swagger document per API version after the
|
||||
/// <see cref="IApiVersionDescriptionProvider"/> service has been resolved from the service container.</remarks>
|
||||
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
|
||||
{
|
||||
readonly IApiVersionDescriptionProvider provider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigureSwaggerOptions"/> class.
|
||||
/// </summary>
|
||||
/// <param name="provider">The <see cref="IApiVersionDescriptionProvider">provider</see> used to generate Swagger documents.</param>
|
||||
public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) => this.provider = provider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure( SwaggerGenOptions options )
|
||||
{
|
||||
// add a swagger document for each discovered API version
|
||||
// note: you might choose to skip or document deprecated API versions differently
|
||||
foreach ( var description in provider.ApiVersionDescriptions )
|
||||
{
|
||||
options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) );
|
||||
}
|
||||
}
|
||||
|
||||
static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description )
|
||||
{
|
||||
var info = new OpenApiInfo()
|
||||
{
|
||||
Title = "Sockeye API",
|
||||
Version = description.ApiVersion.ToString(),
|
||||
Description = "Sockeye service management software API",
|
||||
|
||||
};
|
||||
|
||||
if ( description.IsDeprecated )
|
||||
{
|
||||
info.Description += " This API version has been deprecated.";
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
server/ControllerHelpers/ApiCreatedResponse.cs
Normal file
19
server/ControllerHelpers/ApiCreatedResponse.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiCreatedResponse
|
||||
{
|
||||
|
||||
public object Data { get; }
|
||||
|
||||
public ApiCreatedResponse(object result)
|
||||
{
|
||||
Data = result;
|
||||
}
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
106
server/ControllerHelpers/ApiCustomExceptionFilter.cs
Normal file
106
server/ControllerHelpers/ApiCustomExceptionFilter.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Util;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is essentially an unhandled exception handler
|
||||
/// </summary>
|
||||
public class ApiCustomExceptionFilter : IExceptionFilter
|
||||
{
|
||||
private readonly ILogger log;
|
||||
|
||||
// public ApiCustomExceptionFilter(ILoggerFactory logger)
|
||||
// {
|
||||
// if (logger == null)
|
||||
// {
|
||||
// throw new ArgumentNullException(nameof(logger));
|
||||
// }
|
||||
|
||||
// this.log = logger.CreateLogger("Server Exception");
|
||||
// }
|
||||
|
||||
public ApiCustomExceptionFilter(ILogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
this.log = logger;
|
||||
}
|
||||
|
||||
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
HttpStatusCode status = HttpStatusCode.InternalServerError;
|
||||
String message = String.Empty;
|
||||
#region If need to refine this further and deal with specific types
|
||||
// var exceptionType = context.Exception.GetType();
|
||||
// if (exceptionType == typeof(UnauthorizedAccessException))
|
||||
// {
|
||||
// message = "Unauthorized Access";
|
||||
// status = HttpStatusCode.Unauthorized;
|
||||
// }
|
||||
// else if (exceptionType == typeof(NotImplementedException))
|
||||
// {
|
||||
// message = "A server error occurred.";
|
||||
// status = HttpStatusCode.NotImplemented;
|
||||
// }
|
||||
// // else if (exceptionType == typeof(MyAppException))
|
||||
// // {
|
||||
// // message = context.Exception.ToString();
|
||||
// // status = HttpStatusCode.InternalServerError;
|
||||
// // }
|
||||
// else
|
||||
// {
|
||||
#endregion
|
||||
message = context.Exception.Message;
|
||||
status = HttpStatusCode.InternalServerError;
|
||||
//}
|
||||
|
||||
//No need to log test exceptions to check and filter out
|
||||
bool loggableError = true;
|
||||
|
||||
if (message.StartsWith("Test exception"))
|
||||
loggableError = false;
|
||||
|
||||
//LOG IT
|
||||
if (loggableError)
|
||||
log.LogError(context.Exception, "Error");
|
||||
|
||||
//Notify ops notification issue
|
||||
NotifyEventHelper.AddOpsProblemEvent("Server API internal error, see log for more details", context.Exception).Forget();//.Wait();
|
||||
|
||||
HttpResponse response = context.HttpContext.Response;
|
||||
response.StatusCode = (int)status;
|
||||
response.ContentType = "application/json; charset=utf-8";
|
||||
|
||||
//This line is critical, without it the response is not proper and fails in various clients (postman, xunit tests with httpclient)
|
||||
context.ExceptionHandled = true;
|
||||
|
||||
|
||||
response.WriteAsync(JsonConvert.SerializeObject(
|
||||
new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, "generalerror", "Server internal error; see server log for details"),
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
30
server/ControllerHelpers/ApiDetailError.cs
Normal file
30
server/ControllerHelpers/ApiDetailError.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Newtonsoft.Json;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Detail error for inner part of error response
|
||||
/// </summary>
|
||||
public class ApiDetailError
|
||||
{
|
||||
/* WAIT, why does this have CODE AND Error??! */
|
||||
// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
// public string Code { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Message { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Target { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Error { get; internal set; }
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
37
server/ControllerHelpers/ApiError.cs
Normal file
37
server/ControllerHelpers/ApiError.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ApiError
|
||||
{
|
||||
public string Code { get; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public List<ApiDetailError> Details { get; internal set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Message { get; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Target { get; }
|
||||
|
||||
public ApiError(ApiErrorCode apiCode, string message = null, string target = null)
|
||||
{
|
||||
Code = ((int)apiCode).ToString();
|
||||
Target = target;
|
||||
Message=message;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
122
server/ControllerHelpers/ApiErrorResponse.cs
Normal file
122
server/ControllerHelpers/ApiErrorResponse.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiErrorResponse
|
||||
{
|
||||
|
||||
[JsonIgnore]
|
||||
private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger<ApiErrorResponse>();
|
||||
|
||||
//Mandatory properties
|
||||
public ApiError Error { get; }
|
||||
|
||||
|
||||
|
||||
|
||||
//Generic error
|
||||
public ApiErrorResponse(ApiErrorCode apiCode, string target = null, string message = null)
|
||||
{
|
||||
|
||||
//try to get a stock message if nothing specified
|
||||
if (message == null)
|
||||
{
|
||||
message = ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(apiCode);
|
||||
}
|
||||
|
||||
Error = new ApiError(apiCode, message, target);
|
||||
|
||||
log.LogDebug("apiCode={0}, target={1}, message={2}", apiCode, target, message);
|
||||
}
|
||||
|
||||
|
||||
//Bad request (MODELSTATE ISSUE) error response handling
|
||||
|
||||
public ApiErrorResponse(ModelStateDictionary modelState)
|
||||
{
|
||||
|
||||
if (modelState.IsValid)
|
||||
{
|
||||
throw new ArgumentException("ModelState must be invalid", nameof(modelState));
|
||||
}
|
||||
|
||||
|
||||
//Set outer error and then put validation in details
|
||||
|
||||
Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.VALIDATION_FAILED));
|
||||
|
||||
|
||||
//https://www.jerriepelser.com/blog/validation-response-aspnet-core-webapi/
|
||||
//Message = "Validation Failed";
|
||||
Error.Details = new List<ApiDetailError>();
|
||||
|
||||
|
||||
foreach (var key in modelState.Keys)
|
||||
{
|
||||
var vErrors = modelState[key].Errors;
|
||||
foreach (ModelError m in vErrors)
|
||||
{
|
||||
string msg = "";
|
||||
if (!string.IsNullOrWhiteSpace(m.ErrorMessage))
|
||||
{
|
||||
msg += m.ErrorMessage + ". ";
|
||||
}
|
||||
if (m.Exception != null && !string.IsNullOrWhiteSpace(m.Exception.Message))
|
||||
{
|
||||
msg += "Exception: " + m.Exception.Message;
|
||||
}
|
||||
//example this produces
|
||||
//
|
||||
Error.Details.Add(new ApiDetailError() { Target = key, Message = msg, Error = ((int)ApiErrorCode.VALIDATION_INVALID_VALUE).ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log.LogDebug("BadRequest - Validation error");
|
||||
}
|
||||
|
||||
|
||||
//Business rule validation error response
|
||||
public ApiErrorResponse(List<ValidationError> errors)
|
||||
{
|
||||
Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.VALIDATION_FAILED));
|
||||
Error.Details = new List<ApiDetailError>();
|
||||
foreach (ValidationError v in errors)
|
||||
{
|
||||
Error.Details.Add(new ApiDetailError() { Target = v.Target, Message = v.Message, Error = ((int)v.Code).ToString() });
|
||||
}
|
||||
log.LogDebug("BadRequest - Validation error");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public void AddDetailError(ApiErrorCode apiCode, string target = null, string message = null)
|
||||
// {
|
||||
// if (Error.Details == null)
|
||||
// {
|
||||
// Error.Details = new List<ApiDetailError>();
|
||||
// }
|
||||
|
||||
// //try to get a stock message if nothing specified
|
||||
// if (message == null)
|
||||
// {
|
||||
// message = ApiErrorCodeStockMessage.GetMessage(apiCode);
|
||||
// }
|
||||
|
||||
// Error.Details.Add(new ApiDetailError() { Code = ((int)apiCode).ToString(), Target = target, Message = message });
|
||||
// }
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
34
server/ControllerHelpers/ApiNotAuthorizedResponse.cs
Normal file
34
server/ControllerHelpers/ApiNotAuthorizedResponse.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class ApiNotAuthorizedResponse
|
||||
{
|
||||
|
||||
[JsonIgnore]
|
||||
private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger<ApiNotAuthorizedResponse>();
|
||||
|
||||
//Mandatory properties
|
||||
public ApiError Error { get; }
|
||||
|
||||
//Generic error
|
||||
public ApiNotAuthorizedResponse()
|
||||
{
|
||||
Error = new ApiError(ApiErrorCode.NOT_AUTHORIZED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.NOT_AUTHORIZED));
|
||||
|
||||
log.LogDebug("ApiErrorCode={0}, message={1}", (int)ApiErrorCode.NOT_AUTHORIZED, Error.Message);
|
||||
}
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
10
server/ControllerHelpers/ApiOkResponse.cs
Normal file
10
server/ControllerHelpers/ApiOkResponse.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
public static class ApiOkResponse
|
||||
{
|
||||
public static object Response(object result)
|
||||
{
|
||||
return new { Data = result };
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
220
server/ControllerHelpers/ApiServerState.cs
Normal file
220
server/ControllerHelpers/ApiServerState.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains the current status of the server
|
||||
/// is injected everywhere for routes and others to check
|
||||
/// </summary>
|
||||
public class ApiServerState
|
||||
{
|
||||
|
||||
//private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger<ApiServerState>();
|
||||
public enum ServerState
|
||||
{
|
||||
///<summary>Unknown state, used for parsing</summary>
|
||||
UNKNOWN = 0,
|
||||
///<summary>No access for anyone API completely locked down. Not set by user but rather by internal server operations like importing or backup.</summary>
|
||||
Closed = 1,
|
||||
|
||||
///<summary>Access only to API Operations routes. Can be set by Ops user</summary>
|
||||
OpsOnly = 3,
|
||||
///<summary>Open for all users (default). Can be set by Ops user</summary>
|
||||
Open = 4
|
||||
}
|
||||
|
||||
private ServerState _currentState = ServerState.Closed;
|
||||
private ServerState _priorState = ServerState.Closed;
|
||||
private string _reason = string.Empty;
|
||||
private string _priorReason = string.Empty;
|
||||
private bool SYSTEM_LOCK = false;//really this is a license lock but not called that
|
||||
|
||||
public ApiServerState()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal void SetSystemLock(string reason)
|
||||
{
|
||||
//Lock down the server for license related issue
|
||||
//Only SuperUser account (id=1) can login or do anything, treats as if server was set to closed even if they change it to open
|
||||
//only way to reset it is to fetch a valid license
|
||||
// This is set to locked in TWO ways:
|
||||
|
||||
// 1) By CoreJobSweeper *if* the user count is mismatched to the license likely due to Users messing directly with the DB
|
||||
//trying to circumvent licensing
|
||||
// 2) By License::InitializeAsync upon finding user count mismatch or expired license key
|
||||
// and initializeasync is called on boot up or when a new key is downloaded and installed only
|
||||
//
|
||||
var msg = $"{reason}\r\nLogin as SuperUser to start evaluation / install license";
|
||||
SetState(ServerState.OpsOnly, msg);
|
||||
SYSTEM_LOCK = true;
|
||||
}
|
||||
|
||||
|
||||
//WARNING: if in future this is used for anything other than a license then it will need to see if locked for another reason before unlocking
|
||||
//recommend putting a code number in the reason then looking to see if it has the matching code
|
||||
internal void ClearSystemLock()
|
||||
{
|
||||
SYSTEM_LOCK = false;
|
||||
SetState(ServerState.Open, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the server state
|
||||
/// </summary>
|
||||
/// <param name="newState"></param>
|
||||
/// <param name="reason"></param>
|
||||
public void SetState(ServerState newState, string reason)
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
|
||||
_reason = reason;//keep the reason even if the state doesn't change
|
||||
if (newState == _currentState) return;
|
||||
|
||||
//Here we will likely need to trigger a notification to users if the state is going to be shutting down or is shut down
|
||||
_priorState = _currentState;//keep the prior state so it can be resumed easily
|
||||
_priorReason = _reason;//keep the original reason
|
||||
|
||||
_currentState = newState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current state of the server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ServerState GetState()
|
||||
{
|
||||
return _currentState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current reason for the state of the server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string Reason
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_currentState == ServerState.Open)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"\"{_reason}\"";
|
||||
// if (_currentState == ServerState.Closed)
|
||||
// return $"{ Sockeye.Biz.TranslationBiz.GetDefaultTranslationAsync("ErrorAPI2000").Result}\r\n{_reason}";
|
||||
// else //opsonly
|
||||
// return $"{ Sockeye.Biz.TranslationBiz.GetDefaultTranslationAsync("ErrorAPI2001").Result}\r\n{_reason}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//get the api error code associated with the server state
|
||||
public ApiErrorCode ApiErrorCode
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (_currentState)
|
||||
{
|
||||
case ServerState.Open:
|
||||
throw new System.NotSupportedException("ApiServerState:ApiErrorCode - No error code is associated with server state OPEN");
|
||||
case ServerState.OpsOnly:
|
||||
return ApiErrorCode.API_OPS_ONLY;
|
||||
|
||||
case ServerState.Closed:
|
||||
return ApiErrorCode.API_CLOSED;
|
||||
|
||||
}
|
||||
throw new System.NotSupportedException("ApiServerState:ApiErrorCode - No error code is associated with server state UNKNOWN");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void SetOpsOnly(string reason)
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(ServerState.OpsOnly, reason);
|
||||
}
|
||||
|
||||
|
||||
public void SetClosed(string reason)
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(ServerState.Closed, reason);
|
||||
}
|
||||
|
||||
public void SetOpen()
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(ServerState.Open, string.Empty);
|
||||
}
|
||||
|
||||
public void ResumePriorState()
|
||||
{
|
||||
//No changes allowed during a system lock
|
||||
if (SYSTEM_LOCK) return;
|
||||
SetState(_priorState, _priorReason);
|
||||
}
|
||||
|
||||
|
||||
public bool IsOpsOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentState == ServerState.OpsOnly && !SYSTEM_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentState != ServerState.Closed && _currentState != ServerState.OpsOnly && !SYSTEM_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentState == ServerState.Closed || SYSTEM_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
// public bool IsOpenOrOpsOnly
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// return (IsOpen || IsOpsOnly) && !SYSTEM_LOCK;
|
||||
// }
|
||||
// }
|
||||
|
||||
public bool IsSystemLocked
|
||||
{
|
||||
get
|
||||
{
|
||||
return SYSTEM_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
180
server/ControllerHelpers/ApiUploadProcessor.cs
Normal file
180
server/ControllerHelpers/ApiUploadProcessor.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Util;
|
||||
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
//Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
|
||||
//See AttachmentController at bottom of class for example of form that works with this code
|
||||
|
||||
/// <summary>
|
||||
/// Handle processing uplod form with potentially huge files being uploaded (which means can't use simplest built in upload handler method)
|
||||
/// </summary>
|
||||
internal static class ApiUploadProcessor
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// handle upload
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <returns><see cref="ApiUploadedFilesResult"/> list of files and form field data (if present)</returns>
|
||||
internal static async Task<ApiUploadedFilesResult> ProcessUploadAsync(Microsoft.AspNetCore.Http.HttpContext httpContext)
|
||||
{
|
||||
|
||||
ApiUploadedFilesResult result = new ApiUploadedFilesResult();
|
||||
FormOptions _defaultFormOptions = new FormOptions();
|
||||
try
|
||||
{
|
||||
// Used to accumulate all the form url encoded key value pairs in the
|
||||
// request.
|
||||
var formAccumulator = new KeyValueAccumulator();
|
||||
|
||||
var boundary = MultipartRequestHelper.GetBoundary(
|
||||
MediaTypeHeaderValue.Parse(httpContext.Request.ContentType),
|
||||
_defaultFormOptions.MultipartBoundaryLengthLimit);
|
||||
var reader = new MultipartReader(boundary, httpContext.Request.Body);
|
||||
|
||||
var section = await reader.ReadNextSectionAsync();
|
||||
|
||||
while (section != null)
|
||||
{
|
||||
ContentDispositionHeaderValue contentDisposition;
|
||||
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
|
||||
|
||||
if (hasContentDispositionHeader)
|
||||
{
|
||||
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
|
||||
{
|
||||
|
||||
string filePathAndName = string.Empty;
|
||||
var CleanedUploadFileName = contentDisposition.FileName.Value.Replace("\"", "");
|
||||
|
||||
//get temp file path and temp file name
|
||||
filePathAndName = FileUtil.NewRandomAttachmentFilesFolderFileName;
|
||||
|
||||
//save to disk
|
||||
using (var stream = new FileStream(filePathAndName, FileMode.Create))
|
||||
{
|
||||
await section.Body.CopyToAsync(stream);
|
||||
}
|
||||
result.UploadedFiles.Add(new UploadedFileInfo()
|
||||
{
|
||||
InitialUploadedPathName = filePathAndName,
|
||||
OriginalFileName = CleanedUploadFileName,
|
||||
MimeType = section.ContentType
|
||||
|
||||
});
|
||||
}
|
||||
else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
|
||||
{
|
||||
// Content-Disposition: form-data; name="key"
|
||||
//
|
||||
// value
|
||||
|
||||
// Do not limit the key name length here because the
|
||||
// multipart headers length limit is already in effect.
|
||||
var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
|
||||
var encoding = GetEncoding(section);
|
||||
using (var streamReader = new StreamReader(
|
||||
section.Body,
|
||||
encoding,
|
||||
detectEncodingFromByteOrderMarks: true,
|
||||
bufferSize: 1024,
|
||||
leaveOpen: true))
|
||||
{
|
||||
// The value length limit is enforced by MultipartBodyLengthLimit
|
||||
var value = await streamReader.ReadToEndAsync();
|
||||
if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
value = String.Empty;
|
||||
}
|
||||
formAccumulator.Append(key.Value, value);
|
||||
|
||||
if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
|
||||
{
|
||||
throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drains any remaining section body that has not been consumed and
|
||||
// reads the headers for the next section.
|
||||
section = await reader.ReadNextSectionAsync();
|
||||
}
|
||||
|
||||
//Get any extra form fields and return them
|
||||
result.FormFieldData = formAccumulator.GetResults();
|
||||
return result;
|
||||
}
|
||||
catch (Microsoft.AspNetCore.Http.BadHttpRequestException ex)
|
||||
{
|
||||
//most commonly here due to file too large
|
||||
result.Error = $"Code:{ex.StatusCode}, Error: {ex.Message}";
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void DeleteTempUploadFile(ApiUploadedFilesResult uploadFormData)
|
||||
{
|
||||
if (uploadFormData.UploadedFiles.Count > 0)
|
||||
{
|
||||
foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
|
||||
{
|
||||
System.IO.File.Delete(a.InitialUploadedPathName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Encoding GetEncoding(MultipartSection section)
|
||||
{
|
||||
MediaTypeHeaderValue mediaType;
|
||||
var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
|
||||
// UTF-7 is insecure and should not be honored. UTF-8 will succeed in
|
||||
// most cases.
|
||||
if (!hasMediaTypeHeader || Encoding.UTF8.Equals(mediaType.Encoding))
|
||||
{
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
return mediaType.Encoding;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains result of upload form processor
|
||||
/// </summary>
|
||||
public class ApiUploadedFilesResult
|
||||
{
|
||||
public Dictionary<string, Microsoft.Extensions.Primitives.StringValues> FormFieldData { get; set; }
|
||||
public List<UploadedFileInfo> UploadedFiles { get; set; }
|
||||
public string Error { get; set; }
|
||||
|
||||
public ApiUploadedFilesResult()
|
||||
{
|
||||
FormFieldData = new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>();
|
||||
UploadedFiles = new List<UploadedFileInfo>();
|
||||
Error = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
228
server/ControllerHelpers/Authorized.cs
Normal file
228
server/ControllerHelpers/Authorized.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using EnumsNET;
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
//AUTHORIZATION ROLES: NOTE - this is only 'stage1' of generally checking rights, individual objects can also have business rules that affect access exactly as these roles do
|
||||
//Most objects won't need more than this but some specialized ones will have further checks depending on biz rules
|
||||
|
||||
internal static class Authorized
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// User has any role restricted or full
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="CheckRoles"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasAnyRole(IDictionary<object, object> HttpContextItems, AuthorizationRoles CheckRoles)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
return HasAnyRole(currentUserRoles, CheckRoles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User has any role restricted or full
|
||||
/// </summary>
|
||||
/// <param name="currentUserRoles"></param>
|
||||
/// <param name="CheckRoles"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasAnyRole(AuthorizationRoles currentUserRoles, AuthorizationRoles CheckRoles)
|
||||
{
|
||||
if (currentUserRoles.HasAnyFlags(CheckRoles))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// any access at all?
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasAnyRole(IDictionary<object, object> HttpContextItems, SockType aType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
return HasAnyRole(currentUserRoles, aType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User has any access at all to this object?
|
||||
/// </summary>
|
||||
/// <param name="currentUserRoles"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasAnyRole(AuthorizationRoles currentUserRoles, SockType aType)
|
||||
{
|
||||
var RoleSet = BizRoles.GetRoleSet(aType);
|
||||
if (RoleSet == null) return false;
|
||||
var AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change | RoleSet.Select;
|
||||
return currentUserRoles.HasAnyFlags(AllowedRoles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// READ FULL RECORD (not just name and id)
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasSelectRole(IDictionary<object, object> HttpContextItems, SockType aType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
return HasSelectRole(currentUserRoles, aType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SELECT BY NAME
|
||||
/// </summary>
|
||||
/// <param name="currentUserRoles"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasSelectRole(AuthorizationRoles currentUserRoles, SockType aType)
|
||||
{
|
||||
var RoleSet = BizRoles.GetRoleSet(aType);
|
||||
if (RoleSet == null) return false;
|
||||
|
||||
//NOTE: this assumes that if you can change you can read
|
||||
if (currentUserRoles.HasAnyFlags(RoleSet.Change))
|
||||
return true;
|
||||
|
||||
if (currentUserRoles.HasAnyFlags(RoleSet.ReadFullRecord))
|
||||
return true;
|
||||
|
||||
if (currentUserRoles.HasAnyFlags(RoleSet.Select))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// READ FULL RECORD (not just name and id)
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasReadFullRole(IDictionary<object, object> HttpContextItems, SockType aType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
return HasReadFullRole(currentUserRoles, aType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// READ FULL RECORD (not just name and id)
|
||||
/// </summary>
|
||||
/// <param name="currentUserRoles"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasReadFullRole(AuthorizationRoles currentUserRoles, SockType aType)
|
||||
{
|
||||
//NOTE: this assumes that if you can change you can read
|
||||
var RoleSet = BizRoles.GetRoleSet(aType);
|
||||
if (RoleSet == null) return false;
|
||||
var AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
return currentUserRoles.HasAnyFlags(AllowedRoles);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// CREATE
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasCreateRole(IDictionary<object, object> HttpContextItems, SockType aType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
return HasCreateRole(currentUserRoles, aType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CREATE
|
||||
/// </summary>
|
||||
/// <param name="currentUserRoles"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasCreateRole(AuthorizationRoles currentUserRoles, SockType aType)
|
||||
{
|
||||
var RoleSet = BizRoles.GetRoleSet(aType);
|
||||
if (RoleSet == null) return false;
|
||||
if (currentUserRoles.HasAnyFlags(RoleSet.Change))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MODIFY
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="aType"></param>
|
||||
|
||||
/// <returns></returns>
|
||||
internal static bool HasModifyRole(IDictionary<object, object> HttpContextItems, SockType aType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
return HasModifyRole(currentUserRoles, aType);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MODIFY
|
||||
/// </summary>
|
||||
/// <param name="currentUserRoles"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasModifyRole(AuthorizationRoles currentUserRoles, SockType aType)
|
||||
{
|
||||
var RoleSet = BizRoles.GetRoleSet(aType);
|
||||
if (RoleSet == null) return false;
|
||||
if (currentUserRoles.HasAnyFlags(RoleSet.Change))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DELETE
|
||||
/// </summary>
|
||||
/// <param name="HttpContextItems"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
//For now just going to treat as a modify, but for maximum flexibility keeping this as a separate method in case we change our minds in future
|
||||
internal static bool HasDeleteRole(IDictionary<object, object> HttpContextItems, SockType aType)
|
||||
{
|
||||
AuthorizationRoles currentUserRoles = UserRolesFromContext.Roles(HttpContextItems);
|
||||
long currentUserId = UserIdFromContext.Id(HttpContextItems);
|
||||
return HasDeleteRole(currentUserRoles, aType);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DELETE
|
||||
/// </summary>
|
||||
/// <param name="currentUserRoles"></param>
|
||||
/// <param name="aType"></param>
|
||||
/// <returns></returns>
|
||||
//For now just going to treat as a modify, but for maximum flexibility keeping this as a separate method in case we change our minds in future
|
||||
internal static bool HasDeleteRole(AuthorizationRoles currentUserRoles, SockType aType)
|
||||
{
|
||||
var RoleSet = BizRoles.GetRoleSet(aType);
|
||||
if (RoleSet == null) return false;
|
||||
if (currentUserRoles.HasAnyFlags(RoleSet.Change))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
//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>
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
var formValueProviderFactory = context.ValueProviderFactories
|
||||
.OfType<FormValueProviderFactory>()
|
||||
.FirstOrDefault();
|
||||
if (formValueProviderFactory != null)
|
||||
{
|
||||
context.ValueProviderFactories.Remove(formValueProviderFactory);
|
||||
}
|
||||
|
||||
var jqueryFormValueProviderFactory = context.ValueProviderFactories
|
||||
.OfType<JQueryFormValueProviderFactory>()
|
||||
.FirstOrDefault();
|
||||
if (jqueryFormValueProviderFactory != null)
|
||||
{
|
||||
context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
77
server/ControllerHelpers/MultipartRequestHelper.cs
Normal file
77
server/ControllerHelpers/MultipartRequestHelper.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class MultipartRequestHelper
|
||||
{
|
||||
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
|
||||
// The spec says 70 characters is a reasonable limit.
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="lengthLimit"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
|
||||
{
|
||||
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
|
||||
if (string.IsNullOrWhiteSpace(boundary))
|
||||
{
|
||||
throw new InvalidDataException("Missing content-type boundary.");
|
||||
}
|
||||
|
||||
if (boundary.Length > lengthLimit)
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
$"Multipart boundary length limit {lengthLimit} exceeded.");
|
||||
}
|
||||
|
||||
return boundary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsMultipartContentType(string contentType)
|
||||
{
|
||||
return !string.IsNullOrEmpty(contentType)
|
||||
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentDisposition"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
|
||||
{
|
||||
// Content-Disposition: form-data; name="key";
|
||||
return contentDisposition != null
|
||||
&& contentDisposition.DispositionType.Equals("form-data")
|
||||
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
|
||||
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="contentDisposition"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
|
||||
{
|
||||
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
|
||||
return contentDisposition != null
|
||||
&& contentDisposition.DispositionType.Equals("form-data")
|
||||
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|
||||
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
server/ControllerHelpers/UserIdFromContext.cs
Normal file
16
server/ControllerHelpers/UserIdFromContext.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
internal static class UserIdFromContext
|
||||
{
|
||||
internal static long Id(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
|
||||
long? l = (long?)HttpContextItems["AY_USER_ID"];
|
||||
if (l==null)
|
||||
return 0L;
|
||||
return (long)l;
|
||||
}
|
||||
}
|
||||
}//eons
|
||||
20
server/ControllerHelpers/UserNameFromContext.cs
Normal file
20
server/ControllerHelpers/UserNameFromContext.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using EnumsNET;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class UserNameFromContext
|
||||
{
|
||||
internal static string Name(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
string s = (string)HttpContextItems["AY_USERNAME"];
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return "UNKNOWN USER NAME";
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
17
server/ControllerHelpers/UserRolesFromContext.cs
Normal file
17
server/ControllerHelpers/UserRolesFromContext.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class UserRolesFromContext
|
||||
{
|
||||
internal static AuthorizationRoles Roles(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
return (AuthorizationRoles)HttpContextItems["AY_ROLES"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
15
server/ControllerHelpers/UserTranslationIdFromContext.cs
Normal file
15
server/ControllerHelpers/UserTranslationIdFromContext.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
internal static class UserTranslationIdFromContext
|
||||
{
|
||||
internal static long Id(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
long? l = (long?)HttpContextItems["AY_TRANSLATION_ID"];
|
||||
if (l == null)
|
||||
return Sockeye.Util.ServerBootConfig.SOCKEYE_DEFAULT_TRANSLATION_ID;
|
||||
return (long)l;
|
||||
}
|
||||
}
|
||||
}//eons
|
||||
17
server/ControllerHelpers/UserTypeFromContext.cs
Normal file
17
server/ControllerHelpers/UserTypeFromContext.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.Api.ControllerHelpers
|
||||
{
|
||||
|
||||
|
||||
internal static class UserTypeFromContext
|
||||
{
|
||||
internal static UserType Type(IDictionary<object, object> HttpContextItems)
|
||||
{
|
||||
return (UserType)HttpContextItems["AY_USER_TYPE"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}//eons
|
||||
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
|
||||
81
server/DataList/AttachmentDataList.cs
Normal file
81
server/DataList/AttachmentDataList.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class AttachmentDataList : DataListProcessingBase
|
||||
{
|
||||
public AttachmentDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.FileAttachment;
|
||||
SQLFrom = "from afileattachment left join auser on (afileattachment.attachedByUserId=auser.id)";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "displayfilename", "object", "size", "username", "notes", "exists" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "displayfilename", "+" }, { "size", "-" } };
|
||||
|
||||
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "FileAttachment",
|
||||
FieldKey = "displayfilename",
|
||||
SockType = (int)SockType.FileAttachment,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "afileattachment.id",
|
||||
SqlValueColumnName = "afileattachment.displayfilename",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "User",
|
||||
FieldKey = "username",
|
||||
SockType = (int)SockType.User,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "auser.id",
|
||||
SqlValueColumnName = "auser.name",
|
||||
IsRowId = false
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AttachmentExists",
|
||||
FieldKey = "exists",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "afileattachment.exists"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Object",
|
||||
FieldKey = "object",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "afileattachment.AttachToObjectid",
|
||||
SqlValueColumnName = $"AYGETNAME(afileattachment.AttachToObjectid, afileattachment.attachtosockType,{translationId})",
|
||||
SqlATypeColumnName = "afileattachment.attachtosockType",
|
||||
Translate = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "FileSize",
|
||||
FieldKey = "size",
|
||||
UiFieldDataType = (int)UiFieldDataType.MemorySize,
|
||||
SqlValueColumnName = "afileattachment.size"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AttachmentNotes",
|
||||
FieldKey = "notes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "afileattachment.notes"
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
326
server/DataList/CustomerDataList.cs
Normal file
326
server/DataList/CustomerDataList.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Models;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class CustomerDataList : DataListProcessingBase, IDataListInternalCriteria
|
||||
{
|
||||
public CustomerDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.Customer;
|
||||
SQLFrom = @"FROM ACUSTOMER LEFT JOIN AHEADOFFICE ON (ACUSTOMER.HEADOFFICEID = AHEADOFFICE.ID)";
|
||||
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "customername", "customerphone1", "customeremail", "customerheadoffice" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "customername", "+" } };
|
||||
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerName",
|
||||
FieldKey = "customername",
|
||||
SockType = (int)SockType.Customer,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "acustomer.id",
|
||||
SqlValueColumnName = "acustomer.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerNotes",
|
||||
FieldKey = "customernotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.notes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "customeractive",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "acustomer.active"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "customertags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "acustomer.tags"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "WebAddress",
|
||||
FieldKey = "customerwebaddress",
|
||||
UiFieldDataType = (int)UiFieldDataType.HTTP,
|
||||
SqlValueColumnName = "acustomer.webaddress"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerAlertNotes",
|
||||
FieldKey = "CustomerAlertNotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.alertnotes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOffice",
|
||||
FieldKey = "customerheadoffice",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SockType = (int)SockType.HeadOffice,
|
||||
SqlIdColumnName = "aheadoffice.id",
|
||||
SqlValueColumnName = "aheadoffice.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerBillHeadOffice",
|
||||
FieldKey = "customerbillheadoffice",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "acustomer.billheadoffice"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerTechNotes",
|
||||
FieldKey = "customertechnotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.technotes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerAccountNumber",
|
||||
FieldKey = "customeraccountnumber",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.accountnumber"
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerPhone1",
|
||||
FieldKey = "customerphone1",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "acustomer.phone1"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerPhone2",
|
||||
FieldKey = "customerphone2",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "acustomer.phone2"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerPhone3",
|
||||
FieldKey = "customerphone3",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "acustomer.phone3"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerPhone4",
|
||||
FieldKey = "customerphone4",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "acustomer.phone4"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerPhone5",
|
||||
FieldKey = "customerphone5",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "acustomer.phone5"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerEmail",
|
||||
FieldKey = "customeremail",
|
||||
UiFieldDataType = (int)UiFieldDataType.EmailAddress,
|
||||
SqlValueColumnName = "acustomer.emailaddress"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalDeliveryAddress",
|
||||
FieldKey = "customerpostaddress",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.postaddress"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalCity",
|
||||
FieldKey = "customerpostcity",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.postcity"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalStateProv",
|
||||
FieldKey = "customerpostregion",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.postregion"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalCountry",
|
||||
FieldKey = "customerpostcountry",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.postcountry"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalPostal",
|
||||
FieldKey = "customerpostcode",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.postcode"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressDeliveryAddress",
|
||||
FieldKey = "customeraddress",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.address"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressCity",
|
||||
FieldKey = "customercity",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.city"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressStateProv",
|
||||
FieldKey = "customerregion",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.region"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressCountry",
|
||||
FieldKey = "customercountry",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.country"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostal",
|
||||
FieldKey = "customeraddresspostal",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomer.addresspostal"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressLatitude",
|
||||
FieldKey = "customerlatitude",
|
||||
UiFieldDataType = (int)UiFieldDataType.Decimal,
|
||||
SqlValueColumnName = "acustomer.latitude"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressLongitude",
|
||||
FieldKey = "customerlongitude",
|
||||
UiFieldDataType = (int)UiFieldDataType.Decimal,
|
||||
SqlValueColumnName = "acustomer.longitude"
|
||||
});
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom1", FieldKey = "customercustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom2", FieldKey = "customercustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom3", FieldKey = "customercustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom4", FieldKey = "customercustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom5", FieldKey = "customercustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom6", FieldKey = "customercustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom7", FieldKey = "customercustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom8", FieldKey = "customercustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom9", FieldKey = "customercustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom10", FieldKey = "customercustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom11", FieldKey = "customercustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom12", FieldKey = "customercustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom13", FieldKey = "customercustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom14", FieldKey = "customercustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom15", FieldKey = "customercustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "CustomerCustom16", FieldKey = "customercustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "acustomer.customfields" });
|
||||
|
||||
|
||||
//META COLUMNS
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "metaheadoffice",
|
||||
UiFieldDataType = (int)UiFieldDataType.InternalId,
|
||||
SqlIdColumnName = "aheadoffice.id",
|
||||
SqlValueColumnName = "aheadoffice.id",
|
||||
IsMeta = true //"I'm So Meta Even This Acronym"
|
||||
});
|
||||
}
|
||||
|
||||
public List<DataListFilterOption> DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria)
|
||||
{
|
||||
List<DataListFilterOption> ret = new List<DataListFilterOption>();
|
||||
|
||||
//ClientCriteria format for this list is "OBJECTID,AYATYPE"
|
||||
var crit = (clientCriteria ?? "").Split(',').Select(z => z.Trim()).ToArray();
|
||||
if (crit.Length > 1)
|
||||
{
|
||||
//for now just show all customers of headoffice
|
||||
int nType = 0;
|
||||
if (!int.TryParse(crit[1], out nType)) return ret;
|
||||
SockType forType = (SockType)nType;
|
||||
if (forType != SockType.HeadOffice) return ret;
|
||||
|
||||
long lId = 0;
|
||||
if (!long.TryParse(crit[0], out lId)) return ret;
|
||||
if (lId == 0) return ret;
|
||||
|
||||
//Have valid type, have an id, so filter away
|
||||
switch (forType)
|
||||
{
|
||||
case SockType.HeadOffice:
|
||||
{
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metaheadoffice" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = crit[0], op = DataListFilterComparisonOperator.Equality });
|
||||
ret.Add(FilterOption);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
87
server/DataList/CustomerNoteDataList.cs
Normal file
87
server/DataList/CustomerNoteDataList.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Models;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class CustomerNoteDataList : DataListProcessingBase, IDataListInternalCriteria
|
||||
{
|
||||
public CustomerNoteDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.CustomerNote;
|
||||
SQLFrom = "from acustomernote left join auser on (acustomernote.userid=auser.id)";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "notedate", "notes", "username" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "notedate", "-" } };
|
||||
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "User",
|
||||
FieldKey = "username",
|
||||
SockType = (int)SockType.User,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "auser.id",
|
||||
SqlValueColumnName = "auser.name",
|
||||
IsRowId = false
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerNoteNotes",
|
||||
FieldKey = "notes",
|
||||
SockType = (int)SockType.CustomerNote,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "acustomernote.id",
|
||||
SqlValueColumnName = "acustomernote.notes",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerNoteNoteDate",
|
||||
FieldKey = "notedate",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "acustomernote.notedate"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "customernotetags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "acustomernote.tags"
|
||||
});
|
||||
|
||||
//META column
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "metacustomer",
|
||||
UiFieldDataType = (int)UiFieldDataType.InternalId,
|
||||
SqlIdColumnName = "acustomernote.customerid",
|
||||
SqlValueColumnName = "acustomernote.customerid",
|
||||
IsMeta = true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public List<DataListFilterOption> DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria)
|
||||
{
|
||||
List<DataListFilterOption> ret = new List<DataListFilterOption>();
|
||||
//ClientCriteria MUST be CustomerId
|
||||
if (string.IsNullOrWhiteSpace(clientCriteria))
|
||||
throw new System.ArgumentNullException("CustomerNoteDataList - ClientCriteria is empty, should be Customer ID");
|
||||
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metacustomer" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = clientCriteria, op = DataListFilterComparisonOperator.Equality });
|
||||
|
||||
ret.Add(FilterOption);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
75
server/DataList/CustomerNotificationDeliveryLogDataList.cs
Normal file
75
server/DataList/CustomerNotificationDeliveryLogDataList.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class CustomerNotificationDeliveryLogDataList : DataListProcessingBase
|
||||
{
|
||||
public CustomerNotificationDeliveryLogDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.OpsNotificationSettings;
|
||||
SQLFrom = "from acustomernotifydeliverylog left join acustomernotifysubscription on acustomernotifysubscription.id = acustomernotifydeliverylog.customernotifysubscriptionid";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "CustomerNotifySubscription", "Processed", "NotifyEventType", "CustomerTags", "Failed", "Errors" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "Processed", "-" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerNotifySubscription",
|
||||
FieldKey = "CustomerNotifySubscription",
|
||||
SockType = (int)SockType.CustomerNotifySubscription,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "acustomernotifysubscription.id",
|
||||
SqlValueColumnName = "acustomernotifysubscription.id",
|
||||
IsRowId = false
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Processed",
|
||||
FieldKey = "Processed",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "acustomernotifydeliverylog.processed"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "NotifyEventType",
|
||||
FieldKey = "NotifyEventType",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()),
|
||||
SqlValueColumnName = "acustomernotifysubscription.eventtype"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "CustomerTags",
|
||||
FieldKey = "CustomerTags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "acustomernotifysubscription.customertags"
|
||||
});
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Failed",
|
||||
FieldKey = "Failed",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "acustomernotifydeliverylog.fail"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Errors",
|
||||
FieldKey = "Errors",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "acustomernotifydeliverylog.error"
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
56
server/DataList/DataListFactory.cs
Normal file
56
server/DataList/DataListFactory.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal static class DataListFactory
|
||||
{
|
||||
|
||||
//Instantiate list object specified
|
||||
//this is safe as it's only attempting to load assemblies in the Sockeye.DataList namespace so can't attempt to instantiate some random object or nefarious object
|
||||
//returns null if doesn't exist
|
||||
internal static IDataListProcessing GetAyaDataList(string ListKey, long translationId)
|
||||
{
|
||||
System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly();
|
||||
return ass.CreateInstance($"Sockeye.DataList.{ListKey}", false, System.Reflection.BindingFlags.Default, null, new object[] { translationId }, null, null) as IDataListProcessing;
|
||||
|
||||
}
|
||||
|
||||
//List all the datalist types available
|
||||
internal static List<string> GetListOfAllDataListKeyNames()
|
||||
{
|
||||
//https://stackoverflow.com/a/42574373/8939
|
||||
|
||||
List<string> ret = new List<string>();
|
||||
System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly();
|
||||
|
||||
foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes)
|
||||
{
|
||||
if (!ti.IsAbstract && ti.ImplementedInterfaces.Contains(typeof(IDataListProcessing)))
|
||||
{
|
||||
ret.Add(ti.Name);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
//Verify listkey
|
||||
internal static bool ListKeyIsValid(string listKey)
|
||||
{
|
||||
|
||||
System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly();
|
||||
|
||||
foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes)
|
||||
{
|
||||
if (!ti.IsAbstract && ti.ImplementedInterfaces.Contains(typeof(IDataListProcessing)))
|
||||
{
|
||||
if (ti.Name == listKey)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
310
server/DataList/DataListFetcher.cs
Normal file
310
server/DataList/DataListFetcher.cs
Normal file
@@ -0,0 +1,310 @@
|
||||
//#define AYSHOWQUERYINFO
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sockeye.Models;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal static class DataListFetcher
|
||||
{
|
||||
|
||||
#if (AYSHOWQUERYINFO)
|
||||
#if (DEBUG)
|
||||
#warning FYI AYSHOWQUERYINFO is defined
|
||||
#else
|
||||
#error ### HOLDUP: AYSHOWQUERYINFO is defined in a RELEASE BUILD!!!!
|
||||
#endif
|
||||
#endif
|
||||
////////////////////////////////////////////////
|
||||
// Get the data list data requested
|
||||
//
|
||||
//
|
||||
internal static async Task<DataListReturnData> GetResponseAsync(AyContext ct, DataListTableProcessingOptions dataListTableProcessingOptions, IDataListProcessing DataList, AuthorizationRoles userRoles, ILogger log, long userId)
|
||||
{
|
||||
//#BUILD THE QUERY
|
||||
|
||||
//SELECT CLAUSE
|
||||
var qSelect = DataListSqlSelectBuilder.BuildForDataTableListResponse(DataList.FieldDefinitions, dataListTableProcessingOptions.AllUniqueColumnKeysReferenced);
|
||||
|
||||
//FROM CLAUSE
|
||||
var qFrom = DataList.SQLFrom;
|
||||
|
||||
var qWhere = string.Empty;
|
||||
var qOrderBy = string.Empty;
|
||||
|
||||
//WHERE CLAUSE - FILTER
|
||||
qWhere = DataListSqlFilterCriteriaBuilder.DataFilterToSQLCriteria(DataList.FieldDefinitions, dataListTableProcessingOptions);
|
||||
|
||||
//ORDER BY CLAUSE - SORT
|
||||
//BUILD ORDER BY
|
||||
qOrderBy = DataListSqlFilterOrderByBuilder.DataFilterToSQLOrderBy(DataList.FieldDefinitions, dataListTableProcessingOptions);
|
||||
|
||||
//LIMIT AND OFFSET CLAUSE - PAGING
|
||||
dataListTableProcessingOptions.Offset = dataListTableProcessingOptions.Offset ?? DataListTableProcessingOptions.DefaultOffset;
|
||||
dataListTableProcessingOptions.Limit = dataListTableProcessingOptions.Limit ?? DataListTableProcessingOptions.DefaultLimit;
|
||||
var qLimitOffset = $"LIMIT {dataListTableProcessingOptions.Limit} OFFSET {dataListTableProcessingOptions.Offset}";
|
||||
|
||||
//PUT IT ALL TOGETHER
|
||||
string qDataQuery = string.Empty;
|
||||
string qTotalRecordsQuery = string.Empty;
|
||||
|
||||
qDataQuery = $"{qSelect.Select} {qFrom} {qWhere} {qOrderBy} {qLimitOffset}".Replace(" ", " ");
|
||||
qTotalRecordsQuery = $"SELECT COUNT(*) {qFrom} {qWhere}".Replace(" ", " ");
|
||||
|
||||
//RETURN OBJECTS
|
||||
int returnRowColumnCount = dataListTableProcessingOptions.Columns.Count();
|
||||
List<List<DataListField>> rows = new List<List<DataListField>>();
|
||||
long totalRecordCount = 0;
|
||||
#if (DEBUG && AYSHOWQUERYINFO)
|
||||
System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
|
||||
|
||||
#endif
|
||||
|
||||
//QUERY THE DB
|
||||
using (var command = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
await ct.Database.OpenConnectionAsync();
|
||||
|
||||
//GET DATA RETURN ROWS
|
||||
command.CommandText = qDataQuery;
|
||||
try
|
||||
{
|
||||
#if (DEBUG && AYSHOWQUERYINFO)
|
||||
stopWatch.Start();
|
||||
#endif
|
||||
using (var dr = await command.ExecuteReaderAsync())
|
||||
{
|
||||
#if (DEBUG && AYSHOWQUERYINFO)
|
||||
stopWatch.Stop();
|
||||
log.LogInformation($"(debug) DataListFetcher:GetResponse DATA query took {stopWatch.ElapsedMilliseconds}ms to execute: {qDataQuery}");
|
||||
stopWatch.Reset();
|
||||
#endif
|
||||
while (dr.Read())
|
||||
{
|
||||
List<DataListField> row = new List<DataListField>(returnRowColumnCount);
|
||||
//INSERT REMAINING FIELDS FROM TEMPLATE INTO THE RETURN ROWS LIST
|
||||
foreach (string TemplateField in dataListTableProcessingOptions.Columns)
|
||||
{
|
||||
//get the AyaObjectFieldDefinition
|
||||
DataListFieldDefinition f = DataList.FieldDefinitions.FirstOrDefault(z => z.FieldKey == TemplateField);
|
||||
if (f == null)
|
||||
{
|
||||
|
||||
log.LogError($"DataListFetcher:GetResponseAsync Template field '{TemplateField}' was NOT found in the field definitions for data list {DataList.ToString()}");
|
||||
continue;
|
||||
}
|
||||
if (f.IsCustomField)
|
||||
{
|
||||
DataListField AyaField = new DataListField();
|
||||
//could be null
|
||||
var rawValue = dr.GetValue(qSelect.map[f.GetSqlValueColumnName()]);
|
||||
if (rawValue != null)
|
||||
{
|
||||
string cust = rawValue.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(cust))
|
||||
{
|
||||
JObject j = JObject.Parse(cust);
|
||||
//convert field name to cust name then get value
|
||||
var InternalCustomFieldName = FormFieldOptionalCustomizableReference.TranslateLTCustomFieldToInternalCustomFieldName(TemplateField);
|
||||
//Sometimes a custom field is specified but doesn't exist in the collection so don't assume it's there
|
||||
JToken o = j[InternalCustomFieldName];
|
||||
if (o != null)
|
||||
AyaField.v = o.Value<object>();
|
||||
else
|
||||
AyaField.v = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
AyaField.v = null;
|
||||
}
|
||||
}
|
||||
row.Add(AyaField);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataListField AyaField = new DataListField();
|
||||
AyaField.v = dr.GetValue(qSelect.map[f.GetSqlValueColumnName()]);
|
||||
|
||||
if (f.IsRowId)
|
||||
{
|
||||
AyaField.rid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AyaField.rid = null;
|
||||
}
|
||||
|
||||
if (f.SqlIdColumnName != null)
|
||||
{
|
||||
var ordinal = qSelect.map[f.SqlIdColumnName];
|
||||
if (!await dr.IsDBNullAsync(ordinal))
|
||||
AyaField.i = dr.GetInt64(ordinal);
|
||||
}
|
||||
|
||||
if (f.SqlATypeColumnName != null)
|
||||
{
|
||||
var ordinal = qSelect.map[f.SqlATypeColumnName];
|
||||
if (!await dr.IsDBNullAsync(ordinal))
|
||||
AyaField.ot = dr.GetInt32(ordinal);
|
||||
}
|
||||
|
||||
if (f.SqlColorColumnName != null)
|
||||
{
|
||||
var ordinal = qSelect.map[f.SqlColorColumnName];
|
||||
if (!await dr.IsDBNullAsync(ordinal))
|
||||
AyaField.clr = dr.GetString(ordinal);
|
||||
}
|
||||
|
||||
row.Add(AyaField);
|
||||
}
|
||||
}
|
||||
rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
//GET TOTAL RECORD COUNT
|
||||
command.CommandText = qTotalRecordsQuery;
|
||||
#if (DEBUG && AYSHOWQUERYINFO)
|
||||
stopWatch.Start();
|
||||
#endif
|
||||
using (var dr = await command.ExecuteReaderAsync())
|
||||
{
|
||||
#if (DEBUG && AYSHOWQUERYINFO)
|
||||
stopWatch.Stop();
|
||||
log.LogInformation($"(debug) DataListFetcher:GetResponse COUNT query took {stopWatch.ElapsedMilliseconds}ms to execute: {qTotalRecordsQuery}");
|
||||
#endif
|
||||
if (dr.Read())
|
||||
{
|
||||
totalRecordCount = dr.GetInt64(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Npgsql.PostgresException e)
|
||||
{
|
||||
//log out the exception and the query
|
||||
log.LogError("DataListFetcher:GetResponseAsync query failed. Data Query was:");
|
||||
log.LogError(qDataQuery);
|
||||
log.LogError("Count Query was:");
|
||||
log.LogError(qTotalRecordsQuery);
|
||||
log.LogError(e, "DB Exception");
|
||||
throw new System.Exception("DataListFetcher:GetResponseAsync - Query failed see log");
|
||||
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
//ensure any other type of exception gets surfaced properly
|
||||
//log out the exception and the query
|
||||
log.LogError("DataListFetcher:GetResponseAsync unexpected failure. Data Query was:");
|
||||
log.LogError(qDataQuery);
|
||||
log.LogError("Count Query was:");
|
||||
log.LogError(qTotalRecordsQuery);
|
||||
log.LogError(e, "Exception");
|
||||
throw new System.Exception("DataListFetcher:GetResponseAsync - unexpected failure see log");
|
||||
}
|
||||
}
|
||||
|
||||
//BUILD THE COLUMNS RETURN PROPERTY JSON FRAGMENT
|
||||
Newtonsoft.Json.Linq.JArray ColumnsJSON = null;
|
||||
ColumnsJSON = DataList.GenerateReturnListColumns(dataListTableProcessingOptions.Columns);
|
||||
return new DataListReturnData(rows,
|
||||
totalRecordCount,
|
||||
ColumnsJSON,
|
||||
dataListTableProcessingOptions.SortBy,
|
||||
dataListTableProcessingOptions.Filter.Where(z => z.Column.StartsWith("meta") == false).ToList(),
|
||||
dataListTableProcessingOptions.HiddenAffectiveColumns);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Get a list of id's of the datalist results for reporting
|
||||
// (and other uses like job ops, exporting etc)
|
||||
// called from RehydrateIdList only
|
||||
//
|
||||
internal static async Task<long[]> GetIdListResponseAsync(
|
||||
AyContext ct,
|
||||
DataListSelectedProcessingOptions dataListSelectionOptions,
|
||||
IDataListProcessing DataList,
|
||||
AuthorizationRoles userRoles,
|
||||
ILogger log,
|
||||
long userId,
|
||||
bool limitForReportDesigner)
|
||||
{
|
||||
//#BUILD THE QUERY
|
||||
|
||||
//SELECT FRAGMENT COLUMNS FROM TEMPLATE
|
||||
var qSelect = DataListSqlSelectBuilder.BuildForIdListResponse(DataList.FieldDefinitions, dataListSelectionOptions);
|
||||
|
||||
//FROM CLAUSE
|
||||
var qFrom = DataList.SQLFrom;
|
||||
|
||||
var qWhere = string.Empty;
|
||||
var qOrderBy = string.Empty;
|
||||
|
||||
//WHERE CLAUSE - FILTER
|
||||
qWhere = DataListSqlFilterCriteriaBuilder.DataFilterToSQLCriteria(DataList.FieldDefinitions, dataListSelectionOptions);
|
||||
|
||||
//ORDER BY CLAUSE - SORT
|
||||
qOrderBy = DataListSqlFilterOrderByBuilder.DataFilterToSQLOrderBy(DataList.FieldDefinitions, dataListSelectionOptions);
|
||||
|
||||
//LIMIT (if report designer)
|
||||
var qLimit = string.Empty;
|
||||
if (limitForReportDesigner)
|
||||
qLimit = "LIMIT 5";
|
||||
|
||||
//PUT IT ALL TOGETHER
|
||||
string qDataQuery = string.Empty;
|
||||
|
||||
qDataQuery = $"{qSelect} {qFrom} {qWhere} {qOrderBy} {qLimit} ".Replace(" ", " ");
|
||||
|
||||
//RETURN OBJECTS
|
||||
var retList = new List<long>();
|
||||
|
||||
using (var command = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
await ct.Database.OpenConnectionAsync();
|
||||
command.CommandText = qDataQuery;
|
||||
try
|
||||
{
|
||||
using (var dr = await command.ExecuteReaderAsync())
|
||||
{
|
||||
while (dr.Read())
|
||||
{
|
||||
//only one column and it's the zeroth id column
|
||||
if (!dr.IsDBNull(0))
|
||||
retList.Add(dr.GetInt64(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Npgsql.PostgresException e)
|
||||
{
|
||||
//log out the exception and the query
|
||||
log.LogError("DataListFetcher:GetIdListResponseAsync query failed unexpectedly. IDList Query was:");
|
||||
log.LogError(qDataQuery);
|
||||
|
||||
log.LogError(e, "DB Exception");
|
||||
throw new System.Exception("DataListFetcher:GetIdListResponseAsync - Query failed see log");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
//ensure any other type of exception gets surfaced properly
|
||||
//log out the exception and the query
|
||||
log.LogError("DataListFetcher:GetIdListResponseAsync unexpected failure. IDList Query was:");
|
||||
log.LogError(qDataQuery);
|
||||
|
||||
log.LogError(e, "Exception");
|
||||
throw new System.Exception("DataListFetcher:GetIdListResponseAsync - unexpected failure see log");
|
||||
}
|
||||
}
|
||||
return retList.ToArray();
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
17
server/DataList/DataListField.cs
Normal file
17
server/DataList/DataListField.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
public class DataListField
|
||||
{
|
||||
public object v { get; set; }//v for vvvvvvvv?
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm
|
||||
public long? i { get; set; }//id value
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm
|
||||
public bool? rid { get; set; } //row id for opening entire row by one field id
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm
|
||||
public int? ot { get; set; }//openable type
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] //https://www.newtonsoft.com/json/help/html/JsonPropertyPropertyLevelSetting.htm
|
||||
public string clr { get; set; }//color
|
||||
}
|
||||
}
|
||||
94
server/DataList/DataListFieldDefinition.cs
Normal file
94
server/DataList/DataListFieldDefinition.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
//This class defines a field used for returning data in list format for UI grid lists and reporting
|
||||
public class DataListFieldDefinition
|
||||
{
|
||||
//CLIENT / SERVER Unique identifier used at BOTH client and server
|
||||
//also the sql valuecolumnname if identical
|
||||
public string FieldKey { get; set; }
|
||||
|
||||
//CLIENT Use only for display
|
||||
public string TKey { get; set; }
|
||||
|
||||
//CLIENT Use only for display to disambiguate things like
|
||||
//Tags in main workorder and Tags in Workorder Item and Tags in Unit (all on same list)
|
||||
public string TKeySection { get; set; }
|
||||
|
||||
//CLIENT / SERVER - client display server validation purposes
|
||||
public bool IsCustomField { get; set; }
|
||||
|
||||
//CLIENT / SERVER - client display server validation purposes
|
||||
public bool IsFilterable { get; set; }
|
||||
|
||||
//CLIENT / SERVER - client display server validation purposes
|
||||
public bool IsSortable { get; set; }
|
||||
|
||||
//SERVER - indicates internal only meta column, not a client thing
|
||||
public bool IsMeta { get; set; }
|
||||
|
||||
//CLIENT Use only for display
|
||||
public int UiFieldDataType { get; set; }
|
||||
|
||||
//CLIENT Use only for display
|
||||
public string EnumType { get; set; }
|
||||
|
||||
//SERVER / CLIENT - used to identify the column that represents the entire row ID and object
|
||||
//MUST be present in all datalists and displayed at the client
|
||||
public bool IsRowId { get; set; }
|
||||
|
||||
//CLIENT / SERVER - client display and to indicate what object to open , Server for formatting return object
|
||||
public int SockType { get; set; }
|
||||
|
||||
//CLIENT - indicates client must translate the values in this column (typically computed columns based on aygetname procedure)
|
||||
public bool Translate { get; set; }
|
||||
|
||||
//SERVER - for building sql queries
|
||||
//don't return these properties when api user fetches field list definitions in DataListController
|
||||
[JsonIgnore]
|
||||
public string SqlIdColumnName { get; set; }
|
||||
[JsonIgnore]
|
||||
public string SqlValueColumnName { get; set; }
|
||||
[JsonIgnore]
|
||||
public string SqlATypeColumnName { get; set; }//column to fetch the SockType openabel for this field to set it dynamically instead of preset
|
||||
[JsonIgnore]
|
||||
public string SqlColorColumnName { get; set; }//column to fetch the color if applicable to this field
|
||||
|
||||
public DataListFieldDefinition()
|
||||
{
|
||||
//most common defaults
|
||||
IsCustomField = false;
|
||||
IsFilterable = true;
|
||||
IsSortable = true;
|
||||
IsRowId = false;
|
||||
IsMeta = false;
|
||||
Translate = false;
|
||||
//Set openable object type to no type which is the default and means it's not a link to another object
|
||||
SockType = (int)Biz.SockType.NoType;
|
||||
SqlATypeColumnName = null;//must be null as that is checked against specifically
|
||||
SqlColorColumnName = null;//must be null to be ignored properly
|
||||
|
||||
}
|
||||
|
||||
//Get column to query for display name or use FieldName if there is no difference
|
||||
public string GetSqlValueColumnName()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SqlValueColumnName))
|
||||
{
|
||||
return FieldKey.ToLowerInvariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SqlValueColumnName;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasIdColumn()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(SqlIdColumnName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
43
server/DataList/DataListFilterComparisonOperator.cs
Normal file
43
server/DataList/DataListFilterComparisonOperator.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
public static class DataListFilterComparisonOperator
|
||||
{
|
||||
//NOTE: no LIKE or NOT LIKE deliberately
|
||||
//StartsWith and EndsWith and Contains and NotContains cover all the most common cases and avoid needing a special set of LIKE characters people have to type and we have to document
|
||||
public const string Equality = "=";
|
||||
public const string GreaterThan = ">";
|
||||
public const string GreaterThanOrEqualTo = ">=";
|
||||
public const string LessThan = "<";
|
||||
public const string LessThanOrEqualTo = "<=";
|
||||
public const string NotEqual = "!=";
|
||||
public const string StartsWith = "%-";
|
||||
public const string EndsWith = "-%";
|
||||
public const string Contains = "-%-";
|
||||
public const string NotContains = "!-%-";
|
||||
|
||||
public static List<string> operators = null;
|
||||
public static List<string> Operators
|
||||
{
|
||||
get
|
||||
{
|
||||
if (operators == null)
|
||||
{
|
||||
operators = new List<string>();
|
||||
operators.Add(Equality);
|
||||
operators.Add(GreaterThan);
|
||||
operators.Add(GreaterThanOrEqualTo);
|
||||
operators.Add(LessThan);
|
||||
operators.Add(LessThanOrEqualTo);
|
||||
operators.Add(NotEqual);
|
||||
operators.Add(StartsWith);
|
||||
operators.Add(EndsWith);
|
||||
operators.Add(Contains);
|
||||
operators.Add(NotContains);
|
||||
|
||||
}
|
||||
return operators;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
210
server/DataList/DataListProcessingBase.cs
Normal file
210
server/DataList/DataListProcessingBase.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// DataList object base class
|
||||
/// </summary>
|
||||
internal abstract class DataListProcessingBase : IDataListProcessing
|
||||
{
|
||||
//CoreBizObject add here
|
||||
//well, not here exactly but add a new DATALIST class if it will be displayed as a list anywhere in the UI or reported on
|
||||
public DataListProcessingBase()
|
||||
{
|
||||
/*
|
||||
NOTE: all sql identifiers need to be explicitly identified as understood by postgres
|
||||
|
||||
DefaultColumns = new List<string>() { "XXX", "XXXX", "XXXX", "XXXX", "XXXX", "XXX", "XXXX", "XXXX", "XXXX", "XXXX" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "XXXX", "+" }, { "XXXX", "-" } };
|
||||
*/
|
||||
}
|
||||
|
||||
public string SQLFrom { get; set; }
|
||||
public List<DataListFieldDefinition> FieldDefinitions { get; set; }
|
||||
public AuthorizationRoles AllowedRoles { get; set; }
|
||||
public SockType DefaultListAType { get; set; }
|
||||
//public long CurrentUserId { get; set; }
|
||||
//public long CurrentUserTranslationId { get; set; }
|
||||
public List<string> DefaultColumns { get; set; }
|
||||
public Dictionary<string, string> DefaultSortBy { get; set; }
|
||||
|
||||
//set defaults if not provided in listOptions
|
||||
public void SetListOptionDefaultsIfNecessary(Models.DataListProcessingBase listOptions)
|
||||
{
|
||||
//columns, filter and sortby could all be null
|
||||
if (listOptions.Filter == null)
|
||||
listOptions.Filter = new List<DataListFilterOption>();
|
||||
|
||||
if (listOptions.SortBy == null)
|
||||
listOptions.SortBy = new Dictionary<string, string>();
|
||||
|
||||
//Check Columns
|
||||
if (listOptions is DataListTableProcessingOptions)
|
||||
{
|
||||
var dlto = ((DataListTableProcessingOptions)listOptions);
|
||||
if (dlto.Columns == null)
|
||||
dlto.Columns = new List<string>();
|
||||
//if this doesn't work then just ditch this method in favor of local code, it's not really saving much
|
||||
if (dlto.Columns.Count == 0)
|
||||
dlto.Columns = DefaultColumns;
|
||||
}
|
||||
|
||||
//Check SortBy
|
||||
if (listOptions.SortBy.Count == 0)
|
||||
listOptions.SortBy = DefaultSortBy;
|
||||
|
||||
//Check filter
|
||||
if (listOptions.Filter == null)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Newtonsoft.Json.Linq.JArray GenerateReturnListColumns(List<string> columns)
|
||||
{
|
||||
var CustomFieldDefinitions = GetCustomFieldDefinitionsForList();
|
||||
|
||||
//Generate JSON fragment to return with column definitions
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.Append("[");
|
||||
|
||||
bool FirstColumnAdded = false;
|
||||
|
||||
foreach (string s in columns)
|
||||
{
|
||||
DataListFieldDefinition o = FieldDefinitions.FirstOrDefault(z => z.FieldKey == s);
|
||||
#if (DEBUG)
|
||||
//Developers little helper
|
||||
if (o == null)
|
||||
{
|
||||
throw new System.ArgumentNullException($"DEV ERROR in AyaDataList::GenerateReturnListColumns - field {s} specified in columns was NOT found in ObjectFields list");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (o != null)
|
||||
{//Here is where we can vet the field name, if it doesn't exist. For production we'll just ignore those ones
|
||||
|
||||
if (FirstColumnAdded)
|
||||
sb.Append(",");
|
||||
sb.Append("{");
|
||||
//Build required part of column definition
|
||||
if (!o.IsCustomField)
|
||||
sb.Append($"\"cm\":\"{o.TKey}\",\"dt\":{(int)o.UiFieldDataType}");
|
||||
else
|
||||
{
|
||||
//insert specific type for this custom field
|
||||
if (CustomFieldDefinitions.ContainsKey(o.TKey))
|
||||
{
|
||||
var customFieldType = CustomFieldDefinitions[o.TKey];
|
||||
sb.Append($"\"cm\":\"{o.TKey}\",\"dt\":{customFieldType}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//this is normal as there may not be a definition for a Custom field but it's been requested so just treat it like text
|
||||
sb.Append($"\"cm\":\"{o.TKey}\",\"dt\":{(int)UiFieldDataType.Text}");
|
||||
}
|
||||
}
|
||||
|
||||
//Has a AyAType? (linkable / openable)
|
||||
if (o.SockType != 0)
|
||||
sb.Append($",\"sock\":{(int)o.SockType}");
|
||||
|
||||
//Row ID column?
|
||||
if (o.IsRowId)
|
||||
{
|
||||
sb.Append($",\"rid\":1");
|
||||
}
|
||||
|
||||
//Has a Enumtype?
|
||||
if (!string.IsNullOrEmpty(o.EnumType))
|
||||
sb.Append($",\"et\":\"{Sockeye.Util.StringUtil.TrimTypeName(o.EnumType)}\"");
|
||||
|
||||
|
||||
//field key needed for sorting etc
|
||||
sb.Append($",\"fk\":\"{o.FieldKey}\"");
|
||||
|
||||
//Not Sortable?
|
||||
if (!o.IsSortable)
|
||||
sb.Append($",\"ns\":1");
|
||||
|
||||
//Not Filterable?
|
||||
if (!o.IsFilterable)
|
||||
sb.Append($",\"nf\":1");
|
||||
|
||||
//translate required?
|
||||
if (o.Translate)
|
||||
sb.Append($",\"tra\":1");
|
||||
|
||||
|
||||
sb.Append("}");
|
||||
FirstColumnAdded = true;
|
||||
|
||||
}
|
||||
}
|
||||
sb.Append("]");
|
||||
|
||||
return JArray.Parse(sb.ToString());
|
||||
}
|
||||
|
||||
|
||||
//Find and return a dictionary of all custom fields definitions for all types in list
|
||||
//used to build the column array and define specific type defined for custom fields so client datatable
|
||||
//knows how to format it
|
||||
private Dictionary<string, int> GetCustomFieldDefinitionsForList()
|
||||
{
|
||||
//all keys and types can go in the same list since they are unique to each type of list
|
||||
//i.e. both users and widget custom fields can be in the same list
|
||||
Dictionary<string, int> ret = new Dictionary<string, int>();
|
||||
List<string> typesProcessed = new List<string>();
|
||||
//custom fields handling
|
||||
foreach (DataListFieldDefinition d in this.FieldDefinitions)
|
||||
{
|
||||
if (d.IsCustomField)
|
||||
{
|
||||
//this relies on the convention I'm using of SockType name as the first part of all custom fields lT keys, e.g.
|
||||
//WidgetCustom1 -> Widget
|
||||
var aysockTypename = d.TKey.Split("Custom")[0];
|
||||
if (!typesProcessed.Contains(aysockTypename))
|
||||
{
|
||||
//make sure we do each type only once
|
||||
typesProcessed.Add(aysockTypename);
|
||||
//fetch it and set it
|
||||
using (var ct = Sockeye.Util.ServiceProviderProvider.DBContext)
|
||||
{
|
||||
var fc = ct.FormCustom.AsNoTracking().SingleOrDefault(z => z.FormKey == aysockTypename);
|
||||
//normal condition
|
||||
if (fc == null)
|
||||
continue;
|
||||
|
||||
//iterate the fields and add each custom one with a type to the return dictionary
|
||||
var flds = JArray.Parse(fc.Template);
|
||||
foreach (JToken t in flds)
|
||||
{
|
||||
if (t["type"] != null)
|
||||
{
|
||||
ret.Add(t["fld"].Value<string>(), t["type"].Value<int>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
27
server/DataList/DataListReturnData.cs
Normal file
27
server/DataList/DataListReturnData.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Models;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
public class DataListReturnData
|
||||
{
|
||||
public object Data { get; }
|
||||
public long TotalRecordCount { get; }
|
||||
public object Columns { get; }
|
||||
public Dictionary<string, string> SortBy { get; set; }
|
||||
public List<DataListFilterOption> Filter { get; set; }
|
||||
//All columns that are hidden but are affecting the query (sorting, filtering)
|
||||
//so in UI can show that there are hidden columns affecting the result set
|
||||
public List<string> HiddenAffectiveColumns {get;set;}
|
||||
|
||||
public DataListReturnData(object returnItems, long totalRecordCount, Newtonsoft.Json.Linq.JArray columns, Dictionary<string, string> sortBy, List<DataListFilterOption> filter, List<string> hiddenAffectiveColumns)
|
||||
{
|
||||
Data = returnItems;
|
||||
TotalRecordCount = totalRecordCount;
|
||||
Columns = columns;
|
||||
SortBy = sortBy;
|
||||
Filter = filter;
|
||||
HiddenAffectiveColumns=hiddenAffectiveColumns;
|
||||
}
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
1179
server/DataList/DataListSqlFilterCriteriaBuilder.cs
Normal file
1179
server/DataList/DataListSqlFilterCriteriaBuilder.cs
Normal file
File diff suppressed because it is too large
Load Diff
51
server/DataList/DataListSqlFilterOrderByBuilder.cs
Normal file
51
server/DataList/DataListSqlFilterOrderByBuilder.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
public static class DataListSqlFilterOrderByBuilder
|
||||
{
|
||||
public static string DataFilterToSQLOrderBy(List<DataListFieldDefinition> objectFieldsList, Models.DataListProcessingBase listOptions)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
bool SortItemAdded = false;
|
||||
foreach (KeyValuePair<string, string> kvSort in listOptions.SortBy)
|
||||
{
|
||||
//Get the correct sql column name
|
||||
DataListFieldDefinition DataListField = objectFieldsList.FirstOrDefault(z => z.FieldKey == kvSort.Key);
|
||||
//No sorting on custom fields!
|
||||
if (DataListField.IsCustomField)
|
||||
continue;
|
||||
#if (DEBUG)
|
||||
//Developers little helper
|
||||
if (DataListField == null)
|
||||
throw new System.ArgumentNullException($"DEV ERROR in DataListSqlFilterOrderByBuilder.cs: field {kvSort.Key} specified in template was NOT found in ObjectFields list");
|
||||
|
||||
#endif
|
||||
var SQLValueColumnName = DataListField.GetSqlValueColumnName();
|
||||
if (SortItemAdded)
|
||||
sb.Append(", ");
|
||||
else
|
||||
sb.Append(" ");
|
||||
sb.Append(SQLValueColumnName);
|
||||
sb.Append(" ");
|
||||
sb.Append(kvSort.Value == "+" ? "ASC" : "DESC");
|
||||
SortItemAdded = true;
|
||||
}
|
||||
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
//no sort specified so default it
|
||||
DataListFieldDefinition rid = objectFieldsList.FirstOrDefault(z => z.IsRowId == true);
|
||||
if (rid != null)
|
||||
return $"ORDER BY {rid.SqlIdColumnName} DESC";
|
||||
else
|
||||
return string.Empty; //no default column so no idea how to sort
|
||||
}
|
||||
else
|
||||
return "ORDER BY" + sb.ToString();
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
126
server/DataList/DataListSqlSelectBuilder.cs
Normal file
126
server/DataList/DataListSqlSelectBuilder.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Sockeye.Models;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class SqlSelectBuilderResult
|
||||
{
|
||||
internal Dictionary<string, int> map { get; set; }
|
||||
internal string Select { get; set; }
|
||||
}
|
||||
internal static class DataListSqlSelectBuilder
|
||||
{
|
||||
|
||||
//Build the SELECT portion of a list query based on the columns
|
||||
internal static SqlSelectBuilderResult BuildForDataTableListResponse(List<DataListFieldDefinition> objectFieldsList, List<string> columns)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("SELECT ");
|
||||
//keep track of which custom fields columns were added already
|
||||
//this ensures that if there is more than one set of custom fields like from two different objects in the list
|
||||
//only unique ones will be returned by query
|
||||
//map sql column name to ordinal name
|
||||
Dictionary<string, int> map = new Dictionary<string, int>();
|
||||
int nOrdinal = 0;
|
||||
|
||||
var firstColumnAdded = false;
|
||||
foreach (string ColumnName in columns)
|
||||
{
|
||||
DataListFieldDefinition o = objectFieldsList.FirstOrDefault(z => z.FieldKey == ColumnName);
|
||||
#if (DEBUG)
|
||||
//Developers little helper
|
||||
if (o == null)
|
||||
{
|
||||
throw new System.ArgumentNullException($"## DEV ERROR in DataListSqlSelectBuilder.cs:BuildForDataTableListResponse() field {ColumnName} specified in columns was NOT found in the data list's ObjectFields list, a defined fieldkey name differs from the columns key name");
|
||||
}
|
||||
#endif
|
||||
if (o != null)
|
||||
{//Ignore missing fields in production
|
||||
if (o.IsCustomField)
|
||||
{ //if any are custom field then add custom fields column to query
|
||||
var CustomFieldSqlColumnName = o.GetSqlValueColumnName();
|
||||
//has it been added yet?
|
||||
if (!map.ContainsKey(CustomFieldSqlColumnName))
|
||||
{ //nope
|
||||
if (firstColumnAdded)
|
||||
sb.Append(", ");
|
||||
sb.Append(CustomFieldSqlColumnName);
|
||||
firstColumnAdded = true;
|
||||
map.Add(CustomFieldSqlColumnName, nOrdinal++);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var valueColumnName = o.GetSqlValueColumnName();
|
||||
if (!map.ContainsKey(valueColumnName))
|
||||
{
|
||||
if (firstColumnAdded)
|
||||
sb.Append(", ");
|
||||
sb.Append(valueColumnName);
|
||||
firstColumnAdded = true;
|
||||
map.Add(valueColumnName, nOrdinal++);
|
||||
}
|
||||
|
||||
//does it also have an ID column?
|
||||
var idColumnName = o.SqlIdColumnName;
|
||||
if (!string.IsNullOrWhiteSpace(idColumnName))
|
||||
{
|
||||
if (!map.ContainsKey(idColumnName))
|
||||
{
|
||||
if (firstColumnAdded)
|
||||
sb.Append(", ");
|
||||
sb.Append(idColumnName);
|
||||
firstColumnAdded = true;
|
||||
map.Add(idColumnName, nOrdinal++);
|
||||
}
|
||||
}
|
||||
|
||||
//does it also have an openable SockType column?
|
||||
var sockTypeColumnName = o.SqlATypeColumnName;
|
||||
if (!string.IsNullOrWhiteSpace(sockTypeColumnName))
|
||||
{
|
||||
if (!map.ContainsKey(sockTypeColumnName))
|
||||
{
|
||||
if (firstColumnAdded)
|
||||
sb.Append(", ");
|
||||
sb.Append(sockTypeColumnName);
|
||||
firstColumnAdded = true;
|
||||
map.Add(sockTypeColumnName, nOrdinal++);
|
||||
}
|
||||
}
|
||||
|
||||
//does it also have a Color column?
|
||||
var ayaColorColumnName = o.SqlColorColumnName;
|
||||
if (!string.IsNullOrWhiteSpace(ayaColorColumnName))
|
||||
{
|
||||
if (!map.ContainsKey(ayaColorColumnName))
|
||||
{
|
||||
if (firstColumnAdded)
|
||||
sb.Append(", ");
|
||||
sb.Append(ayaColorColumnName);
|
||||
firstColumnAdded = true;
|
||||
map.Add(ayaColorColumnName, nOrdinal++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new SqlSelectBuilderResult() { map = map, Select = sb.ToString() };
|
||||
}//eom
|
||||
|
||||
|
||||
//Build the SELECT portion of a list query but only to return rowid's
|
||||
internal static string BuildForIdListResponse(List<DataListFieldDefinition> fieldDefinitions, DataListSelectedProcessingOptions dataListSelectionOptions)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("SELECT ");
|
||||
//note: only need rowid column for these queries, the where conditions don't rely on any defined column names as they explicitly refer to the exact identifier known to postgres
|
||||
//Note: IsRowId field should *always* exist for any list that is intended to be used in an idlist response
|
||||
var o = fieldDefinitions.FirstOrDefault(z => z.IsRowId == true);
|
||||
sb.Append(o.SqlIdColumnName);
|
||||
return sb.ToString();
|
||||
}//eom
|
||||
}//eoc
|
||||
}//ens
|
||||
75
server/DataList/EventDataList.cs
Normal file
75
server/DataList/EventDataList.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class EventDataList : DataListProcessingBase
|
||||
{
|
||||
public EventDataList(long translationId)
|
||||
{
|
||||
//NOTE: used this type because it's full BizFull and read only Bizrestricted only which is appropriate and there is no event type
|
||||
DefaultListAType = SockType.Global;
|
||||
SQLFrom = "from aevent left join auser on (aevent.userid=auser.id)";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "eventcreated", "event", "object", "SockType", "username", "textra" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "eventcreated", "-" } };
|
||||
|
||||
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "EventCreated",
|
||||
FieldKey = "eventcreated",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "aevent.created"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Event",
|
||||
FieldKey = "event",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockEvent).ToString()),
|
||||
SqlValueColumnName = "aevent.ayevent"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "SockType",
|
||||
FieldKey = "SockType",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()),
|
||||
SqlValueColumnName = "aevent.socktype"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Object",
|
||||
FieldKey = "object",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "aevent.ayid",
|
||||
SqlValueColumnName = $"AYGETNAME(aevent.ayid, aevent.socktype,{translationId})",
|
||||
SqlATypeColumnName = "aevent.socktype"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "username",
|
||||
TKey = "User",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SockType = (int)SockType.User,
|
||||
SqlIdColumnName = "auser.id",
|
||||
SqlValueColumnName = "auser.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "EventTextra",
|
||||
FieldKey = "textra",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aevent.textra"
|
||||
});
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
240
server/DataList/HeadOfficeDataList.cs
Normal file
240
server/DataList/HeadOfficeDataList.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class HeadOfficeDataList : DataListProcessingBase
|
||||
{
|
||||
public HeadOfficeDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.HeadOffice;
|
||||
SQLFrom = "from aheadoffice";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "headofficename", "headofficephone1", "headofficeemail" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "headofficename", "+" } };
|
||||
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficeName",
|
||||
FieldKey = "headofficename",
|
||||
SockType = (int)SockType.HeadOffice,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "aheadoffice.id",
|
||||
SqlValueColumnName = "aheadoffice.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficeNotes",
|
||||
FieldKey = "headofficenotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.notes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "headofficeactive",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "aheadoffice.active"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "headofficetags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "aheadoffice.tags"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "WebAddress",
|
||||
FieldKey = "headofficewebaddress",
|
||||
UiFieldDataType = (int)UiFieldDataType.HTTP,
|
||||
SqlValueColumnName = "aheadoffice.webaddress"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficeAccountNumber",
|
||||
FieldKey = "headofficeaccountnumber",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.accountnumber"
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficePhone1",
|
||||
FieldKey = "headofficephone1",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "aheadoffice.phone1"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficePhone2",
|
||||
FieldKey = "headofficephone2",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "aheadoffice.phone2"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficePhone3",
|
||||
FieldKey = "headofficephone3",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "aheadoffice.phone3"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficePhone4",
|
||||
FieldKey = "headofficephone4",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "aheadoffice.phone4"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficePhone5",
|
||||
FieldKey = "headofficephone5",
|
||||
UiFieldDataType = (int)UiFieldDataType.PhoneNumber,
|
||||
SqlValueColumnName = "aheadoffice.phone5"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOfficeEmail",
|
||||
FieldKey = "headofficeemail",
|
||||
UiFieldDataType = (int)UiFieldDataType.EmailAddress,
|
||||
SqlValueColumnName = "aheadoffice.emailaddress"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalDeliveryAddress",
|
||||
FieldKey = "headofficepostaddress",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.postaddress"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalCity",
|
||||
FieldKey = "headofficepostcity",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.postcity"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalStateProv",
|
||||
FieldKey = "headofficepostregion",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.postregion"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalCountry",
|
||||
FieldKey = "headofficepostcountry",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.postcountry"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostalPostal",
|
||||
FieldKey = "headofficepostcode",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.postcode"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressDeliveryAddress",
|
||||
FieldKey = "headofficeaddress",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.address"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressCity",
|
||||
FieldKey = "headofficecity",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.city"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressStateProv",
|
||||
FieldKey = "headofficeregion",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.region"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressCountry",
|
||||
FieldKey = "headofficecountry",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.country"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressPostal",
|
||||
FieldKey = "headofficeaddresspostal",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "aheadoffice.addresspostal"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressLatitude",
|
||||
FieldKey = "headofficelatitude",
|
||||
UiFieldDataType = (int)UiFieldDataType.Decimal,
|
||||
SqlValueColumnName = "aheadoffice.latitude"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AddressLongitude",
|
||||
FieldKey = "headofficelongitude",
|
||||
UiFieldDataType = (int)UiFieldDataType.Decimal,
|
||||
SqlValueColumnName = "aheadoffice.longitude"
|
||||
});
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom1", FieldKey = "headofficecustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom2", FieldKey = "headofficecustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom3", FieldKey = "headofficecustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom4", FieldKey = "headofficecustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom5", FieldKey = "headofficecustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom6", FieldKey = "headofficecustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom7", FieldKey = "headofficecustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom8", FieldKey = "headofficecustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom9", FieldKey = "headofficecustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom10", FieldKey = "headofficecustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom11", FieldKey = "headofficecustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom12", FieldKey = "headofficecustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom13", FieldKey = "headofficecustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom14", FieldKey = "headofficecustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom15", FieldKey = "headofficecustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "HeadOfficeCustom16", FieldKey = "headofficecustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "aheadoffice.customfields" });
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
11
server/DataList/IDataListInternalCriteria.cs
Normal file
11
server/DataList/IDataListInternalCriteria.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Sockeye.Models;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal interface IDataListInternalCriteria
|
||||
{
|
||||
//Additional criteria for security or other reasons
|
||||
//hard coded into some lists (e.g. MemoDataList so users can't get other people's memos)
|
||||
//clientCriteria is additional criteria provided by client to list to process as it sees fit (e.g. CustomerNoteDataList requires customer id from client)
|
||||
System.Collections.Generic.List<DataListFilterOption> DataListInternalCriteria(long currentUserId, Sockeye.Biz.AuthorizationRoles userRoles, string clientCriteria);
|
||||
}
|
||||
}
|
||||
23
server/DataList/IDataListProcessing.cs
Normal file
23
server/DataList/IDataListProcessing.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal interface IDataListProcessing
|
||||
{
|
||||
//sql query from fragment with table joins et
|
||||
string SQLFrom { get; set; }
|
||||
//List of fields for this object
|
||||
List<DataListFieldDefinition> FieldDefinitions { get; set; }
|
||||
//allowed roles to access this list
|
||||
AuthorizationRoles AllowedRoles { get; set; }
|
||||
//Default object type to open for rows of this list (use no object if no)
|
||||
SockType DefaultListAType { get; set; }
|
||||
//Defaults when none is specified (see DataListOptions for formats and notes)
|
||||
List<string> DefaultColumns { get; set; }
|
||||
Dictionary<string, string> DefaultSortBy { get; set; }
|
||||
void SetListOptionDefaultsIfNecessary(Models.DataListProcessingBase listOptions);
|
||||
Newtonsoft.Json.Linq.JArray GenerateReturnListColumns(List<string> columns);
|
||||
|
||||
//long CurrentUserTranslationId { get; set; }
|
||||
}
|
||||
}
|
||||
138
server/DataList/InsideUserDataList.cs
Normal file
138
server/DataList/InsideUserDataList.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Models;
|
||||
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class InsideUserDataList : DataListProcessingBase, IDataListInternalCriteria
|
||||
{
|
||||
|
||||
public InsideUserDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.User;
|
||||
SQLFrom = "from auser "
|
||||
+ "left join auseroptions on auser.id=auseroptions.userid "
|
||||
+ "left join atranslation on auseroptions.translationid = atranslation.id";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "name", "employeenumber", "active", "usertype", "lastlogin" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "name", "+" } };
|
||||
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "User",
|
||||
FieldKey = "name",
|
||||
SockType = (int)SockType.User,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "auser.id",
|
||||
SqlValueColumnName = "auser.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "UserEmployeeNumber",
|
||||
FieldKey = "employeenumber",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "auser.employeenumber"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "active",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "auser.active"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AllowLogin",
|
||||
FieldKey = "allowlogin",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "auser.allowlogin"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "UserType",
|
||||
FieldKey = "usertype",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()),
|
||||
SqlValueColumnName = "auser.usertype"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AuthorizationRoles",
|
||||
FieldKey = "roles",
|
||||
UiFieldDataType = (int)UiFieldDataType.Roles,
|
||||
//NOTE: not technically an enum list but this will trigger datagrid at client to fetch roles for special handling
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()),
|
||||
SqlValueColumnName = "auser.roles"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "LastLogin",
|
||||
FieldKey = "lastlogin",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "auser.lastlogin"
|
||||
});
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AuthTwoFactor",
|
||||
FieldKey = "AuthTwoFactor",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "auser.twofactorenabled"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Translation",
|
||||
FieldKey = "Translation",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SockType = (int)SockType.Translation,
|
||||
SqlIdColumnName = "atranslation.id",
|
||||
SqlValueColumnName = "atranslation.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "Tags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "auser.tags"
|
||||
});
|
||||
|
||||
//META COLUMNS
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
|
||||
FieldKey = "metausertype",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()),
|
||||
SqlValueColumnName = "auser.usertype",
|
||||
IsMeta = true
|
||||
});
|
||||
}
|
||||
|
||||
public List<DataListFilterOption> DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria)
|
||||
{
|
||||
List<DataListFilterOption> ret = new List<DataListFilterOption>();
|
||||
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metausertype" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.Customer).ToString(), op = DataListFilterComparisonOperator.NotEqual });
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.HeadOffice).ToString(), op = DataListFilterComparisonOperator.NotEqual });
|
||||
|
||||
ret.Add(FilterOption);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
41
server/DataList/IntegrationDataList.cs
Normal file
41
server/DataList/IntegrationDataList.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class IntegrationDataList : DataListProcessingBase
|
||||
{
|
||||
public IntegrationDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.Integration;
|
||||
SQLFrom = "from aintegration";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "integrationname" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "integrationname", "+" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "IntegrationName",
|
||||
FieldKey = "integrationname",
|
||||
SockType = (int)SockType.Integration,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "aintegration.id",
|
||||
SqlValueColumnName = "aintegration.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "partassemblyactive",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "aintegration.active"
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
123
server/DataList/MemoDataList.cs
Normal file
123
server/DataList/MemoDataList.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class MemoDataList : DataListProcessingBase, IDataListInternalCriteria
|
||||
{
|
||||
public MemoDataList(long translationId)
|
||||
{
|
||||
|
||||
DefaultListAType = SockType.Memo;
|
||||
SQLFrom = "from amemo left join auser on (amemo.fromid=auser.id)";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "MemoSubject", "MemoFromID", "MemoSent", "MemoViewed" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "MemoSent", "-" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "MemoSubject",
|
||||
FieldKey = "MemoSubject",
|
||||
SockType = (int)SockType.Memo,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "amemo.id",
|
||||
SqlValueColumnName = "amemo.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "MemoMessage",
|
||||
FieldKey = "MemoMessage",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "amemo.notes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "MemoFromID",
|
||||
FieldKey = "MemoFromID",
|
||||
SockType = (int)SockType.User,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "auser.id",
|
||||
SqlValueColumnName = "auser.name",
|
||||
IsRowId = false
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "MemoSent",
|
||||
FieldKey = "MemoSent",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "amemo.sent"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "MemoReplied",
|
||||
FieldKey = "MemoReplied",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "amemo.replied"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "MemoViewed",
|
||||
FieldKey = "MemoViewed",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "amemo.viewed"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "MemoTags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "amemo.tags"
|
||||
});
|
||||
|
||||
//META column
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "metamemoto",
|
||||
UiFieldDataType = (int)UiFieldDataType.InternalId,
|
||||
SqlIdColumnName = "amemo.toid",
|
||||
SqlValueColumnName = "amemo.toid",
|
||||
IsMeta = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom1", FieldKey = "MemoCustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom2", FieldKey = "MemoCustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom3", FieldKey = "MemoCustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom4", FieldKey = "MemoCustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom5", FieldKey = "MemoCustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom6", FieldKey = "MemoCustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom7", FieldKey = "MemoCustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom8", FieldKey = "MemoCustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom9", FieldKey = "MemoCustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom10", FieldKey = "MemoCustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom11", FieldKey = "MemoCustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom12", FieldKey = "MemoCustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom13", FieldKey = "MemoCustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom14", FieldKey = "MemoCustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom15", FieldKey = "MemoCustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "MemoCustom16", FieldKey = "MemoCustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "amemo.customfields" });
|
||||
|
||||
}
|
||||
|
||||
public List<DataListFilterOption> DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria)
|
||||
{
|
||||
List<DataListFilterOption> ret = new List<DataListFilterOption>();
|
||||
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metamemoto" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = currentUserId.ToString(), op = DataListFilterComparisonOperator.Equality });
|
||||
|
||||
ret.Add(FilterOption);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
84
server/DataList/NotificationDeliveryLogDataList.cs
Normal file
84
server/DataList/NotificationDeliveryLogDataList.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class NotificationDeliveryLogDataList : DataListProcessingBase
|
||||
{
|
||||
public NotificationDeliveryLogDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.OpsNotificationSettings;
|
||||
SQLFrom = @"from anotifydeliverylog
|
||||
left join anotifysubscription on anotifysubscription.id = anotifydeliverylog.notifysubscriptionid
|
||||
left join auser on anotifysubscription.userid=auser.id";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "Processed", "NotifyEventType", "SockType", "User", "Failed", "Errors" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "Processed", "-" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
// FieldDefinitions.Add(new DataListFieldDefinition
|
||||
// {
|
||||
// TKey = "NotifySubscription",
|
||||
// FieldKey = "NotifySubscription",
|
||||
// SockType = (int)SockType.NotifySubscription,
|
||||
// UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
// SqlIdColumnName = "anotifysubscription.id",
|
||||
// SqlValueColumnName = "anotifysubscription.id",
|
||||
// IsRowId = false
|
||||
// });
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Processed",
|
||||
FieldKey = "Processed",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "anotifydeliverylog.processed"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "NotifyEventType",
|
||||
FieldKey = "NotifyEventType",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(NotifyEventType).ToString()),
|
||||
SqlValueColumnName = "anotifysubscription.eventtype"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "SockType",
|
||||
FieldKey = "SockType",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()),
|
||||
SqlValueColumnName = "anotifysubscription.socktype"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Failed",
|
||||
FieldKey = "Failed",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "anotifydeliverylog.fail"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Errors",
|
||||
FieldKey = "Errors",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "anotifydeliverylog.error"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "User",
|
||||
TKey = "User",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SockType = (int)SockType.User,
|
||||
SqlIdColumnName = "auser.id",
|
||||
SqlValueColumnName = "auser.name"
|
||||
});
|
||||
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
151
server/DataList/OutsideUserDataList.cs
Normal file
151
server/DataList/OutsideUserDataList.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class OutsideUserDataList : DataListProcessingBase, IDataListInternalCriteria
|
||||
{
|
||||
|
||||
public OutsideUserDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.Customer;
|
||||
SQLFrom = "from auser left join aheadoffice on (auser.headofficeid=aheadoffice.id) "
|
||||
+ "left join acustomer on (auser.customerid=acustomer.id)"
|
||||
+ "left join auseroptions on auser.id=auseroptions.userid "
|
||||
+ "left join atranslation on auseroptions.translationid = atranslation.id";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "name", "active", "allowlogin", "usercustomer", "userheadoffice", "lastlogin" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "name", "+" } };
|
||||
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "User",
|
||||
FieldKey = "name",
|
||||
SockType = (int)SockType.User,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "auser.id",
|
||||
SqlValueColumnName = "auser.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "active",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "auser.active"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AllowLogin",
|
||||
FieldKey = "allowlogin",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "auser.allowlogin"
|
||||
});
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "UserType",
|
||||
FieldKey = "usertype",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()),
|
||||
SqlValueColumnName = "auser.usertype"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AuthorizationRoles",
|
||||
FieldKey = "roles",
|
||||
UiFieldDataType = (int)UiFieldDataType.Roles,
|
||||
//NOTE: not technically an enum list but this will trigger datagrid at client to fetch roles for special handling
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()),
|
||||
SqlValueColumnName = "auser.roles"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "LastLogin",
|
||||
FieldKey = "lastlogin",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "auser.lastlogin"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "HeadOffice",
|
||||
FieldKey = "userheadoffice",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SockType = (int)SockType.HeadOffice,
|
||||
SqlIdColumnName = "aheadoffice.id",
|
||||
SqlValueColumnName = "aheadoffice.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Customer",
|
||||
FieldKey = "usercustomer",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SockType = (int)SockType.Customer,
|
||||
SqlIdColumnName = "acustomer.id",
|
||||
SqlValueColumnName = "acustomer.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "AuthTwoFactor",
|
||||
FieldKey = "AuthTwoFactor",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "auser.twofactorenabled"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Translation",
|
||||
FieldKey = "Translation",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SockType = (int)SockType.Translation,
|
||||
SqlIdColumnName = "atranslation.id",
|
||||
SqlValueColumnName = "atranslation.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "Tags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "auser.tags"
|
||||
});
|
||||
|
||||
//META COLUMNS
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "metausertype",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(UserType).ToString()),
|
||||
SqlValueColumnName = "auser.usertype",
|
||||
IsMeta = true
|
||||
});
|
||||
}
|
||||
|
||||
public List<DataListFilterOption> DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria)
|
||||
{
|
||||
List<DataListFilterOption> ret = new List<DataListFilterOption>();
|
||||
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metausertype" };
|
||||
FilterOption.Any = true;
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.Customer).ToString(), op = DataListFilterComparisonOperator.Equality });
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = ((int)UserType.HeadOffice).ToString(), op = DataListFilterComparisonOperator.Equality });
|
||||
|
||||
ret.Add(FilterOption);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
103
server/DataList/ReminderDataList.cs
Normal file
103
server/DataList/ReminderDataList.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class ReminderDataList : DataListProcessingBase, IDataListInternalCriteria
|
||||
{
|
||||
public ReminderDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.Reminder;
|
||||
SQLFrom = "from areminder";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "ReminderName", "ReminderNotes", "ReminderStartDate", "ReminderStopDate" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "ReminderStartDate", "-" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReminderName",
|
||||
FieldKey = "ReminderName",
|
||||
SockType = (int)SockType.Reminder,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "areminder.id",
|
||||
SqlValueColumnName = "areminder.name",
|
||||
SqlColorColumnName = "areminder.color",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReminderNotes",
|
||||
FieldKey = "ReminderNotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "areminder.notes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReminderStartDate",
|
||||
FieldKey = "ReminderStartDate",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "areminder.startdate"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReminderStopDate",
|
||||
FieldKey = "ReminderStopDate",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "areminder.stopdate"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "ReminderTags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "areminder.tags"
|
||||
});
|
||||
|
||||
//META column
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "metareminderuser",
|
||||
UiFieldDataType = (int)UiFieldDataType.InternalId,
|
||||
SqlIdColumnName = "areminder.userid",
|
||||
SqlValueColumnName = "areminder.userid",
|
||||
IsMeta = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom1", FieldKey = "ReminderCustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom2", FieldKey = "ReminderCustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom3", FieldKey = "ReminderCustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom4", FieldKey = "ReminderCustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom5", FieldKey = "ReminderCustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom6", FieldKey = "ReminderCustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom7", FieldKey = "ReminderCustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom8", FieldKey = "ReminderCustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom9", FieldKey = "ReminderCustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom10", FieldKey = "ReminderCustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom11", FieldKey = "ReminderCustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom12", FieldKey = "ReminderCustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom13", FieldKey = "ReminderCustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom14", FieldKey = "ReminderCustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom15", FieldKey = "ReminderCustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReminderCustom16", FieldKey = "ReminderCustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areminder.customfields" });
|
||||
}
|
||||
|
||||
//Ensure only current user can fetch their reminders
|
||||
public List<DataListFilterOption> DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria)
|
||||
{
|
||||
List<DataListFilterOption> ret = new List<DataListFilterOption>();
|
||||
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metareminderuser" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = currentUserId.ToString(), op = DataListFilterComparisonOperator.Equality });
|
||||
|
||||
ret.Add(FilterOption);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
65
server/DataList/ReportDataList.cs
Normal file
65
server/DataList/ReportDataList.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class ReportDataList : DataListProcessingBase
|
||||
{
|
||||
public ReportDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.Report;
|
||||
SQLFrom = "from aReport";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "name", "aType", "ReportNotes", "active" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "name", "+" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Report",
|
||||
FieldKey = "name",
|
||||
SockType = (int)SockType.Report,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "aReport.id",
|
||||
SqlValueColumnName = "aReport.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "SockType",
|
||||
FieldKey = "aType",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()),
|
||||
SqlValueColumnName = "areport.SockType"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReportNotes",
|
||||
FieldKey = "ReportNotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "areport.notes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "active",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "aReport.active"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "SelectRoles",
|
||||
FieldKey = "roles",
|
||||
UiFieldDataType = (int)UiFieldDataType.Roles,
|
||||
//NOTE: not technically an enum list but this will trigger datagrid at client to fetch roles for special handling
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(AuthorizationRoles).ToString()),
|
||||
SqlValueColumnName = "areport.roles"
|
||||
});
|
||||
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
208
server/DataList/ReviewDataList.cs
Normal file
208
server/DataList/ReviewDataList.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Models;
|
||||
using Sockeye.Biz;
|
||||
using System.Linq;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class ReviewDataList : DataListProcessingBase, IDataListInternalCriteria
|
||||
{
|
||||
public ReviewDataList(long translationId)
|
||||
{
|
||||
|
||||
DefaultListAType = SockType.Review;
|
||||
SQLFrom = "from areview "
|
||||
+ "left join auser uassto on (areview.userid=uassto.id) "
|
||||
+ "left join auser uassby on (areview.assignedbyuserid=uassby.id)";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "ReviewCompletedDate", "ReviewDate", "ReviewName", "Object", "SockType", "ReviewNotes", "ReviewUserId" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "ReviewCompletedDate", "-" }, { "ReviewDate", "+" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Object",
|
||||
FieldKey = "Object",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "areview.objectid",
|
||||
SqlValueColumnName = $"AYGETNAME(areview.objectid, areview.aType,{translationId})",
|
||||
SqlATypeColumnName = "areview.aType",
|
||||
Translate=true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "SockType",
|
||||
FieldKey = "SockType",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()),
|
||||
SqlValueColumnName = "areview.aType"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReviewName",
|
||||
FieldKey = "ReviewName",
|
||||
SockType = (int)SockType.Review,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "areview.id",
|
||||
SqlValueColumnName = "areview.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReviewNotes",
|
||||
FieldKey = "ReviewNotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "areview.notes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReviewDate",
|
||||
FieldKey = "ReviewDate",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "areview.reviewdate"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReviewCompletedDate",
|
||||
FieldKey = "ReviewCompletedDate",
|
||||
UiFieldDataType = (int)UiFieldDataType.DateTime,
|
||||
SqlValueColumnName = "areview.completeddate"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReviewCompletionNotes",
|
||||
FieldKey = "ReviewCompletionNotes",
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlValueColumnName = "areview.completionnotes"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReviewUserId",
|
||||
FieldKey = "ReviewUserId",
|
||||
SockType = (int)SockType.User,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "uassto.id",
|
||||
SqlValueColumnName = "uassto.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReviewAssignedByUserId",
|
||||
FieldKey = "ReviewAssignedByUserId",
|
||||
SockType = (int)SockType.User,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "uassby.id",
|
||||
SqlValueColumnName = "uassby.name"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "ReviewTags",
|
||||
UiFieldDataType = (int)UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "areview.tags"
|
||||
});
|
||||
|
||||
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom1", FieldKey = "ReviewCustom1", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom2", FieldKey = "ReviewCustom2", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom3", FieldKey = "ReviewCustom3", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom4", FieldKey = "ReviewCustom4", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom5", FieldKey = "ReviewCustom5", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom6", FieldKey = "ReviewCustom6", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom7", FieldKey = "ReviewCustom7", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom8", FieldKey = "ReviewCustom8", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom9", FieldKey = "ReviewCustom9", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom10", FieldKey = "ReviewCustom10", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom11", FieldKey = "ReviewCustom11", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom12", FieldKey = "ReviewCustom12", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom13", FieldKey = "ReviewCustom13", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom14", FieldKey = "ReviewCustom14", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom15", FieldKey = "ReviewCustom15", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
FieldDefinitions.Add(new DataListFieldDefinition { TKey = "ReviewCustom16", FieldKey = "ReviewCustom16", IsCustomField = true, IsFilterable = false, IsSortable = false, SqlValueColumnName = "areview.customfields" });
|
||||
|
||||
|
||||
|
||||
//META COLUMNS
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "metareviewuser",
|
||||
UiFieldDataType = (int)UiFieldDataType.InternalId,
|
||||
SqlIdColumnName = "areview.userid",
|
||||
SqlValueColumnName = "areview.userid",
|
||||
IsMeta = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
FieldKey = "metaobjectid",
|
||||
UiFieldDataType = (int)UiFieldDataType.InternalId,
|
||||
SqlIdColumnName = "areview.objectid",
|
||||
SqlValueColumnName = "areview.objectid",
|
||||
IsMeta = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
|
||||
FieldKey = "metaobjecttype",
|
||||
UiFieldDataType = (int)UiFieldDataType.Enum,
|
||||
EnumType = Sockeye.Util.StringUtil.TrimTypeName(typeof(SockType).ToString()),
|
||||
SqlValueColumnName = "areview.aType",
|
||||
IsMeta = true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public List<DataListFilterOption> DataListInternalCriteria(long currentUserId, AuthorizationRoles userRoles, string clientCriteria)
|
||||
{
|
||||
List<DataListFilterOption> ret = new List<DataListFilterOption>();
|
||||
bool HasSupervisorRole =
|
||||
userRoles.HasFlag(AuthorizationRoles.BizAdmin)
|
||||
|| userRoles.HasFlag(AuthorizationRoles.Service)
|
||||
|| userRoles.HasFlag(AuthorizationRoles.Inventory)
|
||||
|| userRoles.HasFlag(AuthorizationRoles.Sales)
|
||||
|| userRoles.HasFlag(AuthorizationRoles.Accounting);
|
||||
|
||||
if (!HasSupervisorRole)
|
||||
{
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metareviewuser" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = currentUserId.ToString(), op = DataListFilterComparisonOperator.Equality });
|
||||
|
||||
ret.Add(FilterOption);
|
||||
}
|
||||
|
||||
|
||||
//ClientCriteria format for this list is "OBJECTID,AYATYPE"
|
||||
var crit = (clientCriteria ?? "").Split(',').Select(z => z.Trim()).ToArray();
|
||||
if (crit.Length > 1)
|
||||
{
|
||||
//OBJECTID criteria
|
||||
if (crit[0] != "0")
|
||||
{
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metaobjectid" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = crit[0], op = DataListFilterComparisonOperator.Equality });
|
||||
ret.Add(FilterOption);
|
||||
}
|
||||
|
||||
//AYATYPE criteria
|
||||
if (!string.IsNullOrWhiteSpace(crit[1]))
|
||||
{
|
||||
DataListFilterOption FilterOption = new DataListFilterOption() { Column = "metaobjecttype" };
|
||||
FilterOption.Items.Add(new DataListColumnFilter() { value = crit[1], op = DataListFilterComparisonOperator.Equality });
|
||||
ret.Add(FilterOption);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
45
server/DataList/TranslationDataList.cs
Normal file
45
server/DataList/TranslationDataList.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.DataList
|
||||
{
|
||||
internal class TranslationDataList : DataListProcessingBase
|
||||
{
|
||||
public TranslationDataList(long translationId)
|
||||
{
|
||||
DefaultListAType = SockType.Translation;
|
||||
SQLFrom = "from atranslation";
|
||||
var RoleSet = BizRoles.GetRoleSet(DefaultListAType);
|
||||
AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change;
|
||||
DefaultColumns = new List<string>() { "name", "stock", "cjkindex" };
|
||||
DefaultSortBy = new Dictionary<string, string>() { { "name", "+" } };
|
||||
FieldDefinitions = new List<DataListFieldDefinition>();
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "Translation",
|
||||
FieldKey = "name",
|
||||
SockType = (int)SockType.Translation,
|
||||
UiFieldDataType = (int)UiFieldDataType.Text,
|
||||
SqlIdColumnName = "atranslation.id",
|
||||
SqlValueColumnName = "atranslation.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "GlobalCJKIndex",
|
||||
FieldKey = "cjkindex",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "atranslation.cjkindex"
|
||||
});
|
||||
|
||||
FieldDefinitions.Add(new DataListFieldDefinition
|
||||
{
|
||||
TKey = "ReadOnly",
|
||||
FieldKey = "stock",
|
||||
UiFieldDataType = (int)UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "atranslation.stock"
|
||||
});
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
30
server/PickList/AyaPickList.cs
Normal file
30
server/PickList/AyaPickList.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json.Linq;
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
/// <summary>
|
||||
/// PickList object base class
|
||||
/// </summary>
|
||||
internal abstract class AyaPickList : IAyaPickList
|
||||
{
|
||||
public AyaPickList()
|
||||
{ }
|
||||
public string SQLFrom { get; set; }
|
||||
public List<AyaPickListFieldDefinition> ColumnDefinitions { get; set; }
|
||||
public AuthorizationRoles AllowedRoles { get; set; }
|
||||
public SockType DefaultListAType { get; set; }
|
||||
public string DefaultTemplate { get; set; }
|
||||
//return array of field keys in list view
|
||||
public List<string> GetFieldListFromTemplate(JArray template)
|
||||
{
|
||||
List<string> ret = new List<string>();
|
||||
for (int i = 0; i < template.Count; i++)
|
||||
{
|
||||
var cm = template[i];
|
||||
ret.Add(cm["fld"].Value<string>());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
42
server/PickList/AyaPickListFieldDefinition.cs
Normal file
42
server/PickList/AyaPickListFieldDefinition.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Sockeye.Biz;
|
||||
using Newtonsoft.Json;
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
//This class defines a field used for pick list templating querying processing editing and returning
|
||||
public class AyaPickListFieldDefinition
|
||||
{
|
||||
//CLIENT / SERVER Unique identifier used at BOTH client and server
|
||||
//also the sql displaycolumnname if identical
|
||||
public string FieldKey { get; set; }
|
||||
//CLIENT Use only for display
|
||||
public string TKey { get; set; }
|
||||
// Used for casting query
|
||||
public UiFieldDataType ColumnDataType { get; set; }
|
||||
public bool IsRowId { get; set; }//both indicates is row ID but also that it's required as the only required field. Not technically necessary maybe but to prevent foot shooting.
|
||||
public bool IsActiveColumn { get; set; }
|
||||
[JsonIgnore]
|
||||
public string SqlIdColumnName { get; set; }
|
||||
[JsonIgnore]
|
||||
public string SqlValueColumnName { get; set; }
|
||||
|
||||
public AyaPickListFieldDefinition()
|
||||
{
|
||||
//most common defaults
|
||||
IsRowId = false;
|
||||
IsActiveColumn = false;
|
||||
}
|
||||
|
||||
//Get column to query for display name or use FieldName if there is no difference
|
||||
public string GetSqlValueColumnName()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SqlValueColumnName))
|
||||
{
|
||||
return FieldKey.ToLowerInvariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SqlValueColumnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
server/PickList/CustomerPickList.cs
Normal file
86
server/PickList/CustomerPickList.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sockeye.Biz;
|
||||
using System.Linq;
|
||||
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
internal class CustomerPickList : AyaPickList, IAyaPickListVariant
|
||||
{
|
||||
public CustomerPickList()
|
||||
{
|
||||
|
||||
DefaultListAType = SockType.Customer;
|
||||
SQLFrom = "from acustomer ";
|
||||
AllowedRoles = BizRoles.GetRoleSet(DefaultListAType).Select;
|
||||
dynamic dTemplate = new JArray();
|
||||
|
||||
dynamic cm = new JObject();
|
||||
cm.fld = "customername";
|
||||
dTemplate.Add(cm);
|
||||
|
||||
cm = new JObject();
|
||||
cm.fld = "customertags";
|
||||
dTemplate.Add(cm);
|
||||
|
||||
base.DefaultTemplate = dTemplate.ToString(Newtonsoft.Json.Formatting.None);
|
||||
|
||||
//NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely
|
||||
ColumnDefinitions = new List<AyaPickListFieldDefinition>();
|
||||
ColumnDefinitions.Add(new AyaPickListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "customeractive",
|
||||
ColumnDataType = UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "acustomer.active",
|
||||
IsActiveColumn = true
|
||||
});
|
||||
ColumnDefinitions.Add(new AyaPickListFieldDefinition
|
||||
{
|
||||
TKey = "Name",
|
||||
FieldKey = "customername",
|
||||
ColumnDataType = UiFieldDataType.Text,
|
||||
SqlIdColumnName = "acustomer.id",
|
||||
SqlValueColumnName = "acustomer.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
ColumnDefinitions.Add(new AyaPickListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "customertags",
|
||||
ColumnDataType = UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "acustomer.tags"
|
||||
});
|
||||
|
||||
}
|
||||
public string GetVariantCriteria(string variant)
|
||||
{
|
||||
//Currently the only variant is a object type and id to indicate headoffice
|
||||
|
||||
//ClientCriteria format for this list is "OBJECTID,AYATYPE"
|
||||
var crit = (variant ?? "").Split(',').Select(z => z.Trim()).ToArray();
|
||||
if (crit.Length > 1)
|
||||
{
|
||||
int nType = 0;
|
||||
if (!int.TryParse(crit[1], out nType)) return string.Empty;
|
||||
SockType forType = (SockType)nType;
|
||||
if (forType != SockType.HeadOffice) return string.Empty;
|
||||
|
||||
long lId = 0;
|
||||
if (!long.TryParse(crit[0], out lId)) return string.Empty;
|
||||
if (lId == 0) return string.Empty;
|
||||
|
||||
//Have valid type, have an id, so filter away
|
||||
switch (forType)
|
||||
{
|
||||
case SockType.HeadOffice:
|
||||
{
|
||||
return $"acustomer.headofficeid = {lId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
56
server/PickList/HeadOfficePickList.cs
Normal file
56
server/PickList/HeadOfficePickList.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
internal class HeadOfficePickList : AyaPickList
|
||||
{
|
||||
public HeadOfficePickList()
|
||||
{
|
||||
|
||||
DefaultListAType = SockType.HeadOffice;
|
||||
SQLFrom = "from aheadoffice";
|
||||
AllowedRoles = BizRoles.GetRoleSet(DefaultListAType).Select;
|
||||
dynamic dTemplate = new JArray();
|
||||
|
||||
dynamic cm = new JObject();
|
||||
cm.fld = "headofficename";
|
||||
dTemplate.Add(cm);
|
||||
|
||||
cm = new JObject();
|
||||
cm.fld = "headofficetags";
|
||||
dTemplate.Add(cm);
|
||||
|
||||
base.DefaultTemplate = dTemplate.ToString(Newtonsoft.Json.Formatting.None);
|
||||
|
||||
//NOTE: Due to the join, all the sql id and name fields that can conflict with the joined table need to be specified completely
|
||||
ColumnDefinitions = new List<AyaPickListFieldDefinition>();
|
||||
ColumnDefinitions.Add(new AyaPickListFieldDefinition
|
||||
{
|
||||
TKey = "Active",
|
||||
FieldKey = "headofficeactive",
|
||||
ColumnDataType = UiFieldDataType.Bool,
|
||||
SqlValueColumnName = "aheadoffice.active",
|
||||
IsActiveColumn = true
|
||||
});
|
||||
ColumnDefinitions.Add(new AyaPickListFieldDefinition
|
||||
{
|
||||
TKey = "Name",
|
||||
FieldKey = "headofficename",
|
||||
ColumnDataType = UiFieldDataType.Text,
|
||||
SqlIdColumnName = "aheadoffice.id",
|
||||
SqlValueColumnName = "aheadoffice.name",
|
||||
IsRowId = true
|
||||
});
|
||||
|
||||
ColumnDefinitions.Add(new AyaPickListFieldDefinition
|
||||
{
|
||||
TKey = "Tags",
|
||||
FieldKey = "headofficetags",
|
||||
ColumnDataType = UiFieldDataType.Tags,
|
||||
SqlValueColumnName = "aheadoffice.tags"
|
||||
});
|
||||
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
20
server/PickList/IAyaPickList.cs
Normal file
20
server/PickList/IAyaPickList.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sockeye.Biz;
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
internal interface IAyaPickList
|
||||
{
|
||||
//sql query from fragment with table joins et
|
||||
string SQLFrom { get; set; }
|
||||
//List of fields for this object
|
||||
List<AyaPickListFieldDefinition> ColumnDefinitions { get; set; }
|
||||
//allowed roles to access this list
|
||||
AuthorizationRoles AllowedRoles { get; set; }
|
||||
//Default object type to open for rows of this list (use no object if no)
|
||||
SockType DefaultListAType { get; set; }
|
||||
//Default / STOCK template when none is specified
|
||||
string DefaultTemplate { get; set; }
|
||||
List<string> GetFieldListFromTemplate(JArray fieldListArray);
|
||||
}
|
||||
}
|
||||
7
server/PickList/IAyaPickListVariant.cs
Normal file
7
server/PickList/IAyaPickListVariant.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
internal interface IAyaPickListVariant
|
||||
{
|
||||
string GetVariantCriteria(string variant);
|
||||
}
|
||||
}
|
||||
71
server/PickList/PickListFactory.cs
Normal file
71
server/PickList/PickListFactory.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Models;
|
||||
|
||||
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
internal static class PickListFactory
|
||||
{
|
||||
|
||||
//Instantiate list object specified from type
|
||||
internal static IAyaPickList GetAyaPickList(SockType sockType)
|
||||
{
|
||||
switch (sockType)
|
||||
{
|
||||
//CoreBizObject add here if it will be "picked" on any other form
|
||||
|
||||
case SockType.Customer:
|
||||
return new CustomerPickList() as IAyaPickList;
|
||||
case SockType.HeadOffice:
|
||||
return new HeadOfficePickList() as IAyaPickList;
|
||||
case SockType.User:
|
||||
return new UserPickList() as IAyaPickList;
|
||||
|
||||
case SockType.Report:
|
||||
return new ReportPickList() as IAyaPickList;
|
||||
|
||||
//@##### WARNING: BE SURE TO ADD NEW TYPES BELOW OR USERS WON"T BE ABLE TO EDIT THE TEMPLATE FOR THEM
|
||||
|
||||
default:
|
||||
throw new System.NotImplementedException($"PICKLIST {sockType} NOT IMPLEMENTED");
|
||||
|
||||
}
|
||||
//return null;
|
||||
}
|
||||
|
||||
//List all the PickList-able object types available
|
||||
internal static List<NameIdItem> GetListOfAllPickListTypes(long TranslationId)
|
||||
{
|
||||
List<string> TranslationKeysToFetch = new List<string>();
|
||||
List<NameIdItem> ret = new List<NameIdItem>();
|
||||
|
||||
List<SockType> values = new List<SockType>();
|
||||
values.Add(SockType.Customer);
|
||||
values.Add(SockType.HeadOffice);
|
||||
values.Add(SockType.Report);
|
||||
values.Add(SockType.User);
|
||||
|
||||
|
||||
//### NEW ONES HERE
|
||||
|
||||
|
||||
|
||||
foreach (SockType t in values)
|
||||
{
|
||||
// if (t.HasAttribute(typeof(CoreBizObjectAttribute)))
|
||||
// {
|
||||
var name = t.ToString();
|
||||
TranslationKeysToFetch.Add(name);
|
||||
ret.Add(new NameIdItem() { Name = name, Id = (long)t });
|
||||
// }
|
||||
}
|
||||
var LT = TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, TranslationId).Result;
|
||||
foreach (NameIdItem i in ret)
|
||||
{
|
||||
i.Name = LT[i.Name];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
101
server/PickList/PickListFetcher.cs
Normal file
101
server/PickList/PickListFetcher.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sockeye.Models;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
internal static class PickListFetcher
|
||||
{
|
||||
internal static async Task<List<NameIdActiveItem>> GetResponseAsync(IAyaPickList PickList, string autoCompleteQuery,
|
||||
string tagSpecificQuery, bool includeInactive, long[] preIds, string variant, AyContext ct, ILogger log, string overrideTemplate)
|
||||
{
|
||||
|
||||
//Sort out effective Template
|
||||
string Template = null;
|
||||
if (string.IsNullOrWhiteSpace(overrideTemplate))
|
||||
{
|
||||
//Attempt to fetch custom template
|
||||
var t = await ct.PickListTemplate.FirstOrDefaultAsync(z => z.Id == ((long)PickList.DefaultListAType));
|
||||
if (t == null)
|
||||
{
|
||||
Template = PickList.DefaultTemplate;
|
||||
}
|
||||
else
|
||||
{
|
||||
Template = t.Template;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Template = overrideTemplate;
|
||||
}
|
||||
|
||||
//parse the template
|
||||
var jTemplate = JArray.Parse(Template);
|
||||
|
||||
//Get the field key names in a list from the template
|
||||
List<string> TemplateColumnNames = PickList.GetFieldListFromTemplate(jTemplate);
|
||||
|
||||
//BUILD THE QUERY
|
||||
var q = PickListSqlBuilder.Build(PickList, TemplateColumnNames, autoCompleteQuery, tagSpecificQuery, includeInactive, preIds, variant);
|
||||
|
||||
//If want to log all queries
|
||||
//log.LogInformation(q);
|
||||
|
||||
//RETURN OBJECTS
|
||||
var ret = new List<NameIdActiveItem>();
|
||||
|
||||
//QUERY THE DB
|
||||
using (var command = ct.Database.GetDbConnection().CreateCommand())
|
||||
{
|
||||
await ct.Database.OpenConnectionAsync();
|
||||
|
||||
//GET DATA RETURN ROWS
|
||||
command.CommandText = q;
|
||||
try
|
||||
{
|
||||
using (var dr = await command.ExecuteReaderAsync())
|
||||
{
|
||||
while (dr.Read())
|
||||
{
|
||||
//query is always in the same order:
|
||||
//plId, plActive, plName
|
||||
ret.Add(new NameIdActiveItem
|
||||
{
|
||||
Id = dr.GetInt64(0),
|
||||
Active = dr.GetBoolean(1),
|
||||
Name = dr.GetString(2)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Npgsql.PostgresException e)
|
||||
{
|
||||
//log out the exception and the query
|
||||
//This may be called internally in *Biz classes who don't have a log of their own
|
||||
if (log == null)
|
||||
log = Sockeye.Util.ApplicationLogging.CreateLogger("PickListFetcher");
|
||||
log.LogError("PickList query failed unexpectedly. Query was:");
|
||||
log.LogError(q);
|
||||
log.LogError(e, "DB Exception");
|
||||
throw new System.Exception("PickListFetcher - Query failed see log");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
//ensure any other type of exception gets surfaced properly
|
||||
//log out the exception and the query
|
||||
log.LogError("PickListFetcher unexpected failure. Query was:");
|
||||
log.LogError(q);
|
||||
|
||||
log.LogError(e, "Exception");
|
||||
throw new System.Exception("PickListFetcher - unexpected failure see log");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}//eoc
|
||||
}//eons
|
||||
73
server/PickList/PickListOptions.cs
Normal file
73
server/PickList/PickListOptions.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using Sockeye.Biz;
|
||||
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
|
||||
/*
|
||||
/// <param name="params">The SockType object type to select from</param>
|
||||
/// <param name="query">The query to filter the returned list by. Query text as provided will be case sensitively matched to all templated fields.
|
||||
/// independentely of this, if an addition space separated string that begins with two consecutive periods is encountered that will be considered a separate match to the TAGS collection of each object
|
||||
/// So a tag query might be entered as "..zon some" which would match all tags LIKE 'zon' and template fields LIKE 'some'</param>
|
||||
/// <param name="inactive">Include inactive objects in the returned list </param>
|
||||
/// <param name="preIds">Return only specific items (for pre-selected items on forms) </param>
|
||||
/// <param name="variant">Some lists optionally take a variant string, e.g. User type "inside","outside" etc </param>
|
||||
*/
|
||||
public sealed class PickListOptions
|
||||
{
|
||||
|
||||
[FromBody]
|
||||
public SockType SockType { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public string Query { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public bool Inactive { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public List<long> PreselectedIds { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public string ListVariant { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public string Template { get; set; }
|
||||
|
||||
public PickListOptions()
|
||||
{
|
||||
SockType = SockType.NoType;
|
||||
Query = string.Empty;
|
||||
Inactive = false;
|
||||
PreselectedIds = new List<long>();
|
||||
ListVariant = string.Empty;
|
||||
Template = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PickListSingleOptions
|
||||
{
|
||||
|
||||
[FromBody]
|
||||
public SockType SockType { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public long Id { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public string ListVariant { get; set; }
|
||||
|
||||
[FromBody]
|
||||
public string Template { get; set; }
|
||||
|
||||
public PickListSingleOptions()
|
||||
{
|
||||
SockType = SockType.NoType;
|
||||
Id = 0;
|
||||
ListVariant = string.Empty;
|
||||
Template = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
318
server/PickList/PickListSqlBuilder.cs
Normal file
318
server/PickList/PickListSqlBuilder.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Sockeye.Biz;
|
||||
using Sockeye.Util;
|
||||
|
||||
namespace Sockeye.PickList
|
||||
{
|
||||
|
||||
internal static class PickListSqlBuilder
|
||||
{
|
||||
|
||||
|
||||
//Maximum number of results to return at any given time
|
||||
//did a little research and may adjust this but it can be fairly girthy in this day and age
|
||||
//and many people might not want or need to autocomplete type if we provide enough leeway.
|
||||
|
||||
const int MAXIMUM_RESULT_COUNT = 100;
|
||||
|
||||
//Build the query for a picklist request
|
||||
internal static string Build(IAyaPickList pickList, List<string> templateColumnNames, string autoCompleteQuery, string tagSpecificQuery, bool IncludeInactive, long[] preIds, string variant)
|
||||
{
|
||||
|
||||
//determine this in advance as it will be used in a loop later
|
||||
bool HasAutoCompleteQuery = !string.IsNullOrWhiteSpace(autoCompleteQuery);
|
||||
bool HasTagSpecificQuery = !string.IsNullOrWhiteSpace(tagSpecificQuery);
|
||||
|
||||
|
||||
//Variables to collect the data needed to create the actual clauses later
|
||||
List<string> lSelect = new List<string>();
|
||||
List<string> lWhere = new List<string>();
|
||||
List<string> lOrderBy = new List<string>();
|
||||
|
||||
string PlIdSelectFragment = string.Empty;
|
||||
string ActiveSelectFragment = string.Empty;
|
||||
string ActiveWhereFragment = string.Empty;
|
||||
string TagSpecificWhereFragment = string.Empty;
|
||||
string PredefinedOnlyWhereFragment = string.Empty;
|
||||
|
||||
string VariantWhereFragment = string.Empty;
|
||||
bool HasVariantWhereFragment = false;
|
||||
if (!string.IsNullOrWhiteSpace(variant) && pickList is IAyaPickListVariant)
|
||||
{
|
||||
VariantWhereFragment = ((IAyaPickListVariant)pickList).GetVariantCriteria(variant);
|
||||
HasVariantWhereFragment = !string.IsNullOrWhiteSpace(VariantWhereFragment);
|
||||
}
|
||||
|
||||
//PROCESS ROW ID "VALUE" COLUMN
|
||||
//
|
||||
AyaPickListFieldDefinition rowIdColumn = pickList.ColumnDefinitions.FirstOrDefault(z => z.IsRowId == true);
|
||||
//this should only happen with a development error
|
||||
if (rowIdColumn == null)
|
||||
throw new System.ArgumentNullException($"DEV ERROR in PickListSqlBuilder.cs: picklist for {pickList.DefaultListAType.ToString()} has no rowId column specified in columnDefinitions list");
|
||||
PlIdSelectFragment = rowIdColumn.SqlIdColumnName + " as plId";
|
||||
|
||||
|
||||
|
||||
if (preIds.Length > 0)
|
||||
{
|
||||
//select id,name from acustomer where id in(1,3,5,7)
|
||||
//string.Join(",", arr)
|
||||
// PredefinedOnlyWhereFragment = rowIdColumn.SqlIdColumnName + " = " + preId.ToString();
|
||||
PredefinedOnlyWhereFragment = $"{rowIdColumn.SqlIdColumnName} in ({string.Join(",", preIds)})";
|
||||
}
|
||||
|
||||
|
||||
//PROCESS ACTIVE COLUMN
|
||||
//
|
||||
//NOTE: default is to filter *out* inactive objects because that's the most common need at the Client
|
||||
//but we provide the override for that if necessary as there are often (management usually) cases where user needs to select inactive records
|
||||
|
||||
//add active column, fake it if necessary
|
||||
AyaPickListFieldDefinition activeColumn = pickList.ColumnDefinitions.FirstOrDefault(z => z.IsActiveColumn == true);
|
||||
if (activeColumn == null)
|
||||
{
|
||||
|
||||
//no active column which is normal for some types of objects
|
||||
//so make a fake one and return them all as active=true as all lists must return the same format
|
||||
ActiveSelectFragment = "true as plActive";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//we have an active column, set accordingly
|
||||
|
||||
//regardless of wanting to see inactive, we always want to see the column itself
|
||||
ActiveSelectFragment = activeColumn.SqlValueColumnName + " as plActive";
|
||||
|
||||
//this is the normal path unless there is an override
|
||||
//if there is an override to see inactive too then we just don't set the filter on active
|
||||
if (!IncludeInactive)
|
||||
{
|
||||
if (preIds.Length > 0)
|
||||
{
|
||||
//pre-selected need to always appear regardless of active status
|
||||
//ActiveWhereFragment = $"({rowIdColumn.SqlIdColumnName} = {preId}) or ({activeColumn.SqlValueColumnName} = true)";
|
||||
ActiveWhereFragment = $"({rowIdColumn.SqlIdColumnName} in ({string.Join(",", preIds)})) or ({activeColumn.SqlValueColumnName} = true)";
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveWhereFragment = activeColumn.SqlValueColumnName + " = true";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//PROCESS TAG SPECIFIC QUERY
|
||||
//
|
||||
if (HasTagSpecificQuery)
|
||||
{
|
||||
//get the tag column
|
||||
AyaPickListFieldDefinition tagColumn = pickList.ColumnDefinitions.FirstOrDefault(z => z.ColumnDataType == UiFieldDataType.Tags);
|
||||
TagSpecificWhereFragment = $"(array_to_string({tagColumn.GetSqlValueColumnName()},',') like '%{tagSpecificQuery}%')";
|
||||
}
|
||||
|
||||
|
||||
//PROCESS TEMPLATED COLUMNS TO BE RETURNED IN RESULTS
|
||||
//
|
||||
foreach (string ColumnName in templateColumnNames)
|
||||
{
|
||||
|
||||
AyaPickListFieldDefinition o = pickList.ColumnDefinitions.FirstOrDefault(z => z.FieldKey == ColumnName);
|
||||
#if (DEBUG)
|
||||
if (o == null)
|
||||
{
|
||||
throw new System.ArgumentNullException($"DEV ERROR in PickListSqlBuilder.cs: field {ColumnName} specified in template was NOT found in columnDefinitions list");
|
||||
}
|
||||
#endif
|
||||
if (o != null)
|
||||
{//Ignore missing fields in production
|
||||
|
||||
var valueColumnName = o.GetSqlValueColumnName();
|
||||
|
||||
string sWhere = string.Empty;
|
||||
|
||||
//TAGS COLUMN
|
||||
//
|
||||
if (o.ColumnDataType == UiFieldDataType.Tags)
|
||||
{
|
||||
lSelect.Add($"(array_to_string({valueColumnName},','))");
|
||||
//tags can order by without the arraytostring
|
||||
lOrderBy.Add(valueColumnName);
|
||||
|
||||
//THIS is the best filter method for a like comparison to each individual tag:
|
||||
//(array_to_string(acustomer.tags,',') like '%zo%')
|
||||
//Note that a tag specific query takes precendence over this which exists
|
||||
//in cases where there are tags in the template and the user has not specified a tag specific query
|
||||
//so this will handle it as a like query against all tags as a composite string of text just like
|
||||
//all the other templated fields
|
||||
if (HasAutoCompleteQuery && !HasTagSpecificQuery)
|
||||
{
|
||||
if (ServerGlobalBizSettings.Cache.FilterCaseSensitive)
|
||||
sWhere = $"(array_to_string({valueColumnName},',') like '%{autoCompleteQuery}%')";
|
||||
else
|
||||
sWhere = $"(lower(array_to_string({valueColumnName},',')) like lower('%{autoCompleteQuery}%'))";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (o.ColumnDataType == UiFieldDataType.Text || o.ColumnDataType == UiFieldDataType.EmailAddress || o.ColumnDataType == UiFieldDataType.HTTP)
|
||||
{
|
||||
//TEXT COLUMN
|
||||
//
|
||||
lSelect.Add(valueColumnName);
|
||||
lOrderBy.Add(valueColumnName);
|
||||
if (HasAutoCompleteQuery)
|
||||
if (ServerGlobalBizSettings.Cache.FilterCaseSensitive)
|
||||
sWhere = $"({valueColumnName} like '%{autoCompleteQuery}%')";
|
||||
else
|
||||
sWhere = $"(lower({valueColumnName}) like lower('%{autoCompleteQuery}%'))";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//NON-TEXT COLUMN
|
||||
//
|
||||
|
||||
//Note: if any part of a select contatenation query using the || postgres concat is text then all fields are automatically converted to text
|
||||
//so no need to make it text here as the automatic spacing character will force the whole thing to a text concat anyway
|
||||
//ref: https://stackoverflow.com/a/19943343/8939
|
||||
lSelect.Add(valueColumnName);
|
||||
|
||||
//order by for now seems to be best as just order by it's value whatever it is
|
||||
lOrderBy.Add(valueColumnName);
|
||||
|
||||
//Where fragment is different for non text fields: it needs to be cast to text to like query on it
|
||||
if (HasAutoCompleteQuery)
|
||||
if (ServerGlobalBizSettings.Cache.FilterCaseSensitive)
|
||||
sWhere = $"(cast ({valueColumnName} as text) like '%{autoCompleteQuery}%')";
|
||||
else
|
||||
sWhere = $"(lower(cast ({valueColumnName} as text)) like lower('%{autoCompleteQuery}%'))";
|
||||
}
|
||||
|
||||
|
||||
if (HasAutoCompleteQuery && !string.IsNullOrWhiteSpace(sWhere))//swhere can be empty on a tag in a tag specific query
|
||||
lWhere.Add(sWhere);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
//SELECT
|
||||
sb.Append("select ");
|
||||
|
||||
//ID COLUMN
|
||||
sb.Append(PlIdSelectFragment);
|
||||
sb.Append(", ");
|
||||
|
||||
//ACTIVE COLUMN
|
||||
sb.Append(ActiveSelectFragment);
|
||||
sb.Append(", ");
|
||||
|
||||
//nope, this will return null if any of the values are null, very bad for this use, instead
|
||||
//select name || ' ' || serial || ' ' || array_to_string(tags,',') as display from acustomer
|
||||
//this, on the other hand will work even if all of them are null
|
||||
//concat_ws(' ', acustomer.name, acustomer.serial, auser.name)
|
||||
|
||||
sb.Append("concat_ws(' ', ");
|
||||
foreach (string s in lSelect)
|
||||
{
|
||||
sb.Append(s);
|
||||
sb.Append(",");
|
||||
}
|
||||
//clear trailing comma
|
||||
sb.Length -= 1;
|
||||
sb.Append(") as plname");
|
||||
|
||||
//FROM
|
||||
sb.Append(" ");
|
||||
sb.Append(pickList.SQLFrom);
|
||||
|
||||
|
||||
//WHERE
|
||||
//there is a condition where there is no where (inactive=true and no query of any kind)
|
||||
if (preIds.Length > 0 || lWhere.Count > 0 || HasTagSpecificQuery || HasVariantWhereFragment || IncludeInactive == false)
|
||||
{
|
||||
sb.Append(" where ");
|
||||
|
||||
if (HasVariantWhereFragment)
|
||||
{
|
||||
sb.Append($"({VariantWhereFragment}) and ");
|
||||
}
|
||||
|
||||
if (HasTagSpecificQuery)
|
||||
{
|
||||
sb.Append(TagSpecificWhereFragment);
|
||||
sb.Append(" and ");
|
||||
}
|
||||
|
||||
if (!IncludeInactive)
|
||||
{
|
||||
sb.Append(ActiveWhereFragment);
|
||||
sb.Append(" and ");
|
||||
}
|
||||
|
||||
if (preIds.Length > 0)
|
||||
{
|
||||
sb.Append(PredefinedOnlyWhereFragment);
|
||||
sb.Append(" and ");
|
||||
}
|
||||
|
||||
if (lWhere.Count > 0)
|
||||
{
|
||||
//Put all the regular query terms in parenthesis to ensure it's all treated as one criteria
|
||||
|
||||
sb.Append("(");
|
||||
foreach (string s in lWhere)
|
||||
{
|
||||
sb.Append(s);
|
||||
sb.Append(" or ");
|
||||
}
|
||||
//clear trailing or
|
||||
|
||||
sb.Length -= 4;
|
||||
//enclosing parenthesis
|
||||
|
||||
|
||||
sb.Append(")");
|
||||
}
|
||||
else
|
||||
{
|
||||
//we might have a trailing and to remove
|
||||
//{select acustomer.id as plId, acustomer.active as plActive, concat_ws(' ', acustomer.name) as plname from acustomer where (acustomer.headofficeid = 2) and }
|
||||
if(sb.ToString().EndsWith(" and "))
|
||||
sb.Length-=5;
|
||||
//
|
||||
// if (!IncludeInactive || HasTagSpecificQuery || preIds.Length > 0)
|
||||
// {
|
||||
// //trailing " and " to remove
|
||||
// sb.Length -= 5;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
//ORDER BY
|
||||
sb.Append(" order by ");
|
||||
foreach (string s in lOrderBy)
|
||||
{
|
||||
sb.Append(s);
|
||||
sb.Append(",");
|
||||
}
|
||||
//clear trailing comma
|
||||
sb.Length--;
|
||||
|
||||
//LIMIT
|
||||
sb.Append($" limit {MAXIMUM_RESULT_COUNT}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
}
|
||||
//"select acustomer.id as plId || ' 'acustomer.active as plActive || ' 'acustomer.name || ' 'acustomer.serial || ' 'auser.name as plname from acustomer left join auser on (acustomer.userid=auser.id)
|
||||
//where acustomer.active = true and ((acustomer.name like '%on%') or (cast (acustomer.serial as text) like '%on%') or (auser.name like '%on%')) order by acustomer.name,acustomer.serial,auser.name limit 100"
|
||||
|
||||
|
||||
}//eoc
|
||||
}//ens
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user