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.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 { 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_.searchCache; return Ok(new SearchdomainSearchesResults() { 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}); Dictionary searchCache = searchdomain_.searchCache; 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}); Dictionary searchCache = searchdomain_.searchCache; 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; return Ok(new SearchdomainUpdateResults(){Success = true}); } /// /// Get the query cache size of a searchdomain /// /// Name of the searchdomain [HttpGet("QueryCache/Size")] public ActionResult GetSearchCacheSize([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_.searchCache; long sizeInBytes = 0; foreach (var entry in searchCache) { sizeInBytes += sizeof(int); // string length prefix sizeInBytes += entry.Key.Length * sizeof(char); // string characters sizeInBytes += entry.Value.EstimateSize(); } return Ok(new SearchdomainSearchCacheSizeResults() { QueryCacheSizeBytes = sizeInBytes, 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 }); } }