From b5611c4c40008ba9b570d3df3b864cabb05a8559 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Wed, 13 May 2020 22:12:58 +0000 Subject: [PATCH] --- server/AyaNova/biz/ContractBiz.cs | 226 +++++++++++------------ server/AyaNova/biz/HeadOfficeBiz.cs | 240 ++++++++++++------------- server/AyaNova/biz/LoanUnitBiz.cs | 238 ++++++++++++------------ server/AyaNova/biz/PMTemplateBiz.cs | 238 ++++++++++++------------ server/AyaNova/biz/PartBiz.cs | 239 ++++++++++++------------ server/AyaNova/biz/ProjectBiz.cs | 240 ++++++++++++------------- server/AyaNova/biz/PurchaseOrderBiz.cs | 240 ++++++++++++------------- server/AyaNova/biz/UnitBiz.cs | 239 ++++++++++++------------ server/AyaNova/biz/UnitModelBiz.cs | 240 ++++++++++++------------- server/AyaNova/biz/VendorBiz.cs | 240 ++++++++++++------------- 10 files changed, 1098 insertions(+), 1282 deletions(-) diff --git a/server/AyaNova/biz/ContractBiz.cs b/server/AyaNova/biz/ContractBiz.cs index 152ab12f..42a3e4e0 100644 --- a/server/AyaNova/biz/ContractBiz.cs +++ b/server/AyaNova/biz/ContractBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class ContractBiz : BizObject, ISearchAbleObject { - internal ContractBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static ContractBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new ContractBiz(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 ContractBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,12 +32,9 @@ namespace AyaNova.Biz return await ct.Contract.AnyAsync(e => e.Id == id); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //CREATE - - //Called from route and also seeder + // internal async Task CreateAsync(Contract newObject) { await ValidateAsync(newObject, null); @@ -51,122 +42,138 @@ namespace AyaNova.Biz return null; else { - //do stuff with Contract - Contract outObj = newObject; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.Contract.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Contract.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(Contract dbObj) + internal async Task DuplicateAsync(long id) { - - Contract outObj = new Contract(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + Contract dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + Contract newObject = new Contract(); + 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.Contract.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.Contract.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.Contract.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 fetchId, bool logTheGetEvent = true) + //GET + // + internal async Task GetAsync(long id, 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.Contract.SingleOrDefaultAsync(m => m.Id == fetchId); + var ret = await ct.Contract.SingleOrDefaultAsync(m => m.Id == id); if (logTheGetEvent && ret != null) - { - //Log - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); - } + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct); return ret; } - //////////////////////////////////////////////////////////////////////////////////////////////// //UPDATE // - - //put - internal async Task PutAsync(Contract dbObj, Contract inObj) + internal async Task PutAsync(Contract putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + Contract dbObject = await ct.Contract.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } Contract SnapshotOfOriginalDBObj = new Contract(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + Contract dbObject = await ct.Contract.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.Contract.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(Contract obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -182,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(Contract 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.Contract.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(Contract proposedObj, Contract currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -250,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(Contract inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(Contract inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/HeadOfficeBiz.cs b/server/AyaNova/biz/HeadOfficeBiz.cs index fb5d9e26..5f712497 100644 --- a/server/AyaNova/biz/HeadOfficeBiz.cs +++ b/server/AyaNova/biz/HeadOfficeBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class HeadOfficeBiz : BizObject, ISearchAbleObject { - internal HeadOfficeBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static HeadOfficeBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new HeadOfficeBiz(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 HeadOfficeBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,133 +32,148 @@ namespace AyaNova.Biz return await ct.HeadOffice.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.HeadOffice.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(HeadOffice inObj) + // + internal async Task CreateAsync(HeadOffice newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with HeadOffice - HeadOffice outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.HeadOffice.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.HeadOffice.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(HeadOffice dbObj) + internal async Task DuplicateAsync(long id) { - - HeadOffice outObj = new HeadOffice(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + HeadOffice dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + HeadOffice newObject = new HeadOffice(); + 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.HeadOffice.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.HeadOffice.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.HeadOffice.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.HeadOffice.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(HeadOffice dbObj, HeadOffice inObj) + internal async Task PutAsync(HeadOffice putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + HeadOffice dbObject = await ct.HeadOffice.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } HeadOffice SnapshotOfOriginalDBObj = new HeadOffice(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + HeadOffice dbObject = await ct.HeadOffice.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.HeadOffice.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(HeadOffice obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -180,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(HeadOffice 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.HeadOffice.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(HeadOffice proposedObj, HeadOffice currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -248,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(HeadOffice inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(HeadOffice inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/LoanUnitBiz.cs b/server/AyaNova/biz/LoanUnitBiz.cs index c9439630..44daa5d8 100644 --- a/server/AyaNova/biz/LoanUnitBiz.cs +++ b/server/AyaNova/biz/LoanUnitBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class LoanUnitBiz : BizObject, ISearchAbleObject { - internal LoanUnitBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static LoanUnitBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new LoanUnitBiz(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 LoanUnitBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,133 +32,148 @@ namespace AyaNova.Biz return await ct.LoanUnit.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.LoanUnit.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(LoanUnit inObj) + // + internal async Task CreateAsync(LoanUnit newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with LoanUnit - LoanUnit outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.LoanUnit.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.LoanUnit.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(LoanUnit dbObj) + internal async Task DuplicateAsync(long id) { - - LoanUnit outObj = new LoanUnit(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + LoanUnit dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + LoanUnit newObject = new LoanUnit(); + 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.LoanUnit.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.LoanUnit.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.LoanUnit.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.LoanUnit.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(LoanUnit dbObj, LoanUnit inObj) + internal async Task PutAsync(LoanUnit putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + LoanUnit dbObject = await ct.LoanUnit.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } LoanUnit SnapshotOfOriginalDBObj = new LoanUnit(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + LoanUnit dbObject = await ct.LoanUnit.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.LoanUnit.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(LoanUnit obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -180,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(LoanUnit 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.LoanUnit.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(LoanUnit proposedObj, LoanUnit currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -248,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(LoanUnit inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(LoanUnit inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/PMTemplateBiz.cs b/server/AyaNova/biz/PMTemplateBiz.cs index 24ee005e..7f836187 100644 --- a/server/AyaNova/biz/PMTemplateBiz.cs +++ b/server/AyaNova/biz/PMTemplateBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class PMTemplateBiz : BizObject, ISearchAbleObject { - internal PMTemplateBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static PMTemplateBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new PMTemplateBiz(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 PMTemplateBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,133 +32,148 @@ namespace AyaNova.Biz return await ct.PMTemplate.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.PMTemplate.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(PMTemplate inObj) + // + internal async Task CreateAsync(PMTemplate newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with PMTemplate - PMTemplate outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.PMTemplate.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.PMTemplate.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(PMTemplate dbObj) + internal async Task DuplicateAsync(long id) { - - PMTemplate outObj = new PMTemplate(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + PMTemplate dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + PMTemplate newObject = new PMTemplate(); + 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.PMTemplate.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.PMTemplate.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.PMTemplate.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.PMTemplate.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(PMTemplate dbObj, PMTemplate inObj) + internal async Task PutAsync(PMTemplate putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + PMTemplate dbObject = await ct.PMTemplate.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } PMTemplate SnapshotOfOriginalDBObj = new PMTemplate(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + PMTemplate dbObject = await ct.PMTemplate.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.PMTemplate.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(PMTemplate obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -180,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(PMTemplate 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.PMTemplate.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(PMTemplate proposedObj, PMTemplate currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -248,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(PMTemplate inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(PMTemplate inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/PartBiz.cs b/server/AyaNova/biz/PartBiz.cs index f6d8b0c9..2c0f18ac 100644 --- a/server/AyaNova/biz/PartBiz.cs +++ b/server/AyaNova/biz/PartBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class PartBiz : BizObject, ISearchAbleObject { - internal PartBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static PartBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new PartBiz(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 PartBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,134 +32,148 @@ namespace AyaNova.Biz return await ct.Part.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.Part.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(Part inObj) + // + internal async Task CreateAsync(Part newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with Part - Part outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.Part.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Part.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(Part dbObj) + internal async Task DuplicateAsync(long id) { - - Part outObj = new Part(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + Part dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + Part newObject = new Part(); + 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.Part.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.Part.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.Part.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Part.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(Part dbObj, Part inObj) + internal async Task PutAsync(Part putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + Part dbObject = await ct.Part.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } Part SnapshotOfOriginalDBObj = new Part(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + Part dbObject = await ct.Part.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.Part.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(Part obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -181,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(Part 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.Part.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(Part proposedObj, Part currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -249,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(Part inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(Part inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/ProjectBiz.cs b/server/AyaNova/biz/ProjectBiz.cs index 500812ac..12878b9c 100644 --- a/server/AyaNova/biz/ProjectBiz.cs +++ b/server/AyaNova/biz/ProjectBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class ProjectBiz : BizObject, ISearchAbleObject { - internal ProjectBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static ProjectBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new ProjectBiz(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 ProjectBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,133 +32,148 @@ namespace AyaNova.Biz return await ct.Project.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.Project.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(Project inObj) + // + internal async Task CreateAsync(Project newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with Project - Project outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.Project.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Project.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(Project dbObj) + internal async Task DuplicateAsync(long id) { - - Project outObj = new Project(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + Project dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + Project newObject = new Project(); + 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.Project.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.Project.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.Project.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Project.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(Project dbObj, Project inObj) + internal async Task PutAsync(Project putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + Project dbObject = await ct.Project.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } Project SnapshotOfOriginalDBObj = new Project(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + Project dbObject = await ct.Project.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.Project.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(Project obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -180,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(Project 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.Project.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(Project proposedObj, Project currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -248,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(Project inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(Project inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/PurchaseOrderBiz.cs b/server/AyaNova/biz/PurchaseOrderBiz.cs index aed973f5..0f0461b0 100644 --- a/server/AyaNova/biz/PurchaseOrderBiz.cs +++ b/server/AyaNova/biz/PurchaseOrderBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class PurchaseOrderBiz : BizObject, ISearchAbleObject { - internal PurchaseOrderBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static PurchaseOrderBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new PurchaseOrderBiz(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 PurchaseOrderBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,133 +32,148 @@ namespace AyaNova.Biz return await ct.PurchaseOrder.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.PurchaseOrder.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(PurchaseOrder inObj) + // + internal async Task CreateAsync(PurchaseOrder newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with PurchaseOrder - PurchaseOrder outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.PurchaseOrder.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.PurchaseOrder.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(PurchaseOrder dbObj) + internal async Task DuplicateAsync(long id) { - - PurchaseOrder outObj = new PurchaseOrder(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + PurchaseOrder dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + PurchaseOrder newObject = new PurchaseOrder(); + 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.PurchaseOrder.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.PurchaseOrder.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.PurchaseOrder.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.PurchaseOrder.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(PurchaseOrder dbObj, PurchaseOrder inObj) + internal async Task PutAsync(PurchaseOrder putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + PurchaseOrder dbObject = await ct.PurchaseOrder.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } PurchaseOrder SnapshotOfOriginalDBObj = new PurchaseOrder(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + PurchaseOrder dbObject = await ct.PurchaseOrder.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.PurchaseOrder.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(PurchaseOrder obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -180,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(PurchaseOrder 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.PurchaseOrder.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(PurchaseOrder proposedObj, PurchaseOrder currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -248,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(PurchaseOrder inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(PurchaseOrder inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/UnitBiz.cs b/server/AyaNova/biz/UnitBiz.cs index 7763fd62..01464a39 100644 --- a/server/AyaNova/biz/UnitBiz.cs +++ b/server/AyaNova/biz/UnitBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class UnitBiz : BizObject, ISearchAbleObject { - internal UnitBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static UnitBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new UnitBiz(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 UnitBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,134 +32,148 @@ namespace AyaNova.Biz return await ct.Unit.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.Unit.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(Unit inObj) + // + internal async Task CreateAsync(Unit newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with Unit - Unit outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.Unit.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Unit.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(Unit dbObj) + internal async Task DuplicateAsync(long id) { - - Unit outObj = new Unit(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + Unit dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + Unit newObject = new Unit(); + 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.Unit.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.Unit.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.Unit.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Unit.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(Unit dbObj, Unit inObj) + internal async Task PutAsync(Unit putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + Unit dbObject = await ct.Unit.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } Unit SnapshotOfOriginalDBObj = new Unit(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + Unit dbObject = await ct.Unit.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.Unit.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(Unit obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -181,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(Unit 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.Unit.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(Unit proposedObj, Unit currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -249,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(Unit inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(Unit inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/UnitModelBiz.cs b/server/AyaNova/biz/UnitModelBiz.cs index c87b13fa..4cccbd8c 100644 --- a/server/AyaNova/biz/UnitModelBiz.cs +++ b/server/AyaNova/biz/UnitModelBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class UnitModelBiz : BizObject, ISearchAbleObject { - internal UnitModelBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static UnitModelBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new UnitModelBiz(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 UnitModelBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,133 +32,148 @@ namespace AyaNova.Biz return await ct.UnitModel.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.UnitModel.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(UnitModel inObj) + // + internal async Task CreateAsync(UnitModel newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with UnitModel - UnitModel outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.UnitModel.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.UnitModel.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(UnitModel dbObj) + internal async Task DuplicateAsync(long id) { - - UnitModel outObj = new UnitModel(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + UnitModel dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + UnitModel newObject = new UnitModel(); + 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.UnitModel.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.UnitModel.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.UnitModel.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.UnitModel.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(UnitModel dbObj, UnitModel inObj) + internal async Task PutAsync(UnitModel putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + UnitModel dbObject = await ct.UnitModel.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } UnitModel SnapshotOfOriginalDBObj = new UnitModel(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + UnitModel dbObject = await ct.UnitModel.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.UnitModel.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(UnitModel obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -180,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(UnitModel 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.UnitModel.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(UnitModel proposedObj, UnitModel currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -248,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(UnitModel inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(UnitModel inObj) + { + //whatever needs to be check to delete this object + } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/server/AyaNova/biz/VendorBiz.cs b/server/AyaNova/biz/VendorBiz.cs index 0a2ed67f..c99ab705 100644 --- a/server/AyaNova/biz/VendorBiz.cs +++ b/server/AyaNova/biz/VendorBiz.cs @@ -1,16 +1,13 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; - using AyaNova.Util; using AyaNova.Api.ControllerHelpers; using AyaNova.Models; namespace AyaNova.Biz { - internal class VendorBiz : BizObject, ISearchAbleObject { - internal VendorBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles) { ct = dbcontext; @@ -22,15 +19,12 @@ namespace AyaNova.Biz internal static VendorBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) { - if (httpContext != null) return new VendorBiz(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 VendorBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull); } - - //////////////////////////////////////////////////////////////////////////////////////////////// //EXISTS internal async Task ExistsAsync(long id) @@ -38,133 +32,148 @@ namespace AyaNova.Biz return await ct.Vendor.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.Vendor.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(Vendor inObj) + // + internal async Task CreateAsync(Vendor newObject) { - await ValidateAsync(inObj, null); + await ValidateAsync(newObject, null); if (HasErrors) return null; else { - //do stuff with Vendor - Vendor outObj = inObj; - - outObj.Tags = TagUtil.NormalizeTags(outObj.Tags); - outObj.CustomFields = JsonUtil.CompactJson(outObj.CustomFields); - //Save to db - await ct.Vendor.AddAsync(outObj); + newObject.Tags = TagUtil.NormalizeTags(newObject.Tags); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + await ct.Vendor.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; } } - - - //////////////////////////////////////////////////////////////////////////////////////////////// //DUPLICATE // - - internal async Task DuplicateAsync(Vendor dbObj) + internal async Task DuplicateAsync(long id) { - - Vendor outObj = new Vendor(); - CopyObject.Copy(dbObj, outObj, "Wiki"); - // outObj.Name = Util.StringUtil.NameUniquify(outObj.Name, 255); - //generate unique name + Vendor dbObject = await GetAsync(id, false); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } + Vendor newObject = new Vendor(); + 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.Vendor.AnyAsync(m => m.Name == newUniqueName); } while (NotUnique); - - outObj.Name = newUniqueName; - - - outObj.Id = 0; - outObj.Concurrency = 0; - - await ct.Vendor.AddAsync(outObj); + newObject.Name = newUniqueName; + newObject.Id = 0; + newObject.Concurrency = 0; + await ct.Vendor.AddAsync(newObject); 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); + return newObject; + } - //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; - + //////////////////////////////////////////////////////////////////////////////////////////////// + //GET + // + internal async Task GetAsync(long id, bool logTheGetEvent = true) + { + var ret = await ct.Vendor.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(Vendor dbObj, Vendor inObj) + internal async Task PutAsync(Vendor putObject) { - - //make a snapshot of the original for validation but update the original to preserve workflow + Vendor dbObject = await ct.Vendor.SingleOrDefaultAsync(m => m.Id == putObject.Id); + if (dbObject == null) + { + AddError(ApiErrorCode.NOT_FOUND, "id"); + return null; + } Vendor SnapshotOfOriginalDBObj = new Vendor(); - CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj); - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObj, "Id"); - - 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"); + 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(long id) + { + using (var transaction = await ct.Database.BeginTransactionAsync()) + { + try + { + Vendor dbObject = await ct.Vendor.SingleOrDefaultAsync(m => m.Id == id); + ValidateCanDelete(dbObject); + if (HasErrors) + return false; + if (HasErrors) + return false; + ct.Vendor.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; + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //SEARCH + // private async Task SearchIndexAsync(Vendor obj, bool isNew) { - //SEARCH INDEXING var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType); SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields); - if (isNew) await Search.ProcessNewObjectKeywordsAsync(SearchParams); else @@ -180,38 +189,14 @@ namespace AyaNova.Biz return SearchParams; } - //////////////////////////////////////////////////////////////////////////////////////////////// - //DELETE - // - internal async Task DeleteAsync(Vendor 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.Vendor.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; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // - //Can save or update? private async Task ValidateAsync(Vendor proposedObj, Vendor currentObj) { - //run validation and biz rules bool isNew = currentObj == null; //Name required @@ -248,13 +233,10 @@ namespace AyaNova.Biz } - - //Can delete? - // private void ValidateCanDelete(Vendor inObj) - // { - // //whatever needs to be check to delete this object - // } - + private void ValidateCanDelete(Vendor inObj) + { + //whatever needs to be check to delete this object + } ////////////////////////////////////////////////////////////////////////////////////////////////