This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user