From d80f8aaa53716a3ca303f4f0a2557242608feac2 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Tue, 29 Dec 2020 20:11:46 +0000 Subject: [PATCH] --- .../Controllers/ServiceBankController.cs | 92 ++++++++ server/AyaNova/biz/ServiceBankBiz.cs | 200 ++++++++++++++++++ server/AyaNova/models/AyContext.cs | 5 +- server/AyaNova/models/ICoreBizObjectModel.cs | 4 +- server/AyaNova/models/ServiceBank.cs | 6 +- server/AyaNova/util/AySchema.cs | 2 +- 6 files changed, 302 insertions(+), 7 deletions(-) create mode 100644 server/AyaNova/Controllers/ServiceBankController.cs create mode 100644 server/AyaNova/biz/ServiceBankBiz.cs diff --git a/server/AyaNova/Controllers/ServiceBankController.cs b/server/AyaNova/Controllers/ServiceBankController.cs new file mode 100644 index 00000000..f5c85072 --- /dev/null +++ b/server/AyaNova/Controllers/ServiceBankController.cs @@ -0,0 +1,92 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging; +using AyaNova.Models; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Biz; + + + +namespace AyaNova.Api.Controllers +{ + [ApiController] + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/service-bank")] + [Produces("application/json")] + [Authorize] + public class ServiceBankController : ControllerBase + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public ServiceBankController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + /// + /// Create ServiceBank + /// (This object is create / get only, there is no update or delete only adjustments through new entries) + /// + /// + /// From route path + /// + [HttpPost] + public async Task PostServiceBank([FromBody] ServiceBank newObject, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ServiceBankBiz biz = ServiceBankBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + ServiceBank o = await biz.CreateAsync(newObject); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(ServiceBankController.GetServiceBank), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + + /// + /// Get ServiceBank + /// (This object is create / get only, there is no update or delete only adjustments through new entries) + /// + /// + /// ServiceBank + [HttpGet("{id}")] + public async Task GetServiceBank([FromRoute] long id) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + ServiceBankBiz biz = ServiceBankBiz.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)); + } + + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/AyaNova/biz/ServiceBankBiz.cs b/server/AyaNova/biz/ServiceBankBiz.cs new file mode 100644 index 00000000..21935d1e --- /dev/null +++ b/server/AyaNova/biz/ServiceBankBiz.cs @@ -0,0 +1,200 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using AyaNova.Util; +using AyaNova.Api.ControllerHelpers; +using Microsoft.Extensions.Logging; +using AyaNova.Models; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace AyaNova.Biz +{ + internal class ServiceBankBiz : BizObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, INotifiableObject + { + internal ServiceBankBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) + { + ct = dbcontext; + UserId = currentUserId; + UserTranslationId = userTranslationId; + CurrentUserRoles = UserRoles; + BizType = AyaType.ServiceBank; + } + + internal static ServiceBankBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) + { + if (httpContext != null) + return new ServiceBankBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); + else + return new ServiceBankBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //EXISTS + internal async Task ExistsAsync(long id) + { + return await ct.ServiceBank.AnyAsync(z => z.Id == id); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(ServiceBank newObject) + { + await ValidateAsync(newObject, null); + if (HasErrors) + return null; + else + { + await ct.ServiceBank.AddAsync(newObject); + await ct.SaveChangesAsync(); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await HandlePotentialNotificationEvent(AyaEvent.Created, newObject); + return newObject; + } + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.ServiceBank.SingleOrDefaultAsync(m => m.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct); + return ret; + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // + private async Task SearchIndexAsync(ServiceBank obj, bool isNew) + { + var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); + DigestSearchText(obj, SearchParams); + if (isNew) + await Search.ProcessNewObjectKeywordsAsync(SearchParams); + else + await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams); + } + + public async Task GetSearchResultSummary(long id) + { + var obj = await ct.ServiceBank.SingleOrDefaultAsync(z => z.Id == id); + var SearchParams = new Search.SearchIndexProcessObjectParameters(); + DigestSearchText(obj, SearchParams); + return SearchParams; + } + + public void DigestSearchText(ServiceBank obj, Search.SearchIndexProcessObjectParameters searchParams) + { + if (obj != null) + searchParams.AddText(obj.Name); + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //VALIDATION + // + + private async Task ValidateAsync(ServiceBank proposedObj, ServiceBank currentObj) + { + + bool isNew = currentObj == null; + + //Name required + if (string.IsNullOrWhiteSpace(proposedObj.Name)) + AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name"); + + + + //Any form customizations to validate? + var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.ServiceBank.ToString()); + if (FormCustomization != null) + { + //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required + + //validate users choices for required non custom fields + RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj); + + } + + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //REPORTING + // + public async Task GetReportData(long[] idList) + { + JArray ReportData = new JArray(); + while (idList.Any()) + { + var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE); + idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray(); + //query for this batch, comes back in db natural order unfortunately + var batchResults = await ct.ServiceBank.Where(z => batch.Contains(z.Id)).ToArrayAsync(); + //order the results back into original + var orderedList = from id in batch join z in batchResults on id equals z.Id select z; + foreach (ServiceBank w in orderedList) + { + var jo = JObject.FromObject(w); + if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"])) + jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]); + ReportData.Add(jo); + } + } + return ReportData; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // IMPORT EXPORT + // + + public async Task GetExportData(long[] idList) + { + //for now just re-use the report data code + //this may turn out to be the pattern for most biz object types but keeping it seperate allows for custom usage from time to time + return await GetReportData(idList); + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + // NOTIFICATION PROCESSING + // + public async Task HandlePotentialNotificationEvent(AyaEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null) + { + ILogger log = AyaNova.Util.ApplicationLogging.CreateLogger(); + if (ServerBootConfig.SEEDING) return; + log.LogDebug($"HandlePotentialNotificationEvent processing: [AyaType:{this.BizType}, AyaEvent:{ayaEvent}]"); + + bool isNew = currentObj == null; + + + //STANDARD EVENTS FOR ALL OBJECTS + await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct); + + //SPECIFIC EVENTS FOR THIS OBJECT + + }//end of process notifications + + + + + ///////////////////////////////////////////////////////////////////// + + }//eoc + + +}//eons + diff --git a/server/AyaNova/models/AyContext.cs b/server/AyaNova/models/AyContext.cs index 8ca2aac8..e866e2de 100644 --- a/server/AyaNova/models/AyContext.cs +++ b/server/AyaNova/models/AyContext.cs @@ -71,15 +71,16 @@ namespace AyaNova.Models public virtual DbSet WorkOrderItemTravel { get; set; } public virtual DbSet WorkOrderItemUnit { get; set; } - //WorkOrderTemplate public virtual DbSet WorkOrderTemplate { get; set; } public virtual DbSet WorkOrderTemplateItem { get; set; } - public virtual DbSet Logo { get; set; } public virtual DbSet Report { get; set; } public virtual DbSet DashboardView { get; set; } + public virtual DbSet ServiceBank { get; set; } + + //Note: had to add this constructor to work with the code in startup.cs that gets the connection string from the appsettings.json file //and commented out the above on configuring diff --git a/server/AyaNova/models/ICoreBizObjectModel.cs b/server/AyaNova/models/ICoreBizObjectModel.cs index 81e3fadf..ba754da9 100644 --- a/server/AyaNova/models/ICoreBizObjectModel.cs +++ b/server/AyaNova/models/ICoreBizObjectModel.cs @@ -15,8 +15,8 @@ namespace AyaNova.Models public string Name { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } public bool? Active { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } public string Wiki { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } - public string CustomFields { get; set; } - public List Tags { get; set; } + public string CustomFields { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + public List Tags { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } public AyaNova.Biz.AyaType AyaType { get; } } diff --git a/server/AyaNova/models/ServiceBank.cs b/server/AyaNova/models/ServiceBank.cs index 79735de8..a0f4d274 100644 --- a/server/AyaNova/models/ServiceBank.cs +++ b/server/AyaNova/models/ServiceBank.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using AyaNova.Biz; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -13,7 +12,7 @@ namespace AyaNova.Models //NOTE: following pattern outlined here: //https://dba.stackexchange.com/a/19368 - public class ServiceBank + public class ServiceBank : ICoreBizObjectModel { public long Id { get; set; } public uint Concurrency { get; set; } @@ -58,6 +57,9 @@ namespace AyaNova.Models EntryDate = DateTime.UtcNow; } + [NotMapped, JsonIgnore] + public AyaType AyaType { get => AyaType.ServiceBank; } + }//eoc }//eons diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 4409dcc8..afe9e15a 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -403,7 +403,7 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); //create translation text tables - await ExecQueryAsync("CREATE TABLE atranslation (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text not null, stock bool, cjkindex bool default false)"); + await ExecQueryAsync("CREATE TABLE atranslation (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text not null unique, stock bool, cjkindex bool default false)"); await ExecQueryAsync("CREATE TABLE atranslationitem (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, translationid bigint not null REFERENCES atranslation (id), key text not null, display text not null)"); //This is not a well used index, not sure what it's point is