This commit is contained in:
2022-12-16 06:01:23 +00:00
parent 26c2ae5cc9
commit effd96143f
310 changed files with 48715 additions and 0 deletions

View 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;
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Sockeye.Api.ControllerHelpers
{
public class ApiCreatedResponse
{
public object Data { get; }
public ApiCreatedResponse(object result)
{
Data = result;
}
}//eoc
}//eons

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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)
{
}
}
}

View 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));
}
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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}");
}
//------------
}
}

View 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();
}
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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; }
}
//----------
}
}

View 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()));
}
//------------
}
}

View 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

View 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
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
}
}

View 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);
}
}
}

View 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;
}
}
}
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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);
}
}

View 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; }
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}
}
}
}

View 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

View 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

View 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);
}
}

View File

@@ -0,0 +1,7 @@
namespace Sockeye.PickList
{
internal interface IAyaPickListVariant
{
string GetVariantCriteria(string variant);
}
}

View 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

View 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

View 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;
}
}
}

View 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