This commit is contained in:
2018-12-19 00:36:06 +00:00
parent 65253b35fd
commit 4f09dcb6bd
3 changed files with 82 additions and 42 deletions

View File

@@ -89,6 +89,7 @@ TODO SERVER STUFF
- TAG ROUTE: LIST route to fetch tags in Alphabetical order by name based on starts with (or maybe contains) used for quickly autofilling a list at UI level - TAG ROUTE: LIST route to fetch tags in Alphabetical order by name based on starts with (or maybe contains) used for quickly autofilling a list at UI level
- TAG ROUTE: CLOUD route to fetch tags in order of refcount decreasing - TAG ROUTE: CLOUD route to fetch tags in order of refcount decreasing
- TAG repo tests: ensure updates delete and add properly, ensure new adds properly, ensure lists work properly
- Boot server and seed with debug log turned on, see what is being tracked by EF that doesn't need to, seems some of that shit is being tracked. - Boot server and seed with debug log turned on, see what is being tracked by EF that doesn't need to, seems some of that shit is being tracked.
- Docs: pagingOptions, sort and filter need to be documented for API - Docs: pagingOptions, sort and filter need to be documented for API

View File

@@ -12,7 +12,7 @@ namespace AyaNova.Biz
//remove dupes, substitute dashes for spaces, lowercase and shorten if exceed 255 chars //remove dupes, substitute dashes for spaces, lowercase and shorten if exceed 255 chars
public static List<string> NormalizeTags(List<string> inTags) public static List<string> NormalizeTags(List<string> inTags)
{ {
if (inTags==null || inTags.Count == 0) return inTags; if (inTags == null || inTags.Count == 0) return inTags;
List<string> outTags = new List<string>(); List<string> outTags = new List<string>();
foreach (var tag in inTags) foreach (var tag in inTags)
@@ -40,11 +40,35 @@ namespace AyaNova.Biz
} }
public static void ProcessTagsIntoRepository(AyContext ct, List<string> addTags, List<string> removeTags)
public static void ProcessDeleteTagsInRepository(AyContext ct, List<string> deleteTags)
{ {
if (deleteTags.Count == 0) return;
}
public static void ProcessUpdateTagsInRepository(AyContext ct, List<string> newTags, List<string> originalTags = null)
{
if (newTags.Count == 0 && (originalTags == null || originalTags.Count == 0)) return;
List<string> deleteTags=new List<string>();
List<string> addTags=new List<string>();
if(originalTags!=null){
//Update
//This logic is supposed to only come up with CHANGES, if the item is in both lists then it should disappear and not need to be dealt with
//testing will validate it
deleteTags=originalTags.Except(newTags).ToList();
addTags=newTags.Except(originalTags).ToList();
HERE
}else{
//Add
}
//Add / increase reference count for added tags //Add / increase reference count for added tags
//remove / decrease reference count for removed tags //remove / decrease reference count for removed tags
// var v=ct.Event.Any(x=>x.Textra=="word"); // var v=ct.Event.Any(x=>x.Textra=="word");
//https://stackoverflow.com/questions/10233298/increment-a-value-in-postgres //https://stackoverflow.com/questions/10233298/increment-a-value-in-postgres
/* /*
ONE SHOT WAY WHICH IS BOSS!! ONE SHOT WAY WHICH IS BOSS!!
@@ -63,27 +87,27 @@ WHERE name = 'bill'
*/ */
//WAY I PROBABLY SHOULD USE AND GROK for inventory later: //WAY I PROBABLY SHOULD USE AND GROK for inventory later:
//https://stackoverflow.com/questions/14718929/best-practice-to-lock-a-record-for-editing-while-using-entity-framework //https://stackoverflow.com/questions/14718929/best-practice-to-lock-a-record-for-editing-while-using-entity-framework
//Catch the concurrency exception, refetch and try again a certain number of times maximum until it's resolved //Catch the concurrency exception, refetch and try again a certain number of times maximum until it's resolved
//maybe wrap that in a method I can re-use. //maybe wrap that in a method I can re-use.
//Create table tags, word varchar 255, refcount longint, concurrency token //Create table tags, word varchar 255, refcount longint, concurrency token
//so do this: //so do this:
//ADD / INCREMENT TAGS //ADD / INCREMENT TAGS
//START: Get tag word and concurrency token and count //START: Get tag word and concurrency token and count
//if not present, then add it with a count of 0 //if not present, then add it with a count of 0
//If that fails due to others added it or works, either way go back to START: //If that fails due to others added it or works, either way go back to START:
//UPDATE: INCREMENT the refcount and update the record //UPDATE: INCREMENT the refcount and update the record
//If that fails due to a concurrency exception go to START: //If that fails due to a concurrency exception go to START:
//REMOVE / DECREMENT TAGS //REMOVE / DECREMENT TAGS
//Iterate remove tags //Iterate remove tags
@@ -92,7 +116,8 @@ WHERE name = 'bill'
//Fetch the tag if it exists and update it's ref count //Fetch the tag if it exists and update it's ref count
} }

View File

@@ -78,7 +78,7 @@ namespace AyaNova.Biz
//route linked version for external api access //route linked version for external api access
internal async Task<Widget> CreateAsync(Widget inObj) internal async Task<Widget> CreateAsync(Widget inObj)
{ {
Validate(inObj, true); Validate(inObj, null);
if (HasErrors) if (HasErrors)
return null; return null;
else else
@@ -98,7 +98,7 @@ namespace AyaNova.Biz
//Handle child and associated items: //Handle child and associated items:
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);
SearchIndex(outObj, true); SearchIndex(outObj, true);
TagUtil.ProcessUpdateTagsInRepository(ct, outObj.Tags, null);
return outObj; return outObj;
} }
} }
@@ -107,7 +107,7 @@ namespace AyaNova.Biz
//Internal version for seeding //Internal version for seeding
internal Widget Create(AyContext TempContext, Widget inObj) internal Widget Create(AyContext TempContext, Widget inObj)
{ {
Validate(inObj, true); Validate(inObj, null);
if (HasErrors) if (HasErrors)
return null; return null;
else else
@@ -125,6 +125,7 @@ namespace AyaNova.Biz
//Handle child and associated items: //Handle child and associated items:
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext); EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext);
SearchIndex(outObj, true); SearchIndex(outObj, true);
TagUtil.ProcessUpdateTagsInRepository(TempContext, outObj.Tags, null);
return outObj; return outObj;
} }
@@ -143,6 +144,10 @@ namespace AyaNova.Biz
if (inObj.OwnerId == 0) if (inObj.OwnerId == 0)
inObj.OwnerId = dbObj.OwnerId; inObj.OwnerId = dbObj.OwnerId;
//make a snapshot of the original for validation but update the original to preserve workflow
Widget SnapshotOfOriginalDBObj = new Widget();
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
//Replace the db object with the PUT object //Replace the db object with the PUT object
CopyObject.Copy(inObj, dbObj, "Id,Serial"); CopyObject.Copy(inObj, dbObj, "Id,Serial");
@@ -153,13 +158,14 @@ namespace AyaNova.Biz
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken; ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken;
Validate(dbObj, false); Validate(dbObj, SnapshotOfOriginalDBObj);
if (HasErrors) if (HasErrors)
return false; return false;
//Associated items //Associated items
EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
SearchIndex(dbObj, false); SearchIndex(dbObj, false);
TagUtil.ProcessUpdateTagsInRepository(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags);
return true; return true;
} }
@@ -171,13 +177,17 @@ namespace AyaNova.Biz
//Note: Id, OwnerId and Serial are all checked for and disallowed in the validate code by default //Note: Id, OwnerId and Serial are all checked for and disallowed in the validate code by default
if (!ValidateJsonPatch<Widget>.Validate(this, objectPatch)) return false; if (!ValidateJsonPatch<Widget>.Validate(this, objectPatch)) return false;
//make a snapshot of the original for validation but update the original to preserve workflow
Widget SnapshotOfOriginalDBObj = new Widget();
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
//Do the patching //Do the patching
objectPatch.ApplyTo(dbObj); objectPatch.ApplyTo(dbObj);
dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags); dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags);
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken; ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken;
Validate(dbObj, false); Validate(dbObj, SnapshotOfOriginalDBObj);
if (HasErrors) if (HasErrors)
return false; return false;
@@ -185,6 +195,8 @@ namespace AyaNova.Biz
EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct); EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
SearchIndex(dbObj, false); SearchIndex(dbObj, false);
TagUtil.ProcessUpdateTagsInRepository(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags);
return true; return true;
} }
@@ -219,7 +231,7 @@ namespace AyaNova.Biz
EventLogProcessor.DeleteObject(UserId, BizType, dbObj.Id, dbObj.Name, ct); EventLogProcessor.DeleteObject(UserId, BizType, dbObj.Id, dbObj.Name, ct);
ct.SaveChanges(); ct.SaveChanges();
Search.ProcessDeletedObjectKeywords(dbObj.Id, BizType); Search.ProcessDeletedObjectKeywords(dbObj.Id, BizType);
TagUtil.ProcessDeleteTagsInRepository(ct, dbObj.Tags);
return true; return true;
} }
@@ -327,60 +339,62 @@ namespace AyaNova.Biz
// //
//Can save or update? //Can save or update?
private void Validate(Widget inObj, bool isNew) private void Validate(Widget proposedObj, Widget currentObj)
{ {
//run validation and biz rules //run validation and biz rules
if (isNew) bool isNew = currentObj == null;
{
//WARNING: this is not really the "current" object, it's been modified already by caller
// //NEW widgets must be active // if (isNew)
// if (inObj.Active == null || ((bool)inObj.Active) == false) // {
// { // //WARNING: this is not really the "current" object, it's been modified already by caller
// AddError(ValidationErrorType.InvalidValue, "Active", "New widget must be active");
// } // // //NEW widgets must be active
} // // if (inObj.Active == null || ((bool)inObj.Active) == false)
// // {
// // AddError(ValidationErrorType.InvalidValue, "Active", "New widget must be active");
// // }
// }
//OwnerId required //OwnerId required
if (!isNew) if (!isNew)
{ {
if (inObj.OwnerId == 0) if (proposedObj.OwnerId == 0)
AddError(ValidationErrorType.RequiredPropertyEmpty, "OwnerId"); AddError(ValidationErrorType.RequiredPropertyEmpty, "OwnerId");
} }
//Name required //Name required
if (string.IsNullOrWhiteSpace(inObj.Name)) if (string.IsNullOrWhiteSpace(proposedObj.Name))
AddError(ValidationErrorType.RequiredPropertyEmpty, "Name"); AddError(ValidationErrorType.RequiredPropertyEmpty, "Name");
//Name must be less than 255 characters //Name must be less than 255 characters
if (inObj.Name.Length > 255) if (proposedObj.Name.Length > 255)
AddError(ValidationErrorType.LengthExceeded, "Name", "255 max"); AddError(ValidationErrorType.LengthExceeded, "Name", "255 max");
//If name is otherwise OK, check that name is unique //If name is otherwise OK, check that name is unique
if (!PropertyHasErrors("Name")) if (!PropertyHasErrors("Name"))
{ {
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
if (ct.Widget.Any(m => m.Name == inObj.Name && m.Id != inObj.Id)) if (ct.Widget.Any(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id))
{ {
AddError(ValidationErrorType.NotUnique, "Name"); AddError(ValidationErrorType.NotUnique, "Name");
} }
} }
//Start date AND end date must both be null or both contain values //Start date AND end date must both be null or both contain values
if (inObj.StartDate == null && inObj.EndDate != null) if (proposedObj.StartDate == null && proposedObj.EndDate != null)
AddError(ValidationErrorType.RequiredPropertyEmpty, "StartDate"); AddError(ValidationErrorType.RequiredPropertyEmpty, "StartDate");
if (inObj.StartDate != null && inObj.EndDate == null) if (proposedObj.StartDate != null && proposedObj.EndDate == null)
AddError(ValidationErrorType.RequiredPropertyEmpty, "EndDate"); AddError(ValidationErrorType.RequiredPropertyEmpty, "EndDate");
//Start date before end date //Start date before end date
if (inObj.StartDate != null && inObj.EndDate != null) if (proposedObj.StartDate != null && proposedObj.EndDate != null)
if (inObj.StartDate > inObj.EndDate) if (proposedObj.StartDate > proposedObj.EndDate)
AddError(ValidationErrorType.StartDateMustComeBeforeEndDate, "StartDate"); AddError(ValidationErrorType.StartDateMustComeBeforeEndDate, "StartDate");
//Enum is valid value //Enum is valid value
if (!inObj.Roles.IsValid()) if (!proposedObj.Roles.IsValid())
{ {
AddError(ValidationErrorType.InvalidValue, "Roles"); AddError(ValidationErrorType.InvalidValue, "Roles");
} }