Skip to content
Snippets Groups Projects
Commit 0c0b1a68 authored by Petar Hristov's avatar Petar Hristov :speech_balloon: Committed by Sirieam Marie Hunke
Browse files

Update: Refactored search

parent 0ac44e5d
No related branches found
No related tags found
2 merge requests!11Release: Sprint/2022 15 :robot:,!10Update: Refactored search
using Coscine.Api.Search.Models;
using Coscine.Api.Search.Helpers;
using Coscine.Api.Search.Models;
using Coscine.ApiCommons;
using Coscine.Configuration;
using Coscine.SemanticSearch;
using Coscine.SemanticSearch.Clients;
using Coscine.SemanticSearch.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Coscine.Api.Search.Controllers
......@@ -15,7 +18,7 @@ namespace Coscine.Api.Search.Controllers
/// Controller for the search.
/// </summary>
[Authorize]
public class SemanticSearchController : Controller
public class SearchController : Controller
{
private readonly IConfiguration _configuration;
private readonly Authenticator _authenticator;
......@@ -29,7 +32,7 @@ namespace Coscine.Api.Search.Controllers
/// <summary>
/// Default constructor.
/// </summary>
public SemanticSearchController()
public SearchController()
{
_configuration = Program.Configuration;
_authenticator = new Authenticator(this, Program.Configuration);
......@@ -42,7 +45,7 @@ namespace Coscine.Api.Search.Controllers
/// <summary>
/// Search with SemanticSearch;
/// Search
/// </summary>
/// <param name="query">Elasticsearch query</param>
/// <param name="user">Specify user or only public metadata records could be found</param>
......@@ -50,31 +53,35 @@ namespace Coscine.Api.Search.Controllers
/// <param name="languages">Set the used languages</param>
/// <returns>Search results</returns>
[HttpGet("[controller]/{query?}/{user?}/{adv?}/{languages?}")]
public async Task<ActionResult<IEnumerable<SearchResult>>> SearchAsync(string query = "*", bool user = true, bool adv = false, [System.Web.Http.FromUri] string[] languages = null)
public async Task<ActionResult<SearchResult>> SearchAsync(string query = "*", bool user = true, bool adv = false, [System.Web.Http.FromUri] string[] languages = null)
{
try
var currentUser = _authenticator.GetUser();
if (languages is null || languages.Length == 0)
{
if (languages == null || languages.Length == 0)
{
languages = _defaultLanguages;
}
languages = _defaultLanguages;
}
string userId = null;
try
{
var files = await Searchers.SearchForFilesAsync(currentUser.Id, query, adv, includePrivateRecords: user, _connector, _searchClient, languages.ToList());
var projectsToAppend = files.Where(f => f.ProjectId is not null).Select(f => f.ProjectId).Distinct().ToList();
var resourcesToAppend = files.Where(f => f.ProjectId is not null).Select(f => f.ResourceId).Distinct().ToList();
if (user)
// Advanced query looks like so: (vvm.txt) + (belongsToProject: "https://purl.org/coscine/projects/1a6cf186-12cb-4710-81c6-edb981f77761")
var formattedQueryMatch = Regex.Match(query, @"(?<=\().+?(?=\))");
var formattedQuery = formattedQueryMatch.Success ? formattedQueryMatch.Value : query;
var res = new SearchResult()
{
userId = _authenticator.GetUser().Id.ToString();
}
var mapper = new RdfSearchMapper(_connector, _searchClient, languages.ToList());
var results = await mapper.SearchAsync(query, userId, adv);
return Ok(SearchResult.ParseResultsToList(results));
Projects = Searchers.SearchForProjects(currentUser.Id, formattedQuery, projectsToAppend: projectsToAppend),
Resources = Searchers.SearchForResources(currentUser.Id, formattedQuery, resourcesToAppend: resourcesToAppend),
Files = files,
};
return res;
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
}
}
using Coscine.Api.Search.Models;
using Coscine.Database.DataModel;
using Coscine.Database.Models;
using Coscine.Database.ReturnObjects;
using Coscine.Database.Util;
using Coscine.SemanticSearch;
using Coscine.SemanticSearch.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Coscine.Api.Search.Helpers;
/// <summary>
/// Class implementing static searcher methods
/// </summary>
public class Searchers
{
/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <param name="searchQuery"></param>
/// <param name="advancedSearch"></param>
/// <param name="includePrivateRecords"></param>
/// <param name="connector"></param>
/// <param name="searchClient"></param>
/// <param name="languages"></param>
/// <returns>Found files</returns>
public static async Task<IEnumerable<FileSearchResult>> SearchForFilesAsync(Guid userId, string searchQuery, bool advancedSearch, bool includePrivateRecords, IRdfConnector connector, ISearchClient searchClient, List<string> languages)
{
string userIdentifier = includePrivateRecords ? userId.ToString() : null;
var mapper = new RdfSearchMapper(connector, searchClient, languages, GetPublicVisibilityId());
var results = await mapper.SearchAsync(searchQuery, userIdentifier, advancedSearch);
return FileSearchResult.ParseResultsToList(results);
}
/// <summary>
/// This method searches through projects
/// </summary>
/// <param name="userId">ID of the user</param>
/// <param name="searchQuery">Search query</param>
/// <param name="showSubProjects">Shows subprojects</param>
/// <param name="projectsToAppend">List of project IDs to include in the result</param>
/// <returns>Found projects</returns>
public static IEnumerable<ProjectObject> SearchForProjects(Guid userId, string searchQuery, bool showSubProjects = false, List<string> projectsToAppend = null)
{
if (string.IsNullOrEmpty(searchQuery) || searchQuery == "*")
{
searchQuery = "";
}
if (projectsToAppend is null)
{
projectsToAppend = new List<string>();
}
var projectModel = new ProjectModel();
return DatabaseConnection.ConnectToDatabase((db) =>
{
var allSubProjects = (from sp in db.SubProjects select sp.SubProjectId).ToList();
var allSubProjectsList = new List<Guid>();
allSubProjectsList.AddRange(allSubProjects);
return (from p in db.Projects
join pr in db.ProjectRoles on p.Id equals pr.ProjectId into joinedPr
from jpr in joinedPr.DefaultIfEmpty()
join v in db.Visibilities on p.VisibilityId equals v.Id into joinedV
from jv in joinedV.DefaultIfEmpty()
join pd in db.ProjectDisciplines on p.Id equals pd.ProjectId into joinedPd
from jpd in joinedPd.DefaultIfEmpty()
join d in db.Disciplines on jpd.DisciplineId equals d.Id into joinedD
from jd in joinedD.DefaultIfEmpty()
join pi in db.ProjectInstitutes on p.Id equals pi.ProjectId into joinedPi
from jpi in joinedPi.DefaultIfEmpty()
where p.Deleted == false &&
((!showSubProjects && !allSubProjectsList.Contains(p.Id)) ||
(showSubProjects && allSubProjectsList.Contains(p.Id))) &&
(jpr.UserId == userId || jv.DisplayName == "Public") &&
(searchQuery == "" ||
p.ProjectName.Contains(searchQuery) ||
p.Description.Contains(searchQuery) ||
p.Keywords.Contains(searchQuery) ||
p.DisplayName.Contains(searchQuery) ||
p.PrincipleInvestigators.Contains(searchQuery) ||
p.GrantId.Contains(searchQuery) ||
jv.DisplayName.Contains(searchQuery) ||
jd.Url.Contains(searchQuery) ||
jd.DisplayNameDe.Contains(searchQuery) ||
jd.DisplayNameEn.Contains(searchQuery)) ||
projectsToAppend.Contains(p.Id.ToString())
select projectModel.CreateReturnObjectFromDatabaseObject(p)).ToList().OrderBy(element => element.DisplayName).DistinctBy(p => p.Id).ToList();
});
}
/// <summary>
/// This method searches through resources
/// </summary>
/// <param name="userId">ID of the user</param>
/// <param name="searchQuery">Search Query</param>
/// <param name="resourcesToAppend">List of resource IDs to include in the result</param>
/// <returns>Found resources</returns>
public static IEnumerable<ResourceSearchResult> SearchForResources(Guid userId, string searchQuery, List<string> resourcesToAppend = null)
{
if (string.IsNullOrEmpty(searchQuery) || searchQuery == "*")
{
searchQuery = "";
}
if (resourcesToAppend is null)
{
resourcesToAppend = new List<string>();
}
var resourceModel = new ResourceModel();
var resourceObjects = DatabaseConnection.ConnectToDatabase((db) =>
{
var resourceObjects = (from r in db.Resources
join pres in db.ProjectResources on r.Id equals pres.ResourceId into joinedPres
from jpres in joinedPres.DefaultIfEmpty()
join p in db.Projects on jpres.ProjectId equals p.Id into joinedP
from jp in joinedP.DefaultIfEmpty()
join pr in db.ProjectRoles on jp.Id equals pr.ProjectId into joinedPr
from jpr in joinedPr.DefaultIfEmpty()
join v in db.Visibilities on r.VisibilityId equals v.Id into joinedV
from jv in joinedV.DefaultIfEmpty()
join rd in db.ResourceDisciplines on r.Id equals rd.ResourceId into joinedRd
from jrd in joinedRd.DefaultIfEmpty()
join d in db.Disciplines on jrd.DisciplineId equals d.Id into joinedD
from jd in joinedD.DefaultIfEmpty()
join l in db.Licenses on r.LicenseId equals l.Id into joinedL
from jl in joinedL.DefaultIfEmpty()
join rt in db.ResourceTypes on r.TypeId equals rt.Id into joinedRt
from jrt in joinedRt.DefaultIfEmpty()
where jp.Deleted == false &&
(jpr.UserId == userId || jv.DisplayName == "Public") &&
(searchQuery == "" ||
r.ResourceName.Contains(searchQuery) ||
r.DisplayName.Contains(searchQuery) ||
r.ResourceName.Contains(searchQuery) ||
r.Keywords.Contains(searchQuery) ||
r.UsageRights.Contains(searchQuery) ||
r.Description.Contains(searchQuery) ||
r.ApplicationProfile.Contains(searchQuery) ||
jrt.DisplayName.Contains(searchQuery) ||
jl.DisplayName.Contains(searchQuery) ||
jd.DisplayNameDe.Contains(searchQuery) ||
jd.DisplayNameEn.Contains(searchQuery)) ||
resourcesToAppend.Contains(r.Id.ToString())
select resourceModel.CreateReturnObjectFromDatabaseObject(r)).ToList().OrderBy(element => element.DisplayName).DistinctBy(r => r.Id).ToList();
// Remove fixed values
foreach (var resource in resourceObjects)
{
resource.FixedValues = null;
}
return resourceObjects;
});
var projectResourceModel = new ProjectResourceModel();
var parentProjects = projectResourceModel.GetAll();
static Guid? getParentId(ProjectResource projectObject = null)
{
if (projectObject is not null)
{
return projectObject.ProjectId;
}
return null;
}
var result = new List<ResourceSearchResult>(resourceObjects.Count);
result.AddRange(resourceObjects.Select(i => new ResourceSearchResult(i, getParentId(parentProjects.FirstOrDefault(p => p.ResourceId.Equals(i.Id))))));
return result;
}
private static Guid GetPublicVisibilityId()
{
var visibilityModel = new VisibilityModel();
return visibilityModel.GetWhere(v => v.DisplayName.ToLower().Contains("public")).Id; // Handle null.Id case?
}
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Coscine.Api.Search.Models;
/// <summary>
/// File Search Result Object
/// </summary>
public class FileSearchResult
{
/// <summary>
/// Graph Name
/// </summary>
[JsonProperty("graphName")]
public Uri GraphName { get; set; }
/// <summary>
/// ID of Project containing the file
/// </summary>
[JsonProperty("projectId")]
public string ProjectId { get; set; }
/// <summary>
/// ID of Resource containing the file
/// </summary>
[JsonProperty("resourceId")]
public string ResourceId { get; set; }
/// <summary>
/// Search result fields
/// </summary>
[JsonProperty("source")]
public JObject Source { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="key">Search result entry key containing the resource id</param>
/// <param name="projectValue">URI containing the project id</param>
/// <param name="value">Search result</param>
public FileSearchResult(string key, string projectValue, JObject value)
{
GraphName = new Uri(key);
ProjectId = GetGuid(projectValue);
ResourceId = GetGuid(key);
Source = value;
}
/// <summary>
/// Method to convert the Semantic Search dictionary to a list of File Search Result Objects
/// </summary>
/// <param name="results"></param>
/// <returns></returns>
public static IEnumerable<FileSearchResult> ParseResultsToList(IDictionary<string, JObject> results)
{
var output = new List<FileSearchResult>();
foreach (var entry in results)
{
var searchResult = new FileSearchResult(entry.Key, (string)entry.Value["belongsToProject"], entry.Value);
output.Add(searchResult);
}
return output;
}
private static string GetGuid(string value)
{
var regex = new Regex(@"[({]?[a-fA-F0-9]{8}[-]?([a-fA-F0-9]{4}[-]?){3}[a-fA-F0-9]{12}[})]?", RegexOptions.IgnoreCase);
var match = regex.Match(value);
return match.Success ? match.Value : null;
}
}
using Coscine.Database.ReturnObjects;
using System;
namespace Coscine.Api.Search.Models;
public class ResourceSearchResult : ResourceObject
{
public ResourceSearchResult(ResourceObject resourceObject, Guid? parentProjectId) : base(resourceObject.Id, resourceObject.DisplayName, resourceObject.ResourceName, resourceObject.Description, resourceObject.Keywords, resourceObject.UsageRights, resourceObject.Type, resourceObject.Disciplines, resourceObject.Visibility, resourceObject.License, resourceObject.ResourceTypeOption, resourceObject.ApplicationProfile, null, resourceObject.Creator, resourceObject.Archived)
{
ProjectId = parentProjectId;
}
public Guid? ProjectId { get; set; }
}
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using Coscine.Database.ReturnObjects;
using System.Collections.Generic;
namespace Coscine.Api.Search.Models
{
/// <summary>
/// Search result return object
/// </summary>
public class SearchResult
{
[JsonProperty("graphName")]
public Uri GraphName { get; set; }
[JsonProperty("project")]
public Uri Project { get; set; }
[JsonProperty("source")]
public JObject Source { get; set; }
public SearchResult(string key, string projectValue, JObject value)
{
GraphName = new Uri(key);
Project = new Uri(projectValue);
Source = value;
}
public static IEnumerable<SearchResult> ParseResultsToList(IDictionary<string, JObject> results)
{
var output = new List<SearchResult>();
foreach (var entry in results)
{
var searchResult = new SearchResult(entry.Key, (string)entry.Value["belongsToProject"], entry.Value);
output.Add(searchResult);
}
return output;
}
/// <summary>
/// Search result projects
/// </summary>
public IEnumerable<ProjectObject> Projects { get; set; }
/// <summary>
/// Search result resources
/// </summary>
public IEnumerable<ResourceSearchResult> Resources { get; set; }
/// <summary>
/// Search result files
/// </summary>
public IEnumerable<FileSearchResult> Files { get; set; }
}
}
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>Coscine.Api.Search</RootNamespace>
......@@ -10,7 +10,7 @@
<PropertyGroup>
<Authors>RWTH Aachen University</Authors>
<Company>IT Center, RWTH Aachen University</Company>
<Copyright>2021 IT Center, RWTH Aachen University</Copyright>
<Copyright>2022 IT Center, RWTH Aachen University</Copyright>
<Description>Search is a part of the Coscine group.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://git.rwth-aachen.de/coscine/backend/apis/Search</PackageProjectUrl>
......@@ -20,6 +20,6 @@
<PackageReference Include="Coscine.ApiCommons" Version="2.*-*" />
<PackageReference Include="Coscine.Database" Version="2.*-*" />
<PackageReference Include="Coscine.SemanticSearch" Version="1.*-*" />
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.7" />
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
</ItemGroup>
</Project>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment