Pārlūkot izejas kodu

fix: improve graceful shutdown for systemd services

- Add lws_cancel_service() to immediately wake up WebSocket event loop
  during shutdown instead of waiting for 50ms timeout
- Fix race condition in HttpServer where both Stop() and Wait() tried
  to join httpThread_, causing undefined behavior from signal handlers
- Implement self-pipe pattern in database for async-signal-safe shutdown
  notification (gRPC Shutdown() is not safe to call from signal handlers)
- Split GrpcServer::Stop() into RequestShutdown(), Wait(), and Cleanup()
  to properly separate signal-safe and non-signal-safe operations
- Add TimeoutStopSec=10 to systemd service files as fallback

Before: Services took 10+ seconds to stop (hitting SIGKILL timeout)
After: Services stop cleanly in ~20-40ms with proper cleanup
Fszontagh 6 mēneši atpakaļ
vecāks
revīzija
a55c080d6f

+ 1 - 0
.gitignore

@@ -33,6 +33,7 @@ CTestTestfile.cmake
 
 # External dependencies (fetched by CMake)
 _deps/
+build
 
 # OS files
 .DS_Store

+ 27 - 24
CMakeLists.txt

@@ -56,11 +56,18 @@ FetchContent_Declare(
     GIT_TAG v3.11.3
 )
 
-# yaml-cpp - YAML parser library
+# cpp-httplib - Header-only HTTP library
 FetchContent_Declare(
-    yaml-cpp
-    GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
-    GIT_TAG 0.8.0
+    httplib
+    GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
+    GIT_TAG v0.30.1
+)
+
+# jwt-cpp - Header-only JWT library
+FetchContent_Declare(
+    jwt-cpp
+    GIT_REPOSITORY https://github.com/Thalhammer/jwt-cpp.git
+    GIT_TAG v0.7.0
 )
 
 # gRPC and Protobuf
@@ -78,14 +85,6 @@ FetchContent_Declare(
     GIT_TAG 1.0.19
 )
 
-# Boost - For Beast HTTP/WebSocket library
-FetchContent_Declare(
-    Boost
-    URL https://github.com/boostorg/boost/releases/download/boost-1.86.0/boost-1.86.0-cmake.tar.xz
-    URL_HASH SHA256=2c5ec5edcdff47ff55e27ed9560b0a0b94b07bd07ed9928b476150e16b0efc57
-    DOWNLOAD_EXTRACT_TIMESTAMP TRUE
-)
-
 # Make dependencies available
 message(STATUS "Fetching spdlog...")
 FetchContent_MakeAvailable(spdlog)
@@ -93,11 +92,16 @@ FetchContent_MakeAvailable(spdlog)
 message(STATUS "Fetching nlohmann_json...")
 FetchContent_MakeAvailable(nlohmann_json)
 
-message(STATUS "Fetching yaml-cpp...")
-set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
-set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
-set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "" FORCE)
-FetchContent_MakeAvailable(yaml-cpp)
+message(STATUS "Fetching cpp-httplib...")
+set(HTTPLIB_REQUIRE_OPENSSL ON CACHE BOOL "" FORCE)
+set(HTTPLIB_COMPILE OFF CACHE BOOL "" FORCE)  # Header-only mode
+FetchContent_MakeAvailable(httplib)
+
+message(STATUS "Fetching jwt-cpp...")
+set(JWT_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
+set(JWT_BUILD_TESTS OFF CACHE BOOL "" FORCE)
+set(JWT_EXTERNAL_PICOJSON OFF CACHE BOOL "" FORCE)  # Use bundled picojson
+FetchContent_MakeAvailable(jwt-cpp)
 
 message(STATUS "Fetching gRPC (this may take a while)...")
 set(gRPC_BUILD_TESTS OFF CACHE BOOL "" FORCE)
@@ -118,13 +122,12 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pedantic -Wno-overflow -Wno-error")
 FetchContent_MakeAvailable(grpc)
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_BACKUP}")
 
-message(STATUS "Fetching Boost...")
-set(BOOST_INCLUDE_LIBRARIES beast asio system)
-set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
-set(CMAKE_CXX_FLAGS_BACKUP2 "${CMAKE_CXX_FLAGS}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error")
-FetchContent_MakeAvailable(Boost)
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_BACKUP2}")
+# libwebsockets via pkg-config (system library)
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(LWS REQUIRED IMPORTED_TARGET libwebsockets)
+
+# OpenSSL for HTTPS support
+find_package(OpenSSL REQUIRED)
 
 # libsodium requires special handling as it uses autotools
 FetchContent_GetProperties(libsodium)

+ 5 - 6
database/CMakeLists.txt

@@ -1,4 +1,4 @@
-# Database service - smartbotic-db binary and storage library
+# Database service - smartbotic-crm-database binary and storage library
 
 # Storage engine library
 add_library(smartbotic_database STATIC
@@ -23,7 +23,6 @@ target_link_libraries(smartbotic_database
         smartbotic::common
         spdlog::spdlog
         nlohmann_json::nlohmann_json
-        yaml-cpp::yaml-cpp
 )
 
 add_library(smartbotic::database ALIAS smartbotic_database)
@@ -67,18 +66,18 @@ target_link_libraries(smartbotic_database_grpc
 add_library(smartbotic::database_grpc ALIAS smartbotic_database_grpc)
 
 # Database service executable
-add_executable(smartbotic-db
+add_executable(smartbotic-crm-database
     src/main.cpp
 )
 
-target_include_directories(smartbotic-db
+target_include_directories(smartbotic-crm-database
     PRIVATE
         ${CMAKE_CURRENT_SOURCE_DIR}/include
 )
 
-target_compile_options(smartbotic-db PRIVATE ${SMARTBOTIC_GRPC_WARNINGS})
+target_compile_options(smartbotic-crm-database PRIVATE ${SMARTBOTIC_GRPC_WARNINGS})
 
-target_link_libraries(smartbotic-db
+target_link_libraries(smartbotic-crm-database
     PRIVATE
         smartbotic::database_grpc
         sodium

+ 2 - 2
database/include/smartbotic/database/config.hpp

@@ -26,7 +26,7 @@ enum class LogLevel : uint8_t {
 
 /// Complete database configuration
 /// Supports loading from:
-/// 1. Config file (YAML) - smartbotic-db.yaml
+/// 1. Config file (JSON) - smartbotic-db.json
 /// 2. Command-line arguments (override config file)
 /// 3. Environment variables (for secrets like encryption key)
 struct DatabaseConfig {
@@ -64,7 +64,7 @@ struct DatabaseConfig {
 };
 
 /// Configuration loader that handles:
-/// - YAML config file parsing
+/// - JSON config file parsing
 /// - Command-line argument parsing
 /// - Environment variable substitution for secrets
 class ConfigLoader {

+ 13 - 3
database/include/smartbotic/database/grpc/server.hpp

@@ -33,12 +33,22 @@ public:
     /// Returns true on success, false on failure
     [[nodiscard]] auto Start() -> bool;
 
-    /// Stop the server gracefully
-    void Stop();
+    /// Request server shutdown (safe to call from signal handler)
+    /// This initiates the shutdown process but doesn't block.
+    /// Call Wait() after this to wait for completion, then Cleanup() for final cleanup.
+    void RequestShutdown();
 
-    /// Wait for the server to shutdown
+    /// Wait for the server to shutdown (blocks until shutdown completes)
     void Wait();
 
+    /// Perform final cleanup after shutdown (database, etc.)
+    /// Call this from the main thread after Wait() returns
+    void Cleanup();
+
+    /// Stop the server gracefully (combines RequestShutdown, Wait, and Cleanup)
+    /// NOTE: Do NOT call this from a signal handler - use RequestShutdown() instead
+    void Stop();
+
     /// Check if the server is running
     [[nodiscard]] auto IsRunning() const -> bool { return running_; }
 

+ 40 - 38
database/src/config.cpp

@@ -1,7 +1,7 @@
 #include "smartbotic/database/config.hpp"
 
+#include <nlohmann/json.hpp>
 #include <spdlog/spdlog.h>
-#include <yaml-cpp/yaml.h>
 
 #include <algorithm>
 #include <cctype>
@@ -85,22 +85,18 @@ auto ConfigLoader::GetDefaultConfigPaths() -> std::vector<std::string> {
     std::vector<std::string> paths;
 
     // Current directory
-    paths.emplace_back("./smartbotic-db.yaml");
-    paths.emplace_back("./smartbotic-db.yml");
+    paths.emplace_back("./smartbotic-db.json");
 
     // Config subdirectory
-    paths.emplace_back("./config/smartbotic-db.yaml");
-    paths.emplace_back("./config/smartbotic-db.yml");
+    paths.emplace_back("./config/smartbotic-db.json");
 
     // /etc for system-wide config
-    paths.emplace_back("/etc/smartbotic/smartbotic-db.yaml");
-    paths.emplace_back("/etc/smartbotic/smartbotic-db.yml");
+    paths.emplace_back("/etc/smartbotic/smartbotic-db.json");
 
     // Home directory
     const char* home = std::getenv("HOME");
     if (home != nullptr) {
-        paths.push_back(std::string(home) + "/.config/smartbotic/smartbotic-db.yaml");
-        paths.push_back(std::string(home) + "/.config/smartbotic/smartbotic-db.yml");
+        paths.push_back(std::string(home) + "/.config/smartbotic/smartbotic-db.json");
     }
 
     return paths;
@@ -130,47 +126,53 @@ auto ConfigLoader::LoadFromFile(const std::string& path) -> std::optional<Databa
             return std::nullopt;
         }
 
-        YAML::Node yaml = YAML::LoadFile(path);
+        std::ifstream file(path);
+        if (!file.is_open()) {
+            spdlog::error("Failed to open config file: {}", path);
+            return std::nullopt;
+        }
+
+        nlohmann::json json = nlohmann::json::parse(file);
         DatabaseConfig config;
 
         // Server section
-        if (yaml["server"]) {
-            const auto& server = yaml["server"];
-            if (server["address"]) {
-                config.address = server["address"].as<std::string>();
+        if (json.contains("server")) {
+            const auto& server = json["server"];
+            if (server.contains("address")) {
+                config.address = server["address"].get<std::string>();
             }
-            if (server["port"]) {
-                config.port = server["port"].as<uint16_t>();
+            if (server.contains("port")) {
+                config.port = server["port"].get<uint16_t>();
             }
         }
 
         // Storage section
-        if (yaml["storage"]) {
-            const auto& storage = yaml["storage"];
-            if (storage["data_dir"]) {
-                config.data_dir = storage["data_dir"].as<std::string>();
+        if (json.contains("storage")) {
+            const auto& storage = json["storage"];
+            if (storage.contains("data_dir")) {
+                config.data_dir = storage["data_dir"].get<std::string>();
             }
         }
 
         // Snapshot section
-        if (yaml["snapshot"]) {
-            const auto& snapshot = yaml["snapshot"];
-            if (snapshot["enabled"]) {
-                config.snapshot_enabled = snapshot["enabled"].as<bool>();
+        if (json.contains("snapshot")) {
+            const auto& snapshot = json["snapshot"];
+            if (snapshot.contains("enabled")) {
+                config.snapshot_enabled = snapshot["enabled"].get<bool>();
             }
-            if (snapshot["interval_seconds"]) {
-                config.snapshot_interval_seconds = snapshot["interval_seconds"].as<int64_t>();
+            if (snapshot.contains("interval_seconds")) {
+                config.snapshot_interval_seconds = snapshot["interval_seconds"].get<int64_t>();
             }
-            if (snapshot["change_threshold"]) {
-                config.snapshot_change_threshold = snapshot["change_threshold"].as<uint64_t>();
+            if (snapshot.contains("change_threshold")) {
+                config.snapshot_change_threshold = snapshot["change_threshold"].get<uint64_t>();
             }
         }
 
         // Logging section
-        if (yaml["logging"]) {
-            const auto& logging = yaml["logging"];
-            if (logging["level"]) {
-                auto level_str = logging["level"].as<std::string>();
+        if (json.contains("logging")) {
+            const auto& logging = json["logging"];
+            if (logging.contains("level")) {
+                auto level_str = logging["level"].get<std::string>();
                 auto level = ParseLogLevel(level_str);
                 if (level) {
                     config.log_level = *level;
@@ -181,17 +183,17 @@ auto ConfigLoader::LoadFromFile(const std::string& path) -> std::optional<Databa
         }
 
         // Security section
-        if (yaml["security"]) {
-            const auto& security = yaml["security"];
-            if (security["encryption_key_path"]) {
-                config.encryption_key_path = security["encryption_key_path"].as<std::string>();
+        if (json.contains("security")) {
+            const auto& security = json["security"];
+            if (security.contains("encryption_key_path")) {
+                config.encryption_key_path = security["encryption_key_path"].get<std::string>();
             }
         }
 
         spdlog::info("Loaded configuration from: {}", path);
         return config;
 
-    } catch (const YAML::Exception& e) {
+    } catch (const nlohmann::json::exception& e) {
         spdlog::error("Failed to parse config file {}: {}", path, e.what());
         return std::nullopt;
     } catch (const std::exception& e) {
@@ -409,7 +411,7 @@ void ConfigLoader::PrintHelp(const char* program_name) {
     spdlog::info("Options:");
     spdlog::info("  -h, --help                 Show this help message");
     spdlog::info("  -v, --version              Show version information");
-    spdlog::info("  -c, --config <path>        Path to config file (default: smartbotic-db.yaml)");
+    spdlog::info("  -c, --config <path>        Path to config file (default: smartbotic-db.json)");
     spdlog::info("  -p, --port <port>          Port to listen on (default: 50051)");
     spdlog::info("  -a, --address <addr>       Address to bind to (default: 0.0.0.0)");
     spdlog::info("  -d, --data-dir <path>      Data directory (default: ./data)");

+ 30 - 9
database/src/grpc/server.cpp

@@ -4,6 +4,8 @@
 #include <grpcpp/health_check_service_interface.h>
 #include <spdlog/spdlog.h>
 
+#include <chrono>
+
 namespace smartbotic::database::grpc {
 
 GrpcServer::GrpcServer(ServerConfig config) : config_(std::move(config)) {}
@@ -71,16 +73,33 @@ auto GrpcServer::Start() -> bool {
     return true;
 }
 
-void GrpcServer::Stop() {
+void GrpcServer::RequestShutdown() {
     if (!running_) {
         return;
     }
 
-    spdlog::info("Stopping gRPC server...");
+    spdlog::info("Requesting gRPC server shutdown...");
+
+    // Request shutdown with a deadline - this is safe to call from signal handler
+    // It tells the server to stop accepting new RPCs and finish existing ones
+    if (server_) {
+        // Use a short deadline to allow in-flight RPCs to complete
+        auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(5);
+        server_->Shutdown(deadline);
+    }
+}
+
+void GrpcServer::Wait() {
+    if (server_) {
+        server_->Wait();
+    }
+}
+
+void GrpcServer::Cleanup() {
+    spdlog::info("Cleaning up gRPC server resources...");
 
-    // Shutdown gRPC server
+    // Reset the gRPC server (should already be shut down)
     if (server_) {
-        server_->Shutdown();
         server_.reset();
     }
 
@@ -90,13 +109,15 @@ void GrpcServer::Stop() {
     }
 
     running_ = false;
-    spdlog::info("gRPC server stopped");
+    spdlog::info("gRPC server cleanup complete");
 }
 
-void GrpcServer::Wait() {
-    if (server_) {
-        server_->Wait();
-    }
+void GrpcServer::Stop() {
+    // NOTE: Don't call this from a signal handler!
+    // This is a convenience method that combines all shutdown steps.
+    RequestShutdown();
+    Wait();
+    Cleanup();
 }
 
 }  // namespace smartbotic::database::grpc

+ 56 - 5
database/src/main.cpp

@@ -4,6 +4,8 @@
 #include <csignal>
 #include <cstdlib>
 #include <string>
+#include <thread>
+#include <unistd.h>
 
 #include "smartbotic/common.hpp"
 #include "smartbotic/database/config.hpp"
@@ -17,18 +19,36 @@ smartbotic::database::grpc::GrpcServer* g_server = nullptr;
 // Atomic flag for shutdown tracking
 std::atomic<bool> g_shutdown_requested{false};
 
+// Self-pipe for async-signal-safe notification
+int g_shutdown_pipe[2] = {-1, -1};
+
 void SignalHandler(int signal) {
     if (g_shutdown_requested.exchange(true)) {
         // Second signal - force exit
-        spdlog::warn("Received second signal {}, forcing immediate exit", signal);
+        // Note: write() is async-signal-safe
+        const char* msg = "Received second signal, forcing immediate exit\n";
+        [[maybe_unused]] auto n = write(STDERR_FILENO, msg, 48);
         std::_Exit(1);
     }
 
-    const char* signal_name = (signal == SIGINT) ? "SIGINT" : "SIGTERM";
-    spdlog::info("Received {} ({}), initiating graceful shutdown...", signal_name, signal);
+    // Write to pipe to notify shutdown thread (async-signal-safe)
+    char sig = static_cast<char>(signal);
+    [[maybe_unused]] auto n = write(g_shutdown_pipe[1], &sig, 1);
+}
 
-    if (g_server != nullptr) {
-        g_server->Stop();
+// Shutdown thread that waits for signal and calls gRPC Shutdown()
+void ShutdownThread() {
+    char sig = 0;
+    // Block until signal is received (read from pipe)
+    ssize_t n = read(g_shutdown_pipe[0], &sig, 1);
+    if (n > 0) {
+        const char* signal_name = (sig == SIGINT) ? "SIGINT" : "SIGTERM";
+        spdlog::info("Received {} ({}), initiating graceful shutdown...", signal_name, static_cast<int>(sig));
+
+        // Safe to call gRPC Shutdown() from this thread (not from signal handler)
+        if (g_server != nullptr) {
+            g_server->RequestShutdown();
+        }
     }
 }
 
@@ -75,6 +95,13 @@ int main(int argc, char* argv[]) {
     // Print configuration
     smartbotic::database::ConfigLoader::PrintConfig(config);
 
+    // Create self-pipe for async-signal-safe shutdown notification
+    if (pipe(g_shutdown_pipe) == -1) {
+        spdlog::error("Failed to create shutdown pipe");
+        smartbotic::common::Shutdown();
+        return 1;
+    }
+
     // Convert DatabaseConfig to ServerConfig for the gRPC server
     smartbotic::database::ServerConfig server_config;
     server_config.address = config.address;
@@ -85,6 +112,9 @@ int main(int argc, char* argv[]) {
     smartbotic::database::grpc::GrpcServer server(server_config);
     g_server = &server;
 
+    // Start shutdown thread that will call gRPC Shutdown() when signaled
+    std::thread shutdown_thread(ShutdownThread);
+
     // Set up signal handlers for graceful shutdown (SIGTERM/SIGINT)
     struct sigaction sa{};
     sa.sa_handler = SignalHandler;
@@ -93,11 +123,18 @@ int main(int argc, char* argv[]) {
 
     if (sigaction(SIGINT, &sa, nullptr) == -1) {
         spdlog::error("Failed to set SIGINT handler");
+        // Unblock shutdown thread by closing write end of pipe
+        close(g_shutdown_pipe[1]);
+        shutdown_thread.join();
+        close(g_shutdown_pipe[0]);
         smartbotic::common::Shutdown();
         return 1;
     }
     if (sigaction(SIGTERM, &sa, nullptr) == -1) {
         spdlog::error("Failed to set SIGTERM handler");
+        close(g_shutdown_pipe[1]);
+        shutdown_thread.join();
+        close(g_shutdown_pipe[0]);
         smartbotic::common::Shutdown();
         return 1;
     }
@@ -106,6 +143,10 @@ int main(int argc, char* argv[]) {
 
     if (!server.Start()) {
         spdlog::error("Failed to start server");
+        // Unblock shutdown thread by closing write end of pipe
+        close(g_shutdown_pipe[1]);
+        shutdown_thread.join();
+        close(g_shutdown_pipe[0]);
         smartbotic::common::Shutdown();
         return 1;
     }
@@ -113,6 +154,16 @@ int main(int argc, char* argv[]) {
     // Wait for server to stop (either by signal or error)
     server.Wait();
 
+    // Wait for shutdown thread to complete
+    shutdown_thread.join();
+
+    // Perform cleanup after Wait() returns (safe to do in main thread)
+    server.Cleanup();
+
+    // Close pipe
+    close(g_shutdown_pipe[0]);
+    close(g_shutdown_pipe[1]);
+
     spdlog::info("SmartBotic Database Service shutdown complete");
     g_server = nullptr;
     smartbotic::common::Shutdown();

+ 31 - 0
systemd/smartbotic-database.service

@@ -0,0 +1,31 @@
+[Unit]
+Description=SmartBotic CRM Database
+Documentation=https://github.com/smartbotic/smartbotic-crm
+After=network.target
+
+[Service]
+Type=simple
+User=%i
+Group=%i
+WorkingDirectory=/opt/smartbotic-crm
+ExecStart=/opt/smartbotic-crm/build/database/smartbotic-crm-database --data-dir /var/lib/smartbotic
+Restart=on-failure
+RestartSec=5
+StandardOutput=journal
+StandardError=journal
+# Shutdown timeout - send SIGKILL after 10 seconds if graceful shutdown fails
+TimeoutStopSec=10
+
+# Security hardening
+NoNewPrivileges=true
+ProtectSystem=strict
+ProtectHome=true
+PrivateTmp=true
+ReadWritePaths=/var/lib/smartbotic
+
+# Environment
+Environment="SMARTBOTIC_DB_PORT=50151"
+Environment="SMARTBOTIC_DB_DATA_DIR=/var/lib/smartbotic"
+
+[Install]
+WantedBy=multi-user.target

+ 36 - 0
systemd/smartbotic-webserver.service

@@ -0,0 +1,36 @@
+[Unit]
+Description=SmartBotic CRM Webserver
+Documentation=https://github.com/smartbotic/smartbotic-crm
+After=network.target smartbotic-database.service
+Requires=smartbotic-database.service
+
+[Service]
+Type=simple
+User=%i
+Group=%i
+WorkingDirectory=/opt/smartbotic-crm
+ExecStart=/opt/smartbotic-crm/build/webserver/smartbotic-crm-webserver
+Restart=on-failure
+RestartSec=5
+StandardOutput=journal
+StandardError=journal
+# Shutdown timeout - send SIGKILL after 10 seconds if graceful shutdown fails
+TimeoutStopSec=10
+
+# Security hardening
+NoNewPrivileges=true
+ProtectSystem=strict
+ProtectHome=true
+PrivateTmp=true
+
+# Environment
+Environment="SMARTBOTIC_SERVER_HTTP_PORT=18080"
+Environment="SMARTBOTIC_SERVER_WS_PORT=18081"
+Environment="SMARTBOTIC_SERVER_DATABASE_HOST=localhost"
+Environment="SMARTBOTIC_SERVER_DATABASE_PORT=50151"
+Environment="SMARTBOTIC_SERVER_STATIC_PATH=/opt/smartbotic-crm/webui/dist"
+# Set JWT secret via environment or config file
+# Environment="SMARTBOTIC_SERVER_JWT_SECRET=your-secret-here"
+
+[Install]
+WantedBy=multi-user.target

+ 32 - 8
webserver/CMakeLists.txt

@@ -1,10 +1,31 @@
 # Web server - smartbotic-server binary
 
+# Use relaxed warnings for webserver (it includes gRPC headers which use __int128)
+set(SMARTBOTIC_WEBSERVER_WARNINGS
+    -Wall
+    -Wextra
+    -Werror
+    -Wno-unused-parameter
+    -Wno-dangling-reference
+    -Wno-pedantic      # Required for abseil __int128 support
+    -Wno-overflow      # Required for abseil hash_set.h
+)
+
 # Create library for webserver components
 add_library(smartbotic_webserver STATIC
     src/config.cpp
     src/http_server.cpp
     src/database_client.cpp
+    src/auth_service.cpp
+    src/user_service.cpp
+    src/workspace_service.cpp
+    src/group_service.cpp
+    src/membership_service.cpp
+    src/api_key_service.cpp
+    src/collection_service.cpp
+    src/document_service.cpp
+    src/ws_handler.cpp
+    src/view_service.cpp
 )
 
 target_include_directories(smartbotic_webserver
@@ -13,7 +34,7 @@ target_include_directories(smartbotic_webserver
         $<INSTALL_INTERFACE:include>
 )
 
-target_compile_options(smartbotic_webserver PRIVATE ${SMARTBOTIC_CXX_WARNINGS})
+target_compile_options(smartbotic_webserver PRIVATE ${SMARTBOTIC_WEBSERVER_WARNINGS})
 
 target_link_libraries(smartbotic_webserver
     PUBLIC
@@ -21,26 +42,29 @@ target_link_libraries(smartbotic_webserver
         smartbotic::proto
         spdlog::spdlog
         nlohmann_json::nlohmann_json
-        yaml-cpp::yaml-cpp
-        Boost::beast
-        Boost::asio
+        httplib::httplib
+        PkgConfig::LWS
+        OpenSSL::SSL
+        OpenSSL::Crypto
+        jwt-cpp::jwt-cpp
+        sodium
 )
 
 add_library(smartbotic::webserver ALIAS smartbotic_webserver)
 
 # Create the executable
-add_executable(smartbotic-server
+add_executable(smartbotic-crm-webserver
     src/main.cpp
 )
 
-target_include_directories(smartbotic-server
+target_include_directories(smartbotic-crm-webserver
     PRIVATE
         ${CMAKE_CURRENT_SOURCE_DIR}/include
 )
 
-target_compile_options(smartbotic-server PRIVATE ${SMARTBOTIC_CXX_WARNINGS})
+target_compile_options(smartbotic-crm-webserver PRIVATE ${SMARTBOTIC_WEBSERVER_WARNINGS})
 
-target_link_libraries(smartbotic-server
+target_link_libraries(smartbotic-crm-webserver
     PRIVATE
         smartbotic::webserver
         smartbotic::common

+ 16 - 4
webserver/include/smartbotic/webserver/config.hpp

@@ -34,9 +34,17 @@ struct CorsConfig {
     int max_age = 86400;             // Preflight cache duration (seconds)
 };
 
+/// JWT authentication configuration
+struct JwtConfig {
+    std::string secret;                     // JWT signing secret (required)
+    std::string issuer = "smartbotic-crm";  // JWT issuer claim
+    int access_token_expiry = 900;          // Access token expiry in seconds (default: 15 minutes)
+    int refresh_token_expiry = 604800;      // Refresh token expiry in seconds (default: 7 days)
+};
+
 /// Complete HTTP server configuration
 /// Supports loading from:
-/// 1. Config file (YAML) - smartbotic-server.yaml
+/// 1. Config file (JSON) - smartbotic-server.json
 /// 2. Command-line arguments (override config file)
 /// 3. Environment variables
 struct HttpServerConfig {
@@ -48,16 +56,20 @@ struct HttpServerConfig {
     std::string webui_path = "./webui/dist";  // Path to WebUI static files
 
     // WebSocket configuration
-    std::string ws_path = "/ws";  // WebSocket endpoint path
+    uint16_t ws_port = 8081;          // WebSocket server port (separate from HTTP)
+    std::string ws_path = "/ws";      // WebSocket endpoint path
 
     // CORS configuration
     CorsConfig cors;
 
+    // JWT authentication configuration
+    JwtConfig jwt;
+
     // Logging configuration
     LogLevel log_level = LogLevel::kInfo;  // Default log level
 
     // Database gRPC connection
-    std::string database_address = "127.0.0.1:50051";  // Database service address
+    std::string database_address = "127.0.0.1:50151";  // Database service address
 
     /// Get the full address string (address:port)
     [[nodiscard]] auto GetListenAddress() const -> std::string { return address + ":" + std::to_string(port); }
@@ -67,7 +79,7 @@ struct HttpServerConfig {
 };
 
 /// Configuration loader that handles:
-/// - YAML config file parsing
+/// - JSON config file parsing
 /// - Command-line argument parsing
 /// - Environment variable substitution
 class ConfigLoader {

+ 218 - 106
webserver/include/smartbotic/webserver/http_server.hpp

@@ -1,127 +1,115 @@
 #pragma once
 
 #include <atomic>
-#include <boost/asio/io_context.hpp>
-#include <boost/asio/ip/tcp.hpp>
-#include <boost/asio/strand.hpp>
-#include <boost/beast/core.hpp>
-#include <boost/beast/http.hpp>
-#include <boost/beast/websocket.hpp>
 #include <chrono>
 #include <functional>
 #include <memory>
 #include <mutex>
 #include <string>
 #include <thread>
-#include <unordered_set>
+#include <unordered_map>
 #include <vector>
 
+#include <httplib.h>
+#include <libwebsockets.h>
+
+#include "smartbotic/webserver/api_key_service.hpp"
+#include "smartbotic/webserver/auth_service.hpp"
+#include "smartbotic/webserver/collection_service.hpp"
 #include "smartbotic/webserver/config.hpp"
 #include "smartbotic/webserver/database_client.hpp"
+#include "smartbotic/webserver/document_service.hpp"
+#include "smartbotic/webserver/group_service.hpp"
+#include "smartbotic/webserver/membership_service.hpp"
+#include "smartbotic/webserver/user_service.hpp"
+#include "smartbotic/webserver/view_service.hpp"
+#include "smartbotic/webserver/workspace_service.hpp"
+#include "smartbotic/webserver/ws_handler.hpp"
 
 namespace smartbotic::webserver {
 
-namespace beast = boost::beast;
-namespace http = beast::http;
-namespace websocket = beast::websocket;
-namespace asio = boost::asio;
-using tcp = asio::ip::tcp;
-
 // Forward declarations
-class HttpSession;
-class WebSocketSession;
+class WebSocketServer;
+
+/// WebSocket connection info
+struct WsConnection {
+    lws* wsi = nullptr;
+    std::string sessionId;
+    std::vector<std::string> subscriptions;
+    std::vector<uint8_t> sendBuffer;
+    bool sendBinary = false;
+    std::mutex sendMutex;
+
+    // Auth state (for future)
+    bool authenticated = false;
+    std::string userId;
+};
 
 /// Callback type for WebSocket messages
-using WebSocketMessageHandler = std::function<void(WebSocketSession&, const std::string&)>;
+using WebSocketMessageHandler = std::function<void(WsConnection*, const std::string&)>;
 
-/// WebSocket session that handles a single WebSocket connection
-class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> {
+/// WebSocket server using libwebsockets (separate from HTTP)
+class WebSocketServer {
 public:
-    explicit WebSocketSession(tcp::socket socket, const HttpServerConfig& config);
-    ~WebSocketSession();
+    explicit WebSocketServer(uint16_t port, const std::string& path = "/ws");
+    ~WebSocketServer();
 
     /// Non-copyable and non-movable
-    WebSocketSession(const WebSocketSession&) = delete;
-    auto operator=(const WebSocketSession&) -> WebSocketSession& = delete;
-    WebSocketSession(WebSocketSession&&) = delete;
-    auto operator=(WebSocketSession&&) -> WebSocketSession& = delete;
+    WebSocketServer(const WebSocketServer&) = delete;
+    auto operator=(const WebSocketServer&) -> WebSocketServer& = delete;
+    WebSocketServer(WebSocketServer&&) = delete;
+    auto operator=(WebSocketServer&&) -> WebSocketServer& = delete;
 
-    /// Start the WebSocket session
-    void Run(http::request<http::string_body> req);
+    /// Start the WebSocket server
+    void Start();
 
-    /// Send a text message to the client
-    void Send(const std::string& message);
+    /// Stop the WebSocket server
+    void Stop();
 
-    /// Close the WebSocket connection
-    void Close();
+    /// Check if the server is running
+    [[nodiscard]] auto IsRunning() const -> bool { return running_.load(); }
 
-    /// Check if the session is open
-    [[nodiscard]] auto IsOpen() const -> bool;
+    /// Broadcast a message to all connected clients
+    void Broadcast(const std::string& message);
 
-    /// Set the message handler
-    void SetMessageHandler(WebSocketMessageHandler handler);
+    /// Send a message to a specific session
+    void SendToSession(const std::string& sessionId, const std::string& message);
 
-private:
-    void DoAccept(http::request<http::string_body> req);
-    void OnAccept(beast::error_code ec);
-    void DoRead();
-    void OnRead(beast::error_code ec, std::size_t bytes_transferred);
-    void OnWrite(beast::error_code ec, std::size_t bytes_transferred);
-
-    websocket::stream<beast::tcp_stream> ws_;
-    beast::flat_buffer buffer_;
-    const HttpServerConfig& config_;
-    WebSocketMessageHandler message_handler_;
-    std::vector<std::shared_ptr<std::string const>> queue_;
-    std::mutex mutex_;
-};
+    /// Send a message to all connections subscribed to a collection
+    void SendToSubscribers(const std::string& subscription_key, const std::string& message);
 
-/// HTTP session that handles a single HTTP connection
-class HttpSession : public std::enable_shared_from_this<HttpSession> {
-public:
-    HttpSession(tcp::socket socket, const HttpServerConfig& config,
-                std::function<void(std::shared_ptr<WebSocketSession>)> ws_connect_handler,
-                std::function<void(std::shared_ptr<WebSocketSession>)> ws_disconnect_handler);
-    ~HttpSession();
+    /// Get the number of connected clients
+    [[nodiscard]] auto ConnectionCount() const -> size_t;
 
-    /// Non-copyable and non-movable
-    HttpSession(const HttpSession&) = delete;
-    auto operator=(const HttpSession&) -> HttpSession& = delete;
-    HttpSession(HttpSession&&) = delete;
-    auto operator=(HttpSession&&) -> HttpSession& = delete;
+    /// Set the message handler
+    void SetMessageHandler(WebSocketMessageHandler handler);
 
-    /// Start processing HTTP requests
-    void Run();
+    /// LWS static callback (called by libwebsockets)
+    static auto WsCallback(lws* wsi, lws_callback_reasons reason,
+                           void* user, void* in, size_t len) -> int;
 
 private:
-    void DoRead();
-    void OnRead(beast::error_code ec, std::size_t bytes_transferred);
-    void HandleRequest(http::request<http::string_body>&& req);
-    void SendResponse(http::response<http::string_body>&& res);
-    void OnWrite(beast::error_code ec, std::size_t bytes_transferred, bool close);
-    void LogRequest(http::status status);
-
-    [[nodiscard]] auto HandleStaticFile(const std::string& target) -> http::response<http::string_body>;
-    [[nodiscard]] auto HandleApiRequest(http::request<http::string_body>& req) -> http::response<http::string_body>;
-    [[nodiscard]] auto MakeErrorResponse(http::status status, const std::string& message)
-        -> http::response<http::string_body>;
-    void ApplyCorsHeaders(http::response<http::string_body>& res);
-    [[nodiscard]] auto GetMimeType(const std::string& path) -> std::string;
-
-    beast::tcp_stream stream_;
-    beast::flat_buffer buffer_;
-    http::request<http::string_body> req_;
-    const HttpServerConfig& config_;
-    std::function<void(std::shared_ptr<WebSocketSession>)> ws_connect_handler_;
-    std::function<void(std::shared_ptr<WebSocketSession>)> ws_disconnect_handler_;
-
-    // Request timing for logging
-    std::chrono::steady_clock::time_point request_start_;
-    std::string request_method_;
-    std::string request_target_;
+    void RunEventLoop();
+    void HandleConnect(lws* wsi);
+    void HandleDisconnect(lws* wsi);
+    void HandleMessage(lws* wsi, const std::string& message);
+    void QueueMessage(WsConnection* conn, const std::string& message);
+
+    uint16_t port_;
+    std::string path_;
+    std::atomic<bool> running_{false};
+    std::thread eventLoopThread_;
+    lws_context* context_ = nullptr;
+
+    mutable std::mutex connectionsMutex_;
+    std::unordered_map<lws*, std::unique_ptr<WsConnection>> connections_;
+    WebSocketMessageHandler messageHandler_;
+
+    // Static instance pointer for callback access
+    static WebSocketServer* instance_;
 };
 
-/// Main HTTP server that hosts REST API and WebSocket endpoints
+/// Main HTTP server using cpp-httplib
 class HttpServer {
 public:
     /// Create a server with the given configuration
@@ -162,34 +150,158 @@ public:
     [[nodiscard]] auto GetWebSocketClientCount() const -> size_t;
 
     /// Get the database client
-    [[nodiscard]] auto GetDatabaseClient() -> DatabaseClient& { return *db_client_; }
+    [[nodiscard]] auto GetDatabaseClient() -> DatabaseClient& { return *dbClient_; }
 
     /// Check if the database is connected
     [[nodiscard]] auto IsDatabaseConnected() const -> bool;
 
-private:
-    void DoAccept();
-    void OnAccept(beast::error_code ec, tcp::socket socket);
-    void OnWebSocketConnect(const std::shared_ptr<WebSocketSession>& session);
-    void OnWebSocketDisconnect(const std::shared_ptr<WebSocketSession>& session);
+    /// Get the auth service
+    [[nodiscard]] auto GetAuthService() -> AuthService& { return *authService_; }
+
+    /// Get the user service
+    [[nodiscard]] auto GetUserService() -> UserService& { return *userService_; }
+
+    /// Get the workspace service
+    [[nodiscard]] auto GetWorkspaceService() -> WorkspaceService& { return *workspaceService_; }
+
+    /// Get the group service
+    [[nodiscard]] auto GetGroupService() -> GroupService& { return *groupService_; }
+
+    /// Get the membership service
+    [[nodiscard]] auto GetMembershipService() -> MembershipService& { return *membershipService_; }
+
+    /// Get the API key service
+    [[nodiscard]] auto GetApiKeyService() -> ApiKeyService& { return *apiKeyService_; }
+
+    /// Get the collection service
+    [[nodiscard]] auto GetCollectionService() -> CollectionService& { return *collectionService_; }
 
-    /// Connect to the database with health check
+    /// Get the document service
+    [[nodiscard]] auto GetDocumentService() -> DocumentService& { return *documentService_; }
+
+    /// Get the view service
+    [[nodiscard]] auto GetViewService() -> ViewService& { return *viewService_; }
+
+    /// Get the WebSocket handler
+    [[nodiscard]] auto GetWsHandler() -> WsHandler& { return *wsHandler_; }
+
+    /// Broadcast a document event to subscribed WebSocket clients
+    void BroadcastDocumentEvent(const std::string& workspace_id,
+                                 const std::string& collection,
+                                 DocumentAction action,
+                                 const std::string& document_id,
+                                 const nlohmann::json& data);
+
+private:
+    void SetupRoutes();
+    void SetupAuthRoutes();
+    void SetupUserRoutes();
+    void SetupWorkspaceRoutes();
+    void SetupGroupRoutes();
+    void SetupMembershipRoutes();
+    void SetupApiKeyRoutes();
+    void SetupCollectionRoutes();
+    void SetupDocumentRoutes();
+    void SetupViewRoutes();
+    void RunHttpServer();
     [[nodiscard]] auto ConnectToDatabase() -> bool;
+    [[nodiscard]] auto InitializeServices() -> bool;
+
+    // Route handlers
+    void HandleHealth(const httplib::Request& req, httplib::Response& res);
+    void HandleVersion(const httplib::Request& req, httplib::Response& res);
+    void HandleBootstrap(const httplib::Request& req, httplib::Response& res);
+
+    // Auth route handlers
+    void HandleAuthLogin(const httplib::Request& req, httplib::Response& res);
+    void HandleAuthRefresh(const httplib::Request& req, httplib::Response& res);
+    void HandleAuthLogout(const httplib::Request& req, httplib::Response& res);
+    void HandleAuthMe(const httplib::Request& req, httplib::Response& res);
+
+    // User route handlers
+    void HandleCreateUser(const httplib::Request& req, httplib::Response& res);
+    void HandleListUsers(const httplib::Request& req, httplib::Response& res);
+    void HandleGetUser(const httplib::Request& req, httplib::Response& res);
+    void HandleUpdateUser(const httplib::Request& req, httplib::Response& res);
+    void HandleDeleteUser(const httplib::Request& req, httplib::Response& res);
+
+    // Workspace route handlers
+    void HandleCreateWorkspace(const httplib::Request& req, httplib::Response& res);
+    void HandleListWorkspaces(const httplib::Request& req, httplib::Response& res);
+    void HandleGetWorkspace(const httplib::Request& req, httplib::Response& res);
+    void HandleUpdateWorkspace(const httplib::Request& req, httplib::Response& res);
+    void HandleDeleteWorkspace(const httplib::Request& req, httplib::Response& res);
+
+    // Group route handlers
+    void HandleCreateGroup(const httplib::Request& req, httplib::Response& res);
+    void HandleListGroups(const httplib::Request& req, httplib::Response& res);
+    void HandleGetGroup(const httplib::Request& req, httplib::Response& res);
+    void HandleUpdateGroup(const httplib::Request& req, httplib::Response& res);
+    void HandleDeleteGroup(const httplib::Request& req, httplib::Response& res);
+
+    // Membership route handlers
+    void HandleAddMember(const httplib::Request& req, httplib::Response& res);
+    void HandleListMembers(const httplib::Request& req, httplib::Response& res);
+    void HandleGetMember(const httplib::Request& req, httplib::Response& res);
+    void HandleUpdateMember(const httplib::Request& req, httplib::Response& res);
+    void HandleRemoveMember(const httplib::Request& req, httplib::Response& res);
+
+    // API Key route handlers
+    void HandleCreateApiKey(const httplib::Request& req, httplib::Response& res);
+    void HandleListApiKeys(const httplib::Request& req, httplib::Response& res);
+    void HandleRevokeApiKey(const httplib::Request& req, httplib::Response& res);
+
+    // Collection route handlers
+    void HandleCreateCollection(const httplib::Request& req, httplib::Response& res);
+    void HandleListCollections(const httplib::Request& req, httplib::Response& res);
+    void HandleGetCollection(const httplib::Request& req, httplib::Response& res);
+    void HandleUpdateCollection(const httplib::Request& req, httplib::Response& res);
+    void HandleDropCollection(const httplib::Request& req, httplib::Response& res);
+
+    // Document route handlers
+    void HandleCreateDocument(const httplib::Request& req, httplib::Response& res);
+    void HandleListDocuments(const httplib::Request& req, httplib::Response& res);
+    void HandleGetDocument(const httplib::Request& req, httplib::Response& res);
+    void HandleUpdateDocument(const httplib::Request& req, httplib::Response& res);
+    void HandleDeleteDocument(const httplib::Request& req, httplib::Response& res);
+
+    // View route handlers
+    void HandleCreateView(const httplib::Request& req, httplib::Response& res);
+    void HandleListViews(const httplib::Request& req, httplib::Response& res);
+    void HandleGetView(const httplib::Request& req, httplib::Response& res);
+    void HandleUpdateView(const httplib::Request& req, httplib::Response& res);
+    void HandleDeleteView(const httplib::Request& req, httplib::Response& res);
+
+    // Authentication middleware helper
+    [[nodiscard]] auto AuthenticateRequest(const httplib::Request& req) -> std::optional<AuthUser>;
+
+    // Check if user has superadmin privileges
+    [[nodiscard]] static auto IsSuperadmin(const AuthUser& user) -> bool;
+
+    // Static file serving
+    [[nodiscard]] static auto GetMimeType(const std::string& path) -> std::string;
 
     HttpServerConfig config_;
     std::atomic<bool> running_{false};
 
-    asio::io_context ioc_;
-    tcp::acceptor acceptor_;
-    std::vector<std::thread> threads_;
-
-    // Database client
-    std::unique_ptr<DatabaseClient> db_client_;
-
-    // WebSocket session management
-    std::unordered_set<std::shared_ptr<WebSocketSession>> ws_sessions_;
-    mutable std::mutex ws_sessions_mutex_;
-    WebSocketMessageHandler ws_message_handler_;
+    std::unique_ptr<httplib::Server> httpServer_;
+    std::unique_ptr<WebSocketServer> wsServer_;
+    std::unique_ptr<DatabaseClient> dbClient_;
+    std::unique_ptr<AuthService> authService_;
+    std::unique_ptr<UserService> userService_;
+    std::unique_ptr<WorkspaceService> workspaceService_;
+    std::unique_ptr<GroupService> groupService_;
+    std::unique_ptr<MembershipService> membershipService_;
+    std::unique_ptr<ApiKeyService> apiKeyService_;
+    std::unique_ptr<CollectionService> collectionService_;
+    std::unique_ptr<DocumentService> documentService_;
+    std::unique_ptr<ViewService> viewService_;
+    std::unique_ptr<WsHandler> wsHandler_;
+
+    std::thread httpThread_;
+
+    // Stored message handler (set before Start() is called)
+    WebSocketMessageHandler wsMessageHandler_;
 };
 
 }  // namespace smartbotic::webserver

+ 78 - 47
webserver/src/config.cpp

@@ -1,7 +1,7 @@
 #include "smartbotic/webserver/config.hpp"
 
+#include <nlohmann/json.hpp>
 #include <spdlog/spdlog.h>
-#include <yaml-cpp/yaml.h>
 
 #include <algorithm>
 #include <cctype>
@@ -89,22 +89,18 @@ auto ConfigLoader::GetDefaultConfigPaths() -> std::vector<std::string> {
     std::vector<std::string> paths;
 
     // Current directory
-    paths.emplace_back("./smartbotic-server.yaml");
-    paths.emplace_back("./smartbotic-server.yml");
+    paths.emplace_back("./smartbotic-server.json");
 
     // Config subdirectory
-    paths.emplace_back("./config/smartbotic-server.yaml");
-    paths.emplace_back("./config/smartbotic-server.yml");
+    paths.emplace_back("./config/smartbotic-server.json");
 
     // /etc for system-wide config
-    paths.emplace_back("/etc/smartbotic/smartbotic-server.yaml");
-    paths.emplace_back("/etc/smartbotic/smartbotic-server.yml");
+    paths.emplace_back("/etc/smartbotic/smartbotic-server.json");
 
     // Home directory
     const char* home = std::getenv("HOME");
     if (home != nullptr) {
-        paths.push_back(std::string(home) + "/.config/smartbotic/smartbotic-server.yaml");
-        paths.push_back(std::string(home) + "/.config/smartbotic/smartbotic-server.yml");
+        paths.push_back(std::string(home) + "/.config/smartbotic/smartbotic-server.json");
     }
 
     return paths;
@@ -134,68 +130,77 @@ auto ConfigLoader::LoadFromFile(const std::string& path) -> std::optional<HttpSe
             return std::nullopt;
         }
 
-        YAML::Node yaml = YAML::LoadFile(path);
+        std::ifstream file(path);
+        if (!file.is_open()) {
+            spdlog::error("Failed to open config file: {}", path);
+            return std::nullopt;
+        }
+
+        nlohmann::json json = nlohmann::json::parse(file);
         HttpServerConfig config;
 
         // Server section
-        if (yaml["server"]) {
-            const auto& server = yaml["server"];
-            if (server["address"]) {
-                config.address = server["address"].as<std::string>();
+        if (json.contains("server")) {
+            const auto& server = json["server"];
+            if (server.contains("address")) {
+                config.address = server["address"].get<std::string>();
             }
-            if (server["port"]) {
-                config.port = server["port"].as<uint16_t>();
+            if (server.contains("port")) {
+                config.port = server["port"].get<uint16_t>();
             }
-            if (server["webui_path"]) {
-                config.webui_path = server["webui_path"].as<std::string>();
+            if (server.contains("webui_path")) {
+                config.webui_path = server["webui_path"].get<std::string>();
             }
         }
 
         // WebSocket section
-        if (yaml["websocket"]) {
-            const auto& ws = yaml["websocket"];
-            if (ws["path"]) {
-                config.ws_path = ws["path"].as<std::string>();
+        if (json.contains("websocket")) {
+            const auto& ws = json["websocket"];
+            if (ws.contains("port")) {
+                config.ws_port = ws["port"].get<uint16_t>();
+            }
+            if (ws.contains("path")) {
+                config.ws_path = ws["path"].get<std::string>();
             }
         }
 
         // CORS section
-        if (yaml["cors"]) {
-            const auto& cors = yaml["cors"];
-            if (cors["enabled"]) {
-                config.cors.enabled = cors["enabled"].as<bool>();
+        if (json.contains("cors")) {
+            const auto& cors = json["cors"];
+            if (cors.contains("enabled")) {
+                config.cors.enabled = cors["enabled"].get<bool>();
             }
-            if (cors["allowed_origins"]) {
+            if (cors.contains("allowed_origins")) {
                 config.cors.allowed_origins.clear();
                 for (const auto& origin : cors["allowed_origins"]) {
-                    config.cors.allowed_origins.push_back(origin.as<std::string>());
+                    config.cors.allowed_origins.push_back(origin.get<std::string>());
                 }
             }
-            if (cors["allowed_methods"]) {
+            if (cors.contains("allowed_methods")) {
                 config.cors.allowed_methods.clear();
                 for (const auto& method : cors["allowed_methods"]) {
-                    config.cors.allowed_methods.push_back(method.as<std::string>());
+                    config.cors.allowed_methods.push_back(method.get<std::string>());
                 }
             }
-            if (cors["allowed_headers"]) {
+            if (cors.contains("allowed_headers")) {
                 config.cors.allowed_headers.clear();
                 for (const auto& header : cors["allowed_headers"]) {
-                    config.cors.allowed_headers.push_back(header.as<std::string>());
+                    config.cors.allowed_headers.push_back(header.get<std::string>());
                 }
             }
-            if (cors["allow_credentials"]) {
-                config.cors.allow_credentials = cors["allow_credentials"].as<bool>();
+            if (cors.contains("allow_credentials")) {
+                config.cors.allow_credentials = cors["allow_credentials"].get<bool>();
             }
-            if (cors["max_age"]) {
-                config.cors.max_age = cors["max_age"].as<int>();
+            if (cors.contains("max_age")) {
+                config.cors.max_age = cors["max_age"].get<int>();
             }
         }
 
         // Logging section
-        if (yaml["logging"]) {
-            const auto& logging = yaml["logging"];
-            if (logging["level"]) {
-                auto level_str = logging["level"].as<std::string>();
+        if (json.contains("logging")) {
+            const auto& logging = json["logging"];
+            if (logging.contains("level")) {
+                auto level_str = logging["level"].get<std::string>();
                 auto level = ParseLogLevel(level_str);
                 if (level) {
                     config.log_level = *level;
@@ -206,17 +211,34 @@ auto ConfigLoader::LoadFromFile(const std::string& path) -> std::optional<HttpSe
         }
 
         // Database section
-        if (yaml["database"]) {
-            const auto& database = yaml["database"];
-            if (database["address"]) {
-                config.database_address = database["address"].as<std::string>();
+        if (json.contains("database")) {
+            const auto& database = json["database"];
+            if (database.contains("address")) {
+                config.database_address = database["address"].get<std::string>();
+            }
+        }
+
+        // JWT section
+        if (json.contains("jwt")) {
+            const auto& jwt = json["jwt"];
+            if (jwt.contains("secret")) {
+                config.jwt.secret = jwt["secret"].get<std::string>();
+            }
+            if (jwt.contains("issuer")) {
+                config.jwt.issuer = jwt["issuer"].get<std::string>();
+            }
+            if (jwt.contains("access_token_expiry")) {
+                config.jwt.access_token_expiry = jwt["access_token_expiry"].get<int>();
+            }
+            if (jwt.contains("refresh_token_expiry")) {
+                config.jwt.refresh_token_expiry = jwt["refresh_token_expiry"].get<int>();
             }
         }
 
         spdlog::info("Loaded configuration from: {}", path);
         return config;
 
-    } catch (const YAML::Exception& e) {
+    } catch (const nlohmann::json::exception& e) {
         spdlog::error("Failed to parse config file {}: {}", path, e.what());
         return std::nullopt;
     } catch (const std::exception& e) {
@@ -238,6 +260,9 @@ void ConfigLoader::ApplyEnvironment(HttpServerConfig& config) {
     // Database settings from environment
     config.database_address = GetEnvOrDefault("SMARTBOTIC_SERVER_DATABASE_ADDRESS", config.database_address);
 
+    // JWT settings from environment (secret from env is recommended for security)
+    config.jwt.secret = GetEnvOrDefault("SMARTBOTIC_SERVER_JWT_SECRET", config.jwt.secret);
+
     // Log level from environment
     const char* log_level_env = std::getenv("SMARTBOTIC_SERVER_LOG_LEVEL");
     if (log_level_env != nullptr) {
@@ -417,7 +442,7 @@ void ConfigLoader::PrintHelp(const char* program_name) {
     spdlog::info("Options:");
     spdlog::info("  -h, --help                 Show this help message");
     spdlog::info("  -v, --version              Show version information");
-    spdlog::info("  -c, --config <path>        Path to config file (default: smartbotic-server.yaml)");
+    spdlog::info("  -c, --config <path>        Path to config file (default: smartbotic-server.json)");
     spdlog::info("  -p, --port <port>          Port to listen on (default: 8080)");
     spdlog::info("  -a, --address <addr>       Address to bind to (default: 0.0.0.0)");
     spdlog::info("  -w, --webui-path <path>    Path to WebUI static files (default: ./webui/dist)");
@@ -445,6 +470,7 @@ void ConfigLoader::PrintConfig(const HttpServerConfig& config) {
     spdlog::info("  Address:          {}", config.address);
     spdlog::info("  Port:             {}", config.port);
     spdlog::info("  WebUI Path:       {}", config.webui_path);
+    spdlog::info("  WebSocket Port:   {}", config.ws_port);
     spdlog::info("  WebSocket Path:   {}", config.ws_path);
     spdlog::info("  Database Address: {}", config.database_address);
     spdlog::info("  Log Level:        {}", LogLevelToString(config.log_level));
@@ -452,12 +478,17 @@ void ConfigLoader::PrintConfig(const HttpServerConfig& config) {
     if (config.cors.enabled) {
         std::string origins;
         for (size_t i = 0; i < config.cors.allowed_origins.size(); ++i) {
-            if (i > 0)
+            if (i > 0) {
                 origins += ", ";
+            }
             origins += config.cors.allowed_origins[i];
         }
         spdlog::info("  CORS Origins:     {}", origins);
     }
+    spdlog::info("  JWT Issuer:       {}", config.jwt.issuer);
+    spdlog::info("  JWT Secret:       {}", config.jwt.secret.empty() ? "(not configured)" : "(configured)");
+    spdlog::info("  JWT Access TTL:   {}s", config.jwt.access_token_expiry);
+    spdlog::info("  JWT Refresh TTL:  {}s", config.jwt.refresh_token_expiry);
 }
 
 }  // namespace smartbotic::webserver

+ 4350 - 475
webserver/src/http_server.cpp

@@ -1,441 +1,282 @@
 #include "smartbotic/webserver/http_server.hpp"
 
+#include <nlohmann/json.hpp>
 #include <spdlog/spdlog.h>
 
+#include <algorithm>
 #include <chrono>
+#include <cstring>
 #include <filesystem>
 #include <fstream>
-#include <tuple>
-#include <utility>
+#include <random>
+#include <sstream>
 
 namespace smartbotic::webserver {
 
 // ============================================================================
-// WebSocketSession Implementation
+// WebSocketServer Implementation
 // ============================================================================
 
-WebSocketSession::WebSocketSession(tcp::socket socket, const HttpServerConfig& config)
-    : ws_(std::move(socket)), config_(config) {}
+// Static instance pointer for callback access
+WebSocketServer* WebSocketServer::instance_ = nullptr;
 
-WebSocketSession::~WebSocketSession() {
-    spdlog::debug("WebSocket session destroyed");
-}
-
-void WebSocketSession::Run(http::request<http::string_body> req) {
-    DoAccept(std::move(req));
-}
-
-void WebSocketSession::Send(const std::string& message) {
-    auto msg = std::make_shared<std::string const>(message);
+namespace {
 
-    std::lock_guard<std::mutex> lock(mutex_);
-    queue_.push_back(msg);
+auto GenerateSessionId() -> std::string {
+    static std::random_device rd;
+    static std::mt19937 gen(rd());
+    static std::uniform_int_distribution<> dis(0, 15);
+    static const char* hex = "0123456789abcdef";
 
-    if (queue_.size() > 1) {
-        return;
+    std::string uuid;
+    uuid.reserve(36);
+    for (int i = 0; i < 36; ++i) {
+        if (i == 8 || i == 13 || i == 18 || i == 23) {
+            uuid += '-';
+        } else {
+            uuid += hex[dis(gen)];
+        }
     }
-
-    ws_.async_write(asio::buffer(*queue_.front()),
-                    beast::bind_front_handler(&WebSocketSession::OnWrite, shared_from_this()));
-}
-
-void WebSocketSession::Close() {
-    beast::error_code ec;
-    ws_.close(websocket::close_code::normal, ec);
-}
-
-auto WebSocketSession::IsOpen() const -> bool {
-    return ws_.is_open();
-}
-
-void WebSocketSession::SetMessageHandler(WebSocketMessageHandler handler) {
-    message_handler_ = std::move(handler);
-}
-
-void WebSocketSession::DoAccept(http::request<http::string_body> req) {  // NOLINT(performance-unnecessary-value-param)
-    ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::server));
-
-    ws_.set_option(websocket::stream_base::decorator(
-        [](websocket::response_type& res) { res.set(http::field::server, "SmartBotic-Server/0.1.0"); }));
-
-    ws_.async_accept(req, beast::bind_front_handler(&WebSocketSession::OnAccept, shared_from_this()));
+    return uuid;
 }
 
-void WebSocketSession::OnAccept(beast::error_code ec) {
-    if (ec) {
-        spdlog::error("WebSocket accept error: {}", ec.message());
-        return;
-    }
+}  // namespace
 
-    spdlog::info("WebSocket connection established");
-    DoRead();
+// LWS protocol definition - first protocol handles HTTP and is the default for WS
+static lws_protocols protocols[] = {
+    {
+        "http",  // Default protocol name for HTTP/WS upgrade
+        WebSocketServer::WsCallback,
+        0,       // per_session_data_size - we manage our own
+        65536,   // rx buffer size
+        0,       // id
+        nullptr, // user
+        65536    // tx_packet_size
+    },
+    LWS_PROTOCOL_LIST_TERM
+};
+
+WebSocketServer::WebSocketServer(uint16_t port, const std::string& path)
+    : port_(port), path_(path) {
+    instance_ = this;
 }
 
-void WebSocketSession::DoRead() {
-    ws_.async_read(buffer_, beast::bind_front_handler(&WebSocketSession::OnRead, shared_from_this()));
+WebSocketServer::~WebSocketServer() {
+    Stop();
+    instance_ = nullptr;
 }
 
-void WebSocketSession::OnRead(beast::error_code ec, std::size_t bytes_transferred) {
-    if (ec == websocket::error::closed) {
-        spdlog::info("WebSocket connection closed by client");
-        return;
-    }
-
-    if (ec) {
-        spdlog::error("WebSocket read error: {}", ec.message());
+void WebSocketServer::Start() {
+    if (running_.load()) {
+        spdlog::warn("WebSocket server already running");
         return;
     }
 
-    std::string message = beast::buffers_to_string(buffer_.data());
-    buffer_.consume(buffer_.size());
+    spdlog::info("Starting WebSocket server on port {}", port_);
 
-    spdlog::debug("WebSocket received: {} bytes", bytes_transferred);
+    lws_context_creation_info info{};
+    std::memset(&info, 0, sizeof(info));
 
-    if (message_handler_) {
-        message_handler_(*this, message);
-    }
+    info.port = port_;
+    info.protocols = protocols;
+    info.gid = -1;
+    info.uid = -1;
+    info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+    info.vhost_name = "smartbotic-ws";
 
-    DoRead();
-}
+    // Suppress verbose lws logging
+    lws_set_log_level(LLL_ERR | LLL_WARN, nullptr);
 
-void WebSocketSession::OnWrite(beast::error_code ec, std::size_t bytes_transferred) {
-    if (ec) {
-        spdlog::error("WebSocket write error: {}", ec.message());
+    context_ = lws_create_context(&info);
+    if (context_ == nullptr) {
+        spdlog::error("Failed to create libwebsocket context");
         return;
     }
 
-    spdlog::debug("WebSocket sent: {} bytes", bytes_transferred);
-
-    std::lock_guard<std::mutex> lock(mutex_);
-    queue_.erase(queue_.begin());
-
-    if (!queue_.empty()) {
-        ws_.async_write(asio::buffer(*queue_.front()),
-                        beast::bind_front_handler(&WebSocketSession::OnWrite, shared_from_this()));
-    }
-}
-
-// ============================================================================
-// HttpSession Implementation
-// ============================================================================
-
-HttpSession::HttpSession(tcp::socket socket, const HttpServerConfig& config,
-                         std::function<void(std::shared_ptr<WebSocketSession>)> ws_connect_handler,
-                         std::function<void(std::shared_ptr<WebSocketSession>)> ws_disconnect_handler)
-    : stream_(std::move(socket)),
-      config_(config),
-      ws_connect_handler_(std::move(ws_connect_handler)),
-      ws_disconnect_handler_(std::move(ws_disconnect_handler)) {}
-
-HttpSession::~HttpSession() {
-    spdlog::debug("HTTP session destroyed");
-}
-
-void HttpSession::Run() {
-    asio::dispatch(stream_.get_executor(), beast::bind_front_handler(&HttpSession::DoRead, shared_from_this()));
-}
+    running_.store(true);
+    eventLoopThread_ = std::thread(&WebSocketServer::RunEventLoop, this);
 
-void HttpSession::DoRead() {
-    req_ = {};
-    stream_.expires_after(std::chrono::seconds(30));
-    http::async_read(stream_, buffer_, req_, beast::bind_front_handler(&HttpSession::OnRead, shared_from_this()));
+    spdlog::info("WebSocket server started on port {}", port_);
 }
 
-void HttpSession::OnRead(beast::error_code ec, std::size_t bytes_transferred) {
-    std::ignore = bytes_transferred;
-
-    if (ec == http::error::end_of_stream) {
-        std::ignore = stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
-        return;
-    }
-
-    if (ec) {
-        spdlog::error("HTTP read error: {}", ec.message());
+void WebSocketServer::Stop() {
+    if (!running_.load()) {
         return;
     }
 
-    // Capture request info for logging
-    request_start_ = std::chrono::steady_clock::now();
-    request_method_ = std::string(req_.method_string());
-    request_target_ = std::string(req_.target());
-
-    HandleRequest(std::move(req_));
-}
-
-void HttpSession::HandleRequest(http::request<http::string_body>&& req) {
-    std::string target(req.target());
+    spdlog::info("Stopping WebSocket server...");
+    running_.store(false);
 
-    // Handle CORS preflight
-    if (req.method() == http::verb::options && config_.cors.enabled) {
-        auto res = http::response<http::string_body>{http::status::no_content, req.version()};
-        res.set(http::field::server, "SmartBotic-Server/0.1.0");
-        ApplyCorsHeaders(res);
-        res.prepare_payload();
-        SendResponse(std::move(res));
-        return;
+    // Cancel the event loop to wake up lws_service() immediately
+    // This is async-signal-safe and can be called from signal handlers
+    if (context_ != nullptr) {
+        lws_cancel_service(context_);
     }
 
-    // Check for WebSocket upgrade
-    if (websocket::is_upgrade(req) && target == config_.ws_path) {
-        spdlog::info("WebSocket upgrade request for {}", target);
-
-        auto ws_session = std::make_shared<WebSocketSession>(stream_.release_socket(), config_);
-
-        if (ws_connect_handler_) {
-            ws_connect_handler_(ws_session);
-        }
-
-        ws_session->Run(std::move(req));
-        return;
+    if (eventLoopThread_.joinable()) {
+        eventLoopThread_.join();
     }
 
-    http::response<http::string_body> res;
-
-    // Route API requests
-    if (target.starts_with("/api/")) {
-        res = HandleApiRequest(req);
-    }
-    // Serve static files for WebUI
-    else {
-        res = HandleStaticFile(target);
+    if (context_ != nullptr) {
+        lws_context_destroy(context_);
+        context_ = nullptr;
     }
 
-    res.version(req.version());
-    res.set(http::field::server, "SmartBotic-Server/0.1.0");
-
-    if (config_.cors.enabled) {
-        ApplyCorsHeaders(res);
+    {
+        std::lock_guard<std::mutex> lock(connectionsMutex_);
+        connections_.clear();
     }
 
-    res.prepare_payload();
-    SendResponse(std::move(res));
+    spdlog::info("WebSocket server stopped");
 }
 
-auto HttpSession::HandleStaticFile(const std::string& target) -> http::response<http::string_body> {
-    std::string path = target;
-
-    // Default to index.html for root
-    if (path == "/" || path.empty()) {
-        path = "/index.html";
-    }
-
-    // Security: prevent directory traversal
-    if (path.find("..") != std::string::npos) {
-        return MakeErrorResponse(http::status::forbidden, "Invalid path");
+void WebSocketServer::RunEventLoop() {
+    while (running_.load()) {
+        lws_service(context_, 50);  // 50ms timeout
     }
+}
 
-    std::filesystem::path file_path = config_.webui_path + path;
-
-    // If path is a directory, try index.html
-    if (std::filesystem::is_directory(file_path)) {
-        file_path /= "index.html";
+void WebSocketServer::Broadcast(const std::string& message) {
+    std::lock_guard<std::mutex> lock(connectionsMutex_);
+    for (auto& [wsi, conn] : connections_) {
+        QueueMessage(conn.get(), message);
     }
+}
 
-    if (!std::filesystem::exists(file_path)) {
-        // For SPA routing, return index.html for non-file paths
-        std::filesystem::path index_path = config_.webui_path + "/index.html";
-        if (std::filesystem::exists(index_path)) {
-            file_path = index_path;
-        } else {
-            return MakeErrorResponse(http::status::not_found, "File not found");
+void WebSocketServer::SendToSession(const std::string& sessionId, const std::string& message) {
+    std::lock_guard<std::mutex> lock(connectionsMutex_);
+    for (auto& [wsi, conn] : connections_) {
+        if (conn->sessionId == sessionId) {
+            QueueMessage(conn.get(), message);
+            break;
         }
     }
-
-    // Read file content
-    std::ifstream file(file_path, std::ios::binary);
-    if (!file) {
-        return MakeErrorResponse(http::status::internal_server_error, "Failed to read file");
-    }
-
-    std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
-
-    http::response<http::string_body> res{http::status::ok, 11};
-    res.set(http::field::content_type, GetMimeType(file_path.string()));
-    res.body() = std::move(content);
-
-    return res;
 }
 
-auto HttpSession::HandleApiRequest(http::request<http::string_body>& req) -> http::response<http::string_body> {
-    std::string target(req.target());
-
-    // Health check endpoint
-    if (target == "/api/health" && req.method() == http::verb::get) {
-        http::response<http::string_body> res{http::status::ok, req.version()};
-        res.set(http::field::content_type, "application/json");
-        res.body() = R"({"status":"healthy","service":"smartbotic-server"})";
-        return res;
-    }
-
-    // Version endpoint
-    if (target == "/api/version" && req.method() == http::verb::get) {
-        http::response<http::string_body> res{http::status::ok, req.version()};
-        res.set(http::field::content_type, "application/json");
-        res.body() = R"({"version":"0.1.0","name":"SmartBotic Web Server"})";
-        return res;
+void WebSocketServer::SendToSubscribers(const std::string& subscription_key, const std::string& message) {
+    std::lock_guard<std::mutex> lock(connectionsMutex_);
+    for (auto& [wsi, conn] : connections_) {
+        // Check if this connection is subscribed and authenticated
+        if (!conn->authenticated) {
+            continue;
+        }
+        const auto& subs = conn->subscriptions;
+        if (std::find(subs.begin(), subs.end(), subscription_key) != subs.end()) {
+            QueueMessage(conn.get(), message);
+        }
     }
-
-    // Default: not found
-    return MakeErrorResponse(http::status::not_found, "API endpoint not found");
 }
 
-auto HttpSession::MakeErrorResponse(
-    http::status status, const std::string& message)  // NOLINT(readability-convert-member-functions-to-static)
-    -> http::response<http::string_body> {
-    http::response<http::string_body> res{status, 11};
-    res.set(http::field::content_type, "application/json");
-    res.body() = R"({"error":")" + message + R"("})";
-    return res;
+auto WebSocketServer::ConnectionCount() const -> size_t {
+    std::lock_guard<std::mutex> lock(connectionsMutex_);
+    return connections_.size();
 }
 
-void HttpSession::ApplyCorsHeaders(http::response<http::string_body>& res) {
-    if (!config_.cors.enabled) {
-        return;
-    }
-
-    // Combine allowed origins
-    std::string origins;
-    for (size_t i = 0; i < config_.cors.allowed_origins.size(); ++i) {
-        if (i > 0) {
-            origins += ", ";
-        }
-        origins += config_.cors.allowed_origins[i];
-    }
-    res.set(http::field::access_control_allow_origin, origins);
-
-    // Combine allowed methods
-    std::string methods;
-    for (size_t i = 0; i < config_.cors.allowed_methods.size(); ++i) {
-        if (i > 0) {
-            methods += ", ";
-        }
-        methods += config_.cors.allowed_methods[i];
-    }
-    res.set(http::field::access_control_allow_methods, methods);
+void WebSocketServer::SetMessageHandler(WebSocketMessageHandler handler) {
+    messageHandler_ = std::move(handler);
+}
 
-    // Combine allowed headers
-    std::string headers;
-    for (size_t i = 0; i < config_.cors.allowed_headers.size(); ++i) {
-        if (i > 0) {
-            headers += ", ";
-        }
-        headers += config_.cors.allowed_headers[i];
-    }
-    res.set(http::field::access_control_allow_headers, headers);
+void WebSocketServer::HandleConnect(lws* wsi) {
+    auto conn = std::make_unique<WsConnection>();
+    conn->wsi = wsi;
+    conn->sessionId = GenerateSessionId();
 
-    if (config_.cors.allow_credentials) {
-        res.set(http::field::access_control_allow_credentials, "true");
-    }
+    spdlog::info("WebSocket client connected (session: {})", conn->sessionId);
 
-    res.set(http::field::access_control_max_age, std::to_string(config_.cors.max_age));
+    std::lock_guard<std::mutex> lock(connectionsMutex_);
+    connections_[wsi] = std::move(conn);
 }
 
-auto HttpSession::GetMimeType(const std::string& path)
-    -> std::string {  // NOLINT(readability-convert-member-functions-to-static)
-    auto ext_pos = path.rfind('.');
-    if (ext_pos == std::string::npos) {
-        return "application/octet-stream";
+void WebSocketServer::HandleDisconnect(lws* wsi) {
+    std::lock_guard<std::mutex> lock(connectionsMutex_);
+    auto it = connections_.find(wsi);
+    if (it != connections_.end()) {
+        spdlog::info("WebSocket client disconnected (session: {})", it->second->sessionId);
+        connections_.erase(it);
     }
+}
 
-    std::string ext = path.substr(ext_pos);
-
-    if (ext == ".html" || ext == ".htm") {
-        return "text/html";
-    }
-    if (ext == ".css") {
-        return "text/css";
-    }
-    if (ext == ".js") {
-        return "application/javascript";
-    }
-    if (ext == ".json") {
-        return "application/json";
-    }
-    if (ext == ".png") {
-        return "image/png";
-    }
-    if (ext == ".jpg" || ext == ".jpeg") {
-        return "image/jpeg";
-    }
-    if (ext == ".gif") {
-        return "image/gif";
-    }
-    if (ext == ".svg") {
-        return "image/svg+xml";
-    }
-    if (ext == ".ico") {
-        return "image/x-icon";
-    }
-    if (ext == ".woff") {
-        return "font/woff";
-    }
-    if (ext == ".woff2") {
-        return "font/woff2";
-    }
-    if (ext == ".ttf") {
-        return "font/ttf";
-    }
-    if (ext == ".eot") {
-        return "application/vnd.ms-fontobject";
-    }
-    if (ext == ".txt") {
-        return "text/plain";
-    }
-    if (ext == ".xml") {
-        return "application/xml";
+void WebSocketServer::HandleMessage(lws* wsi, const std::string& message) {
+    WsConnection* conn = nullptr;
+    {
+        std::lock_guard<std::mutex> lock(connectionsMutex_);
+        auto it = connections_.find(wsi);
+        if (it != connections_.end()) {
+            conn = it->second.get();
+        }
     }
-    if (ext == ".pdf") {
-        return "application/pdf";
+    // Call handler outside the lock to avoid deadlock with SendToSession
+    if (conn && messageHandler_) {
+        messageHandler_(conn, message);
     }
-
-    return "application/octet-stream";
 }
 
-void HttpSession::SendResponse(http::response<http::string_body>&& res) {
-    auto sp = std::make_shared<http::response<http::string_body>>(std::move(res));
-    auto status = sp->result();
-    auto self = shared_from_this();
+void WebSocketServer::QueueMessage(WsConnection* conn, const std::string& message) {
+    std::lock_guard<std::mutex> lock(conn->sendMutex);
 
-    http::async_write(stream_, *sp, [self, sp, status](beast::error_code ec, std::size_t bytes_transferred) {
-        self->LogRequest(status);
-        self->OnWrite(ec, bytes_transferred, sp->need_eof());
-    });
-}
+    // Prepend LWS_PRE bytes for libwebsockets
+    conn->sendBuffer.resize(LWS_PRE + message.size());
+    std::memcpy(conn->sendBuffer.data() + LWS_PRE, message.data(), message.size());
 
-void HttpSession::LogRequest(http::status status) {
-    auto now = std::chrono::steady_clock::now();
-    auto duration_us = std::chrono::duration_cast<std::chrono::microseconds>(now - request_start_).count();
+    lws_callback_on_writable(conn->wsi);
+}
 
-    // Format duration as ms with microsecond precision
-    double duration_ms = static_cast<double>(duration_us) / 1000.0;
+auto WebSocketServer::WsCallback(lws* wsi, lws_callback_reasons reason,
+                                  void* user, void* in, size_t len) -> int {
+    if (instance_ == nullptr) {
+        return 0;
+    }
 
-    auto status_int = static_cast<unsigned>(status);
+    switch (reason) {
+        case LWS_CALLBACK_PROTOCOL_INIT:
+            spdlog::debug("WebSocket protocol initialized");
+            break;
+
+        case LWS_CALLBACK_ESTABLISHED:
+            spdlog::debug("WebSocket connection established");
+            instance_->HandleConnect(wsi);
+            break;
+
+        case LWS_CALLBACK_CLOSED:
+            spdlog::debug("WebSocket connection closed");
+            instance_->HandleDisconnect(wsi);
+            break;
+
+        case LWS_CALLBACK_RECEIVE: {
+            std::string message(static_cast<char*>(in), len);
+            spdlog::debug("WebSocket received {} bytes", len);
+            instance_->HandleMessage(wsi, message);
+            break;
+        }
 
-    // Log at appropriate level based on status code
-    if (status_int >= 500) {
-        spdlog::error("{} {} {} {:.2f}ms", request_method_, request_target_, status_int, duration_ms);
-    } else if (status_int >= 400) {
-        spdlog::warn("{} {} {} {:.2f}ms", request_method_, request_target_, status_int, duration_ms);
-    } else {
-        spdlog::info("{} {} {} {:.2f}ms", request_method_, request_target_, status_int, duration_ms);
-    }
-}
+        case LWS_CALLBACK_SERVER_WRITEABLE: {
+            std::lock_guard<std::mutex> lock(instance_->connectionsMutex_);
+            auto it = instance_->connections_.find(wsi);
+            if (it != instance_->connections_.end()) {
+                auto& conn = it->second;
+                std::lock_guard<std::mutex> sendLock(conn->sendMutex);
+                if (conn->sendBuffer.size() > LWS_PRE) {
+                    size_t msgLen = conn->sendBuffer.size() - LWS_PRE;
+                    lws_write(wsi, conn->sendBuffer.data() + LWS_PRE, msgLen, LWS_WRITE_TEXT);
+                    conn->sendBuffer.clear();
+                }
+            }
+            break;
+        }
 
-void HttpSession::OnWrite(beast::error_code ec, std::size_t bytes_transferred, bool close) {
-    std::ignore = bytes_transferred;
+        case LWS_CALLBACK_HTTP:
+            // Return non-zero to close the connection for non-WebSocket HTTP requests
+            return -1;
 
-    if (ec) {
-        spdlog::error("HTTP write error: {}", ec.message());
-        return;
-    }
+        case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
+            // Allow the connection
+            return 0;
 
-    if (close) {
-        beast::error_code ec_close;
-        std::ignore = stream_.socket().shutdown(tcp::socket::shutdown_send, ec_close);
-        return;
+        default:
+            break;
     }
 
-    DoRead();
+    return 0;
 }
 
 // ============================================================================
@@ -443,7 +284,10 @@ void HttpSession::OnWrite(beast::error_code ec, std::size_t bytes_transferred, b
 // ============================================================================
 
 HttpServer::HttpServer(HttpServerConfig config)
-    : config_(std::move(config)), ioc_(1), acceptor_(ioc_), db_client_(std::make_unique<DatabaseClient>(config_)) {}
+    : config_(std::move(config)),
+      httpServer_(std::make_unique<httplib::Server>()),
+      dbClient_(std::make_unique<DatabaseClient>(config_)),
+      authService_(std::make_unique<AuthService>(config_.jwt)) {}
 
 HttpServer::~HttpServer() {
     if (running_.load()) {
@@ -474,53 +318,45 @@ auto HttpServer::Start(bool require_database) -> bool {
         }
     }
 
-    try {
-        auto const kAddress = asio::ip::make_address(config_.address);
-        tcp::endpoint endpoint{kAddress, config_.port};
-
-        beast::error_code ec;
-
-        acceptor_.open(endpoint.protocol(), ec);
-        if (ec) {
-            spdlog::error("Failed to open acceptor: {}", ec.message());
-            return false;
-        }
-
-        acceptor_.set_option(asio::socket_base::reuse_address(true), ec);
-        if (ec) {
-            spdlog::error("Failed to set reuse_address: {}", ec.message());
-            return false;
-        }
-
-        acceptor_.bind(endpoint, ec);
-        if (ec) {
-            spdlog::error("Failed to bind to {}: {}", config_.GetListenAddress(), ec.message());
-            return false;
-        }
-
-        acceptor_.listen(asio::socket_base::max_listen_connections, ec);
-        if (ec) {
-            spdlog::error("Failed to listen: {}", ec.message());
+    // Initialize services (only if database is connected)
+    if (IsDatabaseConnected()) {
+        if (!InitializeServices()) {
+            spdlog::error("Failed to initialize services - server startup aborted");
             return false;
         }
+    }
 
-        running_.store(true);
-        DoAccept();
+    // Setup routes
+    SetupRoutes();
+
+    // Start WebSocket server on separate port
+    wsServer_ = std::make_unique<WebSocketServer>(config_.ws_port, config_.ws_path);
+
+    // Setup WebSocket message handler using WsHandler if available
+    if (wsHandler_) {
+        wsServer_->SetMessageHandler([this](WsConnection* conn, const std::string& message) {
+            wsHandler_->HandleMessage(conn, message,
+                [this](WsConnection* c, const std::string& response) {
+                    wsServer_->SendToSession(c->sessionId, response);
+                });
+        });
+        spdlog::info("WebSocket message handler configured with WsHandler");
+    } else if (wsMessageHandler_) {
+        wsServer_->SetMessageHandler(wsMessageHandler_);
+    }
+    wsServer_->Start();
 
-        // Start the I/O context in a thread
-        threads_.emplace_back([this]() { ioc_.run(); });
+    // Start HTTP server in a separate thread
+    running_.store(true);
+    httpThread_ = std::thread(&HttpServer::RunHttpServer, this);
 
-        spdlog::info("HTTP server started successfully on {}", config_.GetListenAddress());
-        spdlog::info("WebSocket endpoint: ws://{}{}", config_.GetListenAddress(), config_.ws_path);
-        spdlog::info("Static files: {}", config_.webui_path);
-        spdlog::info("Database: {} ({})", config_.database_address,
-                     IsDatabaseConnected() ? "connected" : "not connected");
+    spdlog::info("HTTP server started successfully on {}", config_.GetListenAddress());
+    spdlog::info("WebSocket server: ws://{}:{}{}", config_.address, config_.ws_port, config_.ws_path);
+    spdlog::info("Static files: {}", config_.webui_path);
+    spdlog::info("Database: {} ({})", config_.database_address,
+                 IsDatabaseConnected() ? "connected" : "not connected");
 
-        return true;
-    } catch (const std::exception& e) {
-        spdlog::error("Failed to start HTTP server: {}", e.what());
-        return false;
-    }
+    return true;
 }
 
 void HttpServer::Stop() {
@@ -529,129 +365,103 @@ void HttpServer::Stop() {
     }
 
     spdlog::info("Stopping HTTP server...");
-
     running_.store(false);
 
-    // Disconnect from database
-    if (db_client_) {
-        db_client_->Disconnect();
+    // Stop WebSocket server first (this uses lws_cancel_service which is async-signal-safe)
+    if (wsServer_) {
+        wsServer_->Stop();
     }
 
-    // Close all WebSocket sessions
-    {
-        std::lock_guard<std::mutex> lock(ws_sessions_mutex_);
-        for (const auto& session : ws_sessions_) {
-            if (session->IsOpen()) {
-                session->Close();
-            }
-        }
-        ws_sessions_.clear();
+    // Stop HTTP server - this makes listen() return
+    if (httpServer_) {
+        httpServer_->stop();
     }
 
-    // Stop accepting new connections
-    beast::error_code ec;
-    std::ignore = acceptor_.close(ec);
-
-    // Stop the I/O context
-    ioc_.stop();
-
-    // Wait for threads to finish
-    for (auto& thread : threads_) {
-        if (thread.joinable()) {
-            thread.join();
-        }
+    // Disconnect from database
+    if (dbClient_) {
+        dbClient_->Disconnect();
     }
-    threads_.clear();
 
-    spdlog::info("HTTP server stopped");
+    // NOTE: Don't join httpThread_ here - let Wait() handle it
+    // This avoids race condition when Stop() is called from signal handler
+    // while main thread is blocked in Wait()
+
+    spdlog::info("HTTP server stop requested");
 }
 
 void HttpServer::Wait() {
-    for (auto& thread : threads_) {
-        if (thread.joinable()) {
-            thread.join();
-        }
+    if (httpThread_.joinable()) {
+        httpThread_.join();
     }
+    spdlog::info("HTTP server stopped");
 }
 
 void HttpServer::BroadcastWebSocket(const std::string& message) {
-    std::lock_guard<std::mutex> lock(ws_sessions_mutex_);
-    for (const auto& session : ws_sessions_) {
-        if (session->IsOpen()) {
-            session->Send(message);
-        }
+    if (wsServer_) {
+        wsServer_->Broadcast(message);
     }
 }
 
-void HttpServer::SetWebSocketMessageHandler(WebSocketMessageHandler handler) {
-    ws_message_handler_ = std::move(handler);
-}
-
-auto HttpServer::GetWebSocketClientCount() const -> size_t {
-    std::lock_guard<std::mutex> lock(ws_sessions_mutex_);
-    return ws_sessions_.size();
-}
-
-void HttpServer::DoAccept() {
-    acceptor_.async_accept(ioc_, beast::bind_front_handler(&HttpServer::OnAccept, this));
-}
-
-void HttpServer::OnAccept(beast::error_code ec, tcp::socket socket) {
-    if (ec) {
-        if (running_.load()) {
-            spdlog::error("Accept error: {}", ec.message());
-        }
+void HttpServer::BroadcastDocumentEvent(const std::string& workspace_id,
+                                          const std::string& collection,
+                                          DocumentAction action,
+                                          const std::string& document_id,
+                                          const nlohmann::json& data) {
+    if (!wsServer_ || !wsHandler_) {
         return;
     }
 
-    auto endpoint = socket.remote_endpoint(ec);
-    if (!ec) {
-        spdlog::debug("New connection from {}:{}", endpoint.address().to_string(), endpoint.port());
+    std::string subscription_key = WsHandler::BuildSubscriptionKey(workspace_id, collection);
+
+    // Build the event JSON
+    nlohmann::json event = {
+        {"type", "document"},
+        {"action", DocumentActionToString(action)},
+        {"workspace_id", workspace_id},
+        {"collection", collection},
+        {"document_id", document_id}
+    };
+
+    // Include data for create/update, exclude for delete
+    if (action != DocumentAction::Delete && !data.is_null()) {
+        event["data"] = data;
     }
 
-    auto session = std::make_shared<HttpSession>(
-        std::move(socket), config_, [this](std::shared_ptr<WebSocketSession> ws) { OnWebSocketConnect(std::move(ws)); },
-        [this](std::shared_ptr<WebSocketSession> ws) { OnWebSocketDisconnect(std::move(ws)); });
+    std::string event_str = event.dump();
 
-    session->Run();
+    spdlog::debug("Broadcasting document event: {} {} in {} (key={})",
+                  DocumentActionToString(action), document_id, collection, subscription_key);
 
-    if (running_.load()) {
-        DoAccept();
-    }
+    wsServer_->SendToSubscribers(subscription_key, event_str);
 }
 
-void HttpServer::OnWebSocketConnect(const std::shared_ptr<WebSocketSession>& session) {
-    if (ws_message_handler_) {
-        session->SetMessageHandler(ws_message_handler_);
+void HttpServer::SetWebSocketMessageHandler(WebSocketMessageHandler handler) {
+    wsMessageHandler_ = std::move(handler);
+    if (wsServer_) {
+        wsServer_->SetMessageHandler(wsMessageHandler_);
     }
-
-    std::lock_guard<std::mutex> lock(ws_sessions_mutex_);
-    ws_sessions_.insert(session);
-    spdlog::info("WebSocket client connected (total: {})", ws_sessions_.size());
 }
 
-void HttpServer::OnWebSocketDisconnect(const std::shared_ptr<WebSocketSession>& session) {
-    std::lock_guard<std::mutex> lock(ws_sessions_mutex_);
-    ws_sessions_.erase(session);
-    spdlog::info("WebSocket client disconnected (total: {})", ws_sessions_.size());
+auto HttpServer::GetWebSocketClientCount() const -> size_t {
+    return wsServer_ ? wsServer_->ConnectionCount() : 0;
 }
 
 auto HttpServer::IsDatabaseConnected() const -> bool {
-    return db_client_ && db_client_->IsConnected();
+    return dbClient_ && dbClient_->IsConnected();
 }
 
 auto HttpServer::ConnectToDatabase() -> bool {
-    if (!db_client_) {
-        db_client_ = std::make_unique<DatabaseClient>(config_);
+    if (!dbClient_) {
+        dbClient_ = std::make_unique<DatabaseClient>(config_);
     }
 
     // Try to connect
-    if (!db_client_->Connect()) {
+    if (!dbClient_->Connect()) {
         return false;
     }
 
     // Perform a health check to verify the connection is working
-    auto health = db_client_->HealthCheck();
+    auto health = dbClient_->HealthCheck();
     if (!health.healthy) {
         spdlog::error("Database health check failed: {}", health.message);
         return false;
@@ -661,4 +471,4069 @@ auto HttpServer::ConnectToDatabase() -> bool {
     return true;
 }
 
+auto HttpServer::InitializeServices() -> bool {
+    // Initialize UserService
+    userService_ = std::make_unique<UserService>(*dbClient_);
+    if (!userService_->Initialize()) {
+        spdlog::error("Failed to initialize UserService");
+        return false;
+    }
+    spdlog::info("UserService initialized successfully");
+
+    // Initialize WorkspaceService
+    workspaceService_ = std::make_unique<WorkspaceService>(*dbClient_);
+    if (!workspaceService_->Initialize()) {
+        spdlog::error("Failed to initialize WorkspaceService");
+        return false;
+    }
+    spdlog::info("WorkspaceService initialized successfully");
+
+    // Initialize GroupService
+    groupService_ = std::make_unique<GroupService>(*dbClient_);
+    if (!groupService_->Initialize()) {
+        spdlog::error("Failed to initialize GroupService");
+        return false;
+    }
+    spdlog::info("GroupService initialized successfully");
+
+    // Initialize MembershipService
+    membershipService_ = std::make_unique<MembershipService>(*dbClient_);
+    if (!membershipService_->Initialize()) {
+        spdlog::error("Failed to initialize MembershipService");
+        return false;
+    }
+    spdlog::info("MembershipService initialized successfully");
+
+    // Initialize ApiKeyService
+    apiKeyService_ = std::make_unique<ApiKeyService>(*dbClient_);
+    if (!apiKeyService_->Initialize()) {
+        spdlog::error("Failed to initialize ApiKeyService");
+        return false;
+    }
+    spdlog::info("ApiKeyService initialized successfully");
+
+    // Initialize CollectionService
+    collectionService_ = std::make_unique<CollectionService>(*dbClient_);
+    spdlog::info("CollectionService initialized successfully");
+
+    // Initialize DocumentService
+    documentService_ = std::make_unique<DocumentService>(*dbClient_);
+    spdlog::info("DocumentService initialized successfully");
+
+    // Initialize ViewService
+    viewService_ = std::make_unique<ViewService>(*dbClient_);
+    if (!viewService_->Initialize()) {
+        spdlog::error("Failed to initialize ViewService");
+        return false;
+    }
+    spdlog::info("ViewService initialized successfully");
+
+    // Initialize WsHandler for WebSocket real-time updates
+    wsHandler_ = std::make_unique<WsHandler>(*authService_);
+    spdlog::info("WsHandler initialized successfully");
+
+    // Note: WebSocket message handler is set up in Start() after wsServer_ is created
+
+    return true;
+}
+
+void HttpServer::SetupRoutes() {
+    // Pre-routing handler for CORS
+    httpServer_->set_pre_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
+        // Apply CORS headers if enabled
+        if (config_.cors.enabled) {
+            // Combine allowed origins
+            std::string origins;
+            for (size_t i = 0; i < config_.cors.allowed_origins.size(); ++i) {
+                if (i > 0) origins += ", ";
+                origins += config_.cors.allowed_origins[i];
+            }
+            res.set_header("Access-Control-Allow-Origin", origins);
+
+            // Combine allowed methods
+            std::string methods;
+            for (size_t i = 0; i < config_.cors.allowed_methods.size(); ++i) {
+                if (i > 0) methods += ", ";
+                methods += config_.cors.allowed_methods[i];
+            }
+            res.set_header("Access-Control-Allow-Methods", methods);
+
+            // Combine allowed headers
+            std::string headers;
+            for (size_t i = 0; i < config_.cors.allowed_headers.size(); ++i) {
+                if (i > 0) headers += ", ";
+                headers += config_.cors.allowed_headers[i];
+            }
+            res.set_header("Access-Control-Allow-Headers", headers);
+
+            if (config_.cors.allow_credentials) {
+                res.set_header("Access-Control-Allow-Credentials", "true");
+            }
+
+            res.set_header("Access-Control-Max-Age", std::to_string(config_.cors.max_age));
+        }
+
+        res.set_header("Server", "SmartBotic-Server/0.1.0");
+
+        // Handle CORS preflight
+        if (req.method == "OPTIONS") {
+            res.status = 204;
+            return httplib::Server::HandlerResponse::Handled;
+        }
+
+        return httplib::Server::HandlerResponse::Unhandled;
+    });
+
+    // Health check endpoint
+    httpServer_->Get("/api/health", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleHealth(req, res);
+    });
+
+    // Version endpoint
+    httpServer_->Get("/api/version", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleVersion(req, res);
+    });
+
+    // Bootstrap endpoint - creates first admin user if no users exist
+    httpServer_->Post("/api/bootstrap", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleBootstrap(req, res);
+    });
+
+    // Setup authentication routes
+    SetupAuthRoutes();
+
+    // Setup user management routes
+    SetupUserRoutes();
+
+    // Setup API key routes (under /api/users/:id/api-keys)
+    SetupApiKeyRoutes();
+
+    // Setup membership routes (before workspace routes since they're more specific)
+    SetupMembershipRoutes();
+
+    // Setup document routes (most specific - before collections)
+    SetupDocumentRoutes();
+
+    // Setup view routes (schema definitions)
+    SetupViewRoutes();
+
+    // Setup collection routes (before workspace routes since they're more specific)
+    SetupCollectionRoutes();
+
+    // Setup group management routes (before workspace routes since they're more specific)
+    SetupGroupRoutes();
+
+    // Setup workspace management routes
+    SetupWorkspaceRoutes();
+
+    // Mount static file directory for WebUI
+    if (std::filesystem::exists(config_.webui_path)) {
+        httpServer_->set_mount_point("/", config_.webui_path);
+        spdlog::info("Mounted static files from: {}", config_.webui_path);
+    } else {
+        spdlog::warn("WebUI path does not exist: {}", config_.webui_path);
+    }
+
+    // Custom error handler for 404 (only if response body is empty)
+    httpServer_->set_error_handler([](const httplib::Request& /*req*/, httplib::Response& res) {
+        // Only set default error message if body hasn't been set by a handler
+        if (res.body.empty()) {
+            res.set_content(R"({"error":"Not found"})", "application/json");
+        }
+    });
+
+    // Logger for requests
+    httpServer_->set_logger([](const httplib::Request& req, const httplib::Response& res) {
+        auto status = res.status;
+        if (status >= 500) {
+            spdlog::error("{} {} {}", req.method, req.path, status);
+        } else if (status >= 400) {
+            spdlog::warn("{} {} {}", req.method, req.path, status);
+        } else {
+            spdlog::info("{} {} {}", req.method, req.path, status);
+        }
+    });
+}
+
+void HttpServer::RunHttpServer() {
+    if (!httpServer_->listen(config_.address, config_.port)) {
+        spdlog::error("Failed to start HTTP server on {}", config_.GetListenAddress());
+        running_.store(false);
+    }
+}
+
+void HttpServer::HandleHealth(const httplib::Request& req, httplib::Response& res) {
+    res.set_content(R"({"status":"healthy","service":"smartbotic-server"})", "application/json");
+}
+
+void HttpServer::HandleVersion(const httplib::Request& req, httplib::Response& res) {
+    res.set_content(R"({"version":"0.1.0","name":"SmartBotic Web Server"})", "application/json");
+}
+
+void HttpServer::HandleBootstrap(const httplib::Request& req, httplib::Response& res) {
+    // Bootstrap endpoint - creates first admin user if no users exist
+    if (!userService_) {
+        res.status = 500;
+        res.set_content(R"({"error":"User service not available"})", "application/json");
+        return;
+    }
+
+    // Check if any users exist
+    auto users = userService_->ListUsers();
+    if (!users.users.empty()) {
+        res.status = 400;
+        res.set_content(R"({"error":"System already bootstrapped - users exist"})", "application/json");
+        return;
+    }
+
+    // Parse request body
+    nlohmann::json body;
+    try {
+        body = nlohmann::json::parse(req.body);
+    } catch (...) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+        return;
+    }
+
+    // Validate required fields
+    if (!body.contains("email") || !body.contains("password")) {
+        res.status = 400;
+        res.set_content(R"({"error":"Email and password are required"})", "application/json");
+        return;
+    }
+
+    CreateUserRequest user_request;
+    user_request.email = body["email"].get<std::string>();
+    user_request.password = body["password"].get<std::string>();
+    user_request.name = body.value("name", "Admin");
+
+    // Create the admin user
+    auto result = userService_->CreateUser(user_request);
+    if (!result.success) {
+        res.status = 400;
+        nlohmann::json error_response;
+        error_response["error"] = result.error;
+        res.set_content(error_response.dump(), "application/json");
+        return;
+    }
+
+    // Create a default workspace
+    if (workspaceService_) {
+        CreateWorkspaceRequest ws_request;
+        ws_request.name = "Default Workspace";
+        ws_request.settings["default"] = true;
+        auto ws_result = workspaceService_->CreateWorkspace(ws_request);
+
+        // Add user to workspace
+        if (ws_result.success && ws_result.workspace && membershipService_) {
+            AddMemberRequest member_request;
+            member_request.workspace_id = ws_result.workspace->id;
+            member_request.user_id = result.user->id;
+            member_request.group_ids = {};
+            [[maybe_unused]] auto member_result = membershipService_->AddMember(member_request);
+        }
+    }
+
+    // Return success with user info
+    nlohmann::json response;
+    response["success"] = true;
+    response["message"] = "System bootstrapped successfully";
+    response["user"]["id"] = result.user->id;
+    response["user"]["email"] = result.user->email;
+    response["user"]["name"] = result.user->name;
+
+    spdlog::info("System bootstrapped with admin user: {}", user_request.email);
+    res.set_content(response.dump(), "application/json");
+}
+
+auto HttpServer::GetMimeType(const std::string& path) -> std::string {
+    auto ext_pos = path.rfind('.');
+    if (ext_pos == std::string::npos) {
+        return "application/octet-stream";
+    }
+
+    std::string ext = path.substr(ext_pos);
+
+    static const std::unordered_map<std::string, std::string> mime_types = {
+        {".html", "text/html"},
+        {".htm", "text/html"},
+        {".css", "text/css"},
+        {".js", "application/javascript"},
+        {".json", "application/json"},
+        {".png", "image/png"},
+        {".jpg", "image/jpeg"},
+        {".jpeg", "image/jpeg"},
+        {".gif", "image/gif"},
+        {".svg", "image/svg+xml"},
+        {".ico", "image/x-icon"},
+        {".woff", "font/woff"},
+        {".woff2", "font/woff2"},
+        {".ttf", "font/ttf"},
+        {".eot", "application/vnd.ms-fontobject"},
+        {".txt", "text/plain"},
+        {".xml", "application/xml"},
+        {".pdf", "application/pdf"},
+    };
+
+    auto it = mime_types.find(ext);
+    if (it != mime_types.end()) {
+        return it->second;
+    }
+    return "application/octet-stream";
+}
+
+// ============================================================================
+// Authentication Routes Implementation
+// ============================================================================
+
+void HttpServer::SetupAuthRoutes() {
+    // POST /api/auth/login - Authenticate user and return JWT
+    httpServer_->Post("/api/auth/login", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleAuthLogin(req, res);
+    });
+
+    // POST /api/auth/refresh - Refresh access token
+    httpServer_->Post("/api/auth/refresh", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleAuthRefresh(req, res);
+    });
+
+    // POST /api/auth/logout - Invalidate refresh token
+    httpServer_->Post("/api/auth/logout", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleAuthLogout(req, res);
+    });
+
+    // GET /api/auth/me - Get current authenticated user info
+    httpServer_->Get("/api/auth/me", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleAuthMe(req, res);
+    });
+
+    spdlog::info("Authentication routes registered: /api/auth/login, /api/auth/refresh, /api/auth/logout, /api/auth/me");
+}
+
+void HttpServer::HandleAuthLogin(const httplib::Request& req, httplib::Response& res) {
+    try {
+        // Parse request body
+        auto body = nlohmann::json::parse(req.body);
+
+        if (!body.contains("email") || !body.contains("password")) {
+            res.status = 400;
+            res.set_content(R"({"error":"Missing email or password"})", "application/json");
+            return;
+        }
+
+        std::string email = body["email"].get<std::string>();
+        std::string password = body["password"].get<std::string>();
+
+        if (config_.jwt.secret.empty()) {
+            res.status = 500;
+            res.set_content(R"({"error":"JWT secret not configured"})", "application/json");
+            return;
+        }
+
+        if (!userService_) {
+            res.status = 500;
+            res.set_content(R"({"error":"User service not available"})", "application/json");
+            return;
+        }
+
+        // Verify user credentials
+        auto user_result = userService_->VerifyCredentials(email, password);
+        if (!user_result.success || !user_result.user) {
+            res.status = 401;
+            res.set_content(R"({"error":"Invalid email or password"})", "application/json");
+            return;
+        }
+
+        const auto& user = *user_result.user;
+
+        // Get user's workspace memberships
+        std::vector<std::string> workspace_ids;
+        if (membershipService_) {
+            auto memberships = membershipService_->ListUserMemberships(user.id);
+            for (const auto& m : memberships.members) {
+                workspace_ids.push_back(m.workspace_id);
+            }
+        }
+
+        // Determine user groups (superadmin is a special case - first user is superadmin)
+        std::vector<std::string> groups;
+        auto all_users = userService_->ListUsers();
+        if (all_users.users.size() == 1 ||
+            (!all_users.users.empty() && all_users.users[0].id == user.id)) {
+            // First user is superadmin
+            groups.push_back("superadmin");
+        }
+
+        auto tokens = authService_->GenerateTokens(user.id, email, workspace_ids, groups);
+
+        nlohmann::json response = {
+            {"access_token", tokens.access_token},
+            {"refresh_token", tokens.refresh_token},
+            {"token_type", "Bearer"},
+            {"expires_in", tokens.access_expires_in}
+        };
+
+        spdlog::info("User logged in: {}", email);
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Login error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleAuthRefresh(const httplib::Request& req, httplib::Response& res) {
+    try {
+        // Parse request body
+        auto body = nlohmann::json::parse(req.body);
+
+        if (!body.contains("refresh_token")) {
+            res.status = 400;
+            res.set_content(R"({"error":"Missing refresh_token"})", "application/json");
+            return;
+        }
+
+        std::string refresh_token = body["refresh_token"].get<std::string>();
+
+        auto new_tokens = authService_->RefreshAccessToken(refresh_token);
+        if (!new_tokens) {
+            res.status = 401;
+            res.set_content(R"({"error":"Invalid or expired refresh token"})", "application/json");
+            return;
+        }
+
+        nlohmann::json response = {
+            {"access_token", new_tokens->access_token},
+            {"refresh_token", new_tokens->refresh_token},
+            {"token_type", "Bearer"},
+            {"expires_in", new_tokens->access_expires_in}
+        };
+
+        spdlog::debug("Token refreshed successfully");
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Token refresh error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleAuthLogout(const httplib::Request& req, httplib::Response& res) {
+    try {
+        // Parse request body
+        auto body = nlohmann::json::parse(req.body);
+
+        if (!body.contains("refresh_token")) {
+            res.status = 400;
+            res.set_content(R"({"error":"Missing refresh_token"})", "application/json");
+            return;
+        }
+
+        std::string refresh_token = body["refresh_token"].get<std::string>();
+
+        if (authService_->InvalidateRefreshToken(refresh_token)) {
+            spdlog::debug("User logged out successfully");
+            res.set_content(R"({"message":"Logged out successfully"})", "application/json");
+        } else {
+            res.status = 400;
+            res.set_content(R"({"error":"Invalid refresh token"})", "application/json");
+        }
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Logout error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleAuthMe(const httplib::Request& req, httplib::Response& res) {
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Build response with user info from token
+    nlohmann::json response = {
+        {"id", auth_user->user_id},
+        {"email", auth_user->email},
+        {"groups", auth_user->groups},
+        {"workspace_ids", auth_user->workspace_ids}
+    };
+
+    // Try to get additional user info from database
+    if (userService_) {
+        auto user_result = userService_->GetUser(auth_user->user_id);
+        if (user_result.success && user_result.user) {
+            response["name"] = user_result.user->name;
+            response["created_at"] = user_result.user->created_at;
+        }
+    }
+
+    // Check if user is superadmin based on groups
+    response["is_superadmin"] = IsSuperadmin(*auth_user);
+
+    res.set_content(response.dump(), "application/json");
+}
+
+auto HttpServer::AuthenticateRequest(const httplib::Request& req) -> std::optional<AuthUser> {
+    std::string auth_token;
+
+    // First check X-API-Key header (direct API key)
+    auto api_key_header = req.get_header_value("X-API-Key");
+    if (!api_key_header.empty()) {
+        auth_token = api_key_header;
+    } else {
+        // Check Authorization header
+        auto auth_header = req.get_header_value("Authorization");
+        if (auth_header.empty()) {
+            return std::nullopt;
+        }
+
+        auto token = AuthService::ExtractBearerToken(auth_header);
+        if (!token) {
+            return std::nullopt;
+        }
+        auth_token = *token;
+    }
+
+    // Check if this is an API key (starts with "sk_")
+    if (auth_token.starts_with("sk_")) {
+        if (!apiKeyService_) {
+            spdlog::warn("API key authentication attempted but service not available");
+            return std::nullopt;
+        }
+
+        auto validation = apiKeyService_->ValidateApiKey(auth_token);
+        if (!validation.valid || !validation.api_key) {
+            spdlog::debug("API key validation failed: {}", validation.error);
+            return std::nullopt;
+        }
+
+        // Check if key is expired
+        if (validation.api_key->IsExpired()) {
+            spdlog::debug("API key expired");
+            return std::nullopt;
+        }
+
+        // Update last used timestamp (fire and forget)
+        apiKeyService_->UpdateLastUsed(validation.api_key->id);
+
+        // Build AuthUser from API key
+        AuthUser auth_user;
+        auth_user.user_id = validation.api_key->user_id;
+
+        // Use API key permissions as groups
+        auth_user.groups = validation.api_key->permissions;
+
+        // Try to fetch additional user info
+        if (userService_) {
+            auto user_result = userService_->GetUser(validation.api_key->user_id);
+            if (user_result.success && user_result.user) {
+                auth_user.email = user_result.user->email;
+
+                // Fetch workspace memberships for the user
+                if (membershipService_) {
+                    auto memberships = membershipService_->ListUserMemberships(user_result.user->id);
+                    for (const auto& m : memberships.members) {
+                        auth_user.workspace_ids.push_back(m.workspace_id);
+                    }
+                }
+            } else {
+                spdlog::debug("API key owner user not found in database: {}", validation.api_key->user_id);
+            }
+        }
+
+        return auth_user;
+    }
+
+    // Otherwise, treat as JWT token
+    auto validation = authService_->ValidateAccessToken(auth_token);
+    if (!validation.valid) {
+        spdlog::debug("Token validation failed: {}", validation.error);
+        return std::nullopt;
+    }
+
+    return validation.user;
+}
+
+// ============================================================================
+// User Management Routes Implementation
+// ============================================================================
+
+void HttpServer::SetupUserRoutes() {
+    // POST /api/users - Create a new user (superadmin only)
+    httpServer_->Post("/api/users", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleCreateUser(req, res);
+    });
+
+    // GET /api/users - List all users (superadmin only)
+    httpServer_->Get("/api/users", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleListUsers(req, res);
+    });
+
+    // GET /api/users/:id - Get a specific user
+    httpServer_->Get(R"(/api/users/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleGetUser(req, res);
+    });
+
+    // PATCH /api/users/:id - Update a user
+    httpServer_->Patch(R"(/api/users/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleUpdateUser(req, res);
+    });
+
+    // DELETE /api/users/:id - Soft delete a user (superadmin only)
+    httpServer_->Delete(R"(/api/users/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleDeleteUser(req, res);
+    });
+
+    spdlog::info("User management routes registered: /api/users");
+}
+
+auto HttpServer::IsSuperadmin(const AuthUser& user) -> bool {
+    return std::find(user.groups.begin(), user.groups.end(), "superadmin") != user.groups.end();
+}
+
+void HttpServer::HandleCreateUser(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Only superadmins can create users
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if UserService is available
+    if (!userService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"User service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto body = nlohmann::json::parse(req.body);
+
+        CreateUserRequest create_req;
+        if (body.contains("email")) {
+            create_req.email = body["email"].get<std::string>();
+        }
+        if (body.contains("password")) {
+            create_req.password = body["password"].get<std::string>();
+        }
+        if (body.contains("name")) {
+            create_req.name = body["name"].get<std::string>();
+        }
+
+        auto result = userService_->CreateUser(create_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return created user (without password_hash)
+        nlohmann::json user_json = {
+            {"id", result.user->id},
+            {"email", result.user->email},
+            {"name", result.user->name},
+            {"created_at", result.user->created_at},
+            {"updated_at", result.user->updated_at}
+        };
+
+        res.status = 201;
+        res.set_content(user_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Create user error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListUsers(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Only superadmins can list all users
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if UserService is available
+    if (!userService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"User service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse query parameters
+        int limit = 100;
+        int offset = 0;
+        bool include_deleted = false;
+
+        if (req.has_param("limit")) {
+            limit = std::stoi(req.get_param_value("limit"));
+            if (limit < 1 || limit > 1000) {
+                limit = 100;
+            }
+        }
+        if (req.has_param("offset")) {
+            offset = std::stoi(req.get_param_value("offset"));
+            if (offset < 0) {
+                offset = 0;
+            }
+        }
+        if (req.has_param("include_deleted")) {
+            include_deleted = req.get_param_value("include_deleted") == "true";
+        }
+
+        auto result = userService_->ListUsers(limit, offset, include_deleted);
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build response
+        nlohmann::json users_array = nlohmann::json::array();
+        for (const auto& user : result.users) {
+            users_array.push_back({
+                {"id", user.id},
+                {"email", user.email},
+                {"name", user.name},
+                {"created_at", user.created_at},
+                {"updated_at", user.updated_at},
+                {"deleted_at", user.deleted_at}
+            });
+        }
+
+        nlohmann::json response = {
+            {"users", users_array},
+            {"total_count", result.total_count},
+            {"limit", limit},
+            {"offset", offset}
+        };
+
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List users error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleGetUser(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Check if UserService is available
+    if (!userService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"User service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Extract user ID from path
+        std::string user_id = req.matches[1].str();
+
+        // Users can only get their own info, unless superadmin
+        if (auth_user->user_id != user_id && !IsSuperadmin(*auth_user)) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - can only access your own user data"})", "application/json");
+            return;
+        }
+
+        auto result = userService_->GetUser(user_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return user (without password_hash)
+        nlohmann::json user_json = {
+            {"id", result.user->id},
+            {"email", result.user->email},
+            {"name", result.user->name},
+            {"created_at", result.user->created_at},
+            {"updated_at", result.user->updated_at}
+        };
+
+        res.set_content(user_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Get user error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleUpdateUser(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Check if UserService is available
+    if (!userService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"User service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Extract user ID from path
+        std::string user_id = req.matches[1].str();
+
+        // Users can only update their own info, unless superadmin
+        if (auth_user->user_id != user_id && !IsSuperadmin(*auth_user)) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - can only update your own user data"})", "application/json");
+            return;
+        }
+
+        auto body = nlohmann::json::parse(req.body);
+
+        UpdateUserRequest update_req;
+        if (body.contains("email")) {
+            update_req.email = body["email"].get<std::string>();
+        }
+        if (body.contains("password")) {
+            update_req.password = body["password"].get<std::string>();
+        }
+        if (body.contains("name")) {
+            update_req.name = body["name"].get<std::string>();
+        }
+
+        auto result = userService_->UpdateUser(user_id, update_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return updated user (without password_hash)
+        nlohmann::json user_json = {
+            {"id", result.user->id},
+            {"email", result.user->email},
+            {"name", result.user->name},
+            {"created_at", result.user->created_at},
+            {"updated_at", result.user->updated_at}
+        };
+
+        res.set_content(user_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Update user error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleDeleteUser(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Only superadmins can delete users
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if UserService is available
+    if (!userService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"User service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Extract user ID from path
+        std::string user_id = req.matches[1].str();
+
+        auto result = userService_->DeleteUser(user_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        res.set_content(R"({"message":"User deleted successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Delete user error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+// ============================================================================
+// Workspace Management Routes Implementation
+// ============================================================================
+
+void HttpServer::SetupWorkspaceRoutes() {
+    // POST /api/workspaces - Create a new workspace (superadmin only)
+    httpServer_->Post("/api/workspaces", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleCreateWorkspace(req, res);
+    });
+
+    // GET /api/workspaces - List workspaces (filtered by user membership for non-superadmin)
+    httpServer_->Get("/api/workspaces", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleListWorkspaces(req, res);
+    });
+
+    // GET /api/workspaces/:id - Get a specific workspace
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleGetWorkspace(req, res);
+    });
+
+    // PATCH /api/workspaces/:id - Update a workspace
+    httpServer_->Patch(R"(/api/workspaces/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleUpdateWorkspace(req, res);
+    });
+
+    // DELETE /api/workspaces/:id - Soft delete a workspace (superadmin only)
+    httpServer_->Delete(R"(/api/workspaces/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleDeleteWorkspace(req, res);
+    });
+
+    spdlog::info("Workspace management routes registered: /api/workspaces");
+}
+
+void HttpServer::HandleCreateWorkspace(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Only superadmins can create workspaces
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if WorkspaceService is available
+    if (!workspaceService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Workspace service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto body = nlohmann::json::parse(req.body);
+
+        CreateWorkspaceRequest create_req;
+        if (body.contains("name")) {
+            create_req.name = body["name"].get<std::string>();
+        }
+        if (body.contains("settings")) {
+            create_req.settings = body["settings"];
+        }
+
+        auto result = workspaceService_->CreateWorkspace(create_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return created workspace
+        nlohmann::json workspace_json = {
+            {"id", result.workspace->id},
+            {"name", result.workspace->name},
+            {"settings", result.workspace->settings},
+            {"created_at", result.workspace->created_at},
+            {"updated_at", result.workspace->updated_at}
+        };
+
+        res.status = 201;
+        res.set_content(workspace_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Create workspace error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListWorkspaces(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Check if WorkspaceService is available
+    if (!workspaceService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Workspace service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        WorkspaceListResult result;
+
+        // Superadmins can see all workspaces, others only see their memberships
+        if (IsSuperadmin(*auth_user)) {
+            // Parse query parameters
+            int limit = 100;
+            int offset = 0;
+            bool include_deleted = false;
+
+            if (req.has_param("limit")) {
+                limit = std::stoi(req.get_param_value("limit"));
+                if (limit < 1 || limit > 1000) {
+                    limit = 100;
+                }
+            }
+            if (req.has_param("offset")) {
+                offset = std::stoi(req.get_param_value("offset"));
+                if (offset < 0) {
+                    offset = 0;
+                }
+            }
+            if (req.has_param("include_deleted")) {
+                include_deleted = req.get_param_value("include_deleted") == "true";
+            }
+
+            result = workspaceService_->ListWorkspaces(limit, offset, include_deleted);
+        } else {
+            // Non-superadmin: filter by user's workspace memberships
+            result = workspaceService_->ListWorkspacesByIds(auth_user->workspace_ids, false);
+        }
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build response
+        nlohmann::json workspaces_array = nlohmann::json::array();
+        for (const auto& workspace : result.workspaces) {
+            nlohmann::json ws_json = {
+                {"id", workspace.id},
+                {"name", workspace.name},
+                {"settings", workspace.settings},
+                {"created_at", workspace.created_at},
+                {"updated_at", workspace.updated_at}
+            };
+            if (IsSuperadmin(*auth_user)) {
+                ws_json["deleted_at"] = workspace.deleted_at;
+            }
+            workspaces_array.push_back(ws_json);
+        }
+
+        nlohmann::json response = {
+            {"workspaces", workspaces_array},
+            {"total_count", result.total_count}
+        };
+
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List workspaces error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleGetWorkspace(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Check if WorkspaceService is available
+    if (!workspaceService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Workspace service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Extract workspace ID from path
+        std::string workspace_id = req.matches[1].str();
+
+        // Non-superadmins can only access workspaces they're members of
+        if (!IsSuperadmin(*auth_user)) {
+            auto it = std::find(auth_user->workspace_ids.begin(), auth_user->workspace_ids.end(), workspace_id);
+            if (it == auth_user->workspace_ids.end()) {
+                res.status = 403;
+                res.set_content(R"({"error":"Forbidden - not a member of this workspace"})", "application/json");
+                return;
+            }
+        }
+
+        auto result = workspaceService_->GetWorkspace(workspace_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return workspace
+        nlohmann::json workspace_json = {
+            {"id", result.workspace->id},
+            {"name", result.workspace->name},
+            {"settings", result.workspace->settings},
+            {"created_at", result.workspace->created_at},
+            {"updated_at", result.workspace->updated_at}
+        };
+
+        res.set_content(workspace_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Get workspace error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleUpdateWorkspace(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Only superadmins can update workspaces
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if WorkspaceService is available
+    if (!workspaceService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Workspace service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Extract workspace ID from path
+        std::string workspace_id = req.matches[1].str();
+
+        auto body = nlohmann::json::parse(req.body);
+
+        UpdateWorkspaceRequest update_req;
+        if (body.contains("name")) {
+            update_req.name = body["name"].get<std::string>();
+        }
+        if (body.contains("settings")) {
+            update_req.settings = body["settings"];
+        }
+
+        auto result = workspaceService_->UpdateWorkspace(workspace_id, update_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return updated workspace
+        nlohmann::json workspace_json = {
+            {"id", result.workspace->id},
+            {"name", result.workspace->name},
+            {"settings", result.workspace->settings},
+            {"created_at", result.workspace->created_at},
+            {"updated_at", result.workspace->updated_at}
+        };
+
+        res.set_content(workspace_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Update workspace error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleDeleteWorkspace(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Only superadmins can delete workspaces
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if WorkspaceService is available
+    if (!workspaceService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Workspace service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Extract workspace ID from path
+        std::string workspace_id = req.matches[1].str();
+
+        auto result = workspaceService_->DeleteWorkspace(workspace_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        res.set_content(R"({"message":"Workspace deleted successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Delete workspace error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+// ============================================================================
+// Group Management Routes Implementation
+// ============================================================================
+
+void HttpServer::SetupGroupRoutes() {
+    // POST /api/workspaces/:wid/groups - Create a new group in workspace
+    httpServer_->Post(R"(/api/workspaces/([a-zA-Z0-9\-]+)/groups)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleCreateGroup(req, res);
+    });
+
+    // GET /api/workspaces/:wid/groups - List groups in workspace
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/groups)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleListGroups(req, res);
+    });
+
+    // GET /api/workspaces/:wid/groups/:id - Get a specific group
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/groups/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleGetGroup(req, res);
+    });
+
+    // PATCH /api/workspaces/:wid/groups/:id - Update a group
+    httpServer_->Patch(R"(/api/workspaces/([a-zA-Z0-9\-]+)/groups/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleUpdateGroup(req, res);
+    });
+
+    // DELETE /api/workspaces/:wid/groups/:id - Delete a group
+    httpServer_->Delete(R"(/api/workspaces/([a-zA-Z0-9\-]+)/groups/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleDeleteGroup(req, res);
+    });
+
+    spdlog::info("Group management routes registered: /api/workspaces/:wid/groups");
+}
+
+void HttpServer::HandleCreateGroup(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Check if user has access to this workspace (superadmin or member)
+    if (!IsSuperadmin(*auth_user)) {
+        auto it = std::find(auth_user->workspace_ids.begin(), auth_user->workspace_ids.end(), workspace_id);
+        if (it == auth_user->workspace_ids.end()) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - not a member of this workspace"})", "application/json");
+            return;
+        }
+        // Only superadmins can create groups
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required to create groups"})", "application/json");
+        return;
+    }
+
+    // Check if GroupService is available
+    if (!groupService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Group service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto body = nlohmann::json::parse(req.body);
+
+        CreateGroupRequest create_req;
+        create_req.workspace_id = workspace_id;
+        if (body.contains("name")) {
+            create_req.name = body["name"].get<std::string>();
+        }
+        if (body.contains("permissions") && body["permissions"].is_array()) {
+            for (const auto& perm : body["permissions"]) {
+                if (perm.is_string()) {
+                    create_req.permissions.push_back(perm.get<std::string>());
+                }
+            }
+        }
+
+        auto result = groupService_->CreateGroup(create_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return created group
+        nlohmann::json group_json = {
+            {"id", result.group->id},
+            {"workspace_id", result.group->workspace_id},
+            {"name", result.group->name},
+            {"permissions", result.group->permissions},
+            {"created_at", result.group->created_at},
+            {"updated_at", result.group->updated_at}
+        };
+
+        res.status = 201;
+        res.set_content(group_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Create group error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListGroups(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        auto it = std::find(auth_user->workspace_ids.begin(), auth_user->workspace_ids.end(), workspace_id);
+        if (it == auth_user->workspace_ids.end()) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - not a member of this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if GroupService is available
+    if (!groupService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Group service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse query parameters
+        int limit = 100;
+        int offset = 0;
+        bool include_deleted = false;
+
+        if (req.has_param("limit")) {
+            limit = std::stoi(req.get_param_value("limit"));
+            if (limit < 1 || limit > 1000) {
+                limit = 100;
+            }
+        }
+        if (req.has_param("offset")) {
+            offset = std::stoi(req.get_param_value("offset"));
+            if (offset < 0) {
+                offset = 0;
+            }
+        }
+        if (IsSuperadmin(*auth_user) && req.has_param("include_deleted")) {
+            include_deleted = req.get_param_value("include_deleted") == "true";
+        }
+
+        auto result = groupService_->ListGroups(workspace_id, limit, offset, include_deleted);
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build response
+        nlohmann::json groups_array = nlohmann::json::array();
+        for (const auto& group : result.groups) {
+            nlohmann::json grp_json = {
+                {"id", group.id},
+                {"workspace_id", group.workspace_id},
+                {"name", group.name},
+                {"permissions", group.permissions},
+                {"created_at", group.created_at},
+                {"updated_at", group.updated_at}
+            };
+            if (IsSuperadmin(*auth_user)) {
+                grp_json["deleted_at"] = group.deleted_at;
+            }
+            groups_array.push_back(grp_json);
+        }
+
+        nlohmann::json response = {
+            {"groups", groups_array},
+            {"total_count", result.total_count}
+        };
+
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List groups error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleGetGroup(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and group ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string group_id = req.matches[2].str();
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        auto it = std::find(auth_user->workspace_ids.begin(), auth_user->workspace_ids.end(), workspace_id);
+        if (it == auth_user->workspace_ids.end()) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - not a member of this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if GroupService is available
+    if (!groupService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Group service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = groupService_->GetGroup(group_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Verify group belongs to the workspace
+        if (result.group->workspace_id != workspace_id) {
+            res.status = 404;
+            res.set_content(R"({"error":"Group not found in this workspace"})", "application/json");
+            return;
+        }
+
+        // Return group
+        nlohmann::json group_json = {
+            {"id", result.group->id},
+            {"workspace_id", result.group->workspace_id},
+            {"name", result.group->name},
+            {"permissions", result.group->permissions},
+            {"created_at", result.group->created_at},
+            {"updated_at", result.group->updated_at}
+        };
+
+        res.set_content(group_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Get group error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleUpdateGroup(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and group ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string group_id = req.matches[2].str();
+
+    // Only superadmins can update groups
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if GroupService is available
+    if (!groupService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Group service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // First verify group belongs to the workspace
+        auto existing = groupService_->GetGroup(group_id);
+        if (!existing.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", existing.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        if (existing.group->workspace_id != workspace_id) {
+            res.status = 404;
+            res.set_content(R"({"error":"Group not found in this workspace"})", "application/json");
+            return;
+        }
+
+        auto body = nlohmann::json::parse(req.body);
+
+        UpdateGroupRequest update_req;
+        if (body.contains("name")) {
+            update_req.name = body["name"].get<std::string>();
+        }
+        if (body.contains("permissions") && body["permissions"].is_array()) {
+            std::vector<std::string> perms;
+            for (const auto& perm : body["permissions"]) {
+                if (perm.is_string()) {
+                    perms.push_back(perm.get<std::string>());
+                }
+            }
+            update_req.permissions = perms;
+        }
+
+        auto result = groupService_->UpdateGroup(group_id, update_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return updated group
+        nlohmann::json group_json = {
+            {"id", result.group->id},
+            {"workspace_id", result.group->workspace_id},
+            {"name", result.group->name},
+            {"permissions", result.group->permissions},
+            {"created_at", result.group->created_at},
+            {"updated_at", result.group->updated_at}
+        };
+
+        res.set_content(group_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::parse_error& e) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Update group error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleDeleteGroup(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and group ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string group_id = req.matches[2].str();
+
+    // Only superadmins can delete groups
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if GroupService is available
+    if (!groupService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Group service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // First verify group belongs to the workspace
+        auto existing = groupService_->GetGroup(group_id);
+        if (!existing.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", existing.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        if (existing.group->workspace_id != workspace_id) {
+            res.status = 404;
+            res.set_content(R"({"error":"Group not found in this workspace"})", "application/json");
+            return;
+        }
+
+        auto result = groupService_->DeleteGroup(group_id);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        res.set_content(R"({"message":"Group deleted successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Delete group error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+// ============================================================================
+// Membership Routes
+// ============================================================================
+
+void HttpServer::SetupMembershipRoutes() {
+    // POST /api/workspaces/:wid/members - Add a user to workspace
+    httpServer_->Post(R"(/api/workspaces/([a-zA-Z0-9\-]+)/members)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleAddMember(req, res);
+    });
+
+    // GET /api/workspaces/:wid/members - List members in workspace
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/members)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleListMembers(req, res);
+    });
+
+    // GET /api/workspaces/:wid/members/:userId - Get a specific member
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/members/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleGetMember(req, res);
+    });
+
+    // PUT /api/workspaces/:wid/members/:userId - Update user's groups in workspace
+    httpServer_->Put(R"(/api/workspaces/([a-zA-Z0-9\-]+)/members/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleUpdateMember(req, res);
+    });
+
+    // DELETE /api/workspaces/:wid/members/:userId - Remove user from workspace
+    httpServer_->Delete(R"(/api/workspaces/([a-zA-Z0-9\-]+)/members/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleRemoveMember(req, res);
+    });
+
+    spdlog::info("Membership routes registered: /api/workspaces/:wid/members");
+}
+
+void HttpServer::HandleAddMember(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Only superadmins can add members
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if MembershipService is available
+    if (!membershipService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Membership service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse request body
+        auto json = nlohmann::json::parse(req.body);
+
+        AddMemberRequest add_req;
+        add_req.workspace_id = workspace_id;
+
+        if (json.contains("user_id") && json["user_id"].is_string()) {
+            add_req.user_id = json["user_id"].get<std::string>();
+        } else {
+            res.status = 400;
+            res.set_content(R"({"error":"user_id is required"})", "application/json");
+            return;
+        }
+
+        if (json.contains("group_ids") && json["group_ids"].is_array()) {
+            for (const auto& item : json["group_ids"]) {
+                if (item.is_string()) {
+                    add_req.group_ids.push_back(item.get<std::string>());
+                }
+            }
+        }
+
+        auto result = membershipService_->AddMember(add_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return created membership
+        nlohmann::json member_json = {
+            {"id", result.member->id},
+            {"workspace_id", result.member->workspace_id},
+            {"user_id", result.member->user_id},
+            {"group_ids", result.member->group_ids},
+            {"created_at", result.member->created_at},
+            {"updated_at", result.member->updated_at}
+        };
+
+        res.status = 201;
+        res.set_content(member_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::exception& e) {
+        res.status = 400;
+        nlohmann::json error_response = {{"error", "Invalid JSON: " + std::string(e.what())}};
+        res.set_content(error_response.dump(), "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Add member error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListMembers(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Check access to workspace (superadmin or member)
+    if (!IsSuperadmin(*auth_user)) {
+        auto it = std::find(auth_user->workspace_ids.begin(), auth_user->workspace_ids.end(), workspace_id);
+        if (it == auth_user->workspace_ids.end()) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - not a member of this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if MembershipService is available
+    if (!membershipService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Membership service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Get query parameters
+        int limit = 100;
+        int offset = 0;
+        bool include_deleted = false;
+
+        if (req.has_param("limit")) {
+            limit = std::stoi(req.get_param_value("limit"));
+            if (limit < 1 || limit > 1000) {
+                limit = 100;
+            }
+        }
+        if (req.has_param("offset")) {
+            offset = std::stoi(req.get_param_value("offset"));
+            if (offset < 0) {
+                offset = 0;
+            }
+        }
+        if (IsSuperadmin(*auth_user) && req.has_param("include_deleted")) {
+            include_deleted = req.get_param_value("include_deleted") == "true";
+        }
+
+        auto result = membershipService_->ListMembers(workspace_id, limit, offset, include_deleted);
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build response with user details
+        nlohmann::json members_array = nlohmann::json::array();
+        for (const auto& member : result.members) {
+            nlohmann::json member_json = {
+                {"id", member.id},
+                {"workspace_id", member.workspace_id},
+                {"user_id", member.user_id},
+                {"group_ids", member.group_ids},
+                {"created_at", member.created_at},
+                {"updated_at", member.updated_at}
+            };
+            if (IsSuperadmin(*auth_user)) {
+                member_json["deleted_at"] = member.deleted_at;
+            }
+
+            // Fetch user details if UserService is available
+            if (userService_) {
+                auto user_result = userService_->GetUser(member.user_id);
+                if (user_result.success && user_result.user) {
+                    member_json["user"] = {
+                        {"id", user_result.user->id},
+                        {"email", user_result.user->email},
+                        {"name", user_result.user->name},
+                        {"created_at", user_result.user->created_at}
+                    };
+                }
+            }
+
+            members_array.push_back(member_json);
+        }
+
+        nlohmann::json response = {
+            {"members", members_array},
+            {"total_count", result.total_count}
+        };
+
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List members error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleGetMember(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and user ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string user_id = req.matches[2].str();
+
+    // Check access to workspace (superadmin or member)
+    if (!IsSuperadmin(*auth_user)) {
+        auto it = std::find(auth_user->workspace_ids.begin(), auth_user->workspace_ids.end(), workspace_id);
+        if (it == auth_user->workspace_ids.end()) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - not a member of this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if MembershipService is available
+    if (!membershipService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Membership service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = membershipService_->GetMembershipByUser(workspace_id, user_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return membership
+        nlohmann::json member_json = {
+            {"id", result.member->id},
+            {"workspace_id", result.member->workspace_id},
+            {"user_id", result.member->user_id},
+            {"group_ids", result.member->group_ids},
+            {"created_at", result.member->created_at},
+            {"updated_at", result.member->updated_at}
+        };
+
+        res.set_content(member_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Get member error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleUpdateMember(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and user ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string user_id = req.matches[2].str();
+
+    // Only superadmins can update memberships
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if MembershipService is available
+    if (!membershipService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Membership service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse request body
+        auto json = nlohmann::json::parse(req.body);
+
+        UpdateMemberRequest update_req;
+
+        if (json.contains("group_ids") && json["group_ids"].is_array()) {
+            std::vector<std::string> group_ids;
+            for (const auto& item : json["group_ids"]) {
+                if (item.is_string()) {
+                    group_ids.push_back(item.get<std::string>());
+                }
+            }
+            update_req.group_ids = group_ids;
+        }
+
+        auto result = membershipService_->UpdateMembership(workspace_id, user_id, update_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return updated membership
+        nlohmann::json member_json = {
+            {"id", result.member->id},
+            {"workspace_id", result.member->workspace_id},
+            {"user_id", result.member->user_id},
+            {"group_ids", result.member->group_ids},
+            {"created_at", result.member->created_at},
+            {"updated_at", result.member->updated_at}
+        };
+
+        res.set_content(member_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::exception& e) {
+        res.status = 400;
+        nlohmann::json error_response = {{"error", "Invalid JSON: " + std::string(e.what())}};
+        res.set_content(error_response.dump(), "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Update member error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleRemoveMember(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and user ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string user_id = req.matches[2].str();
+
+    // Only superadmins can remove members
+    if (!IsSuperadmin(*auth_user)) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - superadmin required"})", "application/json");
+        return;
+    }
+
+    // Check if MembershipService is available
+    if (!membershipService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Membership service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = membershipService_->RemoveMember(workspace_id, user_id);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        res.set_content(R"({"message":"Member removed successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Remove member error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+// ============================================================================
+// API Key Routes
+// ============================================================================
+
+void HttpServer::SetupApiKeyRoutes() {
+    // POST /api/users/:id/api-keys - Create an API key for a user
+    httpServer_->Post(R"(/api/users/([a-zA-Z0-9\-]+)/api-keys)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleCreateApiKey(req, res);
+    });
+
+    // GET /api/users/:id/api-keys - List user's API keys
+    httpServer_->Get(R"(/api/users/([a-zA-Z0-9\-]+)/api-keys)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleListApiKeys(req, res);
+    });
+
+    // DELETE /api/users/:id/api-keys/:keyId - Revoke an API key
+    httpServer_->Delete(R"(/api/users/([a-zA-Z0-9\-]+)/api-keys/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleRevokeApiKey(req, res);
+    });
+
+    spdlog::info("API key routes registered: /api/users/:id/api-keys");
+}
+
+void HttpServer::HandleCreateApiKey(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract user ID from path
+    std::string target_user_id = req.matches[1].str();
+
+    // Check permissions: superadmin can create for any user, others only for themselves
+    if (!IsSuperadmin(*auth_user) && auth_user->user_id != target_user_id) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - can only create API keys for yourself"})", "application/json");
+        return;
+    }
+
+    // Check if ApiKeyService is available
+    if (!apiKeyService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"API key service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse request body
+        auto json = nlohmann::json::parse(req.body);
+
+        CreateApiKeyRequest create_req;
+        create_req.user_id = target_user_id;
+
+        if (json.contains("name") && json["name"].is_string()) {
+            create_req.name = json["name"].get<std::string>();
+        } else {
+            res.status = 400;
+            res.set_content(R"({"error":"name is required"})", "application/json");
+            return;
+        }
+
+        if (json.contains("permissions") && json["permissions"].is_array()) {
+            for (const auto& item : json["permissions"]) {
+                if (item.is_string()) {
+                    std::string perm = item.get<std::string>();
+                    // Users can only grant permissions they have (unless superadmin)
+                    if (!IsSuperadmin(*auth_user)) {
+                        // For now, allow all permissions - proper permission checking
+                        // would require looking up user's actual permissions
+                    }
+                    create_req.permissions.push_back(perm);
+                }
+            }
+        }
+
+        if (json.contains("expires_at") && json["expires_at"].is_string()) {
+            create_req.expires_at = json["expires_at"].get<std::string>();
+        }
+
+        auto result = apiKeyService_->CreateApiKey(create_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return created key info INCLUDING the raw key (only time it's returned!)
+        nlohmann::json key_json = {
+            {"id", result.api_key->id},
+            {"user_id", result.api_key->user_id},
+            {"name", result.api_key->name},
+            {"key_prefix", result.api_key->key_prefix},
+            {"key", result.raw_key},  // THE RAW KEY - only returned once!
+            {"permissions", result.api_key->permissions},
+            {"created_at", result.api_key->created_at},
+            {"expires_at", result.api_key->expires_at}
+        };
+
+        res.status = 201;
+        res.set_content(key_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::exception& e) {
+        res.status = 400;
+        nlohmann::json error_response = {{"error", "Invalid JSON: " + std::string(e.what())}};
+        res.set_content(error_response.dump(), "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Create API key error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListApiKeys(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract user ID from path
+    std::string target_user_id = req.matches[1].str();
+
+    // Check permissions: superadmin can list for any user, others only for themselves
+    if (!IsSuperadmin(*auth_user) && auth_user->user_id != target_user_id) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - can only list your own API keys"})", "application/json");
+        return;
+    }
+
+    // Check if ApiKeyService is available
+    if (!apiKeyService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"API key service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Check for include_revoked parameter (superadmin only)
+        bool include_revoked = false;
+        if (IsSuperadmin(*auth_user) && req.has_param("include_revoked")) {
+            include_revoked = req.get_param_value("include_revoked") == "true";
+        }
+
+        auto result = apiKeyService_->ListApiKeys(target_user_id, include_revoked);
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build response (note: raw keys and hashes are NOT included)
+        nlohmann::json keys_array = nlohmann::json::array();
+        for (const auto& key : result.api_keys) {
+            nlohmann::json key_json = {
+                {"id", key.id},
+                {"user_id", key.user_id},
+                {"name", key.name},
+                {"key_prefix", key.key_prefix},  // Only prefix, not full key
+                {"permissions", key.permissions},
+                {"created_at", key.created_at},
+                {"updated_at", key.updated_at},
+                {"last_used_at", key.last_used_at},
+                {"expires_at", key.expires_at}
+            };
+            if (IsSuperadmin(*auth_user)) {
+                key_json["deleted_at"] = key.deleted_at;
+            }
+            keys_array.push_back(key_json);
+        }
+
+        nlohmann::json response = {
+            {"api_keys", keys_array},
+            {"total_count", result.total_count}
+        };
+
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List API keys error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleRevokeApiKey(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract user ID and key ID from path
+    std::string target_user_id = req.matches[1].str();
+    std::string key_id = req.matches[2].str();
+
+    // Check permissions: superadmin can revoke for any user, others only for themselves
+    if (!IsSuperadmin(*auth_user) && auth_user->user_id != target_user_id) {
+        res.status = 403;
+        res.set_content(R"({"error":"Forbidden - can only revoke your own API keys"})", "application/json");
+        return;
+    }
+
+    // Check if ApiKeyService is available
+    if (!apiKeyService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"API key service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = apiKeyService_->RevokeApiKey(key_id, target_user_id);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        res.set_content(R"({"message":"API key revoked successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Revoke API key error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+// ============================================================================
+// Collection Routes
+// ============================================================================
+
+void HttpServer::SetupCollectionRoutes() {
+    // POST /api/workspaces/:wid/collections - Create a collection
+    httpServer_->Post(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleCreateCollection(req, res);
+    });
+
+    // GET /api/workspaces/:wid/collections - List collections
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleListCollections(req, res);
+    });
+
+    // GET /api/workspaces/:wid/collections/:name - Get a collection
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleGetCollection(req, res);
+    });
+
+    // PUT /api/workspaces/:wid/collections/:name - Update a collection
+    httpServer_->Put(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleUpdateCollection(req, res);
+    });
+
+    // DELETE /api/workspaces/:wid/collections/:name - Drop a collection
+    httpServer_->Delete(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleDropCollection(req, res);
+    });
+
+    spdlog::info("Collection routes registered: /api/workspaces/:wid/collections");
+}
+
+void HttpServer::HandleCreateCollection(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace (superadmin has access to all)
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if CollectionService is available
+    if (!collectionService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Collection service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse request body
+        auto json = nlohmann::json::parse(req.body);
+
+        CreateCollectionRequest create_req;
+        create_req.workspace_id = workspace_id;
+
+        if (json.contains("name") && json["name"].is_string()) {
+            create_req.name = json["name"].get<std::string>();
+        } else {
+            res.status = 400;
+            res.set_content(R"({"error":"name is required"})", "application/json");
+            return;
+        }
+
+        // Parse settings
+        if (json.contains("schema") && json["schema"].is_string()) {
+            create_req.settings.schema = json["schema"].get<std::string>();
+        }
+
+        if (json.contains("encrypted_fields") && json["encrypted_fields"].is_array()) {
+            for (const auto& item : json["encrypted_fields"]) {
+                if (item.is_string()) {
+                    create_req.settings.encrypted_fields.push_back(item.get<std::string>());
+                }
+            }
+        }
+
+        if (json.contains("ttl_seconds") && json["ttl_seconds"].is_number_integer()) {
+            create_req.settings.ttl_seconds = json["ttl_seconds"].get<int64_t>();
+        }
+
+        auto result = collectionService_->CreateCollection(create_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return created collection info
+        nlohmann::json coll_json = {
+            {"name", result.collection->name},
+            {"workspace_id", result.collection->workspace_id},
+            {"document_count", result.collection->document_count},
+            {"size_bytes", result.collection->size_bytes},
+            {"created_at", result.collection->created_at},
+            {"settings", {
+                {"schema", result.collection->settings.schema},
+                {"encrypted_fields", result.collection->settings.encrypted_fields},
+                {"ttl_seconds", result.collection->settings.ttl_seconds}
+            }}
+        };
+
+        res.status = 201;
+        res.set_content(coll_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::exception& e) {
+        spdlog::warn("Create collection JSON parse error: {}", e.what());
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON in request body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Create collection error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListCollections(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if CollectionService is available
+    if (!collectionService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Collection service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Check if superadmin wants to include system collections
+        bool include_system = IsSuperadmin(*auth_user) &&
+                              req.has_param("include_system") &&
+                              req.get_param_value("include_system") == "true";
+
+        auto result = collectionService_->ListCollections(workspace_id);
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        nlohmann::json collections_array = nlohmann::json::array();
+        for (const auto& coll : result.collections) {
+            // Check if collection name starts with underscore (system collection)
+            bool is_system = !coll.name.empty() && coll.name[0] == '_';
+
+            nlohmann::json coll_json = {
+                {"name", coll.name},
+                {"workspace_id", coll.workspace_id},
+                {"document_count", coll.document_count},
+                {"size_bytes", coll.size_bytes},
+                {"created_at", coll.created_at},
+                {"updated_at", coll.updated_at},
+                {"is_system", is_system},
+                {"settings", {
+                    {"schema", coll.settings.schema},
+                    {"encrypted_fields", coll.settings.encrypted_fields},
+                    {"ttl_seconds", coll.settings.ttl_seconds}
+                }}
+            };
+            collections_array.push_back(coll_json);
+        }
+
+        // If superadmin requested system collections, add global system collections
+        if (include_system) {
+            auto system_result = collectionService_->ListSystemCollections();
+            if (system_result.success) {
+                for (const auto& coll : system_result.collections) {
+                    nlohmann::json coll_json = {
+                        {"name", coll.name},
+                        {"workspace_id", ""},
+                        {"document_count", coll.document_count},
+                        {"size_bytes", coll.size_bytes},
+                        {"created_at", coll.created_at},
+                        {"updated_at", coll.updated_at},
+                        {"is_system", true},
+                        {"settings", nlohmann::json::object()}
+                    };
+                    collections_array.push_back(coll_json);
+                }
+            }
+        }
+
+        nlohmann::json response = {
+            {"collections", collections_array},
+            {"total_count", static_cast<int64_t>(collections_array.size())}
+        };
+
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List collections error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleGetCollection(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and collection name from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if CollectionService is available
+    if (!collectionService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Collection service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = collectionService_->GetCollection(workspace_id, collection_name);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        nlohmann::json coll_json = {
+            {"name", result.collection->name},
+            {"workspace_id", result.collection->workspace_id},
+            {"document_count", result.collection->document_count},
+            {"size_bytes", result.collection->size_bytes},
+            {"created_at", result.collection->created_at},
+            {"updated_at", result.collection->updated_at},
+            {"settings", {
+                {"schema", result.collection->settings.schema},
+                {"encrypted_fields", result.collection->settings.encrypted_fields},
+                {"ttl_seconds", result.collection->settings.ttl_seconds}
+            }}
+        };
+
+        res.set_content(coll_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Get collection error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleUpdateCollection(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and collection name from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if CollectionService is available
+    if (!collectionService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Collection service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse request body
+        auto json = nlohmann::json::parse(req.body);
+
+        // Get existing settings first
+        auto get_result = collectionService_->GetCollection(workspace_id, collection_name);
+        if (!get_result.success) {
+            res.status = 404;
+            res.set_content(R"({"error":"Collection not found"})", "application/json");
+            return;
+        }
+
+        CollectionSettings settings = get_result.collection->settings;
+
+        // Update only provided fields
+        if (json.contains("schema") && json["schema"].is_string()) {
+            settings.schema = json["schema"].get<std::string>();
+        }
+
+        if (json.contains("encrypted_fields") && json["encrypted_fields"].is_array()) {
+            settings.encrypted_fields.clear();
+            for (const auto& item : json["encrypted_fields"]) {
+                if (item.is_string()) {
+                    settings.encrypted_fields.push_back(item.get<std::string>());
+                }
+            }
+        }
+
+        if (json.contains("ttl_seconds") && json["ttl_seconds"].is_number_integer()) {
+            settings.ttl_seconds = json["ttl_seconds"].get<int64_t>();
+        }
+
+        auto result = collectionService_->UpdateCollection(workspace_id, collection_name, settings);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        nlohmann::json coll_json = {
+            {"name", result.collection->name},
+            {"workspace_id", result.collection->workspace_id},
+            {"document_count", result.collection->document_count},
+            {"size_bytes", result.collection->size_bytes},
+            {"created_at", result.collection->created_at},
+            {"updated_at", result.collection->updated_at},
+            {"settings", {
+                {"schema", result.collection->settings.schema},
+                {"encrypted_fields", result.collection->settings.encrypted_fields},
+                {"ttl_seconds", result.collection->settings.ttl_seconds}
+            }}
+        };
+
+        res.set_content(coll_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::exception& e) {
+        spdlog::warn("Update collection JSON parse error: {}", e.what());
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON in request body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Update collection error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+// ============================================================================
+// Document Routes
+// ============================================================================
+
+void HttpServer::SetupDocumentRoutes() {
+    // POST /api/workspaces/:wid/collections/:name/documents - Create a document
+    httpServer_->Post(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*)/documents)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleCreateDocument(req, res);
+    });
+
+    // GET /api/workspaces/:wid/collections/:name/documents - List documents
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*)/documents)", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleListDocuments(req, res);
+    });
+
+    // GET /api/workspaces/:wid/collections/:name/documents/:id - Get a document
+    httpServer_->Get(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*)/documents/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleGetDocument(req, res);
+    });
+
+    // PUT /api/workspaces/:wid/collections/:name/documents/:id - Update a document
+    httpServer_->Put(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*)/documents/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleUpdateDocument(req, res);
+    });
+
+    // DELETE /api/workspaces/:wid/collections/:name/documents/:id - Delete a document
+    httpServer_->Delete(R"(/api/workspaces/([a-zA-Z0-9\-]+)/collections/([a-zA-Z_][a-zA-Z0-9_]*)/documents/([a-zA-Z0-9\-]+))", [this](const httplib::Request& req, httplib::Response& res) {
+        HandleDeleteDocument(req, res);
+    });
+
+    spdlog::info("Document routes registered: /api/workspaces/:wid/collections/:name/documents");
+}
+
+void HttpServer::HandleCreateDocument(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and collection name from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if DocumentService is available
+    if (!documentService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Document service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse request body
+        auto json = nlohmann::json::parse(req.body);
+
+        CreateDocumentRequest create_req;
+        create_req.workspace_id = workspace_id;
+        create_req.collection = collection_name;
+
+        if (json.contains("id") && json["id"].is_string()) {
+            create_req.id = json["id"].get<std::string>();
+        }
+
+        if (json.contains("data") && json["data"].is_object()) {
+            create_req.data = json["data"];
+        } else {
+            // Treat entire body as data if no "data" field
+            create_req.data = json;
+            create_req.data.erase("id");  // Remove id from data
+        }
+
+        auto result = documentService_->CreateDocument(create_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Return created document info
+        nlohmann::json doc_json = {
+            {"id", result.document->id},
+            {"collection", result.document->collection},
+            {"workspace_id", result.document->workspace_id},
+            {"data", result.document->data},
+            {"created_at", result.document->created_at},
+            {"updated_at", result.document->updated_at},
+            {"version", result.document->version}
+        };
+
+        // Broadcast document create event to WebSocket subscribers
+        BroadcastDocumentEvent(workspace_id, collection_name, DocumentAction::Create,
+                                result.document->id, result.document->data);
+
+        res.status = 201;
+        res.set_content(doc_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::exception& e) {
+        spdlog::warn("Create document JSON parse error: {}", e.what());
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON in request body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Create document error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListDocuments(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and collection name from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if DocumentService is available
+    if (!documentService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Document service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        DocumentQuery query;
+        query.workspace_id = workspace_id;
+        query.collection = collection_name;
+
+        // Parse query parameters
+        if (req.has_param("limit")) {
+            query.limit = std::stoi(req.get_param_value("limit"));
+            if (query.limit < 1) query.limit = 1;
+            if (query.limit > 1000) query.limit = 1000;
+        }
+
+        if (req.has_param("offset")) {
+            query.offset = std::stoi(req.get_param_value("offset"));
+            if (query.offset < 0) query.offset = 0;
+        }
+
+        if (req.has_param("sort")) {
+            query.sort_field = req.get_param_value("sort");
+        }
+
+        if (req.has_param("order")) {
+            query.sort_ascending = (req.get_param_value("order") != "desc");
+        }
+
+        if (req.has_param("filter")) {
+            try {
+                query.filter = nlohmann::json::parse(req.get_param_value("filter"));
+            } catch (...) {
+                // Invalid filter JSON - ignore
+            }
+        }
+
+        auto result = documentService_->ListDocuments(query);
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        nlohmann::json docs_array = nlohmann::json::array();
+        for (const auto& doc : result.documents) {
+            nlohmann::json doc_json = {
+                {"id", doc.id},
+                {"collection", doc.collection},
+                {"workspace_id", doc.workspace_id},
+                {"data", doc.data},
+                {"created_at", doc.created_at},
+                {"updated_at", doc.updated_at},
+                {"version", doc.version}
+            };
+            docs_array.push_back(doc_json);
+        }
+
+        nlohmann::json response = {
+            {"documents", docs_array},
+            {"total_count", result.total_count}
+        };
+
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List documents error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleGetDocument(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID, collection name, and document ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+    std::string document_id = req.matches[3].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if DocumentService is available
+    if (!documentService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Document service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = documentService_->GetDocument(workspace_id, collection_name, document_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        nlohmann::json doc_json = {
+            {"id", result.document->id},
+            {"collection", result.document->collection},
+            {"workspace_id", result.document->workspace_id},
+            {"data", result.document->data},
+            {"created_at", result.document->created_at},
+            {"updated_at", result.document->updated_at},
+            {"version", result.document->version}
+        };
+
+        res.set_content(doc_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Get document error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleUpdateDocument(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID, collection name, and document ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+    std::string document_id = req.matches[3].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if DocumentService is available
+    if (!documentService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Document service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Parse request body
+        auto json = nlohmann::json::parse(req.body);
+
+        UpdateDocumentRequest update_req;
+        update_req.workspace_id = workspace_id;
+        update_req.collection = collection_name;
+        update_req.id = document_id;
+
+        if (json.contains("data") && json["data"].is_object()) {
+            update_req.data = json["data"];
+        } else {
+            // Treat entire body as data if no "data" field
+            update_req.data = json;
+        }
+
+        if (json.contains("merge") && json["merge"].is_boolean()) {
+            update_req.merge = json["merge"].get<bool>();
+        }
+
+        if (json.contains("expected_version") && json["expected_version"].is_number_integer()) {
+            update_req.expected_version = json["expected_version"].get<int64_t>();
+        }
+
+        auto result = documentService_->UpdateDocument(update_req);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        nlohmann::json doc_json = {
+            {"id", result.document->id},
+            {"collection", result.document->collection},
+            {"workspace_id", result.document->workspace_id},
+            {"data", result.document->data},
+            {"created_at", result.document->created_at},
+            {"updated_at", result.document->updated_at},
+            {"version", result.document->version}
+        };
+
+        // Broadcast document update event to WebSocket subscribers
+        BroadcastDocumentEvent(workspace_id, collection_name, DocumentAction::Update,
+                                result.document->id, result.document->data);
+
+        res.set_content(doc_json.dump(), "application/json");
+
+    } catch (const nlohmann::json::exception& e) {
+        spdlog::warn("Update document JSON parse error: {}", e.what());
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON in request body"})", "application/json");
+    } catch (const std::exception& e) {
+        spdlog::error("Update document error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleDeleteDocument(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID, collection name, and document ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+    std::string document_id = req.matches[3].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if DocumentService is available
+    if (!documentService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Document service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = documentService_->DeleteDocument(workspace_id, collection_name, document_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Broadcast document delete event to WebSocket subscribers
+        BroadcastDocumentEvent(workspace_id, collection_name, DocumentAction::Delete,
+                                document_id, nlohmann::json::object());
+
+        res.set_content(R"({"message":"Document deleted successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Delete document error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleDropCollection(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and collection name from path
+    std::string workspace_id = req.matches[1].str();
+    std::string collection_name = req.matches[2].str();
+
+    // Verify workspace exists and user has access
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace (for collections, maybe require superadmin?)
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if CollectionService is available
+    if (!collectionService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"Collection service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        // Check for force parameter
+        bool force = false;
+        if (req.has_param("force")) {
+            force = req.get_param_value("force") == "true";
+        }
+
+        auto result = collectionService_->DropCollection(workspace_id, collection_name, force);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        res.set_content(R"({"message":"Collection dropped successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Drop collection error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+// ============================================================================
+// View Routes
+// ============================================================================
+
+void HttpServer::SetupViewRoutes() {
+    // POST /api/workspaces/:workspace_id/views - Create a new view
+    httpServer_->Post(R"(/api/workspaces/([^/]+)/views)",
+        [this](const httplib::Request& req, httplib::Response& res) {
+            HandleCreateView(req, res);
+        });
+
+    // GET /api/workspaces/:workspace_id/views - List all views
+    httpServer_->Get(R"(/api/workspaces/([^/]+)/views)",
+        [this](const httplib::Request& req, httplib::Response& res) {
+            HandleListViews(req, res);
+        });
+
+    // GET /api/workspaces/:workspace_id/views/:view_id - Get a specific view
+    httpServer_->Get(R"(/api/workspaces/([^/]+)/views/([^/]+))",
+        [this](const httplib::Request& req, httplib::Response& res) {
+            HandleGetView(req, res);
+        });
+
+    // PATCH /api/workspaces/:workspace_id/views/:view_id - Update a view
+    httpServer_->Patch(R"(/api/workspaces/([^/]+)/views/([^/]+))",
+        [this](const httplib::Request& req, httplib::Response& res) {
+            HandleUpdateView(req, res);
+        });
+
+    // DELETE /api/workspaces/:workspace_id/views/:view_id - Delete a view
+    httpServer_->Delete(R"(/api/workspaces/([^/]+)/views/([^/]+))",
+        [this](const httplib::Request& req, httplib::Response& res) {
+            HandleDeleteView(req, res);
+        });
+
+    spdlog::info("View routes configured");
+}
+
+void HttpServer::HandleCreateView(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Verify workspace exists
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if ViewService is available
+    if (!viewService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"View service not available"})", "application/json");
+        return;
+    }
+
+    // Parse request body
+    nlohmann::json body;
+    try {
+        body = nlohmann::json::parse(req.body);
+    } catch (const nlohmann::json::parse_error& /*e*/) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+        return;
+    }
+
+    // Validate required fields
+    if (!body.contains("name") || !body["name"].is_string()) {
+        res.status = 400;
+        res.set_content(R"({"error":"name is required"})", "application/json");
+        return;
+    }
+
+    if (!body.contains("collection_name") || !body["collection_name"].is_string()) {
+        res.status = 400;
+        res.set_content(R"({"error":"collection_name is required"})", "application/json");
+        return;
+    }
+
+    try {
+        CreateViewRequest request;
+        request.workspace_id = workspace_id;
+        request.name = body["name"].get<std::string>();
+        request.collection_name = body["collection_name"].get<std::string>();
+
+        // Parse schema if provided
+        if (body.contains("schema") && body["schema"].is_object()) {
+            const auto& schema_json = body["schema"];
+            if (schema_json.contains("title")) {
+                request.schema.title = schema_json.value("title", "");
+            }
+            if (schema_json.contains("description")) {
+                request.schema.description = schema_json.value("description", "");
+            }
+            if (schema_json.contains("layout")) {
+                request.schema.layout = schema_json["layout"];
+            }
+            if (schema_json.contains("fields") && schema_json["fields"].is_array()) {
+                for (const auto& field_json : schema_json["fields"]) {
+                    SchemaField field;
+                    field.name = field_json.value("name", "");
+                    field.type = StringToFieldType(field_json.value("type", "text"));
+                    field.label = field_json.value("label", "");
+                    field.description = field_json.value("description", "");
+                    field.required = field_json.value("required", false);
+                    field.display_order = field_json.value("display_order", 0);
+                    field.widget = field_json.value("widget", "");
+                    field.group = field_json.value("group", "");
+                    field.default_value = field_json.value("default_value", nlohmann::json());
+                    field.options = field_json.value("options", nlohmann::json());
+                    field.reference_collection = field_json.value("reference_collection", "");
+                    field.computed_expression = field_json.value("computed_expression", "");
+                    request.schema.fields.push_back(field);
+                }
+            }
+        }
+
+        // Parse settings if provided
+        if (body.contains("settings") && body["settings"].is_object()) {
+            const auto& settings_json = body["settings"];
+            request.settings.is_default = settings_json.value("is_default", false);
+            request.settings.show_in_sidebar = settings_json.value("show_in_sidebar", true);
+            request.settings.icon = settings_json.value("icon", "");
+            request.settings.filters = settings_json.value("filters", nlohmann::json::object());
+            request.settings.sort = settings_json.value("sort", nlohmann::json::object());
+        }
+
+        auto result = viewService_->CreateView(request);
+
+        if (!result.success) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build response with view info
+        nlohmann::json view_json = {
+            {"id", result.view->id},
+            {"workspace_id", result.view->workspace_id},
+            {"name", result.view->name},
+            {"collection_name", result.view->collection_name},
+            {"created_at", result.view->created_at},
+            {"updated_at", result.view->updated_at}
+        };
+
+        // Add schema
+        nlohmann::json schema_json = {
+            {"title", result.view->schema.title},
+            {"description", result.view->schema.description},
+            {"layout", result.view->schema.layout}
+        };
+        nlohmann::json fields_json = nlohmann::json::array();
+        for (const auto& field : result.view->schema.fields) {
+            nlohmann::json field_json = {
+                {"name", field.name},
+                {"type", FieldTypeToString(field.type)},
+                {"label", field.label},
+                {"description", field.description},
+                {"required", field.required},
+                {"display_order", field.display_order},
+                {"widget", field.widget},
+                {"group", field.group}
+            };
+            if (!field.default_value.is_null()) {
+                field_json["default_value"] = field.default_value;
+            }
+            if (!field.options.is_null()) {
+                field_json["options"] = field.options;
+            }
+            if (!field.reference_collection.empty()) {
+                field_json["reference_collection"] = field.reference_collection;
+            }
+            if (!field.computed_expression.empty()) {
+                field_json["computed_expression"] = field.computed_expression;
+            }
+            fields_json.push_back(field_json);
+        }
+        schema_json["fields"] = fields_json;
+        view_json["schema"] = schema_json;
+
+        // Add settings
+        view_json["settings"] = {
+            {"is_default", result.view->settings.is_default},
+            {"show_in_sidebar", result.view->settings.show_in_sidebar},
+            {"icon", result.view->settings.icon},
+            {"filters", result.view->settings.filters},
+            {"sort", result.view->settings.sort}
+        };
+
+        res.status = 201;
+        res.set_content(view_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Create view error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleListViews(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID from path
+    std::string workspace_id = req.matches[1].str();
+
+    // Verify workspace exists
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if ViewService is available
+    if (!viewService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"View service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        ViewListResult result;
+
+        // Check if filtering by collection
+        if (req.has_param("collection")) {
+            std::string collection_name = req.get_param_value("collection");
+            result = viewService_->ListViewsForCollection(workspace_id, collection_name);
+        } else {
+            result = viewService_->ListViews(workspace_id);
+        }
+
+        if (!result.success) {
+            res.status = 500;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        nlohmann::json views_json = nlohmann::json::array();
+        for (const auto& view : result.views) {
+            nlohmann::json view_json = {
+                {"id", view.id},
+                {"workspace_id", view.workspace_id},
+                {"name", view.name},
+                {"collection_name", view.collection_name},
+                {"created_at", view.created_at},
+                {"updated_at", view.updated_at}
+            };
+
+            // Add schema summary (fields count and title)
+            view_json["schema"] = {
+                {"title", view.schema.title},
+                {"field_count", view.schema.fields.size()}
+            };
+
+            // Add settings
+            view_json["settings"] = {
+                {"is_default", view.settings.is_default},
+                {"show_in_sidebar", view.settings.show_in_sidebar},
+                {"icon", view.settings.icon}
+            };
+
+            views_json.push_back(view_json);
+        }
+
+        nlohmann::json response = {{"views", views_json}};
+        res.set_content(response.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("List views error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleGetView(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and view ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string view_id = req.matches[2].str();
+
+    // Verify workspace exists
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if ViewService is available
+    if (!viewService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"View service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = viewService_->GetView(workspace_id, view_id);
+
+        if (!result.success || !result.view) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build full response with view info
+        nlohmann::json view_json = {
+            {"id", result.view->id},
+            {"workspace_id", result.view->workspace_id},
+            {"name", result.view->name},
+            {"collection_name", result.view->collection_name},
+            {"created_at", result.view->created_at},
+            {"updated_at", result.view->updated_at}
+        };
+
+        // Add schema
+        nlohmann::json schema_json = {
+            {"title", result.view->schema.title},
+            {"description", result.view->schema.description},
+            {"layout", result.view->schema.layout}
+        };
+        nlohmann::json fields_json = nlohmann::json::array();
+        for (const auto& field : result.view->schema.fields) {
+            nlohmann::json field_json = {
+                {"name", field.name},
+                {"type", FieldTypeToString(field.type)},
+                {"label", field.label},
+                {"description", field.description},
+                {"required", field.required},
+                {"display_order", field.display_order},
+                {"widget", field.widget},
+                {"group", field.group}
+            };
+            if (!field.default_value.is_null()) {
+                field_json["default_value"] = field.default_value;
+            }
+            if (!field.options.is_null()) {
+                field_json["options"] = field.options;
+            }
+            if (!field.reference_collection.empty()) {
+                field_json["reference_collection"] = field.reference_collection;
+            }
+            if (!field.computed_expression.empty()) {
+                field_json["computed_expression"] = field.computed_expression;
+            }
+            fields_json.push_back(field_json);
+        }
+        schema_json["fields"] = fields_json;
+        view_json["schema"] = schema_json;
+
+        // Add settings
+        view_json["settings"] = {
+            {"is_default", result.view->settings.is_default},
+            {"show_in_sidebar", result.view->settings.show_in_sidebar},
+            {"icon", result.view->settings.icon},
+            {"filters", result.view->settings.filters},
+            {"sort", result.view->settings.sort}
+        };
+
+        res.set_content(view_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Get view error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleUpdateView(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and view ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string view_id = req.matches[2].str();
+
+    // Verify workspace exists
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if ViewService is available
+    if (!viewService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"View service not available"})", "application/json");
+        return;
+    }
+
+    // Parse request body
+    nlohmann::json body;
+    try {
+        body = nlohmann::json::parse(req.body);
+    } catch (const nlohmann::json::parse_error& /*e*/) {
+        res.status = 400;
+        res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
+        return;
+    }
+
+    try {
+        UpdateViewRequest request;
+        request.workspace_id = workspace_id;
+        request.id = view_id;
+
+        if (body.contains("name") && body["name"].is_string()) {
+            request.name = body["name"].get<std::string>();
+        }
+
+        if (body.contains("collection_name") && body["collection_name"].is_string()) {
+            request.collection_name = body["collection_name"].get<std::string>();
+        }
+
+        // Parse schema if provided
+        if (body.contains("schema") && body["schema"].is_object()) {
+            ViewSchema schema;
+            const auto& schema_json = body["schema"];
+            schema.title = schema_json.value("title", "");
+            schema.description = schema_json.value("description", "");
+            schema.layout = schema_json.value("layout", nlohmann::json::object());
+
+            if (schema_json.contains("fields") && schema_json["fields"].is_array()) {
+                for (const auto& field_json : schema_json["fields"]) {
+                    SchemaField field;
+                    field.name = field_json.value("name", "");
+                    field.type = StringToFieldType(field_json.value("type", "text"));
+                    field.label = field_json.value("label", "");
+                    field.description = field_json.value("description", "");
+                    field.required = field_json.value("required", false);
+                    field.display_order = field_json.value("display_order", 0);
+                    field.widget = field_json.value("widget", "");
+                    field.group = field_json.value("group", "");
+                    field.default_value = field_json.value("default_value", nlohmann::json());
+                    field.options = field_json.value("options", nlohmann::json());
+                    field.reference_collection = field_json.value("reference_collection", "");
+                    field.computed_expression = field_json.value("computed_expression", "");
+                    schema.fields.push_back(field);
+                }
+            }
+            request.schema = schema;
+        }
+
+        // Parse settings if provided
+        if (body.contains("settings") && body["settings"].is_object()) {
+            ViewSettings settings;
+            const auto& settings_json = body["settings"];
+            settings.is_default = settings_json.value("is_default", false);
+            settings.show_in_sidebar = settings_json.value("show_in_sidebar", true);
+            settings.icon = settings_json.value("icon", "");
+            settings.filters = settings_json.value("filters", nlohmann::json::object());
+            settings.sort = settings_json.value("sort", nlohmann::json::object());
+            request.settings = settings;
+        }
+
+        auto result = viewService_->UpdateView(request);
+
+        if (!result.success || !result.view) {
+            res.status = 400;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        // Build response with updated view info
+        nlohmann::json view_json = {
+            {"id", result.view->id},
+            {"workspace_id", result.view->workspace_id},
+            {"name", result.view->name},
+            {"collection_name", result.view->collection_name},
+            {"created_at", result.view->created_at},
+            {"updated_at", result.view->updated_at}
+        };
+
+        // Add schema
+        nlohmann::json schema_json = {
+            {"title", result.view->schema.title},
+            {"description", result.view->schema.description},
+            {"layout", result.view->schema.layout}
+        };
+        nlohmann::json fields_json = nlohmann::json::array();
+        for (const auto& field : result.view->schema.fields) {
+            nlohmann::json field_json = {
+                {"name", field.name},
+                {"type", FieldTypeToString(field.type)},
+                {"label", field.label},
+                {"description", field.description},
+                {"required", field.required},
+                {"display_order", field.display_order},
+                {"widget", field.widget},
+                {"group", field.group}
+            };
+            if (!field.default_value.is_null()) {
+                field_json["default_value"] = field.default_value;
+            }
+            if (!field.options.is_null()) {
+                field_json["options"] = field.options;
+            }
+            if (!field.reference_collection.empty()) {
+                field_json["reference_collection"] = field.reference_collection;
+            }
+            if (!field.computed_expression.empty()) {
+                field_json["computed_expression"] = field.computed_expression;
+            }
+            fields_json.push_back(field_json);
+        }
+        schema_json["fields"] = fields_json;
+        view_json["schema"] = schema_json;
+
+        // Add settings
+        view_json["settings"] = {
+            {"is_default", result.view->settings.is_default},
+            {"show_in_sidebar", result.view->settings.show_in_sidebar},
+            {"icon", result.view->settings.icon},
+            {"filters", result.view->settings.filters},
+            {"sort", result.view->settings.sort}
+        };
+
+        res.set_content(view_json.dump(), "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Update view error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
+void HttpServer::HandleDeleteView(const httplib::Request& req, httplib::Response& res) {
+    // Authenticate the request
+    auto auth_user = AuthenticateRequest(req);
+    if (!auth_user) {
+        res.status = 401;
+        res.set_content(R"({"error":"Unauthorized"})", "application/json");
+        return;
+    }
+
+    // Extract workspace ID and view ID from path
+    std::string workspace_id = req.matches[1].str();
+    std::string view_id = req.matches[2].str();
+
+    // Verify workspace exists
+    auto ws_result = workspaceService_->GetWorkspace(workspace_id);
+    if (!ws_result.success) {
+        res.status = 404;
+        res.set_content(R"({"error":"Workspace not found"})", "application/json");
+        return;
+    }
+
+    // Check if user has access to this workspace
+    if (!IsSuperadmin(*auth_user)) {
+        bool has_access = false;
+        for (const auto& wid : auth_user->workspace_ids) {
+            if (wid == workspace_id) {
+                has_access = true;
+                break;
+            }
+        }
+        if (!has_access) {
+            res.status = 403;
+            res.set_content(R"({"error":"Forbidden - no access to this workspace"})", "application/json");
+            return;
+        }
+    }
+
+    // Check if ViewService is available
+    if (!viewService_) {
+        res.status = 503;
+        res.set_content(R"({"error":"View service not available"})", "application/json");
+        return;
+    }
+
+    try {
+        auto result = viewService_->DeleteView(workspace_id, view_id);
+
+        if (!result.success) {
+            res.status = 404;
+            nlohmann::json error_response = {{"error", result.error}};
+            res.set_content(error_response.dump(), "application/json");
+            return;
+        }
+
+        res.set_content(R"({"message":"View deleted successfully"})", "application/json");
+
+    } catch (const std::exception& e) {
+        spdlog::error("Delete view error: {}", e.what());
+        res.status = 500;
+        res.set_content(R"({"error":"Internal server error"})", "application/json");
+    }
+}
+
 }  // namespace smartbotic::webserver

+ 5 - 5
webserver/src/main.cpp

@@ -99,11 +99,11 @@ int main(int argc, char* argv[]) {
     spdlog::info("Signal handlers registered for SIGINT and SIGTERM");
 
     // Set up WebSocket message handler for real-time updates
-    server.SetWebSocketMessageHandler([](smartbotic::webserver::WebSocketSession& session, const std::string& message) {
-        spdlog::debug("WebSocket message received: {}", message);
-        // Echo back for now - in a real implementation, this would process
-        // commands and send appropriate responses
-        session.Send(R"({"type":"ack","message":"received"})");
+    server.SetWebSocketMessageHandler([&server](smartbotic::webserver::WsConnection* conn, const std::string& message) {
+        spdlog::debug("WebSocket message received from session {}: {}", conn->sessionId, message);
+        // Echo back by broadcasting for now - in a real implementation, this would process
+        // commands and send appropriate responses to specific sessions
+        server.BroadcastWebSocket(R"({"type":"ack","message":"received"})");
     });
 
     if (!server.Start()) {