From a45abb746726e2dc16cc434b6b31feee27f61799 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Wed, 13 May 2020 21:18:18 +0000 Subject: [PATCH] --- .../AyaNova/Controllers/CustomerController.cs | 5 - .../AyaNova/Controllers/WidgetController.cs | 135 +++++--- server/AyaNova/biz/CustomerBiz.cs | 10 +- server/AyaNova/biz/WidgetBiz.cs | 294 +++++++++++------- 4 files changed, 284 insertions(+), 160 deletions(-) diff --git a/server/AyaNova/Controllers/CustomerController.cs b/server/AyaNova/Controllers/CustomerController.cs index 14e2c882..ce36268c 100644 --- a/server/AyaNova/Controllers/CustomerController.cs +++ b/server/AyaNova/Controllers/CustomerController.cs @@ -11,7 +11,6 @@ using AyaNova.Biz; namespace AyaNova.Api.Controllers { - [ApiController] [ApiVersion("8.0")] [Route("api/v{version:apiVersion}/[controller]")] @@ -23,7 +22,6 @@ namespace AyaNova.Api.Controllers private readonly ILogger log; private readonly ApiServerState serverState; - /// /// ctor /// @@ -84,7 +82,6 @@ namespace AyaNova.Api.Controllers return CreatedAtAction(nameof(CustomerController.GetCustomer), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); } - /// /// Get Customer /// @@ -105,8 +102,6 @@ namespace AyaNova.Api.Controllers return Ok(ApiOkResponse.Response(o, !Authorized.HasModifyRole(HttpContext.Items, biz.BizType))); } - - /// /// Put (update) Customer /// diff --git a/server/AyaNova/Controllers/WidgetController.cs b/server/AyaNova/Controllers/WidgetController.cs index 8292f46e..08d7efaf 100644 --- a/server/AyaNova/Controllers/WidgetController.cs +++ b/server/AyaNova/Controllers/WidgetController.cs @@ -46,8 +46,38 @@ namespace AyaNova.Api.Controllers serverState = apiServerState; } + // /// + // /// Create widget + // /// + // /// + // /// From route path + // /// + // [HttpPost] + // public async Task PostWidget([FromBody] Widget newObject, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // //Instantiate the business object handler + // WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); + + // //If a user has change roles + // if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // //Create and validate + // Widget o = await biz.CreateAsync(newObject); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction(nameof(WidgetController.GetWidget), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + // } + /// - /// Create widget + /// Create Widget /// /// /// From route path @@ -57,27 +87,79 @@ namespace AyaNova.Api.Controllers { if (!serverState.IsOpen) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - - //Instantiate the business object handler WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); - - //If a user has change roles if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) return StatusCode(403, new ApiNotAuthorizedResponse()); - if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); - - //Create and validate Widget o = await biz.CreateAsync(newObject); if (o == null) return BadRequest(new ApiErrorResponse(biz.Errors)); else return CreatedAtAction(nameof(WidgetController.GetWidget), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); - - } + + // /// + // /// Duplicate widget + // /// + // /// Create a duplicate of this items id + // /// From route path + // /// + // [HttpPost("duplicate/{id}")] + // public async Task DuplicateWidget([FromRoute] long id, ApiVersion apiVersion) + // { + // if (!serverState.IsOpen) + // return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + + // //Instantiate the business object handler + // WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); + + // //If a user has change roles + // if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + // return StatusCode(403, new ApiNotAuthorizedResponse()); + + // if (!ModelState.IsValid) + // return BadRequest(new ApiErrorResponse(ModelState)); + + // var oSrc = await biz.GetAsync(id, false); + // if (oSrc == null) + // return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); + + // //Create and validate + // Widget o = await biz.DuplicateAsync(oSrc); + // if (o == null) + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // else + // return CreatedAtAction(nameof(WidgetController.GetWidget), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + + // } + + /// + /// Duplicate Widget + /// (Wiki and Attachments are not duplicated) + /// + /// Source object id + /// From route path + /// Widget + [HttpPost("duplicate/{id}")] + public async Task DuplicateWidget([FromRoute] long id, ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); + if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + Widget o = await biz.DuplicateAsync(id); + if (o == null) + return BadRequest(new ApiErrorResponse(biz.Errors)); + else + return CreatedAtAction(nameof(WidgetController.GetWidget), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); + } + + /// /// Get full widget object /// @@ -154,40 +236,7 @@ namespace AyaNova.Api.Controllers - /// - /// Duplicate widget - /// - /// Create a duplicate of this items id - /// From route path - /// - [HttpPost("duplicate/{id}")] - public async Task DuplicateWidget([FromRoute] long id, ApiVersion apiVersion) - { - if (!serverState.IsOpen) - return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - //Instantiate the business object handler - WidgetBiz biz = WidgetBiz.GetBiz(ct, HttpContext); - - //If a user has change roles - if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType)) - return StatusCode(403, new ApiNotAuthorizedResponse()); - - if (!ModelState.IsValid) - return BadRequest(new ApiErrorResponse(ModelState)); - - var oSrc = await biz.GetAsync(id, false); - if (oSrc == null) - return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND)); - - //Create and validate - Widget o = await biz.DuplicateAsync(oSrc); - if (o == null) - return BadRequest(new ApiErrorResponse(biz.Errors)); - else - return CreatedAtAction(nameof(WidgetController.GetWidget), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o)); - - } diff --git a/server/AyaNova/biz/CustomerBiz.cs b/server/AyaNova/biz/CustomerBiz.cs index 948e6e27..c5571b91 100644 --- a/server/AyaNova/biz/CustomerBiz.cs +++ b/server/AyaNova/biz/CustomerBiz.cs @@ -6,10 +6,8 @@ using AyaNova.Models; namespace AyaNova.Biz { - internal class CustomerBiz : BizObject, ISearchAbleObject { - internal CustomerBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -27,8 +25,6 @@ namespace AyaNova.Biz return new CustomerBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -90,8 +86,7 @@ namespace AyaNova.Biz } //////////////////////////////////////////////////////////////////////////////////////////////// - // GET - // + //GET // internal async Task GetAsync(long id, bool logTheGetEvent = true) { @@ -138,7 +133,6 @@ namespace AyaNova.Biz return dbObject; } - //////////////////////////////////////////////////////////////////////////////////////////////// //DELETE // @@ -174,7 +168,7 @@ namespace AyaNova.Biz } //////////////////////////////////////////////////////////////////////////////////////////////// - // SEARCH + //SEARCH // private async Task SearchIndexAsync(Customer obj, bool isNew) { diff --git a/server/AyaNova/biz/WidgetBiz.cs b/server/AyaNova/biz/WidgetBiz.cs index fbf12374..355a06cf 100644 --- a/server/AyaNova/biz/WidgetBiz.cs +++ b/server/AyaNova/biz/WidgetBiz.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using EnumsNET; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; @@ -8,10 +7,8 @@ using AyaNova.Models; namespace AyaNova.Biz { - internal class WidgetBiz : BizObject, IJobObject, ISearchAbleObject { - internal WidgetBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -23,15 +20,12 @@ namespace AyaNova.Biz internal static WidgetBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new WidgetBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items)); - else//when called internally for internal ops there will be no context so need to set default values for that + else return new WidgetBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -39,40 +33,21 @@ namespace AyaNova.Biz return await ct.Widget.AnyAsync(e => e.Id == id); } - //////////////////////////////////////////////////////////////////////////////////////////////// - /// GET - /// - /// - - internal async Task GetAsync(long fetchId, bool logTheGetEvent = true) - { - //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.Widget.SingleOrDefaultAsync(m => m.Id == fetchId); - if (logTheGetEvent && ret != null) - { - //Log - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); - } - return ret; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE - - //Called from route and also seeder + // internal async Task CreateAsync(Widget newObject) { await ValidateAsync(newObject, null); if (HasErrors) return null; else - { + { newObject.Serial = ServerBootConfig.WIDGET_SERIAL.GetNext(); newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); - newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.Widget.AddAsync(newObject); - await ct.SaveChangesAsync(); + await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); await SearchIndexAsync(newObject, true); await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); @@ -80,84 +55,215 @@ namespace AyaNova.Biz } } + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //DUPLICATE + // // + // internal async Task DuplicateAsync(Widget dbObj) + // { + + // Widget outObj = new Widget(); + // CopyObject.Copy(dbObj, outObj, "Wiki"); + // // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); + // //generate unique name + // string newUniqueName = string.Empty; + // bool NotUnique = true; + // long l = 1; + // do + // { + // newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + // NotUnique = await ct.Widget.AnyAsync(m => m.Name == newUniqueName); + // } while (NotUnique); + + // outObj.Name = newUniqueName; + // outObj.Id = 0; + // outObj.Concurrency = 0; + + // //Test get serial id visible id number from generator + // outObj.Serial = ServerBootConfig.WIDGET_SERIAL.GetNext(); + + // await ct.Widget.AddAsync(outObj); + // await ct.SaveChangesAsync(); + + // //Handle child and associated items: + // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); + // await SearchIndexAsync(outObj, true); + // await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); + // return outObj; + + // } //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(Widget dbObj) + internal async Task DuplicateAsync(long id) { - - Widget outObj = new Widget(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + Widget dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + Widget newObject = new Widget(); + CopyObject.Copy(dbObject, newObject, "Wiki"); string newUniqueName = string.Empty; bool NotUnique = true; long l = 1; do { - newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObj.Name, l++, 255); + newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObject.Name, l++, 255); NotUnique = await ct.Widget.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - //Test get serial id visible id number from generator - outObj.Serial = ServerBootConfig.WIDGET_SERIAL.GetNext(); - - await ct.Widget.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Serial = ServerBootConfig.WIDGET_SERIAL.GetNext(); + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.Widget.AddAsync(newObject); await ct.SaveChangesAsync(); - - //Handle child and associated items: - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); - await SearchIndexAsync(outObj, true); - await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, outObj.Tags, null); - return outObj; - + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); + await SearchIndexAsync(newObject, true); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null); + return newObject; } + //////////////////////////////////////////////////////////////////////////////////////////////// + // GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Widget.SingleOrDefaultAsync(m => m.Id == id); + if (logTheGetEvent && ret != null) + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct); + return ret; + } + + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //UPDATE + // // + + // //put + // internal async Task PutAsync(Widget dbObj, Widget inObj) + // { + + // //make a snapshot of the original for validation but update the original to preserve workflow + // Widget SnapshotOfOriginalDBObj = new Widget(); + // CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); + + // //Replace the db object with the PUT object + // CopyObject.Copy(inObj, dbObj, "Id,Serial"); + + // dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); + // dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); + + // //Set "original" value of concurrency token to input token + // //this will allow EF to check it out + // ct.Entry(dbObj).OriginalValues["Concurrency"] = inObj.Concurrency; + + + // await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); + // if (HasErrors) + // return false; + + // //Log event and save context + // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); + // await SearchIndexAsync(dbObj, false); + // await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); + + // return true; + // } + //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // - - //put - internal async Task PutAsync(Widget dbObj, Widget inObj) + internal async Task PutAsync(Widget putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + Widget dbObject = await ct.Widget.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } Widget SnapshotOfOriginalDBObj = new Widget(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id,Serial"); - - dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); - dbObj.CustomFields = JsonUtil.CompactJson(dbObj.CustomFields); - - //Set "original" value of concurrency token to input token - //this will allow EF to check it out - ct.Entry(dbObj).OriginalValues["Concurrency"] = inObj.Concurrency; - - - await ValidateAsync(dbObj, SnapshotOfOriginalDBObj); - if (HasErrors) - return false; - - //Log event and save context - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); - await SearchIndexAsync(dbObj, false); - await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags); - - return true; + CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj); + CopyObject.Copy(putObject, dbObject, "Id,Serial"); + dbObject.Tags = TagUtil.NormalizeTags(dbObject.Tags); + dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields); + ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency; + await ValidateAsync(dbObject, SnapshotOfOriginalDBObj); + if (HasErrors) return null; + try + { + await ct.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await ExistsAsync(putObject.Id)) + AddError(ApiErrorCode.NOT_FOUND); + else + AddError(ApiErrorCode.CONCURRENCY_CONFLICT); + return null; + } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); + await SearchIndexAsync(dbObject, false); + await TagUtil.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags); + return dbObject; } + // //////////////////////////////////////////////////////////////////////////////////////////////// + // //DELETE + // // + // internal async Task DeleteAsync(Widget dbObj) + // { + // //Determine if the object can be deleted, do the deletion tentatively + // //Probably also in here deal with tags and associated search text etc + + // //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); + // if (HasErrors) + // return false; + // ct.Widget.Remove(dbObj); + // await ct.SaveChangesAsync(); + + // //Log event + // await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); + // await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); + // await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); + // return true; + // } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + Widget dbObject = await ct.Widget.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.Widget.Remove(dbObject); + await ct.SaveChangesAsync(); + + //Log event + await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); + await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType); + await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags); + //all good do the commit + await transaction.CommitAsync(); + } + catch + { + //Just re-throw for now, let exception handler deal, but in future may want to deal with this more here + throw; + } + return true; + } + } private async Task SearchIndexAsync(Widget obj, bool isNew) { @@ -181,26 +287,6 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(Widget dbObj) - { - //Determine if the object can be deleted, do the deletion tentatively - //Probably also in here deal with tags and associated search text etc - - //NOT REQUIRED NOW BUT IF IN FUTURE ValidateCanDelete(dbObj); - if (HasErrors) - return false; - ct.Widget.Remove(dbObj); - await ct.SaveChangesAsync(); - - //Log event - await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObj.Id, dbObj.Name, ct); - await Search.ProcessDeletedObjectKeywordsAsync(dbObj.Id, BizType); - await TagUtil.ProcessDeleteTagsInRepositoryAsync(ct, dbObj.Tags); - return true; - }