| 1365 | } |
| 1366 | |
| 1367 | static void handle_layout(cbm_http_conn_t *c, const cbm_http_req_t *req) { |
| 1368 | char project[256] = {0}; |
| 1369 | char max_str[32] = {0}; |
| 1370 | char graph_str[32] = {0}; |
| 1371 | |
| 1372 | if (!cbm_http_query_param(req->query, "project", project, (int)sizeof(project)) || |
| 1373 | project[0] == '\0') { |
| 1374 | cbm_http_replyf(c, 400, g_cors_json, "{\"error\":\"missing project parameter\"}"); |
| 1375 | return; |
| 1376 | } |
| 1377 | |
| 1378 | int max_nodes = 0; /* 0 → layout default budget */ |
| 1379 | if (cbm_http_query_param(req->query, "max_nodes", max_str, (int)sizeof(max_str))) { |
| 1380 | int v = atoi(max_str); |
| 1381 | if (v > 0) |
| 1382 | max_nodes = v; |
| 1383 | } |
| 1384 | |
| 1385 | /* graph=missed (#963): lay out the derived miss graph (shadow project |
| 1386 | * "<name>::missed" inside the SAME db file) instead of the code graph — |
| 1387 | * only files the indexer could not fully cover, as their file structure. |
| 1388 | * The db file still resolves from the validated base project name. */ |
| 1389 | bool missed_graph = false; |
| 1390 | if (cbm_http_query_param(req->query, "graph", graph_str, (int)sizeof(graph_str))) { |
| 1391 | missed_graph = strcmp(graph_str, "missed") == 0; |
| 1392 | } |
| 1393 | char scoped_project[320]; |
| 1394 | if (missed_graph) { |
| 1395 | cbm_store_coverage_shadow_project(scoped_project, sizeof(scoped_project), project); |
| 1396 | } else { |
| 1397 | snprintf(scoped_project, sizeof(scoped_project), "%s", project); |
| 1398 | } |
| 1399 | |
| 1400 | char db_path[1024]; |
| 1401 | db_path_for_project(project, db_path, sizeof(db_path)); |
| 1402 | |
| 1403 | if (!cbm_file_exists(db_path)) { |
| 1404 | cbm_http_replyf(c, 404, g_cors_json, "{\"error\":\"project not found\"}"); |
| 1405 | return; |
| 1406 | } |
| 1407 | |
| 1408 | cbm_store_t *store = cbm_store_open_path_query(db_path); |
| 1409 | if (!store) { |
| 1410 | cbm_http_replyf(c, 500, g_cors_json, "{\"error\":\"cannot open store\"}"); |
| 1411 | return; |
| 1412 | } |
| 1413 | |
| 1414 | cbm_layout_result_t *layout = |
| 1415 | cbm_layout_compute(store, scoped_project, CBM_LAYOUT_OVERVIEW, NULL, 0, max_nodes); |
| 1416 | |
| 1417 | /* Find linked projects from CROSS_* edges. Keep `store` open through the |
| 1418 | * linked-projects loop below so we can resolve target Route QNs against |
| 1419 | * the linked stores when populating cross_edges. */ |
| 1420 | char *linked[LAYOUT_MAX_LINKED]; |
| 1421 | int linked_count = find_cross_repo_targets(store, project, linked, LAYOUT_MAX_LINKED); |
| 1422 | |
| 1423 | if (!layout) { |
| 1424 | cbm_store_close(store); |
no test coverage detected