| 1493 | } |
| 1494 | |
| 1495 | static void handle_layout(cbm_http_conn_t *c, const cbm_http_req_t *req) { |
| 1496 | char project[256] = {0}; |
| 1497 | char max_str[32] = {0}; |
| 1498 | char graph_str[32] = {0}; |
| 1499 | |
| 1500 | if (!cbm_http_query_param(req->query, "project", project, (int)sizeof(project)) || |
| 1501 | project[0] == '\0') { |
| 1502 | cbm_http_replyf(c, 400, g_cors_json, "{\"error\":\"missing project parameter\"}"); |
| 1503 | return; |
| 1504 | } |
| 1505 | |
| 1506 | int max_nodes = 0; /* 0 → layout default budget */ |
| 1507 | if (cbm_http_query_param(req->query, "max_nodes", max_str, (int)sizeof(max_str))) { |
| 1508 | int v = atoi(max_str); |
| 1509 | if (v > 0) |
| 1510 | max_nodes = v; |
| 1511 | } |
| 1512 | |
| 1513 | /* graph=missed (#963): lay out the derived miss graph (shadow project |
| 1514 | * "<name>::missed" inside the SAME db file) instead of the code graph — |
| 1515 | * only files the indexer could not fully cover, as their file structure. |
| 1516 | * The db file still resolves from the validated base project name. */ |
| 1517 | bool missed_graph = false; |
| 1518 | if (cbm_http_query_param(req->query, "graph", graph_str, (int)sizeof(graph_str))) { |
| 1519 | missed_graph = strcmp(graph_str, "missed") == 0; |
| 1520 | } |
| 1521 | char scoped_project[320]; |
| 1522 | if (missed_graph) { |
| 1523 | cbm_store_coverage_shadow_project(scoped_project, sizeof(scoped_project), project); |
| 1524 | } else { |
| 1525 | snprintf(scoped_project, sizeof(scoped_project), "%s", project); |
| 1526 | } |
| 1527 | |
| 1528 | char db_path[1024]; |
| 1529 | db_path_for_project(project, db_path, sizeof(db_path)); |
| 1530 | |
| 1531 | if (!cbm_file_exists(db_path)) { |
| 1532 | cbm_http_replyf(c, 404, g_cors_json, "{\"error\":\"project not found\"}"); |
| 1533 | return; |
| 1534 | } |
| 1535 | |
| 1536 | cbm_store_t *store = cbm_store_open_path_query(db_path); |
| 1537 | if (!store) { |
| 1538 | cbm_http_replyf(c, 500, g_cors_json, "{\"error\":\"cannot open store\"}"); |
| 1539 | return; |
| 1540 | } |
| 1541 | |
| 1542 | cbm_layout_result_t *layout = |
| 1543 | cbm_layout_compute(store, scoped_project, CBM_LAYOUT_OVERVIEW, NULL, 0, max_nodes); |
| 1544 | |
| 1545 | /* Find linked projects from CROSS_* edges. Keep `store` open through the |
| 1546 | * linked-projects loop below so we can resolve target Route QNs against |
| 1547 | * the linked stores when populating cross_edges. */ |
| 1548 | char *linked[LAYOUT_MAX_LINKED]; |
| 1549 | int linked_count = find_cross_repo_targets(store, project, linked, LAYOUT_MAX_LINKED); |
| 1550 | |
| 1551 | if (!layout) { |
| 1552 | cbm_store_close(store); |
no test coverage detected