From 152f1e6a217ef6ada22b18369ee81ae0c43cb93a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 03:40:20 +0000 Subject: [PATCH] Convert backend to Drogon HTTP API server Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- BUILD.md | 8 +- README.md | 3 +- backend/CMakeLists.txt | 4 +- backend/README.md | 59 +++++++++-- backend/conanfile.py | 2 +- backend/config.json | 43 ++++++++ backend/src/controllers/MergeController.cc | 112 +++++++++++++++++++++ backend/src/controllers/MergeController.h | 49 +++++++++ backend/src/main.cpp | 102 ++++++------------- 9 files changed, 294 insertions(+), 88 deletions(-) create mode 100644 backend/config.json create mode 100644 backend/src/controllers/MergeController.cc create mode 100644 backend/src/controllers/MergeController.h diff --git a/BUILD.md b/BUILD.md index 7bd84eb..d0172c5 100644 --- a/BUILD.md +++ b/BUILD.md @@ -19,7 +19,7 @@ WizardMerge/ ### C++ Backend -The backend implements the core three-way merge algorithm. +The backend implements the core three-way merge algorithm with an HTTP API server using Drogon. **Prerequisites:** - C++17 compiler (GCC 7+, Clang 6+, MSVC 2017+) @@ -33,11 +33,13 @@ cd backend ./build.sh ``` -**Usage:** +**Run the server:** ```bash -./build/wizardmerge-cli base.txt ours.txt theirs.txt output.txt +./build/wizardmerge-cli ``` +The HTTP server will start on port 8080. Use the POST /api/merge endpoint to perform merges. + See [backend/README.md](backend/README.md) for details. ### TypeScript Frontend diff --git a/README.md b/README.md index 55b4fc2..2cc2789 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ WizardMerge uses a multi-frontend architecture with a high-performance C++ backe - **Location**: `backend/` - **Build System**: CMake + Ninja - **Package Manager**: Conan -- **Features**: Three-way merge algorithm, conflict detection, auto-resolution +- **Web Framework**: Drogon +- **Features**: Three-way merge algorithm, conflict detection, auto-resolution, HTTP API ### Frontend (TypeScript/Next.js) - **Location**: `frontend/` diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt index 0050524..b250072 100644 --- a/backend/CMakeLists.txt +++ b/backend/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # Find dependencies via Conan +find_package(Drogon CONFIG REQUIRED) find_package(GTest QUIET) # Library sources @@ -23,9 +24,10 @@ target_include_directories(wizardmerge # Executable add_executable(wizardmerge-cli src/main.cpp + src/controllers/MergeController.cc ) -target_link_libraries(wizardmerge-cli PRIVATE wizardmerge) +target_link_libraries(wizardmerge-cli PRIVATE wizardmerge Drogon::Drogon) # Tests (if GTest is available) if(GTest_FOUND) diff --git a/backend/README.md b/backend/README.md index 32ac40b..1200a90 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,6 @@ # WizardMerge C++ Backend -This is the C++ backend for WizardMerge implementing the core merge algorithms. +This is the C++ backend for WizardMerge implementing the core merge algorithms with a Drogon-based HTTP API. ## Build System @@ -8,6 +8,7 @@ This is the C++ backend for WizardMerge implementing the core merge algorithms. - **Package Manager**: Conan - **CMake**: Version 3.15+ - **C++ Standard**: C++17 +- **Web Framework**: Drogon ## Building @@ -36,8 +37,8 @@ cd build cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release ninja -# Run the executable -./wizardmerge-cli base.txt ours.txt theirs.txt output.txt +# Run the HTTP server +./wizardmerge-cli ``` ## Testing @@ -53,12 +54,16 @@ ninja test backend/ ├── CMakeLists.txt # CMake build configuration ├── conanfile.py # Conan package definition +├── config.json # Drogon server configuration ├── include/ # Public headers │ └── wizardmerge/ │ └── merge/ │ └── three_way_merge.h ├── src/ # Implementation files │ ├── main.cpp +│ ├── controllers/ +│ │ ├── MergeController.h +│ │ └── MergeController.cc │ └── merge/ │ └── three_way_merge.cpp └── tests/ # Unit tests @@ -69,16 +74,48 @@ backend/ - Three-way merge algorithm (Phase 1.1 from ROADMAP) - Conflict detection and marking - Auto-resolution of common patterns -- Command-line interface +- HTTP API server using Drogon framework +- JSON-based request/response -## Usage +## API Usage + +### Start the server ```sh -wizardmerge-cli +./wizardmerge-cli [config.json] ``` -Arguments: -- `base`: Common ancestor version -- `ours`: Current branch version -- `theirs`: Branch being merged -- `output`: Output file for merged result +The server will start on port 8080 by default (configurable in config.json). + +### POST /api/merge + +Perform a three-way merge operation. + +**Request:** +```json +{ + "base": ["line1", "line2", "line3"], + "ours": ["line1", "line2_modified", "line3"], + "theirs": ["line1", "line2", "line3_modified"] +} +``` + +**Response:** +```json +{ + "merged": ["line1", "line2_modified", "line3_modified"], + "conflicts": [], + "has_conflicts": false +} +``` + +**Example with curl:** +```sh +curl -X POST http://localhost:8080/api/merge \ + -H "Content-Type: application/json" \ + -d '{ + "base": ["line1", "line2", "line3"], + "ours": ["line1", "line2_ours", "line3"], + "theirs": ["line1", "line2_theirs", "line3"] + }' +``` diff --git a/backend/conanfile.py b/backend/conanfile.py index b6abd20..b396fb3 100644 --- a/backend/conanfile.py +++ b/backend/conanfile.py @@ -18,7 +18,7 @@ class WizardMergeConan(ConanFile): exports_sources = "CMakeLists.txt", "src/*", "include/*" # Dependencies - requires = [] + requires = ["drogon/1.9.3"] generators = "CMakeDeps", "CMakeToolchain" diff --git a/backend/config.json b/backend/config.json new file mode 100644 index 0000000..15d99dd --- /dev/null +++ b/backend/config.json @@ -0,0 +1,43 @@ +{ + "app": { + "threads_num": 4, + "enable_session": false, + "session_timeout": 0, + "document_root": "", + "upload_path": "./uploads", + "file_types": [ + "json" + ], + "max_connections": 100000, + "max_connections_per_ip": 0, + "load_libs": [], + "log": { + "log_path": "./logs", + "logfile_base_name": "wizardmerge", + "log_size_limit": 100000000, + "log_level": "INFO" + }, + "run_as_daemon": false, + "relaunch_on_error": false, + "use_sendfile": true, + "use_gzip": true, + "use_brotli": false, + "static_files_cache_time": 0, + "simple_controllers_map": [], + "idle_connection_timeout": 60, + "server_header_field": "WizardMerge/0.1.0", + "enable_server_header": true, + "keepalive_requests": 0, + "pipelining_requests": 0, + "client_max_body_size": "10M", + "client_max_memory_body_size": "10M", + "client_max_websocket_message_size": "128K" + }, + "listeners": [ + { + "address": "0.0.0.0", + "port": 8080, + "https": false + } + ] +} diff --git a/backend/src/controllers/MergeController.cc b/backend/src/controllers/MergeController.cc new file mode 100644 index 0000000..00574c0 --- /dev/null +++ b/backend/src/controllers/MergeController.cc @@ -0,0 +1,112 @@ +/** + * @file MergeController.cc + * @brief Implementation of HTTP controller for merge operations + */ + +#include "MergeController.h" +#include "wizardmerge/merge/three_way_merge.h" +#include + +using namespace wizardmerge::controllers; +using namespace wizardmerge::merge; + +void MergeController::merge( + const HttpRequestPtr &req, + std::function &&callback) { + + // Parse request JSON + auto jsonPtr = req->getJsonObject(); + if (!jsonPtr) { + Json::Value error; + error["error"] = "Invalid JSON in request body"; + auto resp = HttpResponse::newHttpJsonResponse(error); + resp->setStatusCode(k400BadRequest); + callback(resp); + return; + } + + const auto &json = *jsonPtr; + + // Validate required fields + if (!json.isMember("base") || !json.isMember("ours") || !json.isMember("theirs")) { + Json::Value error; + error["error"] = "Missing required fields: base, ours, theirs"; + auto resp = HttpResponse::newHttpJsonResponse(error); + resp->setStatusCode(k400BadRequest); + callback(resp); + return; + } + + // Convert JSON arrays to std::vector + std::vector base; + std::vector ours; + std::vector theirs; + + try { + for (const auto &line : json["base"]) { + base.push_back(line.asString()); + } + for (const auto &line : json["ours"]) { + ours.push_back(line.asString()); + } + for (const auto &line : json["theirs"]) { + theirs.push_back(line.asString()); + } + } catch (const std::exception &e) { + Json::Value error; + error["error"] = "Invalid array format in request"; + auto resp = HttpResponse::newHttpJsonResponse(error); + resp->setStatusCode(k400BadRequest); + callback(resp); + return; + } + + // Perform merge + auto result = three_way_merge(base, ours, theirs); + + // Auto-resolve simple conflicts + result = auto_resolve(result); + + // Build response JSON + Json::Value response; + Json::Value mergedArray(Json::arrayValue); + for (const auto &line : result.merged_lines) { + mergedArray.append(line.content); + } + response["merged"] = mergedArray; + + // Add conflicts information + Json::Value conflictsArray(Json::arrayValue); + for (const auto &conflict : result.conflicts) { + Json::Value conflictObj; + conflictObj["start_line"] = Json::Value::UInt64(conflict.start_line); + conflictObj["end_line"] = Json::Value::UInt64(conflict.end_line); + + Json::Value baseLines(Json::arrayValue); + for (const auto &line : conflict.base_lines) { + baseLines.append(line.content); + } + conflictObj["base_lines"] = baseLines; + + Json::Value ourLines(Json::arrayValue); + for (const auto &line : conflict.our_lines) { + ourLines.append(line.content); + } + conflictObj["our_lines"] = ourLines; + + Json::Value theirLines(Json::arrayValue); + for (const auto &line : conflict.their_lines) { + theirLines.append(line.content); + } + conflictObj["their_lines"] = theirLines; + + conflictsArray.append(conflictObj); + } + response["conflicts"] = conflictsArray; + response["has_conflicts"] = result.has_conflicts(); + + // Return successful response + auto resp = HttpResponse::newHttpJsonResponse(response); + resp->setStatusCode(k200OK); + callback(resp); +} diff --git a/backend/src/controllers/MergeController.h b/backend/src/controllers/MergeController.h new file mode 100644 index 0000000..4d25cd3 --- /dev/null +++ b/backend/src/controllers/MergeController.h @@ -0,0 +1,49 @@ +/** + * @file MergeController.h + * @brief HTTP controller for merge operations + */ + +#ifndef WIZARDMERGE_CONTROLLERS_MERGE_CONTROLLER_H +#define WIZARDMERGE_CONTROLLERS_MERGE_CONTROLLER_H + +#include + +using namespace drogon; + +namespace wizardmerge { +namespace controllers { + +/** + * @brief HTTP controller for three-way merge API + */ +class MergeController : public HttpController { + public: + METHOD_LIST_BEGIN + // POST /api/merge - Perform three-way merge + ADD_METHOD_TO(MergeController::merge, "/api/merge", Post); + METHOD_LIST_END + + /** + * @brief Perform three-way merge operation + * + * Request body should be JSON: + * { + * "base": ["line1", "line2", ...], + * "ours": ["line1", "line2", ...], + * "theirs": ["line1", "line2", ...] + * } + * + * Response: + * { + * "merged": ["line1", "line2", ...], + * "conflicts": [...] + * } + */ + void merge(const HttpRequestPtr &req, + std::function &&callback); +}; + +} // namespace controllers +} // namespace wizardmerge + +#endif // WIZARDMERGE_CONTROLLERS_MERGE_CONTROLLER_H diff --git a/backend/src/main.cpp b/backend/src/main.cpp index c8c45a9..0e4697a 100644 --- a/backend/src/main.cpp +++ b/backend/src/main.cpp @@ -1,83 +1,43 @@ /** * @file main.cpp - * @brief Command-line interface for WizardMerge + * @brief HTTP API server for WizardMerge using Drogon framework */ #include -#include -#include -#include -#include "wizardmerge/merge/three_way_merge.h" +#include +#include "controllers/MergeController.h" -using namespace wizardmerge::merge; - -/** - * @brief Read lines from a file - */ -std::vector read_file(const std::string& filename) { - std::vector lines; - std::ifstream file(filename); - std::string line; - - while (std::getline(file, line)) { - lines.push_back(line); - } - - return lines; -} - -/** - * @brief Write lines to a file - */ -void write_file(const std::string& filename, const std::vector& lines) { - std::ofstream file(filename); - for (const auto& line : lines) { - file << line.content << '\n'; - } -} +using namespace drogon; int main(int argc, char* argv[]) { - if (argc != 5) { - std::cerr << "Usage: " << argv[0] << " \n"; - std::cerr << "Performs three-way merge on three file versions.\n"; + std::cout << "WizardMerge - Intelligent Merge Conflict Resolution API\n"; + std::cout << "======================================================\n"; + std::cout << "Starting HTTP server...\n\n"; + + // Load configuration from file + std::string config_file = "config.json"; + if (argc > 1) { + config_file = argv[1]; + } + + try { + // Load configuration and start server + app().loadConfigFile(config_file); + + std::cout << "Server will listen on port " + << app().getListeners()[0].toPort << "\n"; + std::cout << "Available endpoints:\n"; + std::cout << " POST /api/merge - Three-way merge API\n"; + std::cout << "\nPress Ctrl+C to stop the server.\n\n"; + + // Run the application + app().run(); + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << '\n'; + std::cerr << "Failed to load config file: " << config_file << '\n'; + std::cerr << "Usage: " << argv[0] << " [config.json]\n"; return 1; } - std::string base_file = argv[1]; - std::string ours_file = argv[2]; - std::string theirs_file = argv[3]; - std::string output_file = argv[4]; - - std::cout << "WizardMerge - Intelligent Merge Conflict Resolution\n"; - std::cout << "===================================================\n"; - std::cout << "Base: " << base_file << '\n'; - std::cout << "Ours: " << ours_file << '\n'; - std::cout << "Theirs: " << theirs_file << '\n'; - std::cout << "Output: " << output_file << '\n'; - std::cout << '\n'; - - // Read input files - auto base = read_file(base_file); - auto ours = read_file(ours_file); - auto theirs = read_file(theirs_file); - - // Perform merge - auto result = three_way_merge(base, ours, theirs); - - // Auto-resolve simple conflicts - result = auto_resolve(result); - - // Write output - write_file(output_file, result.merged_lines); - - // Report results - if (result.has_conflicts()) { - std::cout << "Merge completed with " << result.conflicts.size() - << " conflict(s).\n"; - std::cout << "Please review and resolve conflicts in: " << output_file << '\n'; - return 1; - } else { - std::cout << "Merge completed successfully with no conflicts.\n"; - return 0; - } + return 0; }