diff --git a/RelationshipAnalysis/Controllers/AttributesController.cs b/RelationshipAnalysis/Controllers/AttributesController.cs index 577825d..0e0a197 100644 --- a/RelationshipAnalysis/Controllers/AttributesController.cs +++ b/RelationshipAnalysis/Controllers/AttributesController.cs @@ -10,15 +10,15 @@ namespace RelationshipAnalysis.Controllers; public class AttributesController([FromKeyedServices("node")] IAttributesReceiver nodeAttributeReceiver,[FromKeyedServices("edge")] IAttributesReceiver edgeAttributeReceiver) : ControllerBase { [HttpGet("nodes")] - public async Task GetNodeAttributes(int nodeCategoryId) + public async Task GetNodeAttributes(string nodeCategoryName) { - var result = await nodeAttributeReceiver.GetAllAttributes(nodeCategoryId); + var result = await nodeAttributeReceiver.GetAllAttributes(nodeCategoryName); return Ok(result); } [HttpGet("edges")] - public async Task GetEdgeAttributes(int edgeCategoryId) + public async Task GetEdgeAttributes(string edgeCategoryName) { - var result = await edgeAttributeReceiver.GetAllAttributes(edgeCategoryId); + var result = await edgeAttributeReceiver.GetAllAttributes(edgeCategoryName); return Ok(result); } } \ No newline at end of file diff --git a/RelationshipAnalysis/Controllers/Graph/GraphController.cs b/RelationshipAnalysis/Controllers/Graph/GraphController.cs index b3dc3f5..d5e689b 100644 --- a/RelationshipAnalysis/Controllers/Graph/GraphController.cs +++ b/RelationshipAnalysis/Controllers/Graph/GraphController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using RelationshipAnalysis.Dto.Graph; using RelationshipAnalysis.Services.GraphServices.Abstraction; using RelationshipAnalysis.Services.GraphServices.Graph.Abstraction; @@ -9,13 +10,21 @@ namespace RelationshipAnalysis.Controllers.Graph; [Authorize] [Route("api/[controller]")] public class GraphController(IGraphReceiver graphReceiver, - IExpansionGraphReceiver expansionGraphReceiver) : ControllerBase + IExpansionGraphReceiver expansionGraphReceiver, + IGraphSearcherService searchService) : ControllerBase { [HttpGet] public async Task GetGraph() { return Ok(await graphReceiver.GetGraph()); } + + [HttpPost] + public async Task SearchGraph(SearchGraphDto searchGraphDto) + { + var result = await searchService.Search(searchGraphDto); + return StatusCode((int)result.StatusCode, result.Data); + } [HttpGet("expansion")] public async Task GetExpansionGraph([FromQuery] int nodeId,[FromQuery] string sourceCategoryName,[FromQuery] string targetCategoryName,[FromQuery] string edgeCategoryName) { diff --git a/RelationshipAnalysis/Dto/Graph/GraphDto.cs b/RelationshipAnalysis/Dto/Graph/GraphDto.cs index f01d435..e03b5e1 100644 --- a/RelationshipAnalysis/Dto/Graph/GraphDto.cs +++ b/RelationshipAnalysis/Dto/Graph/GraphDto.cs @@ -7,4 +7,5 @@ public class GraphDto { public List Nodes { get; set; } = []; public List Edges { get; set; } = []; + public string Message { get; set; } } \ No newline at end of file diff --git a/RelationshipAnalysis/Dto/Graph/SearchGraphDto.cs b/RelationshipAnalysis/Dto/Graph/SearchGraphDto.cs new file mode 100644 index 0000000..cdc3e5a --- /dev/null +++ b/RelationshipAnalysis/Dto/Graph/SearchGraphDto.cs @@ -0,0 +1,19 @@ +namespace RelationshipAnalysis.Dto.Graph; +using System.ComponentModel.DataAnnotations; + + +public class SearchGraphDto +{ + [Required] + public string SourceCategoryName { get; set; } + [Required] + public string TargetCategoryName { get; set; } + [Required] + public string EdgeCategoryName { get; set; } + [Required] + public Dictionary SourceCategoryClauses { get; set; } + [Required] + public Dictionary TargetCategoryClauses { get; set; } + [Required] + public Dictionary EdgeCategoryClauses { get; set; } +} \ No newline at end of file diff --git a/RelationshipAnalysis/Program.cs b/RelationshipAnalysis/Program.cs index d018cbd..2a9c5c0 100644 --- a/RelationshipAnalysis/Program.cs +++ b/RelationshipAnalysis/Program.cs @@ -43,6 +43,8 @@ using RelationshipAnalysis.Services.Panel.UserPanelServices.UserUpdatePasswordService; using RelationshipAnalysis.Services.Panel.UserPanelServices.UserUpdatePasswordService.Abstraction; using RelationshipAnalysis.Settings.JWT; +using EdgeAttributesReceiver = RelationshipAnalysis.Services.GraphServices.Edge.EdgeAttributesReceiver; +using NodeAttributesReceiver = RelationshipAnalysis.Services.GraphServices.Node.NodeAttributesReceiver; var builder = WebApplication.CreateBuilder(args); builder.Configuration @@ -112,6 +114,7 @@ .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddKeyedSingleton("node") .AddKeyedSingleton("edge") .AddKeyedSingleton("node") diff --git a/RelationshipAnalysis/Resources.Designer.cs b/RelationshipAnalysis/Resources.Designer.cs index 87630b8..a73de16 100644 --- a/RelationshipAnalysis/Resources.Designer.cs +++ b/RelationshipAnalysis/Resources.Designer.cs @@ -50,7 +50,23 @@ internal static string InvalidPasswordMessage { return ResourceManager.GetString("InvalidPasswordMessage", resourceCulture); } } - + + internal static string InvalidClauseInSourceCategory { + get { + return ResourceManager.GetString("InvalidClauseInSourceCategory", resourceCulture); + } + } + internal static string InvalidClauseInDestinationCategory { + get { + return ResourceManager.GetString("InvalidClauseInDestinationCategory", resourceCulture); + } + } + internal static string InvalidClauseInEdgeCategory { + get { + return ResourceManager.GetString("InvalidClauseInEdgeCategory", resourceCulture); + } + } + internal static string SuccessfulLogoutMessage { get { return ResourceManager.GetString("SuccessfulLogoutMessage", resourceCulture); diff --git a/RelationshipAnalysis/Resources.resx b/RelationshipAnalysis/Resources.resx index b6cbc36..f60f93f 100644 --- a/RelationshipAnalysis/Resources.resx +++ b/RelationshipAnalysis/Resources.resx @@ -1,6 +1,16 @@ + + One or more given attributes in source category is invalid + + + One or more given attributes in target category is invalid + + + One or more given attributes in edge category is invalid + + Target Node category is invalid! diff --git a/RelationshipAnalysis/Services/GraphServices/Abstraction/IAttributesReceiver.cs b/RelationshipAnalysis/Services/GraphServices/Abstraction/IAttributesReceiver.cs index 813360e..b57c5ac 100644 --- a/RelationshipAnalysis/Services/GraphServices/Abstraction/IAttributesReceiver.cs +++ b/RelationshipAnalysis/Services/GraphServices/Abstraction/IAttributesReceiver.cs @@ -2,5 +2,5 @@ namespace RelationshipAnalysis.Services.GraphServices.Abstraction; public interface IAttributesReceiver { - Task> GetAllAttributes(int id); + Task> GetAllAttributes(string name); } \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/EdgeAttributesReceiver.cs b/RelationshipAnalysis/Services/GraphServices/Edge/EdgeAttributesReceiver.cs similarity index 78% rename from RelationshipAnalysis/Services/GraphServices/EdgeAttributesReceiver.cs rename to RelationshipAnalysis/Services/GraphServices/Edge/EdgeAttributesReceiver.cs index e66520a..fada0c6 100644 --- a/RelationshipAnalysis/Services/GraphServices/EdgeAttributesReceiver.cs +++ b/RelationshipAnalysis/Services/GraphServices/Edge/EdgeAttributesReceiver.cs @@ -2,18 +2,18 @@ using RelationshipAnalysis.Context; using RelationshipAnalysis.Services.GraphServices.Abstraction; -namespace RelationshipAnalysis.Services.GraphServices; +namespace RelationshipAnalysis.Services.GraphServices.Edge; public class EdgeAttributesReceiver(IServiceProvider serviceProvider) : IAttributesReceiver { - public async Task> GetAllAttributes(int id) + public async Task> GetAllAttributes(string name) { using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); return await context.EdgeAttributes - .Where(ea => ea.EdgeValues.Any(v => v.Edge.EdgeCategoryId == id)) + .Where(ea => ea.EdgeValues.Any(v => v.Edge.EdgeCategory.EdgeCategoryName == name)) .Select(ea => ea.EdgeAttributeName).ToListAsync(); } } \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/EdgeInfoReceiver.cs b/RelationshipAnalysis/Services/GraphServices/Edge/EdgeInfoReceiver.cs similarity index 100% rename from RelationshipAnalysis/Services/GraphServices/EdgeInfoReceiver.cs rename to RelationshipAnalysis/Services/GraphServices/Edge/EdgeInfoReceiver.cs diff --git a/RelationshipAnalysis/Services/GraphServices/Abstraction/IExpansionGraphReceiver.cs b/RelationshipAnalysis/Services/GraphServices/Graph/Abstraction/IExpansionGraphReceiver.cs similarity index 100% rename from RelationshipAnalysis/Services/GraphServices/Abstraction/IExpansionGraphReceiver.cs rename to RelationshipAnalysis/Services/GraphServices/Graph/Abstraction/IExpansionGraphReceiver.cs diff --git a/RelationshipAnalysis/Services/GraphServices/Graph/Abstraction/IGraphSearcherService.cs b/RelationshipAnalysis/Services/GraphServices/Graph/Abstraction/IGraphSearcherService.cs new file mode 100644 index 0000000..f00a129 --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/Graph/Abstraction/IGraphSearcherService.cs @@ -0,0 +1,9 @@ +using RelationshipAnalysis.Dto; +using RelationshipAnalysis.Dto.Graph; + +namespace RelationshipAnalysis.Services.GraphServices.Graph.Abstraction; + +public interface IGraphSearcherService +{ + Task> Search(SearchGraphDto searchGraphDto); +} \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/ExpansionGraphReceiver.cs b/RelationshipAnalysis/Services/GraphServices/Graph/ExpansionGraphReceiver.cs similarity index 71% rename from RelationshipAnalysis/Services/GraphServices/ExpansionGraphReceiver.cs rename to RelationshipAnalysis/Services/GraphServices/Graph/ExpansionGraphReceiver.cs index 4ee02a4..1c22f4e 100644 --- a/RelationshipAnalysis/Services/GraphServices/ExpansionGraphReceiver.cs +++ b/RelationshipAnalysis/Services/GraphServices/Graph/ExpansionGraphReceiver.cs @@ -14,16 +14,19 @@ public async Task GetExpansionGraph(int nodeId, string sourceCategoryN using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); - var inputNodes = await GetInputNodes(sourceCategoryName, context); - var outputNodes = await GetOutputNodes(targetCategoryName, context); - var validEdges = await GetValidEdges(edgeCategoryName, context, inputNodes, outputNodes); + var inputNodes = await GetInputNodes(sourceCategoryName); + var outputNodes = await GetOutputNodes(targetCategoryName); + var validEdges = await GetValidEdges(edgeCategoryName, inputNodes, outputNodes); return graphDtoCreator.CreateResultGraphDto(inputNodes.Union(outputNodes).ToList(), validEdges); } - private async Task> GetValidEdges(string edgeCategoryName, ApplicationDbContext context, List inputNodes, + private async Task> GetValidEdges(string edgeCategoryName, List inputNodes, List outputNodes) { + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var validEdges = await context.Edges.Where(e => e.EdgeCategory.EdgeCategoryName == edgeCategoryName && inputNodes.Contains(e.NodeSource) && @@ -31,15 +34,21 @@ public async Task GetExpansionGraph(int nodeId, string sourceCategoryN return validEdges; } - private async Task> GetOutputNodes(string targetCategoryName, ApplicationDbContext context) + private async Task> GetOutputNodes(string targetCategoryName) { + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var outputNodes = await context.Nodes.Where(n => n.NodeCategory.NodeCategoryName == targetCategoryName).ToListAsync(); return outputNodes; } - private async Task> GetInputNodes(string sourceCategoryName, ApplicationDbContext context) + private async Task> GetInputNodes(string sourceCategoryName) { + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var inputNodes = await context.Nodes.Where(n => n.NodeCategory.NodeCategoryName == sourceCategoryName).ToListAsync(); return inputNodes; diff --git a/RelationshipAnalysis/Services/GraphServices/Graph/GraphSearcherService.cs b/RelationshipAnalysis/Services/GraphServices/Graph/GraphSearcherService.cs new file mode 100644 index 0000000..99d7d68 --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/Graph/GraphSearcherService.cs @@ -0,0 +1,143 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using RelationshipAnalysis.Dto; +using RelationshipAnalysis.Dto.Graph; +using RelationshipAnalysis.Enums; +using RelationshipAnalysis.Models.Graph.Edge; +using RelationshipAnalysis.Models.Graph.Node; +using RelationshipAnalysis.Services.GraphServices.Abstraction; +using RelationshipAnalysis.Services.GraphServices.Graph.Abstraction; +using ApplicationDbContext = RelationshipAnalysis.Context.ApplicationDbContext; + +namespace RelationshipAnalysis.Services.GraphServices.Graph; + +public class GraphSearcherService( + IGraphDtoCreator graphDtoCreator, + IServiceProvider serviceProvider, + [FromKeyedServices("node")] IAttributesReceiver nodeCategoryReceiver, + [FromKeyedServices("edge")] IAttributesReceiver edgeCategoryReceiver) : IGraphSearcherService +{ + public async Task> Search(SearchGraphDto searchGraphDto) + { + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + + var sourceAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.SourceCategoryName); + var targetAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.TargetCategoryName); + var edgeAttributes = await edgeCategoryReceiver.GetAllAttributes(searchGraphDto.EdgeCategoryName); + + var validation = await AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes); + if (validation.StatusCode != StatusCodeType.Success) + { + return validation; + } + + var sourceNodes = await context.Nodes + .Where(n => searchGraphDto.SourceCategoryName == (n.NodeCategory.NodeCategoryName)).ToListAsync(); + var targetNodes = await context.Nodes + .Where(n => searchGraphDto.TargetCategoryName == (n.NodeCategory.NodeCategoryName)).ToListAsync(); + + sourceNodes = sourceNodes.Where(sn => IsNodeValid(sn, searchGraphDto.SourceCategoryClauses)).ToList(); + targetNodes = targetNodes.Where(tn => IsNodeValid(tn, searchGraphDto.TargetCategoryClauses)).ToList(); + + var edges = await GetValidEdges(sourceNodes, targetNodes, searchGraphDto.SourceCategoryName, + searchGraphDto.TargetCategoryName, searchGraphDto.EdgeCategoryName); + + edges = edges.Where(e => IsEdgeValid(e, searchGraphDto.EdgeCategoryClauses)).ToList(); + + validation.Data = graphDtoCreator.CreateResultGraphDto(sourceNodes.Union(targetNodes).ToList(), edges); + return validation; + } + + + private bool IsNodeValid(Models.Graph.Node.Node node, Dictionary clauses) + { + var attributeValues = new Dictionary(); + node.Values.ToList().ForEach(nv => attributeValues.Add(nv.NodeAttribute.NodeAttributeName, nv.ValueData)); + + foreach (var kvp in clauses) + { + var actualValue = attributeValues[kvp.Key]; + if (!actualValue.StartsWith(kvp.Value)) return false; + } + + return true; + } + + private bool IsEdgeValid(Models.Graph.Edge.Edge edge, Dictionary clauses) + { + var attributeValues = new Dictionary(); + edge.EdgeValues.ToList().ForEach(ev => attributeValues.Add(ev.EdgeAttribute.EdgeAttributeName, ev.ValueData)); + + foreach (var kvp in clauses) + { + var actualValue = attributeValues[kvp.Key]; + if (!actualValue.StartsWith(kvp.Value)) return false; + } + + return true; + } + + private async Task> GetValidEdges(List sourceNodes, + List targetNodes, string sourceCategory, string targetCategory, + string edgeCategory) + { + var sourceNodeIds = sourceNodes.Select(n => n.NodeId).ToList(); + var targetNodeIds = targetNodes.Select(n => n.NodeId).ToList(); + + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + var edges = await context.Edges.Include(e => e.EdgeValues) + .ThenInclude(ev => ev.EdgeAttribute) + .Where(e => edgeCategory == (e.EdgeCategory.EdgeCategoryName) && + sourceNodeIds.Contains(e.EdgeSourceNodeId) && + targetNodeIds.Contains(e.EdgeDestinationNodeId)) + .ToListAsync(); + return edges; + } + + private async Task> AreClausesValid(SearchGraphDto searchGraphDto, + List sourceAttributes, List targetAttributes, + List edgeAttributes) + { + if (!searchGraphDto.SourceCategoryClauses.Keys.All(item => sourceAttributes.Contains(item))) + { + return NotFoundResult(Resources.InvalidClauseInSourceCategory); + } + + if (!searchGraphDto.TargetCategoryClauses.Keys.All(item => targetAttributes.Contains(item))) + { + return NotFoundResult(Resources.InvalidClauseInDestinationCategory); + } + + if (!searchGraphDto.EdgeCategoryClauses.Keys.All(item => edgeAttributes.Contains(item))) + { + return NotFoundResult(Resources.InvalidClauseInDestinationCategory); + } + + return SuccessResult(); + } + + private ActionResponse SuccessResult() + { + return new ActionResponse() + { + StatusCode = StatusCodeType.Success, + Data = null + }; + } + + private ActionResponse NotFoundResult(string message) + { + return new ActionResponse() + { + StatusCode = StatusCodeType.NotFound, + Data = new GraphDto() + { + Message = message + } + }; + } +} \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/NodeAttributesReceiver.cs b/RelationshipAnalysis/Services/GraphServices/Node/NodeAttributesReceiver.cs similarity index 69% rename from RelationshipAnalysis/Services/GraphServices/NodeAttributesReceiver.cs rename to RelationshipAnalysis/Services/GraphServices/Node/NodeAttributesReceiver.cs index 472e949..238d5f6 100644 --- a/RelationshipAnalysis/Services/GraphServices/NodeAttributesReceiver.cs +++ b/RelationshipAnalysis/Services/GraphServices/Node/NodeAttributesReceiver.cs @@ -2,17 +2,17 @@ using RelationshipAnalysis.Context; using RelationshipAnalysis.Services.GraphServices.Abstraction; -namespace RelationshipAnalysis.Services.GraphServices; +namespace RelationshipAnalysis.Services.GraphServices.Node; public class NodeAttributesReceiver(IServiceProvider serviceProvider) : IAttributesReceiver { - public async Task> GetAllAttributes(int id) + public async Task> GetAllAttributes(string name) { using var scope = serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); return await context.NodeAttributes - .Where(na => na.Values.Any(v => v.Node.NodeCategoryId == id)) + .Where(na => na.Values.Any(v => v.Node.NodeCategory.NodeCategoryName == name)) .Select(na => na.NodeAttributeName).ToListAsync(); } } \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/NodeInfoReceiver.cs b/RelationshipAnalysis/Services/GraphServices/Node/NodeInfoReceiver.cs similarity index 100% rename from RelationshipAnalysis/Services/GraphServices/NodeInfoReceiver.cs rename to RelationshipAnalysis/Services/GraphServices/Node/NodeInfoReceiver.cs