settings.cpp 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. #include "errors.hpp"
  2. #include "json_http.hpp"
  3. #include "server.hpp"
  4. namespace svapi {
  5. void registerSettingsRoutes(ApiServer& s) {
  6. auto& svr = s.raw(); ServerDeps* d = &s.deps();
  7. svr.Get("/api/v1/settings", [d](const httplib::Request& req, httplib::Response& res) {
  8. requireAdmin(requireKey(*d, req));
  9. auto snap = d->settings.snapshot();
  10. nlohmann::json j = snap->toJson();
  11. bool keySet = !snap->openaiApiKey.empty();
  12. j.erase("openai_api_key");
  13. j["openai_api_key_set"] = keySet;
  14. bool captchaSecretSet = !snap->captchaSecret.empty();
  15. j.erase("captcha_secret");
  16. j["captcha_secret_set"] = captchaSecretSet;
  17. sendJson(res, 200, j);
  18. });
  19. svr.Put("/api/v1/settings", [d](const httplib::Request& req, httplib::Response& res) {
  20. requireAdmin(requireKey(*d, req));
  21. auto body = bodyJson(req);
  22. // Start from the current snapshot (preserve unspecified fields).
  23. Settings updated = *d->settings.snapshot();
  24. if (body.contains("openai_api_base"))
  25. updated.openaiApiBase = body["openai_api_base"].get<std::string>();
  26. // Only overwrite the key if a non-empty string is supplied.
  27. if (body.contains("openai_api_key")) {
  28. std::string k = body["openai_api_key"].get<std::string>();
  29. if (!k.empty()) updated.openaiApiKey = k;
  30. }
  31. if (body.contains("default_embedding_model"))
  32. updated.defaultEmbeddingModel = body["default_embedding_model"].get<std::string>();
  33. if (body.contains("cors_origins") && body["cors_origins"].is_array())
  34. updated.corsOrigins = body["cors_origins"].get<std::vector<std::string>>();
  35. if (body.contains("session_ttl_minutes"))
  36. updated.sessionTtlMinutes = body["session_ttl_minutes"].get<uint32_t>();
  37. if (body.contains("webui_enabled"))
  38. updated.webuiEnabled = body["webui_enabled"].get<bool>();
  39. if (body.contains("default_project"))
  40. updated.defaultProject = body["default_project"].get<std::string>();
  41. if (body.contains("embedding_connect_timeout_sec"))
  42. updated.embeddingConnectTimeoutSec = body["embedding_connect_timeout_sec"].get<uint32_t>();
  43. if (body.contains("embedding_read_timeout_sec"))
  44. updated.embeddingReadTimeoutSec = body["embedding_read_timeout_sec"].get<uint32_t>();
  45. if (body.contains("embedding_cache_size"))
  46. updated.embeddingCacheSize = body["embedding_cache_size"].get<uint32_t>();
  47. if (body.contains("embedding_cache_ttl_sec"))
  48. updated.embeddingCacheTtlSec = body["embedding_cache_ttl_sec"].get<uint32_t>();
  49. if (body.contains("embedding_cache_max_bytes"))
  50. updated.embeddingCacheMaxBytes = body["embedding_cache_max_bytes"].get<uint64_t>();
  51. if (body.contains("embedding_cache_normalize"))
  52. updated.embeddingCacheNormalize = body["embedding_cache_normalize"].get<bool>();
  53. if (body.contains("embedding_client_pool_size"))
  54. updated.embeddingClientPoolSize = body["embedding_client_pool_size"].get<uint32_t>();
  55. if (body.contains("captcha_provider"))
  56. updated.captchaProvider = body["captcha_provider"].get<std::string>();
  57. // Only overwrite the secret if a non-empty string is supplied (mirror openai_api_key handling).
  58. if (body.contains("captcha_secret")) {
  59. std::string cs = body["captcha_secret"].get<std::string>();
  60. if (!cs.empty()) updated.captchaSecret = cs;
  61. }
  62. if (body.contains("captcha_verify_url"))
  63. updated.captchaVerifyUrl = body["captcha_verify_url"].get<std::string>();
  64. d->settings.save(updated);
  65. sendJson(res, 200, {{"ok", true}});
  66. });
  67. // Server-global read-only lock (not per project). GET reflects the DB's own
  68. // authoritative state - the DB can also enter read-only by itself after a
  69. // failed/degraded recovery, not only via an operator's PUT here, so `reason`
  70. // (when non-empty) tells the caller which it was.
  71. svr.Get(R"(/api/v1/admin/readonly)", [d](const httplib::Request& req, httplib::Response& res) {
  72. requireAdmin(requireKey(*d, req));
  73. auto st = d->db.client().getReadOnlyStatus();
  74. nlohmann::json j{{"readonly", st.readOnly}, {"scope", "server"}};
  75. if (!st.reason.empty()) j["reason"] = st.reason;
  76. sendJson(res, 200, j);
  77. });
  78. svr.Put(R"(/api/v1/admin/readonly)", [d](const httplib::Request& req, httplib::Response& res) {
  79. requireAdmin(requireKey(*d, req));
  80. auto body = bodyJson(req);
  81. if (!body.is_object() || !body.contains("readonly") || !body["readonly"].is_boolean())
  82. throw ApiError(ErrCode::Unprocessable, "validation", "readonly (boolean) is required");
  83. const bool ro = body["readonly"].get<bool>();
  84. if (!d->db.client().setReadOnly(ro))
  85. throw ApiError(ErrCode::Unavailable, "db_error", "failed to set read-only mode");
  86. auto st = d->db.client().getReadOnlyStatus();
  87. nlohmann::json j{{"readonly", st.readOnly}, {"scope", "server"}};
  88. if (!st.reason.empty()) j["reason"] = st.reason;
  89. sendJson(res, 200, j);
  90. });
  91. }
  92. } // namespace svapi