Convert backend to Drogon HTTP API server

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-26 03:40:20 +00:00
parent 5ea670db23
commit 152f1e6a21
9 changed files with 294 additions and 88 deletions

View File

@@ -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

View File

@@ -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/`

View File

@@ -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)

View File

@@ -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 <base> <ours> <theirs> <output>
./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"]
}'
```

View File

@@ -18,7 +18,7 @@ class WizardMergeConan(ConanFile):
exports_sources = "CMakeLists.txt", "src/*", "include/*"
# Dependencies
requires = []
requires = ["drogon/1.9.3"]
generators = "CMakeDeps", "CMakeToolchain"

43
backend/config.json Normal file
View File

@@ -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
}
]
}

View File

@@ -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 <json/json.h>
using namespace wizardmerge::controllers;
using namespace wizardmerge::merge;
void MergeController::merge(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&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::string>
std::vector<std::string> base;
std::vector<std::string> ours;
std::vector<std::string> 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);
}

View File

@@ -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 <drogon/HttpController.h>
using namespace drogon;
namespace wizardmerge {
namespace controllers {
/**
* @brief HTTP controller for three-way merge API
*/
class MergeController : public HttpController<MergeController> {
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<void(const HttpResponsePtr &)> &&callback);
};
} // namespace controllers
} // namespace wizardmerge
#endif // WIZARDMERGE_CONTROLLERS_MERGE_CONTROLLER_H

View File

@@ -1,83 +1,43 @@
/**
* @file main.cpp
* @brief Command-line interface for WizardMerge
* @brief HTTP API server for WizardMerge using Drogon framework
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include "wizardmerge/merge/three_way_merge.h"
#include <drogon/drogon.h>
#include "controllers/MergeController.h"
using namespace wizardmerge::merge;
/**
* @brief Read lines from a file
*/
std::vector<std::string> read_file(const std::string& filename) {
std::vector<std::string> 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<Line>& 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] << " <base> <ours> <theirs> <output>\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;
}