This commit is contained in:
2020-01-22 14:57:46 +00:00
parent 4784ceef4c
commit 999577f9b6
2 changed files with 648 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using AyaNova.Models;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Biz;
namespace AyaNova.Api.Controllers
{
/// <summary>
///
/// </summary>
[ApiController]
[ApiVersion("8.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[Produces("application/json")]
[Authorize]
public class DataListTemplateController : ControllerBase
{
private readonly AyContext ct;
private readonly ILogger<DataListTemplateController> log;
private readonly ApiServerState serverState;
/// <summary>
/// ctor
/// </summary>
/// <param name="dbcontext"></param>
/// <param name="logger"></param>
/// <param name="apiServerState"></param>
public DataListTemplateController(AyContext dbcontext, ILogger<DataListTemplateController> logger, ApiServerState apiServerState)
{
ct = dbcontext;
log = logger;
serverState = apiServerState;
}
/// <summary>
/// Get full DataFilter object
///
/// Required roles:
/// Any (for public filter), owned only for private filter
/// </summary>
/// <param name="id"></param>
/// <returns>A single DataFilter</returns>
[HttpGet("{id}")]
public async Task<IActionResult> GetDataFilter([FromRoute] long id)
{
if (serverState.IsClosed)
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
//Instantiate the business object handler
DataListTemplateBiz biz = DataListTemplateBiz.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, !Authorized.HasModifyRole(HttpContext.Items, biz.BizType)));
}
/// <summary>
/// Get DataFilter pick list
///
/// Required roles: Any
///
/// </summary>
/// <returns>List of public or owned data filters for listKey provided</returns>
[HttpGet("PickList", Name = nameof(DataFilterPickList))]
public async Task<IActionResult> DataFilterPickList([FromQuery] string ListKey)
{
if (serverState.IsClosed)
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
//Instantiate the business object handler
DataListTemplateBiz biz = DataListTemplateBiz.GetBiz(ct, HttpContext);
var l = await biz.GetPickListAsync(ListKey);
return Ok(ApiOkResponse.Response(l, true));
}
/// <summary>
/// Put (update) DataFilter
///
/// Required roles:
/// Any (public filter) or owned only (private filter)
///
/// </summary>
/// <param name="id"></param>
/// <param name="inObj"></param>
/// <returns></returns>
[HttpPut("{id}")]
public async Task<IActionResult> PutDataFilter([FromRoute] long id, [FromBody] DataListTemplate inObj)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
//Instantiate the business object handler
DataListTemplateBiz biz = DataListTemplateBiz.GetBiz(ct, HttpContext);
var o = await biz.GetNoLogAsync(id);
if (o == null)
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
try
{
if (!biz.Put(o, inObj))
return BadRequest(new ApiErrorResponse(biz.Errors));
}
catch (DbUpdateConcurrencyException)
{
if (!await biz.ExistsAsync(id))
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
else
return StatusCode(409, new ApiErrorResponse(ApiErrorCode.CONCURRENCY_CONFLICT));
}
return Ok(ApiOkResponse.Response(new { ConcurrencyToken = o.ConcurrencyToken }, true));
}
/// <summary>
/// Post DataFilter
///
/// Required roles:
/// BizAdminFull, InventoryFull, TechFull
/// </summary>
/// <param name="inObj"></param>
/// <param name="apiVersion">Automatically filled from route path, no need to specify in body</param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> PostDataFilter([FromBody] DataListTemplate inObj, ApiVersion apiVersion)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
//Instantiate the business object handler
DataListTemplateBiz biz = DataListTemplateBiz.GetBiz(ct, HttpContext);
//If a user has change roles, or editOwnRoles then they can create, true is passed for isOwner since they are creating so by definition the owner
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
//Create and validate
DataListTemplate o = await biz.CreateAsync(inObj);
if (o == null)
return BadRequest(new ApiErrorResponse(biz.Errors));
else
return CreatedAtAction(nameof(DataListTemplateController.GetDataFilter), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
}
/// <summary>
/// Delete DataFilter
///
/// Required roles:
/// Any if public otherwise creator only
///
/// </summary>
/// <param name="id"></param>
/// <returns>Ok</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteDataFilter([FromRoute] long id)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
//Instantiate the business object handler
DataListTemplateBiz biz = DataListTemplateBiz.GetBiz(ct, HttpContext);
var o = await biz.GetNoLogAsync(id);
if (o == null)
return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
if (!biz.Delete(o))
return BadRequest(new ApiErrorResponse(biz.Errors));
return NoContent();
}
//------------
}//eoc
}//eons

View File

@@ -0,0 +1,424 @@
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using AyaNova.Util;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Models;
using AyaNova.DataList;
namespace AyaNova.Biz
{
internal class DataListTemplateBiz : BizObject
{
internal DataListTemplateBiz(AyContext dbcontext, long currentUserId, long userLocaleId, AuthorizationRoles UserRoles)
{
ct = dbcontext;
UserId = currentUserId;
UserLocaleId = userLocaleId;
CurrentUserRoles = UserRoles;
BizType = AyaType.DataFilter;
}
internal static DataListTemplateBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext)
{
return new DataListTemplateBiz(ct, UserIdFromContext.Id(httpContext.Items), UserLocaleIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
}
//Version for internal use
internal static DataListTemplateBiz GetBizInternal(AyContext ct)
{
return new DataListTemplateBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE_ID, AuthorizationRoles.BizAdminFull);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> ExistsAsync(long id)
{
return await ct.DataListTemplate.AnyAsync(e => e.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
/// GET
internal async Task<DataListTemplate> GetNoLogAsync(long fetchId)
{
//This is simple so nothing more here, but often will be copying to a different output object or some other ops
return await ct.DataListTemplate.SingleOrDefaultAsync(m => m.Id == fetchId);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
internal async Task<DataListTemplate> CreateAsync(DataListTemplate inObj)
{
Validate(inObj, true);
if (HasErrors)
return null;
else
{
//do stuff with datafilter
DataListTemplate outObj = inObj;
outObj.UserId = UserId;
await ct.DataListTemplate.AddAsync(outObj);
await ct.SaveChangesAsync();
//Handle child and associated items:
//EVENT LOG
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);
//SEARCH INDEXING
// Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name);
return outObj;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
internal DataListTemplate Create(AyContext TempContext, DataListTemplate inObj)
{
Validate(inObj, true);
if (HasErrors)
return null;
else
{
//do stuff with datafilter
DataListTemplate outObj = inObj;
outObj.UserId = UserId;
TempContext.DataListTemplate.Add(outObj);
TempContext.SaveChanges();
//Handle child and associated items:
//EVENT LOG
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext);
//SEARCH INDEXING
// Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.Name);
return outObj;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
/// GET
//Get one
internal async Task<DataListTemplate> GetAsync(long fetchId)
{
//This is simple so nothing more here, but often will be copying to a different output object or some other ops
var ret = await ct.DataListTemplate.SingleOrDefaultAsync(m => m.Id == fetchId && (m.Public == true || m.UserId == UserId));
if (ret != null)
{
//Log
EventLogProcessor.LogEventToDatabase(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct);
}
return ret;
}
//get picklist (NOT PAGED)
internal async Task<List<NameIdItem>> GetPickListAsync(string listKey)
{
List<NameIdItem> items = new List<NameIdItem>();
if (!string.IsNullOrWhiteSpace(listKey))
{
items = await ct.DataListTemplate
.AsNoTracking()
.Where(m => m.ListKey == listKey && (m.Public == true || m.UserId == UserId))
.OrderBy(m => m.Name)
.Select(m => new NameIdItem()
{
Id = m.Id,
Name = m.Name
}).ToListAsync();
}
return items;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
//put
internal bool Put(DataListTemplate dbObj, DataListTemplate inObj)
{
//preserve the owner ID if none was specified
if (inObj.UserId == 0)
inObj.UserId = dbObj.UserId;
//Replace the db object with the PUT object
CopyObject.Copy(inObj, dbObj, "Id");
//Set "original" value of concurrency token to input token
//this will allow EF to check it out
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken;
Validate(dbObj, false);
if (HasErrors)
return false;
//Log modification
EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
//Update keywords
// Search.ProcessUpdatedObjectKeywords(UserLocaleId, dbObj.Id, BizType, dbObj.Name, dbObj.Name);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal bool Delete(DataListTemplate dbObj)
{
//Determine if the object can be deleted, do the deletion tentatively
//Probably also in here deal with tags and associated search text etc
ValidateCanDelete(dbObj);
if (HasErrors)
return false;
ct.DataListTemplate.Remove(dbObj);
ct.SaveChanges();
//Delete sibling objects
//Event log process delete
EventLogProcessor.DeleteObject(UserId, BizType, dbObj.Id, dbObj.Name, ct);
ct.SaveChanges();
//Delete search index
//Search.ProcessDeletedObjectKeywords(dbObj.Id, BizType);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
//Can save or update?
private void Validate(DataListTemplate inObj, bool isNew)
{
//UserId required
if (!isNew)
{
if (inObj.UserId == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "UserId");
}
//Name required
if (string.IsNullOrWhiteSpace(inObj.Name))
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name");
//Name must be less than 255 characters
if (inObj.Name.Length > 255)
AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max");
//If name is otherwise OK, check that name is unique
if (!PropertyHasErrors("Name"))
{
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
if (ct.DataListTemplate.Any(m => m.Name == inObj.Name && m.Id != inObj.Id))
{
AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
}
}
if (string.IsNullOrWhiteSpace(inObj.ListKey))
AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListKey");
var DataList = DataListFactory.GetAyaDataList(inObj.ListKey);
// List<AyaDataListFieldDefinition> FieldList = null;
//if (!AyaFormFieldDefinitions.IsValidFormFieldDefinitionKey(inObj.ListKey))
if (DataList == null)
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListKey", $"ListKey \"{inObj.ListKey}\" DataListKey is not valid");
}
// else
// {
// FieldList = AyaDataListFieldDefinition.AyaObjectFields(inObj.ListKey);
// }
if (inObj.ListKey.Length > 255)
AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "ListKey", "255 max");
//Filter json must parse
if (!string.IsNullOrWhiteSpace(inObj.Filter))
{
try
{
var v = JArray.Parse(inObj.Filter);
for (int i = 0; i < v.Count; i++)
{
var filterItem = v[i];
if (filterItem["fld"] == null)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing required \"fld\" property ");
else
{
var fld = filterItem["fld"].Value<string>();
if (string.IsNullOrWhiteSpace(fld))
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, \"fld\" property is empty and required");
//validate the field name if we can
if (DataList != null)
{
var TheField = DataList.FieldDefinitions.SingleOrDefault(x => x.FieldKey.ToLowerInvariant() == fld);
if (TheField == null)
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Filter", $"Filter array item {i}, fld property value \"{fld}\" is not a valid value for ListKey specified");
}
else if (TheField.IsFilterable == false)
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Filter", $"Filter array item {i}, fld property value \"{fld}\" is not filterable");
}
}
}
if (filterItem["op"] == null)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing required \"op\" property ");
else
{
var opType = filterItem["op"].Value<string>();
if (!DataListTemplateComparisonOperator.Operators.Contains(opType))
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Filter", $"Filter array item {i}, \"op\" property value of \"{opType}\" is not a valid FilterComparisonOperator type");
}
if (filterItem["value"] == null)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ");
else
{
if (filterItem["value"].Type == JTokenType.String && string.IsNullOrWhiteSpace(filterItem["value"].Value<string>()))
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ");
if (filterItem["value"].Type == JTokenType.Array && filterItem["value"].Count() == 0)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ARRAY ");
}
//NOTE: value of nothing, null or empty is a valid value so no checking for it here
}
}
catch (Newtonsoft.Json.JsonReaderException ex)
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Filter", "Filter is not valid JSON string: " + ex.Message);
}
}
//VALIDATE SORT
//Filter json must parse
if (!string.IsNullOrWhiteSpace(inObj.Sort))
{
try
{
var v = JArray.Parse(inObj.Sort);
for (int i = 0; i < v.Count; i++)
{
var sortItem = v[i];
if (sortItem["fld"] == null)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Sort", $"Sort array item {i}, object is missing required \"fld\" property ");
else
{
var fld = sortItem["fld"].Value<string>();
if (string.IsNullOrWhiteSpace(fld))
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Sort", $"Sort array item {i}, \"fld\" property is empty and required");
//validate the field name if we can
if (DataList != null)
{
if (!DataList.FieldDefinitions.Exists(x => x.FieldKey.ToLowerInvariant() == fld && x.IsFilterable))
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Sort", $"Sort array item {i}, fld property value \"{fld}\" is not a valid value for ListKey specified");
}
}
}
if (sortItem["dir"] == null)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Sort", $"Sort array item {i}, object is missing required \"dir\" sort direction property ");
else
{
var sortDir = sortItem["dir"].Value<string>();
if (sortDir != "+" && sortDir != "-")
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Sort", $"Sort array item {i}, \"dir\" property value of \"{sortDir}\" is not a valid sort direction value, must be \"+\" or \"-\" only");
}
//NOTE: value of nothing, null or empty is a valid value so no checking for it here
}
}
catch (Newtonsoft.Json.JsonReaderException ex)
{
AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Sort", "Sort is not valid JSON string: " + ex.Message);
}
}
return;
}
//Can delete?
private void ValidateCanDelete(DataListTemplate inObj)
{
//Leaving this off for now
}
// ////////////////////////////////////////////////////////////////////////////////////////////////
// //JOB / OPERATIONS
// //
// public async Task HandleJobAsync(OpsJob job)
// {
// //Hand off the particular job to the corresponding processing code
// //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so
// //basically any error condition during job processing should throw up an exception if it can't be handled
// switch (job.JobType)
// {
// case JobType.TestDataFilterJob:
// await ProcessTestJobAsync(job);
// break;
// default:
// throw new System.ArgumentOutOfRangeException($"DataFilterBiz.HandleJob-> Invalid job type{job.JobType.ToString()}");
// }
// }
//Other job handlers here...
/////////////////////////////////////////////////////////////////////
}//eoc
}//eons