POST /api/adr — save ADR content. Body: {"project":"...","content":"..."} */
| 818 | |
| 819 | /* POST /api/adr — save ADR content. Body: {"project":"...","content":"..."} */ |
| 820 | static void handle_adr_save(cbm_http_server_t *srv, cbm_http_conn_t *c, const cbm_http_req_t *req) { |
| 821 | if (req->body_len == 0 || req->body_len > 16384) { |
| 822 | cbm_http_replyf(c, 400, g_cors_json, "{\"error\":\"invalid body\"}"); |
| 823 | return; |
| 824 | } |
| 825 | |
| 826 | yyjson_doc *doc = yyjson_read(req->body, req->body_len, 0); |
| 827 | if (!doc) { |
| 828 | cbm_http_replyf(c, 400, g_cors_json, "{\"error\":\"invalid json\"}"); |
| 829 | return; |
| 830 | } |
| 831 | |
| 832 | yyjson_val *root = yyjson_doc_get_root(doc); |
| 833 | yyjson_val *v_proj = yyjson_obj_get(root, "project"); |
| 834 | yyjson_val *v_content = yyjson_obj_get(root, "content"); |
| 835 | if (!v_proj || !yyjson_is_str(v_proj) || !v_content || !yyjson_is_str(v_content)) { |
| 836 | yyjson_doc_free(doc); |
| 837 | cbm_http_replyf(c, 400, g_cors_json, "{\"error\":\"missing project or content\"}"); |
| 838 | return; |
| 839 | } |
| 840 | |
| 841 | const char *proj = yyjson_get_str(v_proj); |
| 842 | const char *content = yyjson_get_str(v_content); |
| 843 | |
| 844 | if (srv->mutation_begin && !srv->mutation_begin(srv->mutation_context, proj)) { |
| 845 | yyjson_doc_free(doc); |
| 846 | cbm_http_replyf(c, 423, g_cors_json, |
| 847 | "{\"error\":\"project is busy; retry after indexing\"}"); |
| 848 | return; |
| 849 | } |
| 850 | bool mutation_held = srv->mutation_begin != NULL; |
| 851 | |
| 852 | char db_path[1024]; |
| 853 | db_path_for_project(proj, db_path, sizeof(db_path)); |
| 854 | |
| 855 | cbm_store_t *store = cbm_store_open_path(db_path); |
| 856 | if (!store) { |
| 857 | if (mutation_held) { |
| 858 | srv->mutation_end(srv->mutation_context, proj); |
| 859 | } |
| 860 | yyjson_doc_free(doc); |
| 861 | cbm_http_replyf(c, 500, g_cors_json, "{\"error\":\"cannot open store\"}"); |
| 862 | return; |
| 863 | } |
| 864 | |
| 865 | int rc = cbm_store_adr_store(store, proj, content); |
| 866 | cbm_store_close(store); |
| 867 | if (mutation_held) { |
| 868 | srv->mutation_end(srv->mutation_context, proj); |
| 869 | } |
| 870 | /* proj/content are owned by doc; keep it alive through both SQLite and |
| 871 | * the mutation-end callback. */ |
| 872 | yyjson_doc_free(doc); |
| 873 | |
| 874 | if (rc == CBM_STORE_OK) { |
| 875 | cbm_http_replyf(c, 200, g_cors_json, "{\"saved\":true}"); |
| 876 | } else { |
| 877 | cbm_http_replyf(c, 500, g_cors_json, "{\"error\":\"save failed\"}"); |
no test coverage detected