From 088fe9a650370e1f923e5fe1230ec682327a99e5 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Fri, 21 Sep 2018 00:05:03 +0000 Subject: [PATCH] --- devdocs/coding-standards.txt | 2 +- ...mContext.cs => UserLocaleIdFromContext.cs} | 2 +- .../AyaNova/Controllers/SearchController.cs | 103 ++++++++++++++++++ .../AyaNova/Controllers/WidgetController.cs | 6 +- server/AyaNova/biz/Search.cs | 6 +- test/raven-integration/Search/SearchOps.cs | 64 +++++++++++ 6 files changed, 177 insertions(+), 6 deletions(-) rename server/AyaNova/ControllerHelpers/{LocaleIdFromContext.cs => UserLocaleIdFromContext.cs} (84%) create mode 100644 server/AyaNova/Controllers/SearchController.cs create mode 100644 test/raven-integration/Search/SearchOps.cs diff --git a/devdocs/coding-standards.txt b/devdocs/coding-standards.txt index 6c453c9c..3c238a9a 100644 --- a/devdocs/coding-standards.txt +++ b/devdocs/coding-standards.txt @@ -4,7 +4,7 @@ //TODO: means something that needs to be done but is awaiting something else. This is a *MUST* do. //LOOKAT: means something that I want to think about and revisit later but is not urgent //BEFORE_RELEASE: means something that MUST be changed before release, usually special debugging code for development or testing - +//BUGBUG: means there's a bug here that is known Error messages / Numbers diff --git a/server/AyaNova/ControllerHelpers/LocaleIdFromContext.cs b/server/AyaNova/ControllerHelpers/UserLocaleIdFromContext.cs similarity index 84% rename from server/AyaNova/ControllerHelpers/LocaleIdFromContext.cs rename to server/AyaNova/ControllerHelpers/UserLocaleIdFromContext.cs index c2bfe86c..9c6f1f89 100644 --- a/server/AyaNova/ControllerHelpers/LocaleIdFromContext.cs +++ b/server/AyaNova/ControllerHelpers/UserLocaleIdFromContext.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; namespace AyaNova.Api.ControllerHelpers { - internal static class LocaleIdFromContext + internal static class UserLocaleIdFromContext { internal static long Id(IDictionary HttpContextItems) { diff --git a/server/AyaNova/Controllers/SearchController.cs b/server/AyaNova/Controllers/SearchController.cs new file mode 100644 index 00000000..b029ddad --- /dev/null +++ b/server/AyaNova/Controllers/SearchController.cs @@ -0,0 +1,103 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +using AyaNova.Models; +using AyaNova.Api.ControllerHelpers; +using AyaNova.Biz; + + +namespace AyaNova.Api.Controllers +{ + + /// + /// Search + /// + [ApiVersion("8.0")] + [Route("api/v{version:apiVersion}/[controller]")] + [Produces("application/json")] + [Authorize] + public class SearchController : Controller + { + private readonly AyContext ct; + private readonly ILogger log; + private readonly ApiServerState serverState; + + /// + /// ctor + /// + /// + /// + /// + public SearchController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) + { + ct = dbcontext; + log = logger; + serverState = apiServerState; + } + + + + /// + /// Post search parameters + /// + /// Required roles: Any + /// + /// + /// + /// SearchResult list + [HttpPost] + public async Task PostSearch([FromBody] Search.SearchRequestParameters searchParams) + { + if (!serverState.IsOpen) + { + return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason)); + } + + if (!ModelState.IsValid) + { + return BadRequest(new ApiErrorResponse(ModelState)); + } + + //Do the search + var SearchResults = await Search.DoSearch(ct, UserLocaleIdFromContext.Id(HttpContext.Items), UserRolesFromContext.Roles(HttpContext.Items), searchParams); + + return Ok(new ApiOkResponse(SearchResults)); + + // if (o == null) + // { + // //error return + // return BadRequest(new ApiErrorResponse(biz.Errors)); + // } + // else + // { + + // //save to get Id + // await ct.SaveChangesAsync(); + + // //Log now that we have the Id + // EventLogProcessor.AddEntry(new Event(biz.userId, o.Id, AyaType.Widget, AyaEvent.Created), ct); + // await ct.SaveChangesAsync(); + + // //this will save the context as part of it's operations + // Search.ProcessNewObjectKeywords(ct, UserLocaleIdFromContext.Id(HttpContext.Items), o.Id, AyaType.Widget, o.Name, o.Notes, o.Name); + + + // //return success and link + // return CreatedAtAction("GetWidget", new { id = o.Id }, new ApiCreatedResponse(o)); + // } + } + + + + //------------ + + + }//eoc +}//eons \ No newline at end of file diff --git a/server/AyaNova/Controllers/WidgetController.cs b/server/AyaNova/Controllers/WidgetController.cs index 05f4db32..aa39d05e 100644 --- a/server/AyaNova/Controllers/WidgetController.cs +++ b/server/AyaNova/Controllers/WidgetController.cs @@ -210,7 +210,7 @@ namespace AyaNova.Api.Controllers { //Log EventLogProcessor.AddEntry(new Event(biz.userId, o.Id, AyaType.Widget, AyaEvent.Modified), ct); - Search.ProcessUpdatedObjectKeywords(ct, LocaleIdFromContext.Id(HttpContext.Items), o.Id, AyaType.Widget, o.Name, o.Notes, o.Name); + Search.ProcessUpdatedObjectKeywords(ct, UserLocaleIdFromContext.Id(HttpContext.Items), o.Id, AyaType.Widget, o.Name, o.Notes, o.Name); await ct.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) @@ -290,7 +290,7 @@ namespace AyaNova.Api.Controllers await ct.SaveChangesAsync(); //this will save the context as part of it's operations - Search.ProcessUpdatedObjectKeywords(ct, LocaleIdFromContext.Id(HttpContext.Items), o.Id, AyaType.Widget, o.Name, o.Notes, o.Name); + Search.ProcessUpdatedObjectKeywords(ct, UserLocaleIdFromContext.Id(HttpContext.Items), o.Id, AyaType.Widget, o.Name, o.Notes, o.Name); } catch (DbUpdateConcurrencyException) @@ -358,7 +358,7 @@ namespace AyaNova.Api.Controllers await ct.SaveChangesAsync(); //this will save the context as part of it's operations - Search.ProcessNewObjectKeywords(ct, LocaleIdFromContext.Id(HttpContext.Items), o.Id, AyaType.Widget, o.Name, o.Notes, o.Name); + Search.ProcessNewObjectKeywords(ct, UserLocaleIdFromContext.Id(HttpContext.Items), o.Id, AyaType.Widget, o.Name, o.Notes, o.Name); //return success and link diff --git a/server/AyaNova/biz/Search.cs b/server/AyaNova/biz/Search.cs index ac976605..3571a259 100644 --- a/server/AyaNova/biz/Search.cs +++ b/server/AyaNova/biz/Search.cs @@ -175,7 +175,7 @@ namespace AyaNova.Biz List SearchKeyMatches = new List(); //Build search query based on searchParameters - var q = ct.SearchKey.Where(m => DictionaryMatches.Contains(m.Id)); + var q = ct.SearchKey.Distinct().Where(m => DictionaryMatches.Contains(m.Id)); //In name? if (searchParameters.NameOnly) @@ -188,6 +188,10 @@ namespace AyaNova.Biz //Trigger the search SearchKeyMatches = await q.ToListAsync(); + BUGBUG: the current simple phrase search test is returning records when only one of the two search words matches "simple dogs" +//However only one widget created in the test has both words, teh other only has one and should not return a result +//Need to rejig this so only if all words are matching does it consider it a hit + //PUT THE RESULTS INTO MATCHING OBJECTS LIST foreach (SearchKey SearchKeyMatch in SearchKeyMatches) { diff --git a/test/raven-integration/Search/SearchOps.cs b/test/raven-integration/Search/SearchOps.cs new file mode 100644 index 00000000..dac80867 --- /dev/null +++ b/test/raven-integration/Search/SearchOps.cs @@ -0,0 +1,64 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; + +namespace raven_integration +{ + + public class SearchOps + { + + + + + + /// + /// Test simple phrase only search + /// + [Fact] + public async void PhraseOnlySearchShouldWork() + { + + //CREATE A WIDGET + dynamic D = new JObject(); + D.name = Util.Uniquify("Search Simple Test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "The quick brown and simple fox jumped over the six lazy dogs!"; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long FirstWidgetId = a.ObjectResponse["result"]["id"].Value(); + + D = new JObject(); + D.name = Util.Uniquify("Search Simple SECOND Test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This Widget should not be returned in the search as it only contains a single keyword in the name not both"; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long SecondWidgetId = a.ObjectResponse["result"]["id"].Value(); + + //Now see if can find that widget with a phrase search + dynamic SearchParameters = new JObject(); + SearchParameters.phrase = "simple dogs"; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + + + } + + + + + + //================================================== + + }//eoc +}//eons