| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- #include "errors.hpp"
- #include "json_http.hpp"
- #include "server.hpp"
- namespace svapi {
- void registerSettingsRoutes(ApiServer& s) {
- auto& svr = s.raw(); ServerDeps* d = &s.deps();
- svr.Get("/api/v1/settings", [d](const httplib::Request& req, httplib::Response& res) {
- requireAdmin(requireKey(*d, req));
- auto snap = d->settings.snapshot();
- nlohmann::json j = snap->toJson();
- bool keySet = !snap->openaiApiKey.empty();
- j.erase("openai_api_key");
- j["openai_api_key_set"] = keySet;
- bool captchaSecretSet = !snap->captchaSecret.empty();
- j.erase("captcha_secret");
- j["captcha_secret_set"] = captchaSecretSet;
- sendJson(res, 200, j);
- });
- svr.Put("/api/v1/settings", [d](const httplib::Request& req, httplib::Response& res) {
- requireAdmin(requireKey(*d, req));
- auto body = bodyJson(req);
- // Start from the current snapshot (preserve unspecified fields).
- Settings updated = *d->settings.snapshot();
- if (body.contains("openai_api_base"))
- updated.openaiApiBase = body["openai_api_base"].get<std::string>();
- // Only overwrite the key if a non-empty string is supplied.
- if (body.contains("openai_api_key")) {
- std::string k = body["openai_api_key"].get<std::string>();
- if (!k.empty()) updated.openaiApiKey = k;
- }
- if (body.contains("default_embedding_model"))
- updated.defaultEmbeddingModel = body["default_embedding_model"].get<std::string>();
- if (body.contains("cors_origins") && body["cors_origins"].is_array())
- updated.corsOrigins = body["cors_origins"].get<std::vector<std::string>>();
- if (body.contains("session_ttl_minutes"))
- updated.sessionTtlMinutes = body["session_ttl_minutes"].get<uint32_t>();
- if (body.contains("webui_enabled"))
- updated.webuiEnabled = body["webui_enabled"].get<bool>();
- if (body.contains("default_project"))
- updated.defaultProject = body["default_project"].get<std::string>();
- if (body.contains("embedding_connect_timeout_sec"))
- updated.embeddingConnectTimeoutSec = body["embedding_connect_timeout_sec"].get<uint32_t>();
- if (body.contains("embedding_read_timeout_sec"))
- updated.embeddingReadTimeoutSec = body["embedding_read_timeout_sec"].get<uint32_t>();
- if (body.contains("embedding_cache_size"))
- updated.embeddingCacheSize = body["embedding_cache_size"].get<uint32_t>();
- if (body.contains("embedding_cache_ttl_sec"))
- updated.embeddingCacheTtlSec = body["embedding_cache_ttl_sec"].get<uint32_t>();
- if (body.contains("embedding_cache_max_bytes"))
- updated.embeddingCacheMaxBytes = body["embedding_cache_max_bytes"].get<uint64_t>();
- if (body.contains("embedding_cache_normalize"))
- updated.embeddingCacheNormalize = body["embedding_cache_normalize"].get<bool>();
- if (body.contains("embedding_client_pool_size"))
- updated.embeddingClientPoolSize = body["embedding_client_pool_size"].get<uint32_t>();
- if (body.contains("captcha_provider"))
- updated.captchaProvider = body["captcha_provider"].get<std::string>();
- // Only overwrite the secret if a non-empty string is supplied (mirror openai_api_key handling).
- if (body.contains("captcha_secret")) {
- std::string cs = body["captcha_secret"].get<std::string>();
- if (!cs.empty()) updated.captchaSecret = cs;
- }
- if (body.contains("captcha_verify_url"))
- updated.captchaVerifyUrl = body["captcha_verify_url"].get<std::string>();
- d->settings.save(updated);
- sendJson(res, 200, {{"ok", true}});
- });
- // Server-global read-only lock (not per project). GET reflects the DB's own
- // authoritative state - the DB can also enter read-only by itself after a
- // failed/degraded recovery, not only via an operator's PUT here, so `reason`
- // (when non-empty) tells the caller which it was.
- svr.Get(R"(/api/v1/admin/readonly)", [d](const httplib::Request& req, httplib::Response& res) {
- requireAdmin(requireKey(*d, req));
- auto st = d->db.client().getReadOnlyStatus();
- nlohmann::json j{{"readonly", st.readOnly}, {"scope", "server"}};
- if (!st.reason.empty()) j["reason"] = st.reason;
- sendJson(res, 200, j);
- });
- svr.Put(R"(/api/v1/admin/readonly)", [d](const httplib::Request& req, httplib::Response& res) {
- requireAdmin(requireKey(*d, req));
- auto body = bodyJson(req);
- if (!body.is_object() || !body.contains("readonly") || !body["readonly"].is_boolean())
- throw ApiError(ErrCode::Unprocessable, "validation", "readonly (boolean) is required");
- const bool ro = body["readonly"].get<bool>();
- if (!d->db.client().setReadOnly(ro))
- throw ApiError(ErrCode::Unavailable, "db_error", "failed to set read-only mode");
- auto st = d->db.client().getReadOnlyStatus();
- nlohmann::json j{{"readonly", st.readOnly}, {"scope", "server"}};
- if (!st.reason.empty()) j["reason"] = st.reason;
- sendJson(res, 200, j);
- });
- }
- } // namespace svapi
|