7 changed files with 391 additions and 9 deletions
@ -0,0 +1,101 @@
|
||||
#include "infrastructure/http/HttpServer.h" |
||||
#include <iostream> |
||||
|
||||
namespace nxl::autostore::infrastructure { |
||||
|
||||
HttpServer::HttpServer(int port, const std::string& host) |
||||
: host(host), port(port), running(false) |
||||
{} |
||||
|
||||
HttpServer::~HttpServer() |
||||
{ |
||||
if (running) { |
||||
stop(); |
||||
} |
||||
} |
||||
|
||||
bool HttpServer::start() |
||||
{ |
||||
if (running) { |
||||
return true; |
||||
} |
||||
|
||||
// Set up error handler
|
||||
server.set_error_handler([](const auto& req, auto& res) { |
||||
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>"; |
||||
char buf[BUFSIZ]; |
||||
snprintf(buf, sizeof(buf), fmt, res.status); |
||||
res.set_content(buf, "text/html"); |
||||
}); |
||||
|
||||
// Set up exception handler
|
||||
server.set_exception_handler( |
||||
[](const auto& req, auto& res, std::exception_ptr ep) { |
||||
auto fmt = "<h1>Error 500</h1><p>%s</p>"; |
||||
char buf[BUFSIZ]; |
||||
try { |
||||
std::rethrow_exception(ep); |
||||
} catch (std::exception& e) { |
||||
snprintf(buf, sizeof(buf), fmt, e.what()); |
||||
} catch (...) { |
||||
snprintf(buf, sizeof(buf), fmt, "Unknown Exception"); |
||||
} |
||||
res.set_content(buf, "text/html"); |
||||
res.status = 500; |
||||
}); |
||||
|
||||
std::cout << "Starting HTTP server on " << host << ":" << port << std::endl; |
||||
|
||||
// Start server in a separate thread
|
||||
serverThread = std::thread([this]() { |
||||
std::cout << "Server thread started, listening on " << host << ":" << port |
||||
<< std::endl; |
||||
bool listenResult = server.listen(host.c_str(), port); |
||||
std::cout << "Server stopped, listen result: " << listenResult << std::endl; |
||||
}); |
||||
|
||||
// Give the server a moment to start
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500)); |
||||
|
||||
// Check if server is listening by trying to bind to the port
|
||||
if (!server.is_running()) { |
||||
std::cerr << "Failed to start HTTP server - server is not running" |
||||
<< std::endl; |
||||
if (serverThread.joinable()) { |
||||
serverThread.join(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
running = true; |
||||
std::cout << "HTTP server is running" << std::endl; |
||||
return true; |
||||
} |
||||
|
||||
void HttpServer::stop() |
||||
{ |
||||
if (!running) { |
||||
return; |
||||
} |
||||
|
||||
std::cout << "Stopping HTTP server..." << std::endl; |
||||
server.stop(); |
||||
|
||||
// Wait for the server to stop
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500)); |
||||
|
||||
running = false; |
||||
std::cout << "HTTP server stopped" << std::endl; |
||||
} |
||||
|
||||
bool HttpServer::isRunning() const |
||||
{ |
||||
return running; |
||||
} |
||||
|
||||
httplib::Server& HttpServer::getServer() |
||||
{ |
||||
return server; |
||||
} |
||||
|
||||
} // namespace nxl::autostore::infrastructure
|
||||
@ -0,0 +1,30 @@
|
||||
#pragma once |
||||
|
||||
#include <httplib.h> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <thread> |
||||
|
||||
namespace nxl::autostore::infrastructure { |
||||
|
||||
class HttpServer |
||||
{ |
||||
public: |
||||
explicit HttpServer(int port = 8080, const std::string& host = "0.0.0.0"); |
||||
~HttpServer(); |
||||
|
||||
bool start(); |
||||
void stop(); |
||||
bool isRunning() const; |
||||
|
||||
httplib::Server& getServer(); |
||||
|
||||
private: |
||||
std::string host; |
||||
int port; |
||||
bool running; |
||||
httplib::Server server; |
||||
std::thread serverThread; |
||||
}; |
||||
|
||||
} // namespace nxl::autostore::infrastructure
|
||||
@ -0,0 +1,121 @@
|
||||
#include "webapi/controllers/StoreController.h" |
||||
#include <chrono> |
||||
#include <ctime> |
||||
#include <iomanip> |
||||
#include <sstream> |
||||
|
||||
namespace nxl::autostore::webapi { |
||||
|
||||
StoreController::StoreController(application::IItemRepository& itemRepository, |
||||
application::IClock& clock, |
||||
application::IOrderService& orderService) |
||||
: addItemUseCase(itemRepository, clock, orderService) |
||||
{} |
||||
|
||||
void StoreController::registerRoutes(httplib::Server& server) |
||||
{ |
||||
server.Post("/api/items", |
||||
[this](const httplib::Request& req, httplib::Response& res) { |
||||
this->addItem(req, res); |
||||
}); |
||||
} |
||||
|
||||
void StoreController::addItem(const httplib::Request& req, |
||||
httplib::Response& res) |
||||
{ |
||||
try { |
||||
if (req.body.empty()) { |
||||
res.status = 400; |
||||
res.set_content(createErrorResponse("Request body is empty"), |
||||
"application/json"); |
||||
return; |
||||
} |
||||
|
||||
auto item = parseItemFromJson(req.body); |
||||
|
||||
// Execute the use case with a simple presenter
|
||||
bool success = false; |
||||
addItemUseCase.execute(std::move(item), |
||||
[&success](int result) { success = (result == 1); }); |
||||
|
||||
if (success) { |
||||
res.status = 201; |
||||
res.set_content(createSuccessResponse(item), "application/json"); |
||||
} else { |
||||
res.status = 500; |
||||
res.set_content(createErrorResponse("Failed to add item"), |
||||
"application/json"); |
||||
} |
||||
} catch (const std::exception& e) { |
||||
res.status = 400; |
||||
res.set_content(createErrorResponse(e.what()), "application/json"); |
||||
} |
||||
} |
||||
|
||||
domain::Item StoreController::parseItemFromJson(const std::string& jsonBody) |
||||
{ |
||||
auto json = nlohmann::json::parse(jsonBody); |
||||
|
||||
domain::Item item; |
||||
item.id = json.value("id", ""); |
||||
item.name = json.value("name", ""); |
||||
item.orderUrl = json.value("orderUrl", ""); |
||||
item.userId = json.value("userId", "default-user"); |
||||
|
||||
// Parse expiration date
|
||||
if (json.contains("expirationDate")) { |
||||
std::string dateStr = json["expirationDate"]; |
||||
std::tm tm = {}; |
||||
std::istringstream ss(dateStr); |
||||
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S"); |
||||
if (ss.fail()) { |
||||
throw std::runtime_error("Invalid expiration date format. Use ISO 8601 " |
||||
"format: YYYY-MM-DDTHH:MM:SS"); |
||||
} |
||||
item.expirationDate = |
||||
std::chrono::system_clock::from_time_t(std::mktime(&tm)); |
||||
} else { |
||||
// Default to 24 hours from now
|
||||
item.expirationDate = |
||||
std::chrono::system_clock::now() + std::chrono::hours(24); |
||||
} |
||||
|
||||
if (item.id.empty()) { |
||||
// Generate a simple ID if not provided
|
||||
item.id = "item-" |
||||
+ std::to_string( |
||||
std::chrono::system_clock::now().time_since_epoch().count()); |
||||
} |
||||
|
||||
if (item.name.empty()) { |
||||
throw std::runtime_error("Item name is required"); |
||||
} |
||||
|
||||
return item; |
||||
} |
||||
|
||||
std::string StoreController::createSuccessResponse(const domain::Item& item) |
||||
{ |
||||
nlohmann::json response; |
||||
response["success"] = true; |
||||
response["message"] = "Item added successfully"; |
||||
response["item"]["id"] = item.id; |
||||
response["item"]["name"] = item.name; |
||||
response["item"]["expirationDate"] = |
||||
std::chrono::system_clock::to_time_t(item.expirationDate); |
||||
response["item"]["orderUrl"] = item.orderUrl; |
||||
response["item"]["userId"] = item.userId; |
||||
|
||||
return response.dump(); |
||||
} |
||||
|
||||
std::string StoreController::createErrorResponse(const std::string& message) |
||||
{ |
||||
nlohmann::json response; |
||||
response["success"] = false; |
||||
response["message"] = message; |
||||
|
||||
return response.dump(); |
||||
} |
||||
|
||||
} // namespace nxl::autostore::webapi
|
||||
@ -0,0 +1,32 @@
|
||||
#pragma once |
||||
|
||||
#include "application/commands/AddItem.h" |
||||
#include "application/interfaces/IItemRepository.h" |
||||
#include "application/interfaces/IClock.h" |
||||
#include "application/interfaces/IOrderService.h" |
||||
#include "domain/entities/Item.h" |
||||
#include <httplib.h> |
||||
#include <nlohmann/json.hpp> |
||||
|
||||
namespace nxl::autostore::webapi { |
||||
|
||||
class StoreController |
||||
{ |
||||
public: |
||||
StoreController(application::IItemRepository& itemRepository, |
||||
application::IClock& clock, |
||||
application::IOrderService& orderService); |
||||
|
||||
void registerRoutes(httplib::Server& server); |
||||
|
||||
private: |
||||
void addItem(const httplib::Request& req, httplib::Response& res); |
||||
|
||||
domain::Item parseItemFromJson(const std::string& jsonBody); |
||||
std::string createSuccessResponse(const domain::Item& item); |
||||
std::string createErrorResponse(const std::string& message); |
||||
|
||||
application::AddItem addItemUseCase; |
||||
}; |
||||
|
||||
} // namespace nxl::autostore::webapi
|
||||
Loading…
Reference in new issue