documents.cpp 4.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. #include "collection_registry.hpp"
  2. #include "errors.hpp"
  3. #include "filters.hpp"
  4. #include "json_http.hpp"
  5. #include "server.hpp"
  6. namespace svapi {
  7. namespace {
  8. // Authorize + verify the collection exists; returns the qualified collection name.
  9. std::string scoped(ServerDeps* d, const httplib::Request& req, int projIdx, int collIdx, KeyOp op) {
  10. ApiKey k = requireKey(*d, req);
  11. std::string project = req.matches[projIdx], name = req.matches[collIdx];
  12. requireCapability(*d, k, req, project, name, op);
  13. if (!d->registry.get(project, name)) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
  14. return qualify(project, name);
  15. }
  16. }
  17. void registerDocumentRoutes(ApiServer& s) {
  18. auto& svr = s.raw(); ServerDeps* d = &s.deps();
  19. svr.Post(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents)", [d](const httplib::Request& req, httplib::Response& res) {
  20. std::string c = scoped(d, req, 1, 2, KeyOp::Insert);
  21. auto body = bodyJson(req);
  22. std::string id = body.value("id", "");
  23. nlohmann::json data = body.contains("data") ? body["data"] : body;
  24. if (data.is_object() && data.contains("id")) data.erase("id");
  25. std::string newId = d->db.client().insert(c, data, id);
  26. if (newId.empty()) throw ApiError(ErrCode::Unavailable, "db_error", "insert failed");
  27. sendJson(res, 201, {{"id", newId}});
  28. });
  29. svr.Get(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
  30. std::string c = scoped(d, req, 1, 2, KeyOp::Read);
  31. auto doc = d->db.client().get(c, req.matches[3]);
  32. if (!doc) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
  33. sendJson(res, 200, *doc);
  34. });
  35. svr.Get(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents)", [d](const httplib::Request& req, httplib::Response& res) {
  36. std::string c = scoped(d, req, 1, 2, KeyOp::List);
  37. smartbotic::database::Client::QueryOptions opts;
  38. auto range = req.params.equal_range("filter");
  39. for (auto it = range.first; it != range.second; ++it) opts.filters.push_back(parseFilter(it->second));
  40. auto parseUInt = [](const std::string& v, const char* name) -> uint32_t {
  41. try { return (uint32_t)std::stoul(v); }
  42. catch (...) { throw ApiError(ErrCode::Unprocessable, "validation", std::string(name) + " must be a non-negative integer"); }
  43. };
  44. if (req.has_param("limit")) opts.limit = parseUInt(req.get_param_value("limit"), "limit");
  45. if (req.has_param("offset")) opts.offset = parseUInt(req.get_param_value("offset"), "offset");
  46. if (req.has_param("sort")) opts.sortField = req.get_param_value("sort");
  47. if (req.has_param("desc")) opts.sortDescending = (req.get_param_value("desc") == "true");
  48. auto docs = d->db.client().find(c, opts);
  49. sendJson(res, 200, {{"documents", docs}, {"count", docs.size()}});
  50. });
  51. svr.Patch(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
  52. std::string c = scoped(d, req, 1, 2, KeyOp::Update); std::string id = req.matches[3];
  53. if (!d->db.client().exists(c, id)) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
  54. uint64_t v = d->db.client().patch(c, id, bodyJson(req));
  55. if (v == 0) throw ApiError(ErrCode::Unavailable, "db_error", "patch failed");
  56. sendJson(res, 200, {{"id", id}, {"version", v}});
  57. });
  58. svr.Put(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
  59. std::string c = scoped(d, req, 1, 2, KeyOp::Update); std::string id = req.matches[3];
  60. if (!d->db.client().exists(c, id)) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
  61. if (!d->db.client().update(c, id, bodyJson(req))) throw ApiError(ErrCode::Unavailable, "db_error", "update failed");
  62. sendJson(res, 200, {{"id", id}});
  63. });
  64. svr.Delete(R"(/api/v1/projects/([^/]+)/collections/([^/]+)/documents/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
  65. std::string c = scoped(d, req, 1, 2, KeyOp::Delete); std::string id = req.matches[3];
  66. if (!d->db.client().remove(c, id)) throw ApiError(ErrCode::NotFound, "not_found", "no such document");
  67. sendJson(res, 200, {{"deleted", id}});
  68. });
  69. }
  70. }