using System.ComponentModel.DataAnnotations; using System.Text.Json; using ElmahCore; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Server.Exceptions; using Server.Helper; using Shared; using Shared.Models; namespace Server.Controllers; [ApiController] [Route("[controller]")] public class SearchdomainController : ControllerBase { private readonly ILogger _logger; private readonly IConfiguration _config; private SearchdomainManager _domainManager; public SearchdomainController(ILogger logger, IConfiguration config, SearchdomainManager domainManager) { _logger = logger; _config = config; _domainManager = domainManager; } /// /// Lists all searchdomains /// [HttpGet("/Searchdomains")] public ActionResult List() { List results; try { results = _domainManager.ListSearchdomains(); } catch (Exception) { _logger.LogError("Unable to list searchdomains"); throw; } SearchdomainListResults searchdomainListResults = new() {Searchdomains = results}; return Ok(searchdomainListResults); } /// /// Creates a new searchdomain /// /// Name of the searchdomain /// Optional initial settings [HttpPost] public ActionResult Create([Required]string searchdomain, [FromBody]SearchdomainSettings settings = new()) { try { if (settings.QueryCacheSize <= 0) { settings.QueryCacheSize = 1_000_000; // TODO get rid of this magic number } int id = _domainManager.CreateSearchdomain(searchdomain, settings); return Ok(new SearchdomainCreateResults(){Id = id, Success = true}); } catch (Exception) { _logger.LogError("Unable to create Searchdomain {searchdomain}", [searchdomain]); return Ok(new SearchdomainCreateResults() { Id = null, Success = false, Message = $"Unable to create Searchdomain {searchdomain}" }); } } /// /// Deletes a searchdomain /// /// Name of the searchdomain [HttpDelete] public ActionResult Delete([Required]string searchdomain) { bool success; int deletedEntries; string? message = null; try { success = true; deletedEntries = _domainManager.DeleteSearchdomain(searchdomain); } catch (SearchdomainNotFoundException ex) { _logger.LogError("Unable to delete searchdomain {searchdomain} - not found", [searchdomain]); success = false; deletedEntries = 0; message = $"Unable to delete searchdomain {searchdomain} - not found"; ElmahExtensions.RaiseError(ex); } catch (Exception ex) { _logger.LogError("Unable to delete searchdomain {searchdomain}", [searchdomain]); success = false; deletedEntries = 0; message = ex.Message; ElmahExtensions.RaiseError(ex); } return Ok(new SearchdomainDeleteResults(){Success = success, DeletedEntities = deletedEntries, Message = message}); } /// /// Updates name and settings of a searchdomain /// /// Name of the searchdomain /// Updated name of the searchdomain /// Updated settings of searchdomain [HttpPut] public ActionResult Update([Required]string searchdomain, string newName, [FromBody]SearchdomainSettings? settings) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (settings is null) { Dictionary parameters = new() { {"name", newName}, {"id", searchdomain_.id} }; searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set name = @name WHERE id = @id", parameters); } else { Dictionary parameters = new() { {"name", newName}, {"settings", settings}, {"id", searchdomain_.id} }; searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set name = @name, settings = @settings WHERE id = @id", parameters); } return Ok(new SearchdomainUpdateResults(){Success = true}); } /// /// Gets the query cache of a searchdomain /// /// Name of the searchdomain [HttpGet("Queries")] public ActionResult GetQueries([Required]string searchdomain) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); Dictionary searchCache = searchdomain_.queryCache.AsDictionary(); return Ok(new SearchdomainQueriesResults() { Searches = searchCache, Success = true }); } /// /// Executes a query in the searchdomain /// /// Name of the searchdomain /// Query to execute /// Return only the top N results /// Return the attributes of the object [HttpPost("Query")] public ActionResult Query([Required]string searchdomain, [Required]string query, int? topN, bool returnAttributes = false) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); List<(float, string)> results = searchdomain_.Search(query, topN); List queryResults = [.. results.Select(r => new EntityQueryResult { Name = r.Item2, Value = r.Item1, Attributes = returnAttributes ? (searchdomain_.entityCache.FirstOrDefault(x => x.name == r.Item2)?.attributes ?? null) : null })]; return Ok(new EntityQueryResults(){Results = queryResults, Success = true }); } /// /// Deletes a query from the query cache /// /// Name of the searchdomain /// Query to delete [HttpDelete("Query")] public ActionResult DeleteQuery([Required]string searchdomain, [Required]string query) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); EnumerableLruCache searchCache = searchdomain_.queryCache; bool containsKey = searchCache.ContainsKey(query); if (containsKey) { searchCache.Remove(query); return Ok(new SearchdomainDeleteSearchResult() {Success = true}); } return Ok(new SearchdomainDeleteSearchResult() {Success = false, Message = "Query not found in search cache"}); } /// /// Updates a query from the query cache /// /// Name of the searchdomain /// Query to update /// List of results to apply to the query [HttpPatch("Query")] public ActionResult UpdateQuery([Required]string searchdomain, [Required]string query, [Required][FromBody]List results) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); EnumerableLruCache searchCache = searchdomain_.queryCache; bool containsKey = searchCache.ContainsKey(query); if (containsKey) { DateTimedSearchResult element = searchCache[query]; element.Results = results; searchCache[query] = element; return Ok(new SearchdomainUpdateSearchResult() {Success = true}); } return Ok(new SearchdomainUpdateSearchResult() {Success = false, Message = "Query not found in search cache"}); } /// /// Get the settings of a searchdomain /// /// Name of the searchdomain [HttpGet("Settings")] public ActionResult GetSettings([Required]string searchdomain) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); SearchdomainSettings settings = searchdomain_.settings; return Ok(new SearchdomainSettingsResults() { Settings = settings, Success = true }); } /// /// Update the settings of a searchdomain /// /// Name of the searchdomain [HttpPut("Settings")] public ActionResult UpdateSettings([Required]string searchdomain, [Required][FromBody] SearchdomainSettings request) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); Dictionary parameters = new() { {"settings", JsonSerializer.Serialize(request)}, {"id", searchdomain_.id} }; searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set settings = @settings WHERE id = @id", parameters); searchdomain_.settings = request; searchdomain_.queryCache.Capacity = request.QueryCacheSize; return Ok(new SearchdomainUpdateResults(){Success = true}); } /// /// Get the query cache size of a searchdomain /// /// Name of the searchdomain [HttpGet("QueryCache/Size")] public ActionResult GetQueryCacheSize([Required]string searchdomain) { if (!SearchdomainHelper.IsSearchdomainLoaded(_domainManager, searchdomain)) { return Ok(new SearchdomainQueryCacheSizeResults() { SizeBytes = 0, ElementCount = 0, ElementMaxCount = 0, Success = true }); } (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); int elementCount = searchdomain_.queryCache.Count; int ElementMaxCount = searchdomain_.settings.QueryCacheSize; return Ok(new SearchdomainQueryCacheSizeResults() { SizeBytes = searchdomain_.GetSearchCacheSize(), ElementCount = elementCount, ElementMaxCount = ElementMaxCount, Success = true }); } /// /// Clear the query cache of a searchdomain /// /// Name of the searchdomain [HttpPost("QueryCache/Clear")] public ActionResult InvalidateSearchCache([Required]string searchdomain) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); searchdomain_.InvalidateSearchCache(); return Ok(new SearchdomainInvalidateCacheResults(){Success = true}); } /// /// Get the disk size of a searchdomain in bytes /// /// Name of the searchdomain [HttpGet("Database/Size")] public ActionResult GetDatabaseSize([Required]string searchdomain) { (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); long sizeInBytes = DatabaseHelper.GetSearchdomainDatabaseSize(searchdomain_.helper, searchdomain); return Ok(new SearchdomainGetDatabaseSizeResult() { SearchdomainDatabaseSizeBytes = sizeInBytes, Success = true }); } }