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: 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.
- 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
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>();
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
//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
/*
ONE SHOT WAY WHICH IS BOSS!!
@@ -63,27 +87,27 @@ WHERE name = 'bill'
*/
//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
//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
//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.
//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.
//Create table tags, word varchar 255, refcount longint, concurrency token
//so do this:
//Create table tags, word varchar 255, refcount longint, concurrency token
//so do this:
//ADD / INCREMENT TAGS
//ADD / INCREMENT TAGS
//START: Get tag word and concurrency token and count
//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:
//UPDATE: INCREMENT the refcount and update the record
//If that fails due to a concurrency exception go to START:
//START: Get tag word and concurrency token and count
//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:
//UPDATE: INCREMENT the refcount and update the record
//If that fails due to a concurrency exception go to START:
//REMOVE / DECREMENT TAGS
//REMOVE / DECREMENT TAGS
//Iterate remove tags
@@ -92,7 +116,8 @@ WHERE name = 'bill'
//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
internal async Task<Widget> CreateAsync(Widget inObj)
{
Validate(inObj, true);
Validate(inObj, null);
if (HasErrors)
return null;
else
@@ -98,7 +98,7 @@ namespace AyaNova.Biz
//Handle child and associated items:
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);
SearchIndex(outObj, true);
TagUtil.ProcessUpdateTagsInRepository(ct, outObj.Tags, null);
return outObj;
}
}
@@ -107,7 +107,7 @@ namespace AyaNova.Biz
//Internal version for seeding
internal Widget Create(AyContext TempContext, Widget inObj)
{
Validate(inObj, true);
Validate(inObj, null);
if (HasErrors)
return null;
else
@@ -125,6 +125,7 @@ namespace AyaNova.Biz
//Handle child and associated items:
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext);
SearchIndex(outObj, true);
TagUtil.ProcessUpdateTagsInRepository(TempContext, outObj.Tags, null);
return outObj;
}
@@ -143,6 +144,10 @@ namespace AyaNova.Biz
if (inObj.OwnerId == 0)
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
CopyObject.Copy(inObj, dbObj, "Id,Serial");
@@ -153,13 +158,14 @@ namespace AyaNova.Biz
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken;
Validate(dbObj, false);
Validate(dbObj, SnapshotOfOriginalDBObj);
if (HasErrors)
return false;
//Associated items
EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
SearchIndex(dbObj, false);
TagUtil.ProcessUpdateTagsInRepository(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags);
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
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
objectPatch.ApplyTo(dbObj);
dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags);
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken;
Validate(dbObj, false);
Validate(dbObj, SnapshotOfOriginalDBObj);
if (HasErrors)
return false;
@@ -185,6 +195,8 @@ namespace AyaNova.Biz
EventLogProcessor.LogEventToDatabase(new Event(UserId, dbObj.Id, BizType, AyaEvent.Modified), ct);
SearchIndex(dbObj, false);
TagUtil.ProcessUpdateTagsInRepository(ct, dbObj.Tags, SnapshotOfOriginalDBObj.Tags);
return true;
}
@@ -219,7 +231,7 @@ namespace AyaNova.Biz
EventLogProcessor.DeleteObject(UserId, BizType, dbObj.Id, dbObj.Name, ct);
ct.SaveChanges();
Search.ProcessDeletedObjectKeywords(dbObj.Id, BizType);
TagUtil.ProcessDeleteTagsInRepository(ct, dbObj.Tags);
return true;
}
@@ -327,60 +339,62 @@ namespace AyaNova.Biz
//
//Can save or update?
private void Validate(Widget inObj, bool isNew)
private void Validate(Widget proposedObj, Widget currentObj)
{
//run validation and biz rules
if (isNew)
{
//WARNING: this is not really the "current" object, it's been modified already by caller
bool isNew = currentObj == null;
// //NEW widgets must be active
// if (inObj.Active == null || ((bool)inObj.Active) == false)
// {
// AddError(ValidationErrorType.InvalidValue, "Active", "New widget must be active");
// }
}
// if (isNew)
// {
// //WARNING: this is not really the "current" object, it's been modified already by caller
// // //NEW widgets must be active
// // if (inObj.Active == null || ((bool)inObj.Active) == false)
// // {
// // AddError(ValidationErrorType.InvalidValue, "Active", "New widget must be active");
// // }
// }
//OwnerId required
if (!isNew)
{
if (inObj.OwnerId == 0)
if (proposedObj.OwnerId == 0)
AddError(ValidationErrorType.RequiredPropertyEmpty, "OwnerId");
}
//Name required
if (string.IsNullOrWhiteSpace(inObj.Name))
if (string.IsNullOrWhiteSpace(proposedObj.Name))
AddError(ValidationErrorType.RequiredPropertyEmpty, "Name");
//Name must be less than 255 characters
if (inObj.Name.Length > 255)
if (proposedObj.Name.Length > 255)
AddError(ValidationErrorType.LengthExceeded, "Name", "255 max");
//If name is otherwise OK, check that name is unique
if (!PropertyHasErrors("Name"))
{
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
if (ct.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");
}
}
//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");
if (inObj.StartDate != null && inObj.EndDate == null)
if (proposedObj.StartDate != null && proposedObj.EndDate == null)
AddError(ValidationErrorType.RequiredPropertyEmpty, "EndDate");
//Start date before end date
if (inObj.StartDate != null && inObj.EndDate != null)
if (inObj.StartDate > inObj.EndDate)
if (proposedObj.StartDate != null && proposedObj.EndDate != null)
if (proposedObj.StartDate > proposedObj.EndDate)
AddError(ValidationErrorType.StartDateMustComeBeforeEndDate, "StartDate");
//Enum is valid value
if (!inObj.Roles.IsValid())
if (!proposedObj.Roles.IsValid())
{
AddError(ValidationErrorType.InvalidValue, "Roles");
}