app.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #include "app.hpp"
  2. #include "stores/user_store.hpp"
  3. #include "stores/workspace_store.hpp"
  4. #include "stores/invitation_store.hpp"
  5. #include "stores/assistant_store.hpp"
  6. #include "stores/calendar_store.hpp"
  7. #include "stores/settings_store.hpp"
  8. #include "services/auth_service.hpp"
  9. #include "services/workspace_service.hpp"
  10. #include "services/invitation_service.hpp"
  11. #include "services/assistant_service.hpp"
  12. #include "services/calendar_service.hpp"
  13. #include "routes/auth_routes.hpp"
  14. #include "routes/user_routes.hpp"
  15. #include "routes/workspace_routes.hpp"
  16. #include "routes/invitation_routes.hpp"
  17. #include "routes/assistant_routes.hpp"
  18. #include "routes/calendar_routes.hpp"
  19. #include "routes/settings_routes.hpp"
  20. #include <smartbotic/microbit/common/config_loader.hpp>
  21. #include <spdlog/spdlog.h>
  22. #include <filesystem>
  23. #include <fstream>
  24. namespace smartbotic::microbit {
  25. App::App(const json& config) : config_(config) {
  26. bindAddress_ = ConfigLoader::get<std::string>(config_, "http.bind_address", "0.0.0.0");
  27. httpPort_ = ConfigLoader::get<uint16_t>(config_, "http.port", 8090);
  28. staticFilesPath_ = ConfigLoader::get<std::string>(config_, "http.static_files.path", "webui/dist");
  29. }
  30. App::~App() {
  31. stop();
  32. }
  33. void App::connectDatabase() {
  34. smartbotic::database::Client::Config dbConfig;
  35. dbConfig.address = ConfigLoader::get<std::string>(config_, "database.rpc_address", "localhost:9004");
  36. dbConfig.timeoutMs = ConfigLoader::get<uint32_t>(config_, "database.timeout_ms", 5000);
  37. dbConfig.maxRetries = ConfigLoader::get<uint32_t>(config_, "database.max_retries", 3);
  38. db_ = std::make_unique<smartbotic::database::Client>(dbConfig);
  39. if (!db_->connect()) {
  40. throw std::runtime_error("Failed to connect to database at " + dbConfig.address);
  41. }
  42. spdlog::info("Connected to database at {}", dbConfig.address);
  43. }
  44. void App::initializeComponents() {
  45. // Auth middleware
  46. auth::AuthConfig authConfig;
  47. authConfig.enabled = ConfigLoader::get<bool>(config_, "auth.enabled", true);
  48. authConfig.jwtSecret = ConfigLoader::get<std::string>(config_, "auth.jwt_secret", "");
  49. authConfig.accessTokenLifetimeSec = ConfigLoader::get<uint32_t>(config_, "auth.access_token_lifetime_sec", 900);
  50. authConfig.refreshTokenLifetimeSec = ConfigLoader::get<uint32_t>(config_, "auth.refresh_token_lifetime_sec", 604800);
  51. authConfig.minPasswordLength = ConfigLoader::get<uint32_t>(config_, "auth.password_policy.min_length", 8);
  52. authConfig.requireNumber = ConfigLoader::get<bool>(config_, "auth.password_policy.require_number", true);
  53. authConfig.requireSpecial = ConfigLoader::get<bool>(config_, "auth.password_policy.require_special", false);
  54. if (authConfig.jwtSecret.empty() || authConfig.jwtSecret == "change-me-in-production") {
  55. spdlog::warn("JWT secret not configured or using default. Set JWT_SECRET environment variable.");
  56. }
  57. authMiddleware_ = std::make_unique<auth::AuthMiddleware>(authConfig);
  58. // SMTP client
  59. smtp::SmtpConfig smtpConfig;
  60. smtpConfig.host = ConfigLoader::get<std::string>(config_, "smtp.host", "");
  61. smtpConfig.port = ConfigLoader::get<uint16_t>(config_, "smtp.port", 587);
  62. smtpConfig.username = ConfigLoader::get<std::string>(config_, "smtp.username", "");
  63. smtpConfig.password = ConfigLoader::get<std::string>(config_, "smtp.password", "");
  64. smtpConfig.fromAddress = ConfigLoader::get<std::string>(config_, "smtp.from_address", "noreply@smartbotics.ai");
  65. smtpConfig.fromName = ConfigLoader::get<std::string>(config_, "smtp.from_name", "Smartbotic");
  66. smtpConfig.useTls = ConfigLoader::get<bool>(config_, "smtp.use_tls", true);
  67. smtpClient_ = std::make_unique<smtp::SmtpClient>(smtpConfig);
  68. // CallerAI client
  69. callerai::CallerAIConfig calleraiConfig;
  70. calleraiConfig.apiUrl = ConfigLoader::get<std::string>(config_, "callerai.api_url", "http://localhost:8080");
  71. calleraiConfig.apiKey = ConfigLoader::get<std::string>(config_, "callerai.api_key", "");
  72. calleraiConfig.timeoutSec = ConfigLoader::get<uint32_t>(config_, "callerai.timeout_sec", 30);
  73. calleraiClient_ = std::make_unique<callerai::CallerAIClient>(calleraiConfig);
  74. // Stores
  75. userStore_ = std::make_unique<UserStore>(db_.get());
  76. workspaceStore_ = std::make_unique<WorkspaceStore>(db_.get());
  77. invitationStore_ = std::make_unique<InvitationStore>(db_.get());
  78. assistantStore_ = std::make_unique<AssistantStore>(db_.get());
  79. calendarStore_ = std::make_unique<CalendarStore>(db_.get());
  80. settingsStore_ = std::make_unique<SettingsStore>(db_.get());
  81. // Services
  82. authService_ = std::make_unique<AuthService>(userStore_.get(), authMiddleware_.get());
  83. workspaceService_ = std::make_unique<WorkspaceService>(workspaceStore_.get());
  84. invitationService_ = std::make_unique<InvitationService>(
  85. invitationStore_.get(), workspaceStore_.get(), userStore_.get(), smtpClient_.get());
  86. assistantService_ = std::make_unique<AssistantService>(assistantStore_.get(), calleraiClient_.get());
  87. calendarService_ = std::make_unique<CalendarService>(calendarStore_.get());
  88. }
  89. void App::setupCors(httplib::Server& svr) {
  90. bool corsEnabled = ConfigLoader::get<bool>(config_, "cors.enabled", true);
  91. if (!corsEnabled) return;
  92. svr.Options(R"(/api/.*)", [](const httplib::Request&, httplib::Response& res) {
  93. res.set_header("Access-Control-Allow-Origin", "*");
  94. res.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
  95. res.set_header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With");
  96. res.set_header("Access-Control-Max-Age", "86400");
  97. res.status = 204;
  98. });
  99. svr.set_post_routing_handler([](const httplib::Request&, httplib::Response& res) {
  100. res.set_header("Access-Control-Allow-Origin", "*");
  101. });
  102. }
  103. void App::setupRoutes(httplib::Server& svr) {
  104. // Health check
  105. svr.Get("/api/v1/health", [](const httplib::Request&, httplib::Response& res) {
  106. res.set_content(R"({"status":"ok"})", "application/json");
  107. });
  108. // Auth middleware
  109. svr.set_pre_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
  110. if (req.method == "OPTIONS") {
  111. return httplib::Server::HandlerResponse::Unhandled;
  112. }
  113. if (req.path.find("/api/") != 0) {
  114. return httplib::Server::HandlerResponse::Unhandled;
  115. }
  116. if (authMiddleware_->isPublicPath(req.path)) {
  117. return httplib::Server::HandlerResponse::Unhandled;
  118. }
  119. if (!authMiddleware_->authenticate(req, res)) {
  120. return httplib::Server::HandlerResponse::Handled;
  121. }
  122. return httplib::Server::HandlerResponse::Unhandled;
  123. });
  124. // Register all routes
  125. setupAuthRoutes(svr, *this);
  126. setupUserRoutes(svr, *this);
  127. setupWorkspaceRoutes(svr, *this);
  128. setupInvitationRoutes(svr, *this);
  129. setupAssistantRoutes(svr, *this);
  130. setupCalendarRoutes(svr, *this);
  131. setupSettingsRoutes(svr, *this);
  132. }
  133. void App::start() {
  134. if (running_) return;
  135. spdlog::info("Starting Smartbotic MicroBit v{}", "0.1.0");
  136. connectDatabase();
  137. initializeComponents();
  138. httpServer_ = std::make_unique<httplib::Server>();
  139. setupCors(*httpServer_);
  140. setupRoutes(*httpServer_);
  141. // Serve static files
  142. bool serveStatic = ConfigLoader::get<bool>(config_, "http.static_files.enabled", true);
  143. if (serveStatic && std::filesystem::exists(staticFilesPath_)) {
  144. // Disable caching for index.html so browser always gets the latest version
  145. httpServer_->set_file_request_handler([](const httplib::Request& req, httplib::Response& res) {
  146. if (req.path == "/" || req.path == "/index.html") {
  147. res.set_header("Cache-Control", "no-cache, no-store, must-revalidate");
  148. }
  149. });
  150. httpServer_->set_mount_point("/", staticFilesPath_);
  151. spdlog::info("Serving static files from {}", staticFilesPath_);
  152. // SPA fallback: serve index.html for non-API routes that don't match a file
  153. auto indexPath = std::filesystem::path(staticFilesPath_) / "index.html";
  154. if (std::filesystem::exists(indexPath)) {
  155. httpServer_->set_error_handler([indexPath](const httplib::Request& req, httplib::Response& res) {
  156. if (res.status == 404 && req.path.substr(0, 5) != "/api/") {
  157. std::ifstream ifs(indexPath);
  158. if (ifs) {
  159. std::string body((std::istreambuf_iterator<char>(ifs)),
  160. std::istreambuf_iterator<char>());
  161. res.set_content(body, "text/html");
  162. res.status = 200;
  163. }
  164. }
  165. });
  166. }
  167. }
  168. running_ = true;
  169. httpThread_ = std::thread([this]() {
  170. spdlog::info("HTTP server listening on {}:{}", bindAddress_, httpPort_);
  171. httpServer_->listen(bindAddress_, httpPort_);
  172. });
  173. }
  174. void App::stop() {
  175. if (!running_) return;
  176. running_ = false;
  177. if (httpServer_) {
  178. httpServer_->stop();
  179. }
  180. if (httpThread_.joinable()) {
  181. httpThread_.join();
  182. }
  183. spdlog::info("Smartbotic MicroBit stopped");
  184. }
  185. } // namespace smartbotic::microbit