diff --git a/cpp17/lib/CMakeLists.txt b/cpp17/lib/CMakeLists.txt index 7d40b34..1fdc5b0 100644 --- a/cpp17/lib/CMakeLists.txt +++ b/cpp17/lib/CMakeLists.txt @@ -10,6 +10,10 @@ add_library(${TARGET_NAME} STATIC src/AutoStore.cpp src/infrastructure/repositories/FileUserRepository.cpp src/infrastructure/repositories/FileItemRepository.cpp + src/infrastructure/adapters/HttpOrderService.cpp + src/infrastructure/http/HttpServer.cpp + src/webapi/controllers/StoreController.cpp + src/application/commands/AddItem.cpp ) target_include_directories(${TARGET_NAME} diff --git a/cpp17/lib/include/autostore/AutoStore.h b/cpp17/lib/include/autostore/AutoStore.h index 245e88c..d552c14 100644 --- a/cpp17/lib/include/autostore/AutoStore.h +++ b/cpp17/lib/include/autostore/AutoStore.h @@ -3,14 +3,30 @@ #include #include #include +#include namespace nxl::autostore { +// Forward declarations +namespace application { +class IItemRepository; +class IClock; +class IOrderService; +} // namespace application + +namespace infrastructure { +class HttpServer; +} + +namespace webapi { +class StoreController; +} + class AutoStore { public: explicit AutoStore(std::string_view dataPath); - ~AutoStore() = default; + ~AutoStore(); // Initialize the application bool initialize(); @@ -24,6 +40,17 @@ public: private: std::string dataPath; bool initialized; + + // Dependencies + std::unique_ptr itemRepository; + std::unique_ptr clock; + std::unique_ptr orderService; + std::unique_ptr storeController; + + // HTTP server + std::unique_ptr httpServer; + std::thread serverThread; + bool serverRunning; }; } // namespace nxl::autostore \ No newline at end of file diff --git a/cpp17/lib/src/AutoStore.cpp b/cpp17/lib/src/AutoStore.cpp index 87e9c46..c8242d8 100644 --- a/cpp17/lib/src/AutoStore.cpp +++ b/cpp17/lib/src/AutoStore.cpp @@ -1,21 +1,57 @@ #include "autostore/AutoStore.h" +#include "infrastructure/repositories/FileItemRepository.h" +#include "infrastructure/adapters/SystemClock.h" +#include "infrastructure/adapters/HttpOrderService.h" +#include "webapi/controllers/StoreController.h" +#include "infrastructure/http/HttpServer.h" #include +#include +#include namespace nxl::autostore { AutoStore::AutoStore(std::string_view dataPath) - : dataPath(dataPath), initialized(false) + : dataPath(dataPath), initialized(false), serverRunning(false) {} +AutoStore::~AutoStore() +{ + if (serverRunning) { + stop(); + } +} + bool AutoStore::initialize() { - // TODO: Initialize repositories and services std::cout << "Initializing AutoStore with data path: " << dataPath << std::endl; - // For now, just mark as initialized - initialized = true; - return true; + try { + // Create data directory if it doesn't exist + std::filesystem::create_directories(dataPath); + + // Initialize repositories and services + std::string itemsDbPath = std::filesystem::path(dataPath) / "items.json"; + itemRepository = + std::make_unique(itemsDbPath); + + clock = std::make_unique(); + orderService = std::make_unique(); + + // Initialize HTTP server + httpServer = std::make_unique(8080, "0.0.0.0"); + + // Initialize store controller + storeController = std::make_unique( + *itemRepository, *clock, *orderService); + + initialized = true; + std::cout << "AutoStore initialized successfully" << std::endl; + return true; + } catch (const std::exception& e) { + std::cerr << "Failed to initialize AutoStore: " << e.what() << std::endl; + return false; + } } bool AutoStore::start() @@ -26,16 +62,47 @@ bool AutoStore::start() return false; } - // TODO: Start background services, HTTP server, etc. std::cout << "Starting AutoStore services..." << std::endl; - return true; + try { + // Register routes with the HTTP server + storeController->registerRoutes(httpServer->getServer()); + + // Start HTTP server + if (!httpServer->start()) { + std::cerr << "Failed to start HTTP server" << std::endl; + return false; + } + + serverRunning = true; + std::cout << "AutoStore services started successfully" << std::endl; + std::cout << "HTTP server listening on http://0.0.0.0:8080" << std::endl; + std::cout << "API endpoint: POST http://0.0.0.0:8080/api/items" + << std::endl; + + return true; + } catch (const std::exception& e) { + std::cerr << "Failed to start AutoStore services: " << e.what() + << std::endl; + return false; + } } void AutoStore::stop() { - // TODO: Stop all services + if (!serverRunning) { + return; + } + std::cout << "Stopping AutoStore services..." << std::endl; + + // Stop HTTP server + if (httpServer) { + httpServer->stop(); + } + + serverRunning = false; + std::cout << "AutoStore services stopped" << std::endl; } } // namespace nxl::autostore \ No newline at end of file diff --git a/cpp17/lib/src/infrastructure/http/HttpServer.cpp b/cpp17/lib/src/infrastructure/http/HttpServer.cpp new file mode 100644 index 0000000..f6ebb42 --- /dev/null +++ b/cpp17/lib/src/infrastructure/http/HttpServer.cpp @@ -0,0 +1,101 @@ +#include "infrastructure/http/HttpServer.h" +#include + +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 = "

Error Status: %d

"; + 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 = "

Error 500

%s

"; + 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 \ No newline at end of file diff --git a/cpp17/lib/src/infrastructure/http/HttpServer.h b/cpp17/lib/src/infrastructure/http/HttpServer.h new file mode 100644 index 0000000..cef5a2d --- /dev/null +++ b/cpp17/lib/src/infrastructure/http/HttpServer.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +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 \ No newline at end of file diff --git a/cpp17/lib/src/webapi/controllers/StoreController.cpp b/cpp17/lib/src/webapi/controllers/StoreController.cpp new file mode 100644 index 0000000..817a39c --- /dev/null +++ b/cpp17/lib/src/webapi/controllers/StoreController.cpp @@ -0,0 +1,121 @@ +#include "webapi/controllers/StoreController.h" +#include +#include +#include +#include + +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 \ No newline at end of file diff --git a/cpp17/lib/src/webapi/controllers/StoreController.h b/cpp17/lib/src/webapi/controllers/StoreController.h new file mode 100644 index 0000000..ad3ccc1 --- /dev/null +++ b/cpp17/lib/src/webapi/controllers/StoreController.h @@ -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 +#include + +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 \ No newline at end of file