|
@@ -3,6 +3,9 @@
|
|
|
#include "filters.hpp"
|
|
#include "filters.hpp"
|
|
|
#include "json_http.hpp"
|
|
#include "json_http.hpp"
|
|
|
#include "server.hpp"
|
|
#include "server.hpp"
|
|
|
|
|
+#include <cstdlib>
|
|
|
|
|
+#include <limits>
|
|
|
|
|
+#include <optional>
|
|
|
namespace svapi {
|
|
namespace svapi {
|
|
|
namespace {
|
|
namespace {
|
|
|
// Authorize + verify the collection exists; returns the qualified collection name.
|
|
// Authorize + verify the collection exists; returns the qualified collection name.
|
|
@@ -13,6 +16,19 @@ std::string scoped(ServerDeps* d, const httplib::Request& req, int projIdx, int
|
|
|
if (!d->registry.get(project, name)) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
|
|
if (!d->registry.get(project, name)) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
|
|
|
return qualify(project, name);
|
|
return qualify(project, name);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// Parses ?ttl_seconds=. Returns nullopt when absent — the caller must
|
|
|
|
|
+// distinguish "not supplied" (leave expiry alone) from 0 (clear the expiry).
|
|
|
|
|
+std::optional<uint32_t> ttlParam(const httplib::Request& req) {
|
|
|
|
|
+ if (!req.has_param("ttl_seconds")) return std::nullopt;
|
|
|
|
|
+ const std::string raw = req.get_param_value("ttl_seconds");
|
|
|
|
|
+ if (raw.empty() || raw.find_first_not_of("0123456789") != std::string::npos)
|
|
|
|
|
+ throw ApiError(ErrCode::Unprocessable, "validation", "ttl_seconds must be a non-negative integer");
|
|
|
|
|
+ unsigned long long wide = std::strtoull(raw.c_str(), nullptr, 10);
|
|
|
|
|
+ if (wide > std::numeric_limits<uint32_t>::max())
|
|
|
|
|
+ throw ApiError(ErrCode::Unprocessable, "validation", "ttl_seconds must be a non-negative integer");
|
|
|
|
|
+ return (uint32_t)wide;
|
|
|
|
|
+}
|
|
|
}
|
|
}
|
|
|
void registerDocumentRoutes(ApiServer& s) {
|
|
void registerDocumentRoutes(ApiServer& s) {
|
|
|
auto& svr = s.raw(); ServerDeps* d = &s.deps();
|
|
auto& svr = s.raw(); ServerDeps* d = &s.deps();
|
|
@@ -23,7 +39,8 @@ void registerDocumentRoutes(ApiServer& s) {
|
|
|
std::string id = body.value("id", "");
|
|
std::string id = body.value("id", "");
|
|
|
nlohmann::json data = body.contains("data") ? body["data"] : body;
|
|
nlohmann::json data = body.contains("data") ? body["data"] : body;
|
|
|
if (data.is_object() && data.contains("id")) data.erase("id");
|
|
if (data.is_object() && data.contains("id")) data.erase("id");
|
|
|
- std::string newId = d->db.client().insert(c, data, id);
|
|
|
|
|
|
|
+ const uint32_t ttl = ttlParam(req).value_or(0);
|
|
|
|
|
+ std::string newId = d->db.client().insert(c, data, id, ttl);
|
|
|
if (newId.empty()) throw ApiError(ErrCode::Unavailable, "db_error", "insert failed");
|
|
if (newId.empty()) throw ApiError(ErrCode::Unavailable, "db_error", "insert failed");
|
|
|
sendJson(res, 201, {{"id", newId}});
|
|
sendJson(res, 201, {{"id", newId}});
|
|
|
});
|
|
});
|
|
@@ -55,7 +72,10 @@ void registerDocumentRoutes(ApiServer& s) {
|
|
|
svr.Patch(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
|
|
svr.Patch(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
|
|
|
std::string c = scoped(d, req, 1, 2, KeyOp::Update); std::string id = req.matches[3];
|
|
std::string c = scoped(d, req, 1, 2, KeyOp::Update); std::string id = req.matches[3];
|
|
|
if (!d->db.client().exists(c, id)) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
|
|
if (!d->db.client().exists(c, id)) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
|
|
|
- uint64_t v = d->db.client().patch(c, id, bodyJson(req));
|
|
|
|
|
|
|
+ nlohmann::json patchBody = bodyJson(req);
|
|
|
|
|
+ const auto ttl = ttlParam(req);
|
|
|
|
|
+ uint64_t v = ttl ? d->db.client().patchWithTtl(c, id, patchBody, *ttl)
|
|
|
|
|
+ : d->db.client().patch(c, id, patchBody);
|
|
|
if (v == 0) throw ApiError(ErrCode::Unavailable, "db_error", "patch failed");
|
|
if (v == 0) throw ApiError(ErrCode::Unavailable, "db_error", "patch failed");
|
|
|
sendJson(res, 200, {{"id", id}, {"version", v}});
|
|
sendJson(res, 200, {{"id", id}, {"version", v}});
|
|
|
});
|
|
});
|