| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- #include "settings_routes.hpp"
- #include "../app.hpp"
- #include "../stores/settings_store.hpp"
- #include "../stores/workspace_store.hpp"
- #include <smartbotic/microbit/auth/auth_middleware.hpp>
- #include <smartbotic/microbit/smtp/smtp_client.hpp>
- #include <smartbotic/microbit/callerai/callerai_client.hpp>
- #include <smartbotic/microbit/common/logging.hpp>
- #include <nlohmann/json.hpp>
- #include <spdlog/spdlog.h>
- #include <string>
- #include <vector>
- namespace smartbotic::microbit {
- using json = nlohmann::json;
- // Helper: check if user is owner of any workspace
- static bool isOwnerOfAnyWorkspace(App& app, const std::string& userId) {
- auto workspaces = app.workspaceStore()->listUserWorkspaces(userId);
- for (const auto& ws : workspaces) {
- auto member = app.workspaceStore()->getMember(ws.id, userId);
- if (member && member->role == "owner") {
- return true;
- }
- }
- return false;
- }
- // Helper: mask sensitive fields in settings
- static json maskSensitiveFields(const json& settings) {
- json masked = settings;
- // List of keys whose values should be masked
- static const std::vector<std::string> sensitiveKeys = {
- "smtp_password", "callerai_api_key", "api_key", "secret", "password"
- };
- for (const auto& key : sensitiveKeys) {
- if (masked.contains(key) && masked[key].is_string()) {
- std::string val = masked[key].get<std::string>();
- if (val.size() > 4) {
- masked[key] = std::string(val.size() - 4, '*') + val.substr(val.size() - 4);
- } else if (!val.empty()) {
- masked[key] = "****";
- }
- }
- }
- return masked;
- }
- void setupSettingsRoutes(httplib::Server& svr, App& app) {
- // GET /api/v1/settings -- get global settings (owner of any workspace)
- svr.Get("/api/v1/settings", [&app](const httplib::Request& req, httplib::Response& res) {
- try {
- auto authCtx = app.authMiddleware()->getAuthContext();
- if (!authCtx) {
- res.status = 401;
- res.set_content(R"({"error":"Unauthorized"})", "application/json");
- return;
- }
- if (!isOwnerOfAnyWorkspace(app, authCtx->userId)) {
- res.status = 403;
- res.set_content(json{{"error", "Forbidden: requires owner role in at least one workspace"}}.dump(), "application/json");
- return;
- }
- auto settings = app.settingsStore()->getGlobalSettings();
- json settingsJson = settings.value_or(json::object());
- // Mask sensitive fields before returning
- json masked = maskSensitiveFields(settingsJson);
- res.status = 200;
- res.set_content(json{{"settings", masked}}.dump(), "application/json");
- } catch (const std::exception& e) {
- spdlog::error("Get settings error: {}", e.what());
- res.status = 500;
- res.set_content(json{{"error", "Internal server error"}}.dump(), "application/json");
- }
- });
- // PUT /api/v1/settings -- update global settings (owner)
- svr.Put("/api/v1/settings", [&app](const httplib::Request& req, httplib::Response& res) {
- try {
- auto authCtx = app.authMiddleware()->getAuthContext();
- if (!authCtx) {
- res.status = 401;
- res.set_content(R"({"error":"Unauthorized"})", "application/json");
- return;
- }
- if (!isOwnerOfAnyWorkspace(app, authCtx->userId)) {
- res.status = 403;
- res.set_content(json{{"error", "Forbidden: requires owner role in at least one workspace"}}.dump(), "application/json");
- return;
- }
- auto body = json::parse(req.body);
- bool updated = app.settingsStore()->updateGlobalSettings(body);
- if (!updated) {
- res.status = 500;
- res.set_content(json{{"error", "Failed to update settings"}}.dump(), "application/json");
- return;
- }
- // Return the updated (masked) settings
- auto settings = app.settingsStore()->getGlobalSettings();
- json settingsJson = settings.value_or(json::object());
- json masked = maskSensitiveFields(settingsJson);
- res.status = 200;
- res.set_content(json{{"settings", masked}}.dump(), "application/json");
- } catch (const json::parse_error& e) {
- res.status = 400;
- res.set_content(json{{"error", "Invalid JSON"}}.dump(), "application/json");
- } catch (const std::invalid_argument& e) {
- res.status = 400;
- res.set_content(json{{"error", e.what()}}.dump(), "application/json");
- } catch (const std::exception& e) {
- spdlog::error("Update settings error: {}", e.what());
- res.status = 500;
- res.set_content(json{{"error", "Internal server error"}}.dump(), "application/json");
- }
- });
- // POST /api/v1/settings/test-smtp -- test SMTP connection (owner)
- svr.Post("/api/v1/settings/test-smtp", [&app](const httplib::Request& req, httplib::Response& res) {
- try {
- auto authCtx = app.authMiddleware()->getAuthContext();
- if (!authCtx) {
- res.status = 401;
- res.set_content(R"({"error":"Unauthorized"})", "application/json");
- return;
- }
- if (!isOwnerOfAnyWorkspace(app, authCtx->userId)) {
- res.status = 403;
- res.set_content(json{{"error", "Forbidden: requires owner role in at least one workspace"}}.dump(), "application/json");
- return;
- }
- if (!app.smtpClient()) {
- throw std::invalid_argument("SMTP client is not configured");
- }
- bool success = app.smtpClient()->testConnection();
- if (success) {
- res.status = 200;
- res.set_content(json{{"message", "SMTP connection successful"}}.dump(), "application/json");
- } else {
- res.status = 500;
- res.set_content(json{{"error", "SMTP connection failed"}}.dump(), "application/json");
- }
- } catch (const std::invalid_argument& e) {
- res.status = 400;
- res.set_content(json{{"error", e.what()}}.dump(), "application/json");
- } catch (const std::exception& e) {
- spdlog::error("Test SMTP error: {}", e.what());
- res.status = 500;
- res.set_content(json{{"error", "Internal server error"}}.dump(), "application/json");
- }
- });
- // POST /api/v1/settings/test-callerai -- test CallerAI connection (owner)
- svr.Post("/api/v1/settings/test-callerai", [&app](const httplib::Request& req, httplib::Response& res) {
- try {
- auto authCtx = app.authMiddleware()->getAuthContext();
- if (!authCtx) {
- res.status = 401;
- res.set_content(R"({"error":"Unauthorized"})", "application/json");
- return;
- }
- if (!isOwnerOfAnyWorkspace(app, authCtx->userId)) {
- res.status = 403;
- res.set_content(json{{"error", "Forbidden: requires owner role in at least one workspace"}}.dump(), "application/json");
- return;
- }
- if (!app.calleraiClient()) {
- throw std::invalid_argument("CallerAI client is not configured");
- }
- bool success = app.calleraiClient()->testConnection();
- if (success) {
- res.status = 200;
- res.set_content(json{{"message", "CallerAI connection successful"}}.dump(), "application/json");
- } else {
- res.status = 500;
- res.set_content(json{{"error", "CallerAI connection failed"}}.dump(), "application/json");
- }
- } catch (const std::invalid_argument& e) {
- res.status = 400;
- res.set_content(json{{"error", e.what()}}.dump(), "application/json");
- } catch (const std::exception& e) {
- spdlog::error("Test CallerAI error: {}", e.what());
- res.status = 500;
- res.set_content(json{{"error", "Internal server error"}}.dump(), "application/json");
- }
- });
- }
- } // namespace smartbotic::microbit
|