1313import java .util .LinkedHashMap ;
1414import java .util .List ;
1515import java .util .Map ;
16+ import java .util .Set ;
1617
1718/**
1819 * High-level query service wrapping GraphStore with caching.
@@ -24,90 +25,65 @@ public class QueryService {
2425
2526 private final GraphStore graphStore ;
2627 private final CodeIqConfig config ;
28+ private final StatsService statsService ;
2729
28- public QueryService (GraphStore graphStore , CodeIqConfig config ) {
30+ public QueryService (GraphStore graphStore , CodeIqConfig config , StatsService statsService ) {
2931 this .graphStore = graphStore ;
3032 this .config = config ;
33+ this .statsService = statsService ;
3134 }
3235
3336 @ Cacheable ("graph-stats" )
3437 public Map <String , Object > getStats () {
35- long nodeCount = graphStore .count ();
36- long edgeCount = graphStore .countEdges ();
37- long fileCount = graphStore .countDistinctFiles ();
38+ // Load full graph data and compute rich categorized stats
39+ List <CodeNode > nodes = graphStore .findAll ();
40+ List <CodeEdge > edges = nodes .stream ()
41+ .flatMap (n -> n .getEdges ().stream ())
42+ .toList ();
43+
44+ Map <String , Object > result = statsService .computeStats (nodes , edges );
3845
46+ // Also include raw counts and breakdowns for backward compat
3947 Map <String , Long > nodesByKind = new LinkedHashMap <>();
4048 for (Map <String , Object > row : graphStore .countNodesByKind ()) {
4149 nodesByKind .put ((String ) row .get ("kind" ), ((Number ) row .get ("cnt" )).longValue ());
4250 }
43-
4451 Map <String , Long > nodesByLayer = new LinkedHashMap <>();
4552 for (Map <String , Object > row : graphStore .countNodesByLayer ()) {
4653 nodesByLayer .put ((String ) row .get ("layer" ), ((Number ) row .get ("cnt" )).longValue ());
4754 }
4855
49- // Language breakdown from file extensions
50- Map <String , Long > languages = new LinkedHashMap <>();
51- for (Map <String , Object > row : graphStore .countByFileExtension ()) {
52- String ext = (String ) row .get ("ext" );
53- long cnt = ((Number ) row .get ("cnt" )).longValue ();
54- String lang = extToLanguage (ext );
55- languages .merge (lang , cnt , Long ::sum );
56- }
57-
58- // Return in ComputedStatsResponse format for frontend compatibility
59- Map <String , Object > graph = new LinkedHashMap <>();
60- graph .put ("nodes" , nodeCount );
61- graph .put ("edges" , edgeCount );
62- graph .put ("files" , fileCount );
63-
64- Map <String , Object > result = new LinkedHashMap <>();
65- result .put ("graph" , graph );
66- result .put ("languages" , languages );
67- result .put ("frameworks" , Map .of ());
68- result .put ("infra" , Map .of ("databases" , Map .of (), "messaging" , Map .of (), "cloud" , Map .of ()));
69- result .put ("connections" , Map .of ("rest" , Map .of ("total" , 0 , "by_method" , Map .of ()),
70- "grpc" , 0 , "websocket" , 0 , "producers" , 0 , "consumers" , 0 ));
71- result .put ("auth" , Map .of ());
72- result .put ("architecture" , Map .of ());
73- // Also include raw counts for backward compat
74- result .put ("node_count" , nodeCount );
75- result .put ("edge_count" , edgeCount );
56+ result .put ("node_count" , nodes .size ());
57+ result .put ("edge_count" , edges .size ());
7658 result .put ("nodes_by_kind" , nodesByKind );
7759 result .put ("nodes_by_layer" , nodesByLayer );
7860 return result ;
7961 }
8062
81- private static String extToLanguage (String ext ) {
82- if (ext == null ) return "unknown" ;
83- return switch (ext .toLowerCase ()) {
84- case "java" -> "java" ;
85- case "kt" , "kts" -> "kotlin" ;
86- case "py" -> "python" ;
87- case "js" , "mjs" , "cjs" -> "javascript" ;
88- case "ts" , "tsx" -> "typescript" ;
89- case "go" -> "go" ;
90- case "rs" -> "rust" ;
91- case "cs" -> "csharp" ;
92- case "scala" -> "scala" ;
93- case "cpp" , "cc" , "cxx" , "h" , "hpp" -> "cpp" ;
94- case "c" -> "c" ;
95- case "rb" -> "ruby" ;
96- case "proto" -> "protobuf" ;
97- case "yml" , "yaml" -> "yaml" ;
98- case "json" -> "json" ;
99- case "xml" -> "xml" ;
100- case "tf" -> "terraform" ;
101- case "sql" -> "sql" ;
102- case "md" -> "markdown" ;
103- case "html" , "htm" -> "html" ;
104- case "css" , "scss" , "sass" -> "css" ;
105- case "vue" -> "vue" ;
106- case "svelte" -> "svelte" ;
107- case "jsx" -> "jsx" ;
108- case "sh" , "bash" -> "shell" ;
109- default -> ext ;
110- };
63+ /**
64+ * Get detailed stats for a specific category, or all categories.
65+ * Categories: all, graph, languages, frameworks, infra, connections, auth, architecture
66+ */
67+ @ Cacheable (value = "detailed-stats" , key = "#category" )
68+ public Map <String , Object > getDetailedStats (String category ) {
69+ List <CodeNode > nodes = graphStore .findAll ();
70+ List <CodeEdge > edges = nodes .stream ()
71+ .flatMap (n -> n .getEdges ().stream ())
72+ .toList ();
73+
74+ if (category == null || "all" .equalsIgnoreCase (category )) {
75+ return statsService .computeStats (nodes , edges );
76+ }
77+ Map <String , Object > catResult = statsService .computeCategory (nodes , edges , category );
78+ if (catResult == null ) {
79+ Map <String , Object > error = new LinkedHashMap <>();
80+ error .put ("error" , "Unknown category: " + category
81+ + ". Valid: all, graph, languages, frameworks, infra, connections, auth, architecture" );
82+ return error ;
83+ }
84+ Map <String , Object > result = new LinkedHashMap <>();
85+ result .put (category .toLowerCase (), catResult );
86+ return result ;
11187 }
11288
11389 @ Cacheable ("kinds-list" )
@@ -352,6 +328,44 @@ public List<Map<String, Object>> searchGraph(String query, int limit) {
352328 return results .stream ().map (this ::nodeToMap ).toList ();
353329 }
354330
331+ /**
332+ * Find API endpoints related to an identifier (file, class, entity).
333+ * Searches for matching nodes, then traverses the graph to find connected endpoints.
334+ */
335+ public Map <String , Object > findRelatedEndpoints (String identifier ) {
336+ // Find nodes matching the identifier
337+ List <CodeNode > matches = graphStore .search (identifier , 50 );
338+
339+ // Collect endpoints: any match that IS an endpoint, plus neighbors of matches that are endpoints
340+ Set <String > seenIds = new java .util .LinkedHashSet <>();
341+ List <Map <String , Object >> endpoints = new ArrayList <>();
342+
343+ for (CodeNode match : matches ) {
344+ if (match .getKind () == NodeKind .ENDPOINT || match .getKind () == NodeKind .WEBSOCKET_ENDPOINT ) {
345+ if (seenIds .add (match .getId ())) {
346+ endpoints .add (nodeToMap (match ));
347+ }
348+ }
349+ // Check neighbors for connected endpoints
350+ List <CodeNode > neighbors = graphStore .findNeighbors (match .getId ());
351+ for (CodeNode neighbor : neighbors ) {
352+ if ((neighbor .getKind () == NodeKind .ENDPOINT || neighbor .getKind () == NodeKind .WEBSOCKET_ENDPOINT )
353+ && seenIds .add (neighbor .getId ())) {
354+ Map <String , Object > epMap = nodeToMap (neighbor );
355+ epMap .put ("connected_via" , match .getId ());
356+ endpoints .add (epMap );
357+ }
358+ }
359+ }
360+
361+ Map <String , Object > result = new LinkedHashMap <>();
362+ result .put ("identifier" , identifier );
363+ result .put ("endpoints" , endpoints );
364+ result .put ("count" , endpoints .size ());
365+ result .put ("searched_nodes" , matches .size ());
366+ return result ;
367+ }
368+
355369 // --- Topology ---
356370
357371 @ Cacheable ("topology" )
0 commit comments