(repo_path: Optional[str] = None, cypher_query: Optional[str] = None)
| 141 | |
| 142 | @app.get("/api/graph") |
| 143 | async def get_graph(repo_path: Optional[str] = None, cypher_query: Optional[str] = None): |
| 144 | if not db_manager: |
| 145 | raise HTTPException(status_code=500, detail="Database not initialized") |
| 146 | |
| 147 | get_eid = _get_eid |
| 148 | |
| 149 | try: |
| 150 | nodes_dict = {} |
| 151 | edges = [] |
| 152 | |
| 153 | print(f"DEBUG: Starting get_graph with repo_path={repo_path}", flush=True) |
| 154 | |
| 155 | # This endpoint only ever reads. Open the session in READ access mode so |
| 156 | # the database enforces read-only (Neo4j default_access_mode / FalkorDB |
| 157 | # GRAPH.RO_QUERY) in addition to the regex guard on any user query. |
| 158 | with db_manager.get_driver().session(**_read_session_kwargs(db_manager)) as session: |
| 159 | if cypher_query: |
| 160 | if not _is_read_only_cypher(cypher_query): |
| 161 | raise HTTPException( |
| 162 | status_code=400, |
| 163 | detail=( |
| 164 | "This endpoint only supports read-only Cypher queries. " |
| 165 | "Prohibited keywords like CREATE, MERGE, DELETE, SET, REMOVE, " |
| 166 | "DROP, or CALL apoc are not allowed." |
| 167 | ), |
| 168 | ) |
| 169 | print(f"DEBUG: Executing custom query: {cypher_query}", flush=True) |
| 170 | result = session.run(cypher_query) |
| 171 | elif repo_path: |
| 172 | repo_path = str(Path(repo_path).resolve()) |
| 173 | print(f"DEBUG: Fetching subgraph for: {repo_path}", flush=True) |
| 174 | # Get all nodes within the repository scope |
| 175 | query = """ |
| 176 | MATCH (r:Repository {path: $repo_path}) |
| 177 | OPTIONAL MATCH (r)-[:CONTAINS*0..]->(n) |
| 178 | WITH DISTINCT r, COLLECT(DISTINCT n) as repo_nodes |
| 179 | UNWIND repo_nodes as node |
| 180 | OPTIONAL MATCH (node)-[rel]->(target) |
| 181 | WITH r, node, rel, target, repo_nodes |
| 182 | WHERE target IN repo_nodes OR target = r |
| 183 | RETURN node as n, rel, target as m |
| 184 | """ |
| 185 | result = session.run(query, repo_path=repo_path) |
| 186 | else: |
| 187 | print("DEBUG: Fetching global graph", flush=True) |
| 188 | query = "MATCH (n) OPTIONAL MATCH (n)-[rel]->(m) RETURN n, rel, m LIMIT 50000" |
| 189 | result = session.run(query) |
| 190 | |
| 191 | record_count = 0 |
| 192 | for record in result: |
| 193 | record_count += 1 |
| 194 | # Use .get() to avoid KeyError if the query doesn't return all fields (n, rel, m) |
| 195 | for key in ['n', 'm']: |
| 196 | try: |
| 197 | node = record.get(key) |
| 198 | if node: |
| 199 | eid = get_eid(node) |
| 200 | if eid and eid not in nodes_dict: |
nothing calls this directly
no test coverage detected