|
|
@@ -1,2 +1,72 @@
|
|
|
+#include "collection_registry.hpp"
|
|
|
+#include "errors.hpp"
|
|
|
+#include "filters.hpp"
|
|
|
+#include "json_http.hpp"
|
|
|
#include "server.hpp"
|
|
|
-namespace svapi { void registerDocumentRoutes(ApiServer&) {} }
|
|
|
+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) {
|
|
|
+ ApiKey k = requireKey(*d, req);
|
|
|
+ std::string project = req.matches[projIdx], name = req.matches[collIdx];
|
|
|
+ requireProjectAccess(k, project);
|
|
|
+ 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);
|
|
|
+ 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);
|
|
|
+ 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);
|
|
|
+ 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));
|
|
|
+ if (req.has_param("limit")) opts.limit = (uint32_t)std::stoul(req.get_param_value("limit"));
|
|
|
+ if (req.has_param("offset")) opts.offset = (uint32_t)std::stoul(req.get_param_value("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); 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); 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); 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}});
|
|
|
+ });
|
|
|
+}
|
|
|
+}
|