From 97ad44eae1e01ee8c486e653cd77bd51159764c6 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Sat, 30 Jan 2021 00:53:56 +0000 Subject: [PATCH] --- .../AyaNova/Controllers/DataListController.cs | 16 +- server/AyaNova/DataList/DataListFactory.cs | 17 ++ server/AyaNova/DataList/DataListFetcher.cs | 19 +- .../AyaNova/biz/DataListSavedColumnViewBiz.cs | 215 ++++++------------ server/AyaNova/biz/DataListSavedFilterBiz.cs | 6 +- server/AyaNova/biz/ProjectBiz.cs | 4 +- .../models/DataListTableProcessingOptions.cs | 40 ++-- 7 files changed, 136 insertions(+), 181 deletions(-) diff --git a/server/AyaNova/Controllers/DataListController.cs b/server/AyaNova/Controllers/DataListController.cs index d82fe982..a1feedab 100644 --- a/server/AyaNova/Controllers/DataListController.cs +++ b/server/AyaNova/Controllers/DataListController.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using EnumsNET; namespace AyaNova.Api.Controllers { @@ -74,7 +75,20 @@ namespace AyaNova.Api.Controllers try { - DataListReturnData r = await DataListFetcher.GetResponseAsync(ct, tableRequest, UserRoles, log, UserId); + + DataListSavedColumnViewBiz biz = DataListSavedColumnViewBiz.GetBiz(ct, HttpContext); + var SavedView = await biz.GetAsync(biz.UserId, tableRequest.DataListKey, true); + var DataList = DataListFactory.GetAyaDataList(tableRequest.DataListKey); + if (DataList == null) + return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_FOUND, "DataListKey", $"DataList \"{tableRequest.DataListKey}\" specified does not exist")); + + //check rights + if (!UserRoles.HasAnyFlags(DataList.AllowedRoles)) + return StatusCode(403, new ApiNotAuthorizedResponse()); + + //hydrate the saved view and filter + DataListTableProcessingOptions dataListTableOptions = new DataListTableProcessingOptions(tableRequest, DataList, SavedView); + DataListReturnData r = await DataListFetcher.GetResponseAsync(ct, dataListTableOptions, DataList, UserRoles, log, UserId); return Ok(r); } catch (System.UnauthorizedAccessException) diff --git a/server/AyaNova/DataList/DataListFactory.cs b/server/AyaNova/DataList/DataListFactory.cs index 5f4b41a1..1a3f2402 100644 --- a/server/AyaNova/DataList/DataListFactory.cs +++ b/server/AyaNova/DataList/DataListFactory.cs @@ -35,6 +35,23 @@ namespace AyaNova.DataList return ret; } + //Verify listkey + internal static bool ListKeyIsValid(string listKey) + { + + System.Reflection.Assembly ass = System.Reflection.Assembly.GetEntryAssembly(); + + foreach (System.Reflection.TypeInfo ti in ass.DefinedTypes) + { + if (!ti.IsAbstract && ti.ImplementedInterfaces.Contains(typeof(IDataListProcessing))) + { + if (ti.Name == listKey) + return true; + } + } + return false; + } + }//eoc }//eons \ No newline at end of file diff --git a/server/AyaNova/DataList/DataListFetcher.cs b/server/AyaNova/DataList/DataListFetcher.cs index 2e6c56ee..2e749879 100644 --- a/server/AyaNova/DataList/DataListFetcher.cs +++ b/server/AyaNova/DataList/DataListFetcher.cs @@ -17,24 +17,22 @@ namespace AyaNova.DataList // Get the data list data requested // // - internal static async Task GetResponseAsync(AyContext ct, DataListTableRequest dataListTableRequest, AuthorizationRoles userRoles, ILogger log, long userId) + internal static async Task GetResponseAsync(AyContext ct, DataListTableProcessingOptions dataListTableOptions,IDataListProcessing DataList, AuthorizationRoles userRoles, ILogger log, long userId) { - var DataList = DataListFactory.GetAyaDataList(dataListTableRequest.DataListKey); + // var DataList = DataListFactory.GetAyaDataList(dataListTableRequest.DataListKey); //was the name not found as a list? - if (DataList == null) - throw new System.ArgumentOutOfRangeException($"DataList \"{dataListTableRequest.DataListKey}\" specified does not exist"); + // if (DataList == null) + // throw new System.ArgumentOutOfRangeException($"DataList \"{dataListTableRequest.DataListKey}\" specified does not exist"); - //check rights - if (!userRoles.HasAnyFlags(DataList.AllowedRoles)) - throw new System.UnauthorizedAccessException("User roles insufficient for this datalist"); + //turn the DataListTableRequest into a DataListTableProcesingOptions object here //hydrates filter and column selections etc - DataListTableProcessingOptions dataListTableOptions = new DataListTableProcessingOptions(dataListTableRequest, DataList, ct); + // = new DataListTableProcessingOptions(dataListTableRequest, DataList, ct); //#### TODO: below block into above method to clean it up and centralize it @@ -233,11 +231,8 @@ namespace AyaNova.DataList //BUILD THE COLUMNS RETURN PROPERTY JSON FRAGMENT Newtonsoft.Json.Linq.JArray ColumnsJSON = null; - - ColumnsJSON = DataList.GenerateReturnListColumns(dataListTableOptions.Columns);//<<<-----this next - + ColumnsJSON = DataList.GenerateReturnListColumns(dataListTableOptions.Columns); return new DataListReturnData(rows, totalRecordCount, ColumnsJSON); - } diff --git a/server/AyaNova/biz/DataListSavedColumnViewBiz.cs b/server/AyaNova/biz/DataListSavedColumnViewBiz.cs index 7dd65f37..8a92b602 100644 --- a/server/AyaNova/biz/DataListSavedColumnViewBiz.cs +++ b/server/AyaNova/biz/DataListSavedColumnViewBiz.cs @@ -1,8 +1,6 @@ -using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json.Linq; using Newtonsoft.Json; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; @@ -44,180 +42,109 @@ namespace AyaNova.Biz } + //////////////////////////////////////////////////////////////////////////////////////////////// + //CREATE + // + internal async Task CreateAsync(DataListSavedColumnView newObject) + { + ValidateAsync(newObject); + if (HasErrors) + return null; + else + { + //delete the existing one in favor of this one + if (!await DeleteAsync(newObject.UserId, newObject.ListKey)) + return null; + await ct.DataListSavedColumnView.AddAsync(newObject); + await ct.SaveChangesAsync(); - // //////////////////////////////////////////////////////////////////////////////////////////////// - // //CREATE - // internal async Task CreateAsync(DataListSavedColumnView inObj) - // { - // await ValidateAsync(inObj, true); - // if (HasErrors) - // return null; - // else - // { - // //do stuff with datafilter - // DataListSavedColumnView outObj = inObj; - // outObj.UserId = UserId; - - - // await ct.DataListSavedColumnView.AddAsync(outObj); - // await ct.SaveChangesAsync(); - - // //Handle child and associated items: - - // //EVENT LOG - // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct); - - - - // return outObj; - - // } - // } - - - - - - - // //////////////////////////////////////////////////////////////////////////////////////////////// - // /// GET - - // //Get one - // 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.DataListSavedColumnView.SingleOrDefaultAsync(z => z.Id == fetchId && (z.Public == true || z.UserId == UserId)); - // if (logTheGetEvent && ret != null) - // { - // //Log - // await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct); - // } - // return ret; - // } - - - - - // //get ViewList (NOT PAGED) - // internal async Task> GetViewListAsync(string listKey) - // { - // List items = new List(); - // if (!string.IsNullOrWhiteSpace(listKey)) - // { - // items = await ct.DataListSavedColumnView - // .AsNoTracking() - // .Where(z => z.ListKey == listKey && (z.Public == true || z.UserId == UserId)) - // .OrderBy(z => z.Name) - // .Select(z => new NameIdItem() - // { - // Id = z.Id, - // Name = z.Name - // }).ToListAsync(); - - // } - // return items; - // } + return newObject; + } + } //////////////////////////////////////////////////////////////////////////////////////////////// - //UPDATE + //GET // - - //put - internal async Task PutAsync(DataListSavedColumnView inObj) + //Internal, used by datalistfetcher via datalisttableprocessingoptions constructor + //can be called without full biz instantiation as it doesn't rely on UserId in biz object or other shit + //maybe should be static? + internal async Task GetAsync(long userId, string listKey, bool createDefaultIfNecessary) { - //preserve the owner ID if none was specified - if (inObj.UserId == 0) - inObj.UserId = dbObject.UserId; - - //Replace the db object with the PUT object - CopyObject.Copy(inObj, dbObject, "Id"); - //Set "original" value of concurrency token to input token - //this will allow EF to check it out - ct.Entry(dbObject).OriginalValues["Concurrency"] = inObj.Concurrency; - - await ValidateAsync(dbObject); - if (HasErrors) - return false; - await ct.SaveChangesAsync(); - - //Log modification and save context - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct); - - - - return true; + var ret = await ct.DataListSavedColumnView.AsNoTracking().SingleOrDefaultAsync(z => z.UserId == userId && z.ListKey == listKey); + if(ret==null && createDefaultIfNecessary){ + if(!DataListFactory.ListKeyIsValid(listKey)){ + throw new System.ArgumentOutOfRangeException($"ListKey '{listKey}' is not a valid DataListKey"); + } + ret=new DataListSavedColumnView(); + ret.UserId=UserId; + ret.ListKey=listKey; + var dataList=DataListFactory.GetAyaDataList(listKey); + ret.Columns=JsonConvert.SerializeObject(dataList.DefaultColumns); + ret.Sort=JsonConvert.SerializeObject(dataList.DefaultSortBy); + return await CreateAsync(ret); + } + return ret; } - // //////////////////////////////////////////////////////////////////////////////////////////////// - // //DELETE - // // - // internal async Task DeleteAsync(DataListSavedColumnView dbObject) - // { - // //Determine if the object can be deleted, do the deletion tentatively - // //Probably also in here deal with tags and associated search text etc + //////////////////////////////////////////////////////////////////////////////////////////////// + //DELETE + // + internal async Task DeleteAsync(long userId, string listKey) + { + //this is effectively the RESET route handler + //so it can be called any time even if there is no default list and it's a-ok + //because a new default will be created if needed + var dbObject = await GetAsync(userId, listKey, false); + if (dbObject != null) + { + ct.DataListSavedColumnView.Remove(dbObject); + await ct.SaveChangesAsync(); + } + return true; + } - // //FUTURE POSSIBLE NEED - // //ValidateCanDelete(dbObject); - - // if (HasErrors) - // return false; - // ct.DataListSavedColumnView.Remove(dbObject); - // await ct.SaveChangesAsync(); - - // //Delete sibling objects - - // //Event log process delete - // await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct); - - - // //Delete search index - // //Search.ProcessDeletedObjectKeywords(dbObject.Id, BizType); - - - // return true; - // } //////////////////////////////////////////////////////////////////////////////////////////////// //VALIDATION // //Can save or update? - private async Task ValidateAsync(DataListSavedColumnView inObj) + private void ValidateAsync(DataListSavedColumnView inObj) { - if (inObj.UserId != UserId) AddError(ApiErrorCode.NOT_AUTHORIZED, "UserId", "Only own view can be modified"); - if (string.IsNullOrWhiteSpace(inObj.ListKey)) AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListKey"); - -//TODO TODO TODO -//THIS SHOULD USE NAME LIST INSTEAD OR BE DONE AT OBJECT ABOVE FOR DEFAULTS, SAME FOR FILTER BIZ THIS IS BASED OFF OF NO NEED TO INSTANTIATE - var DataList = DataListFactory.GetAyaDataList(inObj.ListKey); - - if (DataList == null) - { + if (!DataListFactory.ListKeyIsValid(inObj.ListKey)) AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListKey", $"ListKey \"{inObj.ListKey}\" DataListKey is not valid"); - } - - if (inObj.ListKey.Length > 255) - AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "ListKey", "255 max"); - - //check if filter can be reconstructed into a C# filter object + + + //Validate Sort JSON try { - List dataListFilterOptions = JsonConvert.DeserializeObject>(inObj.Filter); + JsonConvert.DeserializeObject>(inObj.Sort); } catch (System.Exception ex) { - AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Filter", "Filter is not valid JSON string, can't convert to List, error: " + ex.Message); + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Sort", "Sort is not valid JSON string, can't convert to valid Dictionary SortBy, error: " + ex.Message); + } + + //Validate Columns JSON + try + { + + JsonConvert.DeserializeObject>(inObj.Columns); + } + catch (System.Exception ex) + { + AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Sort", "Sort is not valid JSON string, can't convert to valid Dictionary SortBy, error: " + ex.Message); } return; diff --git a/server/AyaNova/biz/DataListSavedFilterBiz.cs b/server/AyaNova/biz/DataListSavedFilterBiz.cs index be9f7dfc..960099aa 100644 --- a/server/AyaNova/biz/DataListSavedFilterBiz.cs +++ b/server/AyaNova/biz/DataListSavedFilterBiz.cs @@ -252,9 +252,7 @@ namespace AyaNova.Biz AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListKey"); - var DataList = DataListFactory.GetAyaDataList(inObj.ListKey); - - if (DataList == null) + if (!DataListFactory.ListKeyIsValid(inObj.ListKey)) { AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListKey", $"ListKey \"{inObj.ListKey}\" DataListKey is not valid"); } @@ -265,7 +263,7 @@ namespace AyaNova.Biz //check if filter can be reconstructed into a C# filter object try { - List dataListFilterOptions = JsonConvert.DeserializeObject>(inObj.Filter); + JsonConvert.DeserializeObject>(inObj.Filter); } catch (System.Exception ex) { diff --git a/server/AyaNova/biz/ProjectBiz.cs b/server/AyaNova/biz/ProjectBiz.cs index eacdfe27..0b580e42 100644 --- a/server/AyaNova/biz/ProjectBiz.cs +++ b/server/AyaNova/biz/ProjectBiz.cs @@ -155,9 +155,7 @@ namespace AyaNova.Biz AddError(ApiErrorCode.NOT_FOUND); return false; } - ValidateCanDelete(dbObject); - if (HasErrors) - return false; + ValidateCanDelete(dbObject); if (HasErrors) return false; ct.Project.Remove(dbObject); diff --git a/server/AyaNova/models/DataListTableProcessingOptions.cs b/server/AyaNova/models/DataListTableProcessingOptions.cs index 0e2c2fe4..7e0a1c99 100644 --- a/server/AyaNova/models/DataListTableProcessingOptions.cs +++ b/server/AyaNova/models/DataListTableProcessingOptions.cs @@ -1,41 +1,47 @@ using System.Collections.Generic; using AyaNova.DataList; +using Newtonsoft.Json; namespace AyaNova.Models { internal sealed class DataListTableProcessingOptions : DataListProcessingBase - { + { internal List Columns { get; set; } internal const int MaxPageSize = 1000; internal const int DefaultOffset = 0; - internal const int DefaultLimit = 25; - internal int? Offset { get; set; } - internal int? Limit { get; set; } + internal const int DefaultLimit = 25; + internal int? Offset { get; set; } + internal int? Limit { get; set; } internal DataListTableProcessingOptions( DataListTableRequest request, IDataListProcessing dataList, - AyContext ct) + DataListSavedColumnView savedView, + DataListSavedFilter savedFilter) { //set some values from request - Limit=request.Limit; - Offset=request.Offset; - base.ClientCriteria=request.ClientCriteria; - base.DataListKey=request.DataListKey; - - dataList.SetListOptionDefaultsIfNecessary(this); + Limit = request.Limit; + Offset = request.Offset; + base.ClientCriteria = request.ClientCriteria; + base.DataListKey = request.DataListKey; - //populate some values from saved filter and default columnview - //SET COLUMNS - //get user default DataListProcessingBase for this list key - //SET SORTBY + //SET COLUMNS + Columns = JsonConvert.DeserializeObject>(savedView.Columns); - //SET FILTER + //SET SORTBY + base.SortBy = JsonConvert.DeserializeObject>(savedView.Sort); - //SERVER FILTER PROCESSING HERE CODE SEE DATALISTFETCHER + //SET FILTER + if (request.FilterId != 0) + { + base.Filter = JsonConvert.DeserializeObject>(savedFilter.Filter); + } + + + //SERVER FILTER PROCESSING HERE CODE SEE DATALISTFETCHER