| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #include "app.hpp"
- #include "stores/user_store.hpp"
- #include "stores/workspace_store.hpp"
- #include "stores/invitation_store.hpp"
- #include "stores/assistant_store.hpp"
- #include "stores/calendar_store.hpp"
- #include "stores/settings_store.hpp"
- #include "services/auth_service.hpp"
- #include "services/workspace_service.hpp"
- #include "services/invitation_service.hpp"
- #include "services/assistant_service.hpp"
- #include "services/calendar_service.hpp"
- #include "routes/auth_routes.hpp"
- #include "routes/user_routes.hpp"
- #include "routes/workspace_routes.hpp"
- #include "routes/invitation_routes.hpp"
- #include "routes/assistant_routes.hpp"
- #include "routes/calendar_routes.hpp"
- #include "routes/settings_routes.hpp"
- #include <smartbotic/microbit/common/config_loader.hpp>
- #include <spdlog/spdlog.h>
- #include <filesystem>
- #include <fstream>
- namespace smartbotic::microbit {
- App::App(const json& config) : config_(config) {
- bindAddress_ = ConfigLoader::get<std::string>(config_, "http.bind_address", "0.0.0.0");
- httpPort_ = ConfigLoader::get<uint16_t>(config_, "http.port", 8090);
- staticFilesPath_ = ConfigLoader::get<std::string>(config_, "http.static_files.path", "webui/dist");
- }
- App::~App() {
- stop();
- }
- void App::connectDatabase() {
- smartbotic::database::Client::Config dbConfig;
- dbConfig.address = ConfigLoader::get<std::string>(config_, "database.rpc_address", "localhost:9004");
- dbConfig.timeoutMs = ConfigLoader::get<uint32_t>(config_, "database.timeout_ms", 5000);
- dbConfig.maxRetries = ConfigLoader::get<uint32_t>(config_, "database.max_retries", 3);
- db_ = std::make_unique<smartbotic::database::Client>(dbConfig);
- if (!db_->connect()) {
- throw std::runtime_error("Failed to connect to database at " + dbConfig.address);
- }
- spdlog::info("Connected to database at {}", dbConfig.address);
- }
- void App::initializeComponents() {
- // Auth middleware
- auth::AuthConfig authConfig;
- authConfig.enabled = ConfigLoader::get<bool>(config_, "auth.enabled", true);
- authConfig.jwtSecret = ConfigLoader::get<std::string>(config_, "auth.jwt_secret", "");
- authConfig.accessTokenLifetimeSec = ConfigLoader::get<uint32_t>(config_, "auth.access_token_lifetime_sec", 900);
- authConfig.refreshTokenLifetimeSec = ConfigLoader::get<uint32_t>(config_, "auth.refresh_token_lifetime_sec", 604800);
- authConfig.minPasswordLength = ConfigLoader::get<uint32_t>(config_, "auth.password_policy.min_length", 8);
- authConfig.requireNumber = ConfigLoader::get<bool>(config_, "auth.password_policy.require_number", true);
- authConfig.requireSpecial = ConfigLoader::get<bool>(config_, "auth.password_policy.require_special", false);
- if (authConfig.jwtSecret.empty() || authConfig.jwtSecret == "change-me-in-production") {
- spdlog::warn("JWT secret not configured or using default. Set JWT_SECRET environment variable.");
- }
- authMiddleware_ = std::make_unique<auth::AuthMiddleware>(authConfig);
- // SMTP client
- smtp::SmtpConfig smtpConfig;
- smtpConfig.host = ConfigLoader::get<std::string>(config_, "smtp.host", "");
- smtpConfig.port = ConfigLoader::get<uint16_t>(config_, "smtp.port", 587);
- smtpConfig.username = ConfigLoader::get<std::string>(config_, "smtp.username", "");
- smtpConfig.password = ConfigLoader::get<std::string>(config_, "smtp.password", "");
- smtpConfig.fromAddress = ConfigLoader::get<std::string>(config_, "smtp.from_address", "noreply@smartbotics.ai");
- smtpConfig.fromName = ConfigLoader::get<std::string>(config_, "smtp.from_name", "Smartbotic");
- smtpConfig.useTls = ConfigLoader::get<bool>(config_, "smtp.use_tls", true);
- smtpClient_ = std::make_unique<smtp::SmtpClient>(smtpConfig);
- // CallerAI client
- callerai::CallerAIConfig calleraiConfig;
- calleraiConfig.apiUrl = ConfigLoader::get<std::string>(config_, "callerai.api_url", "http://localhost:8080");
- calleraiConfig.apiKey = ConfigLoader::get<std::string>(config_, "callerai.api_key", "");
- calleraiConfig.timeoutSec = ConfigLoader::get<uint32_t>(config_, "callerai.timeout_sec", 30);
- calleraiClient_ = std::make_unique<callerai::CallerAIClient>(calleraiConfig);
- // Stores
- userStore_ = std::make_unique<UserStore>(db_.get());
- workspaceStore_ = std::make_unique<WorkspaceStore>(db_.get());
- invitationStore_ = std::make_unique<InvitationStore>(db_.get());
- assistantStore_ = std::make_unique<AssistantStore>(db_.get());
- calendarStore_ = std::make_unique<CalendarStore>(db_.get());
- settingsStore_ = std::make_unique<SettingsStore>(db_.get());
- // Services
- authService_ = std::make_unique<AuthService>(userStore_.get(), authMiddleware_.get());
- workspaceService_ = std::make_unique<WorkspaceService>(workspaceStore_.get());
- invitationService_ = std::make_unique<InvitationService>(
- invitationStore_.get(), workspaceStore_.get(), userStore_.get(), smtpClient_.get());
- assistantService_ = std::make_unique<AssistantService>(assistantStore_.get(), calleraiClient_.get());
- calendarService_ = std::make_unique<CalendarService>(calendarStore_.get());
- }
- void App::setupCors(httplib::Server& svr) {
- bool corsEnabled = ConfigLoader::get<bool>(config_, "cors.enabled", true);
- if (!corsEnabled) return;
- svr.Options(R"(/api/.*)", [](const httplib::Request&, httplib::Response& res) {
- res.set_header("Access-Control-Allow-Origin", "*");
- res.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
- res.set_header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With");
- res.set_header("Access-Control-Max-Age", "86400");
- res.status = 204;
- });
- svr.set_post_routing_handler([](const httplib::Request&, httplib::Response& res) {
- res.set_header("Access-Control-Allow-Origin", "*");
- });
- }
- void App::setupRoutes(httplib::Server& svr) {
- // Health check
- svr.Get("/api/v1/health", [](const httplib::Request&, httplib::Response& res) {
- res.set_content(R"({"status":"ok"})", "application/json");
- });
- // Auth middleware
- svr.set_pre_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
- if (req.method == "OPTIONS") {
- return httplib::Server::HandlerResponse::Unhandled;
- }
- if (req.path.find("/api/") != 0) {
- return httplib::Server::HandlerResponse::Unhandled;
- }
- if (authMiddleware_->isPublicPath(req.path)) {
- return httplib::Server::HandlerResponse::Unhandled;
- }
- if (!authMiddleware_->authenticate(req, res)) {
- return httplib::Server::HandlerResponse::Handled;
- }
- return httplib::Server::HandlerResponse::Unhandled;
- });
- // Register all routes
- setupAuthRoutes(svr, *this);
- setupUserRoutes(svr, *this);
- setupWorkspaceRoutes(svr, *this);
- setupInvitationRoutes(svr, *this);
- setupAssistantRoutes(svr, *this);
- setupCalendarRoutes(svr, *this);
- setupSettingsRoutes(svr, *this);
- }
- void App::start() {
- if (running_) return;
- spdlog::info("Starting Smartbotic MicroBit v{}", "0.1.0");
- connectDatabase();
- initializeComponents();
- httpServer_ = std::make_unique<httplib::Server>();
- setupCors(*httpServer_);
- setupRoutes(*httpServer_);
- // Serve static files
- bool serveStatic = ConfigLoader::get<bool>(config_, "http.static_files.enabled", true);
- if (serveStatic && std::filesystem::exists(staticFilesPath_)) {
- // Disable caching for index.html so browser always gets the latest version
- httpServer_->set_file_request_handler([](const httplib::Request& req, httplib::Response& res) {
- if (req.path == "/" || req.path == "/index.html") {
- res.set_header("Cache-Control", "no-cache, no-store, must-revalidate");
- }
- });
- httpServer_->set_mount_point("/", staticFilesPath_);
- spdlog::info("Serving static files from {}", staticFilesPath_);
- // SPA fallback: serve index.html for non-API routes that don't match a file
- auto indexPath = std::filesystem::path(staticFilesPath_) / "index.html";
- if (std::filesystem::exists(indexPath)) {
- httpServer_->set_error_handler([indexPath](const httplib::Request& req, httplib::Response& res) {
- if (res.status == 404 && req.path.substr(0, 5) != "/api/") {
- std::ifstream ifs(indexPath);
- if (ifs) {
- std::string body((std::istreambuf_iterator<char>(ifs)),
- std::istreambuf_iterator<char>());
- res.set_content(body, "text/html");
- res.status = 200;
- }
- }
- });
- }
- }
- running_ = true;
- httpThread_ = std::thread([this]() {
- spdlog::info("HTTP server listening on {}:{}", bindAddress_, httpPort_);
- httpServer_->listen(bindAddress_, httpPort_);
- });
- }
- void App::stop() {
- if (!running_) return;
- running_ = false;
- if (httpServer_) {
- httpServer_->stop();
- }
- if (httpThread_.joinable()) {
- httpThread_.join();
- }
- spdlog::info("Smartbotic MicroBit stopped");
- }
- } // namespace smartbotic::microbit
|