UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,601 lines (1,345 loc) 53.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.pistacheTemplate = void 0; exports.pistacheTemplate = { id: 'pistache', name: 'pistache', displayName: 'Pistache REST Framework', description: 'Modern and elegant HTTP and REST framework for C++ with async support and easy-to-use API', framework: 'pistache', language: 'cpp', version: '0.3.0', tags: ['cpp', 'pistache', 'api', 'rest', 'async', 'http2', 'modern-cpp'], port: 9080, features: ['routing', 'middleware', 'authentication', 'validation', 'cors', 'logging', 'testing', 'docker', 'compression', 'rate-limiting'], dependencies: {}, devDependencies: {}, files: { // CMakeLists.txt 'CMakeLists.txt': `cmake_minimum_required(VERSION 3.16) project({{serviceName}} VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # Find packages find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) find_package(PkgConfig REQUIRED) # Find Pistache using pkg-config pkg_check_modules(Pistache REQUIRED IMPORTED_TARGET libpistache) # Include FetchContent include(FetchContent) # Fetch nlohmann/json FetchContent_Declare( json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v3.11.3 ) FetchContent_MakeAvailable(json) # Fetch spdlog FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG v1.12.0 ) FetchContent_MakeAvailable(spdlog) # Fetch jwt-cpp FetchContent_Declare( jwt-cpp GIT_REPOSITORY https://github.com/Thalhammer/jwt-cpp.git GIT_TAG v0.7.0 ) FetchContent_MakeAvailable(jwt-cpp) # Fetch Google Test FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0 ) FetchContent_MakeAvailable(googletest) # Source files set(SOURCES src/main.cpp src/server/server.cpp src/routes/health_routes.cpp src/routes/user_routes.cpp src/middleware/auth_middleware.cpp src/middleware/cors_middleware.cpp src/middleware/logging_middleware.cpp src/middleware/rate_limit_middleware.cpp src/services/user_service.cpp src/services/cache_service.cpp src/utils/jwt_utils.cpp src/utils/validation.cpp src/config/config.cpp ) # Main executable add_executable(\${PROJECT_NAME} \${SOURCES}) target_include_directories(\${PROJECT_NAME} PRIVATE \${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(\${PROJECT_NAME} PRIVATE PkgConfig::Pistache nlohmann_json::nlohmann_json spdlog::spdlog jwt-cpp::jwt-cpp \${CMAKE_THREAD_LIBS_INIT} \${OPENSSL_LIBRARIES} ) # Test executable enable_testing() set(TEST_SOURCES tests/test_main.cpp tests/test_routes.cpp tests/test_middleware.cpp tests/test_services.cpp tests/test_utils.cpp src/services/user_service.cpp src/services/cache_service.cpp src/utils/jwt_utils.cpp src/utils/validation.cpp src/config/config.cpp ) add_executable(tests \${TEST_SOURCES}) target_include_directories(tests PRIVATE \${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(tests PRIVATE PkgConfig::Pistache nlohmann_json::nlohmann_json spdlog::spdlog jwt-cpp::jwt-cpp GTest::gtest GTest::gtest_main \${CMAKE_THREAD_LIBS_INIT} \${OPENSSL_LIBRARIES} ) add_test(NAME tests COMMAND tests) # Install install(TARGETS \${PROJECT_NAME} DESTINATION bin) install(FILES config.json DESTINATION etc/\${PROJECT_NAME}) `, // Main application 'src/main.cpp': `#include <pistache/endpoint.h> #include <pistache/router.h> #include <spdlog/spdlog.h> #include <csignal> #include <memory> #include "server/server.hpp" #include "config/config.hpp" std::unique_ptr<HttpServer> g_server; void signalHandler(int signal) { spdlog::info("Received signal {}, shutting down...", signal); if (g_server) { g_server->shutdown(); } } int main(int argc, char* argv[]) { // Setup signal handlers signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); // Load configuration Config& config = Config::getInstance(); config.load("config.json"); // Setup logging spdlog::set_level(spdlog::level::from_str(config.getLogLevel())); spdlog::info("Starting {{serviceName}} server..."); try { // Create and start server g_server = std::make_unique<HttpServer>(config); g_server->init(); g_server->start(); } catch (const std::exception& e) { spdlog::error("Server error: {}", e.what()); return 1; } return 0; } `, // Server implementation 'include/server/server.hpp': `#pragma once #include <pistache/endpoint.h> #include <pistache/router.h> #include <memory> #include "config/config.hpp" using namespace Pistache; class HttpServer { private: std::shared_ptr<Http::Endpoint> httpEndpoint_; Rest::Router router_; Config& config_; void setupRoutes(); void setupMiddleware(); public: explicit HttpServer(Config& config); void init(); void start(); void shutdown(); }; `, 'src/server/server.cpp': `#include "server/server.hpp" #include "routes/health_routes.hpp" #include "routes/user_routes.hpp" #include "middleware/cors_middleware.hpp" #include "middleware/logging_middleware.hpp" #include "middleware/auth_middleware.hpp" #include "middleware/rate_limit_middleware.hpp" #include <spdlog/spdlog.h> HttpServer::HttpServer(Config& config) : config_(config) { auto addr = Address(config.getHost(), config.getPort()); httpEndpoint_ = std::make_shared<Http::Endpoint>(addr); } void HttpServer::init() { auto opts = Http::Endpoint::options() .threads(config_.getThreads()) .maxRequestSize(config_.getMaxRequestSize()) .maxResponseSize(config_.getMaxResponseSize()); httpEndpoint_->init(opts); setupMiddleware(); setupRoutes(); } void HttpServer::start() { spdlog::info("Server listening on {}:{}", config_.getHost(), config_.getPort()); httpEndpoint_->setHandler(router_.handler()); httpEndpoint_->serve(); } void HttpServer::shutdown() { httpEndpoint_->shutdown(); } void HttpServer::setupMiddleware() { // Apply middleware in order Rest::Routes::Use(router_, std::make_shared<LoggingMiddleware>()); Rest::Routes::Use(router_, std::make_shared<CorsMiddleware>()); Rest::Routes::Use(router_, std::make_shared<RateLimitMiddleware>()); } void HttpServer::setupRoutes() { // Health routes HealthRoutes::setup(router_); // User routes UserRoutes::setup(router_); // Default handler for 404 router_.addCustomHandler(Rest::Routes::NotFound, [](const Rest::Request& req, Http::ResponseWriter response) { nlohmann::json error; error["error"] = "Not Found"; error["path"] = req.resource(); error["method"] = Http::methodString(req.method()); response.send(Http::Code::Not_Found, error.dump(), MIME(Application, Json)); return Rest::Route::Result::Ok; }); } `, // Config 'include/config/config.hpp': `#pragma once #include <string> #include <nlohmann/json.hpp> #include <fstream> class Config { private: nlohmann::json config_data_; static Config instance_; Config() = default; void setDefaults(); public: static Config& getInstance() { static Config instance; return instance; } void load(const std::string& filename); // Server settings std::string getHost() const { return config_data_.value("host", "0.0.0.0"); } uint16_t getPort() const { return config_data_.value("port", 9080); } int getThreads() const { return config_data_.value("threads", 2); } size_t getMaxRequestSize() const { return config_data_.value("max_request_size", 1048576); } size_t getMaxResponseSize() const { return config_data_.value("max_response_size", 1048576); } // Application settings std::string getDatabaseUrl() const { return config_data_.value("database_url", "sqlite://./data.db"); } std::string getJwtSecret() const { return config_data_.value("jwt_secret", "your-secret-key"); } int getJwtExpiry() const { return config_data_.value("jwt_expiry_hours", 24); } std::string getLogLevel() const { return config_data_.value("log_level", "info"); } // Rate limiting int getRateLimitRequests() const { return config_data_.value("rate_limit_requests", 100); } int getRateLimitWindow() const { return config_data_.value("rate_limit_window_seconds", 60); } // Cache settings bool isCacheEnabled() const { return config_data_.value("cache_enabled", true); } int getCacheTTL() const { return config_data_.value("cache_ttl_seconds", 300); } }; `, 'src/config/config.cpp': `#include "config/config.hpp" #include <spdlog/spdlog.h> void Config::load(const std::string& filename) { try { std::ifstream file(filename); if (!file.is_open()) { spdlog::warn("Config file not found: {}, using defaults", filename); setDefaults(); return; } file >> config_data_; } catch (const std::exception& e) { spdlog::error("Error loading config: {}", e.what()); setDefaults(); } } void Config::setDefaults() { config_data_ = { {"host", "0.0.0.0"}, {"port", 9080}, {"threads", 2}, {"max_request_size", 1048576}, {"max_response_size", 1048576}, {"database_url", "sqlite://./data.db"}, {"jwt_secret", "your-secret-key"}, {"jwt_expiry_hours", 24}, {"log_level", "info"}, {"rate_limit_requests", 100}, {"rate_limit_window_seconds", 60}, {"cache_enabled", true}, {"cache_ttl_seconds", 300} }; } `, // Health Routes 'include/routes/health_routes.hpp': `#pragma once #include <pistache/router.h> class HealthRoutes { public: static void setup(Pistache::Rest::Router& router); private: static void health(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); static void metrics(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); }; `, 'src/routes/health_routes.cpp': `#include "routes/health_routes.hpp" #include <nlohmann/json.hpp> #include <chrono> #include <sys/resource.h> static auto start_time = std::chrono::steady_clock::now(); void HealthRoutes::setup(Pistache::Rest::Router& router) { using namespace Pistache::Rest; Routes::Get(router, "/health", Routes::bind(&HealthRoutes::health)); Routes::Get(router, "/metrics", Routes::bind(&HealthRoutes::metrics)); } void HealthRoutes::health(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { nlohmann::json result; result["status"] = "healthy"; result["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); result["version"] = "1.0.0"; result["service"] = "{{serviceName}}"; response.send(Pistache::Http::Code::Ok, result.dump(), MIME(Application, Json)); } void HealthRoutes::metrics(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { auto now = std::chrono::steady_clock::now(); auto uptime = std::chrono::duration_cast<std::chrono::seconds>(now - start_time).count(); // Get memory usage struct rusage usage; getrusage(RUSAGE_SELF, &usage); nlohmann::json result; result["uptime_seconds"] = uptime; result["memory_usage_kb"] = usage.ru_maxrss; result["requests_total"] = 0; // Would be tracked by middleware result["requests_active"] = 0; result["response_time_avg_ms"] = 0; response.send(Pistache::Http::Code::Ok, result.dump(), MIME(Application, Json)); } `, // User Routes 'include/routes/user_routes.hpp': `#pragma once #include <pistache/router.h> class UserRoutes { public: static void setup(Pistache::Rest::Router& router); private: static void createUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); static void getUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); static void updateUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); static void deleteUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); static void listUsers(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); static void login(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response); }; `, 'src/routes/user_routes.cpp': `#include "routes/user_routes.hpp" #include "services/user_service.hpp" #include "middleware/auth_middleware.hpp" #include "utils/jwt_utils.hpp" #include "utils/validation.hpp" #include <nlohmann/json.hpp> #include <spdlog/spdlog.h> void UserRoutes::setup(Pistache::Rest::Router& router) { using namespace Pistache::Rest; // Public routes Routes::Post(router, "/api/users/register", Routes::bind(&UserRoutes::createUser)); Routes::Post(router, "/api/users/login", Routes::bind(&UserRoutes::login)); // Protected routes (auth middleware will validate) Routes::Get(router, "/api/users", Routes::bind(&UserRoutes::listUsers)); Routes::Get(router, "/api/users/:id", Routes::bind(&UserRoutes::getUser)); Routes::Put(router, "/api/users/:id", Routes::bind(&UserRoutes::updateUser)); Routes::Delete(router, "/api/users/:id", Routes::bind(&UserRoutes::deleteUser)); } void UserRoutes::createUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { try { auto json = nlohmann::json::parse(req.body()); // Validate input auto validation = Validator::validateUserRegistration(json); if (!validation.isValid()) { nlohmann::json error; error["error"] = "Validation failed"; error["details"] = validation.getErrors(); response.send(Pistache::Http::Code::Bad_Request, error.dump(), MIME(Application, Json)); return; } User user; user.email = json["email"]; user.name = json["name"]; user.password = json["password"]; UserService service; auto result = service.createUser(user); if (result) { nlohmann::json resp; resp["id"] = result->id; resp["email"] = result->email; resp["name"] = result->name; response.send(Pistache::Http::Code::Created, resp.dump(), MIME(Application, Json)); } else { nlohmann::json error; error["error"] = "User already exists"; response.send(Pistache::Http::Code::Conflict, error.dump(), MIME(Application, Json)); } } catch (const std::exception& e) { spdlog::error("Error creating user: {}", e.what()); nlohmann::json error; error["error"] = "Invalid request"; error["message"] = e.what(); response.send(Pistache::Http::Code::Bad_Request, error.dump(), MIME(Application, Json)); } } void UserRoutes::getUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { // Check authentication if (!AuthMiddleware::isAuthenticated(req)) { nlohmann::json error; error["error"] = "Unauthorized"; response.send(Pistache::Http::Code::Unauthorized, error.dump(), MIME(Application, Json)); return; } auto id = req.param(":id").as<std::string>(); UserService service; auto user = service.getUser(id); if (user) { nlohmann::json resp; resp["id"] = user->id; resp["email"] = user->email; resp["name"] = user->name; resp["created_at"] = user->created_at; response.send(Pistache::Http::Code::Ok, resp.dump(), MIME(Application, Json)); } else { nlohmann::json error; error["error"] = "User not found"; response.send(Pistache::Http::Code::Not_Found, error.dump(), MIME(Application, Json)); } } void UserRoutes::updateUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { // Check authentication if (!AuthMiddleware::isAuthenticated(req)) { nlohmann::json error; error["error"] = "Unauthorized"; response.send(Pistache::Http::Code::Unauthorized, error.dump(), MIME(Application, Json)); return; } try { auto id = req.param(":id").as<std::string>(); auto json = nlohmann::json::parse(req.body()); User user; user.id = id; if (json.contains("email")) user.email = json["email"]; if (json.contains("name")) user.name = json["name"]; UserService service; auto result = service.updateUser(user); if (result) { nlohmann::json resp; resp["id"] = result->id; resp["email"] = result->email; resp["name"] = result->name; response.send(Pistache::Http::Code::Ok, resp.dump(), MIME(Application, Json)); } else { nlohmann::json error; error["error"] = "User not found"; response.send(Pistache::Http::Code::Not_Found, error.dump(), MIME(Application, Json)); } } catch (const std::exception& e) { nlohmann::json error; error["error"] = "Invalid request"; error["message"] = e.what(); response.send(Pistache::Http::Code::Bad_Request, error.dump(), MIME(Application, Json)); } } void UserRoutes::deleteUser(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { // Check authentication if (!AuthMiddleware::isAuthenticated(req)) { nlohmann::json error; error["error"] = "Unauthorized"; response.send(Pistache::Http::Code::Unauthorized, error.dump(), MIME(Application, Json)); return; } auto id = req.param(":id").as<std::string>(); UserService service; if (service.deleteUser(id)) { response.send(Pistache::Http::Code::No_Content); } else { nlohmann::json error; error["error"] = "User not found"; response.send(Pistache::Http::Code::Not_Found, error.dump(), MIME(Application, Json)); } } void UserRoutes::listUsers(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { // Check authentication if (!AuthMiddleware::isAuthenticated(req)) { nlohmann::json error; error["error"] = "Unauthorized"; response.send(Pistache::Http::Code::Unauthorized, error.dump(), MIME(Application, Json)); return; } int page = 1, limit = 10; auto query = req.query(); if (query.has("page")) page = std::stoi(query.get("page").value()); if (query.has("limit")) limit = std::stoi(query.get("limit").value()); UserService service; auto users = service.listUsers(page, limit); nlohmann::json resp; resp["page"] = page; resp["limit"] = limit; resp["total"] = users.size(); nlohmann::json userArray = nlohmann::json::array(); for (const auto& user : users) { nlohmann::json u; u["id"] = user.id; u["email"] = user.email; u["name"] = user.name; userArray.push_back(u); } resp["users"] = userArray; response.send(Pistache::Http::Code::Ok, resp.dump(), MIME(Application, Json)); } void UserRoutes::login(const Pistache::Rest::Request& req, Pistache::Http::ResponseWriter response) { try { auto json = nlohmann::json::parse(req.body()); // Validate input auto validation = Validator::validateLogin(json); if (!validation.isValid()) { nlohmann::json error; error["error"] = "Validation failed"; error["details"] = validation.getErrors(); response.send(Pistache::Http::Code::Bad_Request, error.dump(), MIME(Application, Json)); return; } std::string email = json["email"]; std::string password = json["password"]; UserService service; auto user = service.authenticate(email, password); if (user) { auto token = JwtUtils::generateToken(user->id, user->email); nlohmann::json resp; resp["token"] = token; resp["user"]["id"] = user->id; resp["user"]["email"] = user->email; resp["user"]["name"] = user->name; response.send(Pistache::Http::Code::Ok, resp.dump(), MIME(Application, Json)); } else { nlohmann::json error; error["error"] = "Invalid credentials"; response.send(Pistache::Http::Code::Unauthorized, error.dump(), MIME(Application, Json)); } } catch (const std::exception& e) { nlohmann::json error; error["error"] = "Invalid request"; error["message"] = e.what(); response.send(Pistache::Http::Code::Bad_Request, error.dump(), MIME(Application, Json)); } } `, // User Model and Service 'include/models/user.hpp': `#pragma once #include <string> struct User { std::string id; std::string email; std::string name; std::string password; std::string created_at; std::string updated_at; }; `, 'include/services/user_service.hpp': `#pragma once #include <optional> #include <vector> #include "models/user.hpp" class UserService { public: std::optional<User> createUser(const User& user); std::optional<User> getUser(const std::string& id); std::optional<User> updateUser(const User& user); bool deleteUser(const std::string& id); std::vector<User> listUsers(int page, int limit); std::optional<User> authenticate(const std::string& email, const std::string& password); private: std::string hashPassword(const std::string& password); bool verifyPassword(const std::string& password, const std::string& hash); }; `, 'src/services/user_service.cpp': `#include "services/user_service.hpp" #include "services/cache_service.hpp" #include <openssl/sha.h> #include <iomanip> #include <sstream> #include <random> #include <chrono> #include <unordered_map> #include <mutex> // Simple in-memory storage for demo static std::unordered_map<std::string, User> users_by_id; static std::unordered_map<std::string, std::string> email_to_id; static std::mutex users_mutex; std::optional<User> UserService::createUser(const User& user) { std::lock_guard<std::mutex> lock(users_mutex); // Check if email already exists if (email_to_id.find(user.email) != email_to_id.end()) { return std::nullopt; } User newUser = user; // Generate unique ID std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(100000, 999999); newUser.id = std::to_string(dis(gen)); // Hash password newUser.password = hashPassword(user.password); // Set timestamps auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); newUser.created_at = std::ctime(&time_t); newUser.updated_at = newUser.created_at; // Save user users_by_id[newUser.id] = newUser; email_to_id[newUser.email] = newUser.id; // Cache the user CacheService::getInstance().set("user:" + newUser.id, newUser); newUser.password = ""; // Don't return password return newUser; } std::optional<User> UserService::getUser(const std::string& id) { // Check cache first auto cached = CacheService::getInstance().get<User>("user:" + id); if (cached) { cached->password = ""; // Don't return password return cached; } std::lock_guard<std::mutex> lock(users_mutex); auto it = users_by_id.find(id); if (it != users_by_id.end()) { User user = it->second; user.password = ""; // Don't return password // Cache the user CacheService::getInstance().set("user:" + id, user); return user; } return std::nullopt; } std::optional<User> UserService::updateUser(const User& user) { std::lock_guard<std::mutex> lock(users_mutex); auto it = users_by_id.find(user.id); if (it == users_by_id.end()) { return std::nullopt; } User& existing = it->second; // Update fields if (!user.email.empty() && user.email != existing.email) { // Update email mapping email_to_id.erase(existing.email); email_to_id[user.email] = user.id; existing.email = user.email; } if (!user.name.empty()) existing.name = user.name; // Update timestamp auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); existing.updated_at = std::ctime(&time_t); // Invalidate cache CacheService::getInstance().remove("user:" + user.id); User result = existing; result.password = ""; // Don't return password return result; } bool UserService::deleteUser(const std::string& id) { std::lock_guard<std::mutex> lock(users_mutex); auto it = users_by_id.find(id); if (it != users_by_id.end()) { email_to_id.erase(it->second.email); users_by_id.erase(it); // Invalidate cache CacheService::getInstance().remove("user:" + id); return true; } return false; } std::vector<User> UserService::listUsers(int page, int limit) { std::lock_guard<std::mutex> lock(users_mutex); std::vector<User> result; int skip = (page - 1) * limit; int count = 0; for (const auto& pair : users_by_id) { if (count >= skip && result.size() < static_cast<size_t>(limit)) { User user = pair.second; user.password = ""; // Don't return password result.push_back(user); } count++; } return result; } std::optional<User> UserService::authenticate(const std::string& email, const std::string& password) { std::lock_guard<std::mutex> lock(users_mutex); auto email_it = email_to_id.find(email); if (email_it == email_to_id.end()) { return std::nullopt; } auto user_it = users_by_id.find(email_it->second); if (user_it != users_by_id.end() && verifyPassword(password, user_it->second.password)) { User user = user_it->second; user.password = ""; // Don't return password return user; } return std::nullopt; } std::string UserService::hashPassword(const std::string& password) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256(reinterpret_cast<const unsigned char*>(password.c_str()), password.length(), hash); std::stringstream ss; for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; } return ss.str(); } bool UserService::verifyPassword(const std::string& password, const std::string& hash) { return hashPassword(password) == hash; } `, // Cache Service 'include/services/cache_service.hpp': `#pragma once #include <unordered_map> #include <chrono> #include <mutex> #include <optional> #include <nlohmann/json.hpp> class CacheService { private: struct CacheEntry { nlohmann::json data; std::chrono::steady_clock::time_point expiry; }; std::unordered_map<std::string, CacheEntry> cache_; mutable std::mutex mutex_; int default_ttl_seconds_ = 300; CacheService() = default; public: static CacheService& getInstance() { static CacheService instance; return instance; } void setDefaultTTL(int seconds) { default_ttl_seconds_ = seconds; } template<typename T> void set(const std::string& key, const T& value, int ttl_seconds = -1) { std::lock_guard<std::mutex> lock(mutex_); if (ttl_seconds < 0) ttl_seconds = default_ttl_seconds_; CacheEntry entry; entry.data = value; entry.expiry = std::chrono::steady_clock::now() + std::chrono::seconds(ttl_seconds); cache_[key] = entry; } template<typename T> std::optional<T> get(const std::string& key) { std::lock_guard<std::mutex> lock(mutex_); auto it = cache_.find(key); if (it == cache_.end()) { return std::nullopt; } // Check expiry if (std::chrono::steady_clock::now() > it->second.expiry) { cache_.erase(it); return std::nullopt; } return it->second.data.get<T>(); } void remove(const std::string& key) { std::lock_guard<std::mutex> lock(mutex_); cache_.erase(key); } void clear() { std::lock_guard<std::mutex> lock(mutex_); cache_.clear(); } size_t size() const { std::lock_guard<std::mutex> lock(mutex_); return cache_.size(); } void cleanup() { std::lock_guard<std::mutex> lock(mutex_); auto now = std::chrono::steady_clock::now(); for (auto it = cache_.begin(); it != cache_.end();) { if (now > it->second.expiry) { it = cache_.erase(it); } else { ++it; } } } }; `, 'src/services/cache_service.cpp': `#include "services/cache_service.hpp" // Implementation is in header due to templates `, // Middleware 'include/middleware/auth_middleware.hpp': `#pragma once #include <pistache/http.h> #include <pistache/router.h> #include "utils/jwt_utils.hpp" class AuthMiddleware { public: static bool isAuthenticated(const Pistache::Rest::Request& req); static std::optional<JwtClaims> getAuthClaims(const Pistache::Rest::Request& req); }; `, 'src/middleware/auth_middleware.cpp': `#include "middleware/auth_middleware.hpp" #include <spdlog/spdlog.h> bool AuthMiddleware::isAuthenticated(const Pistache::Rest::Request& req) { return getAuthClaims(req).has_value(); } std::optional<JwtClaims> AuthMiddleware::getAuthClaims(const Pistache::Rest::Request& req) { auto auth_header = req.headers().tryGet<Pistache::Http::Header::Authorization>(); if (!auth_header) { return std::nullopt; } std::string auth_value = auth_header->value(); // Extract token from "Bearer <token>" if (auth_value.find("Bearer ") != 0) { return std::nullopt; } std::string token = auth_value.substr(7); // Validate token return JwtUtils::validateToken(token); } `, 'include/middleware/cors_middleware.hpp': `#pragma once #include <pistache/middleware.h> #include <pistache/http.h> class CorsMiddleware : public Pistache::Http::Middleware { public: void onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) override; void onResponse(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) override; }; `, 'src/middleware/cors_middleware.cpp': `#include "middleware/cors_middleware.hpp" void CorsMiddleware::onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) { if (req.method() == Pistache::Http::Method::Options) { response.headers() .add<Pistache::Http::Header::AccessControlAllowOrigin>("*") .add<Pistache::Http::Header::AccessControlAllowMethods>("GET, POST, PUT, DELETE, OPTIONS") .add<Pistache::Http::Header::AccessControlAllowHeaders>("Content-Type, Authorization") .add<Pistache::Http::Header::AccessControlMaxAge>("86400"); response.send(Pistache::Http::Code::No_Content); return; } } void CorsMiddleware::onResponse(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) { response.headers() .add<Pistache::Http::Header::AccessControlAllowOrigin>("*") .add<Pistache::Http::Header::AccessControlAllowMethods>("GET, POST, PUT, DELETE, OPTIONS") .add<Pistache::Http::Header::AccessControlAllowHeaders>("Content-Type, Authorization"); } `, 'include/middleware/logging_middleware.hpp': `#pragma once #include <pistache/middleware.h> #include <pistache/http.h> #include <chrono> class LoggingMiddleware : public Pistache::Http::Middleware { private: struct RequestContext { std::chrono::steady_clock::time_point start; }; public: void onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) override; void onResponse(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) override; }; `, 'src/middleware/logging_middleware.cpp': `#include "middleware/logging_middleware.hpp" #include <spdlog/spdlog.h> void LoggingMiddleware::onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) { auto context = std::make_shared<RequestContext>(); context->start = std::chrono::steady_clock::now(); req.associateData(context); spdlog::info("{} {} from {}", req.method(), req.resource(), req.address().host()); } void LoggingMiddleware::onResponse(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) { auto context = req.getData<RequestContext>(); if (context) { auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>( end - context->start); spdlog::info("{} {} {} - {} μs", req.method(), req.resource(), static_cast<int>(response.code()), duration.count()); } } `, 'include/middleware/rate_limit_middleware.hpp': `#pragma once #include <pistache/middleware.h> #include <pistache/http.h> #include <unordered_map> #include <chrono> #include <mutex> class RateLimitMiddleware : public Pistache::Http::Middleware { private: struct RateLimitInfo { int requests = 0; std::chrono::steady_clock::time_point window_start; }; std::unordered_map<std::string, RateLimitInfo> rate_limits_; mutable std::mutex mutex_; int max_requests_ = 100; int window_seconds_ = 60; public: RateLimitMiddleware(int max_requests = 100, int window_seconds = 60) : max_requests_(max_requests), window_seconds_(window_seconds) {} void onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) override; }; `, 'src/middleware/rate_limit_middleware.cpp': `#include "middleware/rate_limit_middleware.hpp" #include <nlohmann/json.hpp> void RateLimitMiddleware::onRequest(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) { std::string client_ip = req.address().host(); auto now = std::chrono::steady_clock::now(); std::lock_guard<std::mutex> lock(mutex_); auto& limit_info = rate_limits_[client_ip]; // Check if we need to reset the window auto window_duration = std::chrono::seconds(window_seconds_); if (now - limit_info.window_start > window_duration) { limit_info.requests = 0; limit_info.window_start = now; } // Check rate limit if (limit_info.requests >= max_requests_) { nlohmann::json error; error["error"] = "Rate limit exceeded"; error["retry_after"] = window_seconds_; response.headers() .add<Pistache::Http::Header::ContentType>(MIME(Application, Json)) .add<Pistache::Http::Header::Raw>("X-RateLimit-Limit", std::to_string(max_requests_)) .add<Pistache::Http::Header::Raw>("X-RateLimit-Remaining", "0") .add<Pistache::Http::Header::Raw>("X-RateLimit-Reset", std::to_string(std::chrono::duration_cast<std::chrono::seconds>( limit_info.window_start + window_duration - std::chrono::steady_clock::epoch() ).count())); response.send(Pistache::Http::Code::Too_Many_Requests, error.dump()); return; } // Increment request count limit_info.requests++; // Add rate limit headers response.headers() .add<Pistache::Http::Header::Raw>("X-RateLimit-Limit", std::to_string(max_requests_)) .add<Pistache::Http::Header::Raw>("X-RateLimit-Remaining", std::to_string(max_requests_ - limit_info.requests)); } `, // Utils 'include/utils/jwt_utils.hpp': `#pragma once #include <string> #include <optional> struct JwtClaims { std::string user_id; std::string email; int64_t exp; }; class JwtUtils { public: static std::string generateToken(const std::string& user_id, const std::string& email); static std::optional<JwtClaims> validateToken(const std::string& token); }; `, 'src/utils/jwt_utils.cpp': `#include "utils/jwt_utils.hpp" #include "config/config.hpp" #include <jwt-cpp/jwt.h> #include <chrono> std::string JwtUtils::generateToken(const std::string& user_id, const std::string& email) { auto now = std::chrono::system_clock::now(); auto exp = now + std::chrono::hours(Config::getInstance().getJwtExpiry()); auto token = jwt::create() .set_issuer("{{serviceName}}") .set_type("JWS") .set_payload_claim("user_id", jwt::claim(user_id)) .set_payload_claim("email", jwt::claim(email)) .set_issued_at(now) .set_expires_at(exp) .sign(jwt::algorithm::hs256{Config::getInstance().getJwtSecret()}); return token; } std::optional<JwtClaims> JwtUtils::validateToken(const std::string& token) { try { auto decoded = jwt::decode(token); auto verifier = jwt::verify() .allow_algorithm(jwt::algorithm::hs256{Config::getInstance().getJwtSecret()}) .with_issuer("{{serviceName}}"); verifier.verify(decoded); JwtClaims claims; claims.user_id = decoded.get_payload_claim("user_id").as_string(); claims.email = decoded.get_payload_claim("email").as_string(); claims.exp = decoded.get_expires_at().time_since_epoch().count(); return claims; } catch (const std::exception& e) { return std::nullopt; } } `, 'include/utils/validation.hpp': `#pragma once #include <string> #include <vector> #include <nlohmann/json.hpp> class ValidationResult { private: bool valid_ = true; std::vector<std::string> errors_; public: void addError(const std::string& error) { valid_ = false; errors_.push_back(error); } bool isValid() const { return valid_; } const std::vector<std::string>& getErrors() const { return errors_; } }; class Validator { public: static ValidationResult validateUserRegistration(const nlohmann::json& data); static ValidationResult validateLogin(const nlohmann::json& data); static bool isValidEmail(const std::string& email); static bool isValidPassword(const std::string& password); }; `, 'src/utils/validation.cpp': `#include "utils/validation.hpp" #include <regex> ValidationResult Validator::validateUserRegistration(const nlohmann::json& data) { ValidationResult result; if (!data.contains("email") || !data["email"].is_string()) { result.addError("Email is required and must be a string"); } else if (!isValidEmail(data["email"])) { result.addError("Invalid email format"); } if (!data.contains("name") || !data["name"].is_string()) { result.addError("Name is required and must be a string"); } else if (data["name"].get<std::string>().length() < 2) { result.addError("Name must be at least 2 characters long"); } if (!data.contains("password") || !data["password"].is_string()) { result.addError("Password is required and must be a string"); } else if (!isValidPassword(data["password"])) { result.addError("Password must be at least 8 characters long"); } return result; } ValidationResult Validator::validateLogin(const nlohmann::json& data) { ValidationResult result; if (!data.contains("email") || !data["email"].is_string()) { result.addError("Email is required and must be a string"); } if (!data.contains("password") || !data["password"].is_string()) { result.addError("Password is required and must be a string"); } return result; } bool Validator::isValidEmail(const std::string& email) { const std::regex pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"); return std::regex_match(email, pattern); } bool Validator::isValidPassword(const std::string& password) { return password.length() >= 8; } `, // Test files 'tests/test_main.cpp': `#include <gtest/gtest.h> int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } `, 'tests/test_routes.cpp': `#include <gtest/gtest.h> #include <pistache/client.h> #include <nlohmann/json.hpp> class RoutesTest : public ::testing::Test { protected: void SetUp() override { // Server should be running for integration tests } }; TEST_F(RoutesTest, HealthCheck) { Pistache::Http::Client client; auto opts = Pistache::Http::Client::options().threads(1); client.init(opts); auto response = client.get("http://localhost:9080/health").send(); response.then([](Pistache::Http::Response res) { EXPECT_EQ(res.code(), Pistache::Http::Code::Ok); EXPECT_FALSE(res.body().empty()); }); client.shutdown(); } TEST_F(RoutesTest, UserRegistration) { Pistache::Http::Client client; auto opts = Pistache::Http::Client::options().threads(1); client.init(opts); nlohmann::json user = { {"email", "test@example.com"}, {"name", "Test User"}, {"password", "password123"} }; auto response = client.post("http://localhost:9080/api/users/register") .body(user.dump()) .send(); response.then([](Pistache::Http::Response res) { EXPECT_EQ(res.code(), Pistache::Http::Code::Created); auto json = nlohmann::json::parse(res.body()); EXPECT_TRUE(json.contains("id")); EXPECT_EQ(json["email"], "test@example.com"); }); client.shutdown(); } `, 'tests/test_middleware.cpp': `#include <gtest/gtest.h> #include "middleware/rate_limit_middleware.hpp" #include "utils/jwt_utils.hpp" TEST(MiddlewareTest, JWTGeneration) { auto token = JwtUtils::generateToken("123", "test@example.com"); EXPECT_FALSE(token.empty()); auto claims = JwtUtils::validateToken(token); EXPECT_TRUE(claims.has_value()); EXPECT_EQ(claims->user_id, "123"); EXPECT_EQ(claims->email, "test@example.com"); } TEST(MiddlewareTest, InvalidJWT) { auto claims = JwtUtils::validateToken("invalid.token.here"); EXPECT_FALSE(claims.has_value()); } `, 'tests/test_services.cpp': `#include <gtest/gtest.h> #include "services/user_service.hpp" #include "services/cache_service.hpp" TEST(ServicesTest, UserCreation) { UserService service; User user; user.email = "service_test@example.com"; user.name = "Service Test"; user.password = "password123"; auto result = service.createUser(user); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result->email, user.email); EXPECT_TRUE(result->password.empty()); } TEST(ServicesTest, CacheOperations) { auto& cache = CacheService::getInstance(); cache.set("test_key", "test_value", 60); auto value = cache.get<std::string>("test_key"); EXPECT_TRUE(value.has_value()); EXPECT_EQ(*value, "test_value"); cache.remove("test_key"); auto removed = cache.get<std::string>("test_key"); EXPECT_FALSE(removed.has_value()); } `, 'tests/test_utils.cpp': `#include <gtest/gtest.h> #include "utils/validation.hpp" TEST(ValidationTest, ValidEmail) { EXPECT_TRUE(Validator::isValidEmail("test@example.com")); EXPECT_TRUE(Validator::isValidEmail("user.name@domain.co.uk")); EXPECT_FALSE(Validator::isValidEmail("invalid-email")); EXPECT_FALSE(Validator::isValidEmail("@example.com")); EXPECT_FALSE(Validator::isValidEmail("test@")); } TEST(ValidationTest, ValidPassword) { EXPECT_TRUE(Validator::isValidPassword("password123")); EXPECT_TRUE(Validator::isValidPassword("verylongpassword")); EXPECT_FALSE(Validator::isValidPassword("short")); EXPECT_FALSE(Validator::isValidPassword("1234567")); } TEST(ValidationTest, UserRegistrationValidation) { nlohmann::json valid_data = { {"email", "test@example.com"}, {"name", "Test User"}, {"password", "password123"} }; auto result = Validator::validateUserRegistration(valid_data); EXPECT_TRUE(result.isValid()); nlohmann::json invalid_data = { {"email", "invalid-email"}, {"name", "T"}, {"password", "short"} }; auto invalid_result = Validator::validateUserRegistration(invalid_data); EXPECT_FALSE(invalid_result.isValid()); EXPECT_EQ(invalid_result.getErrors().size(), 3); } `, // Configuration files 'config.json': `{ "host": "0.0.0.0", "port": 9080, "threads": 2, "max_request_size": 1048576, "max_response_size": 1048576, "database_url": "sqlite://./data.db", "jwt_secret": "your-secret-key-change-in-production", "jwt_expiry_hours": 24, "log_level": "info", "rate_limit_requests": 100, "rate_limit_window_seconds": 60, "cache_enabled": true, "cache_ttl_seconds": 300 } `, // Build script for Pistache 'scripts/install-pistache.sh': `#!/bin/bash set -e # Install Pistache echo "Installing Pistache..." # Install dependencies sudo apt-get update sudo apt-get install -y \\ cmake \\ build-essential \\ libssl-dev \\ pkg-config \\ rapidjson-dev # Clone and build Pistache git clone https://github.com/pistacheio/pistache.git cd pistache mkdir build && cd build cmake -G "Unix Makefiles" \\ -DCMAKE_BUILD_TYPE=Release \\ -DPISTACHE_BUILD_EXAMPLES=OFF \\ -DPISTACHE_BUILD_TESTS=OFF \\ -DPISTACHE_BUILD_DOCS=OFF \\ .. make -j$(nproc) sudo make install echo "Pistache installed successfully!" `, 'Dockerfile': `# Build stage FROM ubuntu:22.04 AS builder # Install dependencies RUN apt-get update && apt-get install -y \\ build-essential \\ cmake \\ git \\ pkg-config \\ libssl-dev \\ rapidjson-dev \\ && rm -rf /var/lib/apt/lists/* # Install Pistache WORKDIR /tmp RUN git clone https://github.com/pistacheio/pistache.git && \\ cd pistache && \\ mkdir build && cd build && \\ cmake -G "Unix Makefiles" \\ -DCMAKE_BUILD_TYPE=Release \\ -DPISTACHE_BUILD_EXAMPLES=OFF \\ -DPISTACHE_BUILD_TESTS=OFF \\ -DPISTACHE_BUILD_DOCS=OFF \\ .. && \\ make -j$(nproc) && \\ make install # Build application WORKDIR /app COPY . . RUN mkdir build && cd build && \\ cmake .. -DCMAKE_BUILD_TYPE=Release && \\ make -j$(nproc) # Runtime stage FROM ubuntu:22.04 # Install runtime dependencies RUN apt-get update && apt-get install -y \\ libssl3 \\ && rm -rf /var/lib/apt/lists/* # Create user RUN useradd -m -s /bin/bash appuser # Copy binary and config COPY --from=builder /app/build/{{serviceName}} /usr/local/bin/ COPY --from=builder /app/config.json /etc/{{serviceName}}/ COPY --from=builder /usr/local/lib/libpistache* /usr/local/lib/ # Update library cache RUN ldconfig # Set ownership RUN chown -R appuser:appuser /etc/{{serviceName}} USER appuser EXPOSE 9080 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost:9080/health || exit 1 CMD ["{{serviceName}}"] `, 'docker-compose.yml': `version: '3.8' services: app: build: . container_name: {{serviceName}} ports: - "9080:9080" volumes: - ./config.json:/etc/{{serviceName}}/config.json:ro - ./logs:/var/lo