107 lines
2.4 KiB
Go
107 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/rand/v2"
|
|
|
|
ops "fn-registry/fn_operations"
|
|
)
|
|
|
|
// searchEntitiesFTS searches entities using FTS and returns matching entities.
|
|
func searchEntitiesFTS(db *ops.DB, query string) ([]ops.Entity, error) {
|
|
if query == "" {
|
|
return db.ListEntities("", "")
|
|
}
|
|
return db.SearchEntities(query, "")
|
|
}
|
|
|
|
// searchGraph returns a GraphData subgraph containing matching entities and their direct relations.
|
|
func searchGraph(db *ops.DB, query string) (GraphData, error) {
|
|
matches, err := searchEntitiesFTS(db, query)
|
|
if err != nil {
|
|
return GraphData{}, fmt.Errorf("searching entities: %w", err)
|
|
}
|
|
|
|
if len(matches) == 0 {
|
|
return GraphData{}, nil
|
|
}
|
|
|
|
matchIDs := map[string]bool{}
|
|
for _, e := range matches {
|
|
matchIDs[e.ID] = true
|
|
}
|
|
|
|
// Get all relations and filter to those touching matched entities
|
|
allRelations, err := db.ListRelations("")
|
|
if err != nil {
|
|
return GraphData{}, err
|
|
}
|
|
|
|
// Collect neighbor IDs
|
|
neighborIDs := map[string]bool{}
|
|
var subRels []ops.Relation
|
|
for _, r := range allRelations {
|
|
if matchIDs[r.FromEntity] || matchIDs[r.ToEntity] {
|
|
subRels = append(subRels, r)
|
|
neighborIDs[r.FromEntity] = true
|
|
neighborIDs[r.ToEntity] = true
|
|
}
|
|
}
|
|
|
|
// Merge match IDs and neighbor IDs
|
|
for id := range matchIDs {
|
|
neighborIDs[id] = true
|
|
}
|
|
|
|
// Get all entities in the subgraph
|
|
allEntities, err := db.ListEntities("", "")
|
|
if err != nil {
|
|
return GraphData{}, err
|
|
}
|
|
|
|
degree := map[string]int{}
|
|
for _, r := range subRels {
|
|
degree[r.FromEntity]++
|
|
degree[r.ToEntity]++
|
|
}
|
|
|
|
nodes := make([]GraphNode, 0)
|
|
for _, e := range allEntities {
|
|
if !neighborIDs[e.ID] {
|
|
continue
|
|
}
|
|
color, ok := entityTypeColors[e.TypeRef]
|
|
if !ok {
|
|
color = "#95a5a6"
|
|
}
|
|
size := 8.0 + math.Min(float64(degree[e.ID])*2.0, 20.0)
|
|
if rs, ok := e.Metadata["risk_score"]; ok {
|
|
if v, ok := toFloat64(rs); ok {
|
|
size += v * 0.1
|
|
}
|
|
}
|
|
nodes = append(nodes, GraphNode{
|
|
ID: e.ID, Label: e.Name, Type: e.TypeRef,
|
|
Color: color, Size: size,
|
|
X: rand.Float64()*100 - 50, Y: rand.Float64()*100 - 50,
|
|
Extra: e.Metadata,
|
|
})
|
|
}
|
|
|
|
edges := make([]GraphEdge, 0)
|
|
for _, r := range subRels {
|
|
w := 1.0
|
|
if r.Weight != nil {
|
|
w = *r.Weight
|
|
}
|
|
edges = append(edges, GraphEdge{
|
|
ID: r.ID, Source: r.FromEntity, Target: r.ToEntity,
|
|
Label: r.Name, Color: "#ffffff30",
|
|
Size: math.Max(w*3, 0.5), Type: "arrow",
|
|
})
|
|
}
|
|
|
|
return GraphData{Nodes: nodes, Edges: edges}, nil
|
|
}
|