| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 |
- #include "collection_registry.hpp"
- #include "errors.hpp"
- #include "json_http.hpp"
- #include "server.hpp"
- namespace svapi {
- void registerCollectionRoutes(ApiServer& s) {
- auto& svr = s.raw(); ServerDeps* d = &s.deps();
- svr.Post(R"(/api/v1/projects/([^/]+)/collections)", [d](const httplib::Request& req, httplib::Response& res) {
- ApiKey k = requireKey(*d, req); std::string project = req.matches[1];
- requireProjectAccess(k, project);
- d->db.ensureProject(project); d->registry.ensure(project);
- auto body = bodyJson(req);
- std::string name = body.value("name", ""), kind = body.value("kind", "json");
- if (name.empty()) throw ApiError(ErrCode::Unprocessable, "validation", "name is required");
- if (name.rfind('_', 0) == 0 || name.rfind("vectorapi_", 0) == 0)
- throw ApiError(ErrCode::Unprocessable, "validation", "name may not start with '_' or the reserved 'vectorapi_' prefix");
- if (kind != "json" && kind != "vector") throw ApiError(ErrCode::Unprocessable, "validation", "kind must be 'json' or 'vector'");
- uint32_t dim = 0; std::string model;
- if (kind == "vector") {
- if (!body.contains("vector_dimension")) throw ApiError(ErrCode::Unprocessable, "validation", "vector_dimension required for vector collections");
- dim = body.value("vector_dimension", 0u);
- if (dim == 0) throw ApiError(ErrCode::Unprocessable, "validation", "vector_dimension must be > 0");
- model = body.value("embedding_model", "");
- }
- if (d->registry.get(project, name)) throw ApiError(ErrCode::Unprocessable, "exists", "collection already exists");
- // Drop any stale DB-layer collection (e.g. left from a prior dropProject that didn't
- // fully clean up its data — DB v2.3.1 Stage F caveat).
- if (d->db.client().getCollectionInfo(qualify(project, name)))
- d->db.client().dropCollection(qualify(project, name));
- if (!d->db.client().createCollection(qualify(project, name), 0, false, 0, dim))
- throw ApiError(ErrCode::Unavailable, "db_error", "failed to create collection");
- d->registry.add(project, {name, kind, dim, model, 0});
- sendJson(res, 201, {{"name", name}, {"kind", kind}, {"vector_dimension", dim}});
- });
- svr.Get(R"(/api/v1/projects/([^/]+)/collections)", [d](const httplib::Request& req, httplib::Response& res) {
- ApiKey k = requireKey(*d, req); std::string project = req.matches[1];
- requireProjectAccess(k, project);
- nlohmann::json out = nlohmann::json::array();
- for (const auto& m : d->registry.list(project)) {
- nlohmann::json e = m.toJson();
- if (auto info = d->db.client().getCollectionInfo(qualify(project, m.name))) {
- e["document_count"] = info->documentCount; e["size_bytes"] = info->sizeBytes;
- }
- out.push_back(e);
- }
- sendJson(res, 200, {{"collections", out}});
- });
- svr.Get(R"(/api/v1/projects/([^/]+)/collections/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
- ApiKey k = requireKey(*d, req); std::string project = req.matches[1], name = req.matches[2];
- requireProjectAccess(k, project);
- auto m = d->registry.get(project, name);
- if (!m) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
- nlohmann::json e = m->toJson();
- if (auto info = d->db.client().getCollectionInfo(qualify(project, name))) {
- e["document_count"] = info->documentCount; e["size_bytes"] = info->sizeBytes;
- }
- sendJson(res, 200, e);
- });
- svr.Delete(R"(/api/v1/projects/([^/]+)/collections/([^/]+))", [d](const httplib::Request& req, httplib::Response& res) {
- ApiKey k = requireKey(*d, req); std::string project = req.matches[1], name = req.matches[2];
- requireProjectAccess(k, project);
- if (!d->registry.get(project, name)) throw ApiError(ErrCode::NotFound, "not_found", "no such collection");
- d->db.client().dropCollection(qualify(project, name));
- d->registry.remove(project, name);
- sendJson(res, 200, {{"deleted", name}});
- });
- }
- }
|