| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576 |
- #include "collection_registry.hpp"
- #include "errors.hpp"
- #include "filters.hpp"
- #include "json_http.hpp"
- #include "server.hpp"
- namespace svapi {
- namespace {
- // Authorize + verify the collection exists; returns the qualified collection name.
- std::string scoped(ServerDeps* d, const httplib::Request& req, int projIdx, int collIdx, KeyOp op) {
- ApiKey k = requireKey(*d, req);
- std::string project = req.matches[projIdx], name = req.matches[collIdx];
- requireCapability(*d, k, req, project, name, op);
- if (!d->registry.get(project, name)) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
- return qualify(project, name);
- }
- }
- void registerDocumentRoutes(ApiServer& s) {
- auto& svr = s.raw(); ServerDeps* d = &s.deps();
- svr.Post(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents)", [d](const httplib::Request& req, httplib::Response& res) {
- std::string c = scoped(d, req, 1, 2, KeyOp::Insert);
- auto body = bodyJson(req);
- std::string id = body.value("id", "");
- nlohmann::json data = body.contains("data") ? body["data"] : body;
- if (data.is_object() && data.contains("id")) data.erase("id");
- std::string newId = d->db.client().insert(c, data, id);
- if (newId.empty()) throw ApiError(ErrCode::Unavailable, "db_error", "insert failed");
- sendJson(res, 201, {{"id", newId}});
- });
- svr.Get(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
- std::string c = scoped(d, req, 1, 2, KeyOp::Read);
- auto doc = d->db.client().get(c, req.matches[3]);
- if (!doc) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
- sendJson(res, 200, *doc);
- });
- svr.Get(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents)", [d](const httplib::Request& req, httplib::Response& res) {
- std::string c = scoped(d, req, 1, 2, KeyOp::List);
- smartbotic::database::Client::QueryOptions opts;
- auto range = req.params.equal_range("filter");
- for (auto it = range.first; it != range.second; ++it) opts.filters.push_back(parseFilter(it->second));
- auto parseUInt = [](const std::string& v, const char* name) -> uint32_t {
- try { return (uint32_t)std::stoul(v); }
- catch (...) { throw ApiError(ErrCode::Unprocessable, "validation", std::string(name) + " must be a non-negative integer"); }
- };
- if (req.has_param("limit")) opts.limit = parseUInt(req.get_param_value("limit"), "limit");
- if (req.has_param("offset")) opts.offset = parseUInt(req.get_param_value("offset"), "offset");
- if (req.has_param("sort")) opts.sortField = req.get_param_value("sort");
- if (req.has_param("desc")) opts.sortDescending = (req.get_param_value("desc") == "true");
- auto docs = d->db.client().find(c, opts);
- sendJson(res, 200, {{"documents", docs}, {"count", docs.size()}});
- });
- 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];
- 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));
- if (v == 0) throw ApiError(ErrCode::Unavailable, "db_error", "patch failed");
- sendJson(res, 200, {{"id", id}, {"version", v}});
- });
- svr.Put(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];
- if (!d->db.client().exists(c, id)) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
- if (!d->db.client().update(c, id, bodyJson(req))) throw ApiError(ErrCode::Unavailable, "db_error", "update failed");
- sendJson(res, 200, {{"id", id}});
- });
- svr.Delete(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
- std::string c = scoped(d, req, 1, 2, KeyOp::Delete); std::string id = req.matches[3];
- if (!d->db.client().remove(c, id)) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
- sendJson(res, 200, {{"deleted", id}});
- });
- }
- }
|