collections.cpp 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. #include "collection_registry.hpp"
  2. #include "errors.hpp"
  3. #include "json_http.hpp"
  4. #include "server.hpp"
  5. namespace svapi {
  6. void registerCollectionRoutes(ApiServer& s) {
  7. auto& svr = s.raw(); ServerDeps* d = &s.deps();
  8. svr.Post(R"(/api/v1/projects/([^/]+)/collections)", [d](const httplib::Request& req, httplib::Response& res) {
  9. ApiKey k = requireKey(*d, req); std::string project = req.matches[1];
  10. requireProjectAccess(k, project);
  11. d->db.ensureProject(project); d->registry.ensure(project);
  12. auto body = bodyJson(req);
  13. std::string name = body.value("name", ""), kind = body.value("kind", "json");
  14. if (name.empty()) throw ApiError(ErrCode::Unprocessable, "validation", "name is required");
  15. if (name.rfind('_', 0) == 0 || name.rfind("vectorapi_", 0) == 0)
  16. throw ApiError(ErrCode::Unprocessable, "validation", "name may not start with '_' or the reserved 'vectorapi_' prefix");
  17. if (kind != "json" && kind != "vector") throw ApiError(ErrCode::Unprocessable, "validation", "kind must be 'json' or 'vector'");
  18. uint32_t dim = 0; std::string model;
  19. if (kind == "vector") {
  20. if (!body.contains("vector_dimension")) throw ApiError(ErrCode::Unprocessable, "validation", "vector_dimension required for vector collections");
  21. dim = body.value("vector_dimension", 0u);
  22. if (dim == 0) throw ApiError(ErrCode::Unprocessable, "validation", "vector_dimension must be > 0");
  23. model = body.value("embedding_model", "");
  24. }
  25. if (d->registry.get(project, name)) throw ApiError(ErrCode::Unprocessable, "exists", "collection already exists");
  26. // Drop any stale DB-layer collection (e.g. left from a prior dropProject that didn't
  27. // fully clean up its data — DB v2.3.1 Stage F caveat).
  28. if (d->db.client().getCollectionInfo(qualify(project, name)))
  29. d->db.client().dropCollection(qualify(project, name));
  30. if (!d->db.client().createCollection(qualify(project, name), 0, false, 0, dim))
  31. throw ApiError(ErrCode::Unavailable, "db_error", "failed to create collection");
  32. d->registry.add(project, {name, kind, dim, model, 0});
  33. sendJson(res, 201, {{"name", name}, {"kind", kind}, {"vector_dimension", dim}});
  34. });
  35. svr.Get(R"(/api/v1/projects/([^/]+)/collections)", [d](const httplib::Request& req, httplib::Response& res) {
  36. ApiKey k = requireKey(*d, req); std::string project = req.matches[1];
  37. requireProjectAccess(k, project);
  38. nlohmann::json out = nlohmann::json::array();
  39. for (const auto& m : d->registry.list(project)) {
  40. nlohmann::json e = m.toJson();
  41. if (auto info = d->db.client().getCollectionInfo(qualify(project, m.name))) {
  42. e["document_count"] = info->documentCount; e["size_bytes"] = info->sizeBytes;
  43. }
  44. out.push_back(e);
  45. }
  46. sendJson(res, 200, {{"collections", out}});
  47. });
  48. svr.Get(R"(/api/v1/projects/([^/]+)/collections/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
  49. ApiKey k = requireKey(*d, req); std::string project = req.matches[1], name = req.matches[2];
  50. requireProjectAccess(k, project);
  51. auto m = d->registry.get(project, name);
  52. if (!m) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
  53. nlohmann::json e = m->toJson();
  54. if (auto info = d->db.client().getCollectionInfo(qualify(project, name))) {
  55. e["document_count"] = info->documentCount; e["size_bytes"] = info->sizeBytes;
  56. }
  57. sendJson(res, 200, e);
  58. });
  59. svr.Delete(R"(/api/v1/projects/([^/]+)/collections/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
  60. ApiKey k = requireKey(*d, req); std::string project = req.matches[1], name = req.matches[2];
  61. requireProjectAccess(k, project);
  62. if (!d->registry.get(project, name)) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
  63. d->db.client().dropCollection(qualify(project, name));
  64. d->registry.remove(project, name);
  65. sendJson(res, 200, {{"deleted", name}});
  66. });
  67. }
  68. }