diff --git a/src/HttpController.cpp b/src/HttpController.cpp index f8914b6..b576216 100644 --- a/src/HttpController.cpp +++ b/src/HttpController.cpp @@ -159,6 +159,23 @@ void HttpController::deleteObject(const drogon::HttpRequestPtr &req, } } +void HttpController::deleteOldObjects(const drogon::HttpRequestPtr &req, + std::function &&callback) { + auto server = Server::getInstance(); + if (server) { + server->handle_delete_old_objects(req, std::move(callback)); + } else { + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setStatusCode(drogon::k500InternalServerError); + // Try to add security headers if server instance is available + auto srv = Server::getInstance(); + if (srv) { + srv->add_security_headers(resp); + } + callback(resp); + } +} + void HttpController::getStatus(const drogon::HttpRequestPtr &req, std::function &&callback) { auto resp = drogon::HttpResponse::newHttpResponse(); diff --git a/src/HttpController.hpp b/src/HttpController.hpp index ea1cb0e..f1ac8d5 100644 --- a/src/HttpController.hpp +++ b/src/HttpController.hpp @@ -17,6 +17,7 @@ public: ADD_METHOD_TO(HttpController::updateObject, "/update", {drogon::Put}); ADD_METHOD_TO(HttpController::getMetadata, "/meta/{1}", {drogon::Get}); ADD_METHOD_TO(HttpController::deleteObject, "/deleteobject", {drogon::Get}); + ADD_METHOD_TO(HttpController::deleteOldObjects, "/deleteoldobjects", {drogon::Get}); ADD_METHOD_TO(HttpController::getStatus, "/status", {drogon::Get}); ADD_METHOD_TO(HttpController::getObject, "/object/{1}", {drogon::Get}); ADD_METHOD_TO(HttpController::getRoot, "/", {drogon::Get}); @@ -53,7 +54,10 @@ public: void deleteObject(const drogon::HttpRequestPtr &req, std::function &&callback); - + + void deleteOldObjects(const drogon::HttpRequestPtr &req, + std::function &&callback); + void getStatus(const drogon::HttpRequestPtr &req, std::function &&callback); diff --git a/src/database.cpp b/src/database.cpp index b760533..d1be438 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -238,7 +238,7 @@ bool Database::get(const std::string& hash_or_labeltag, dbEntry& entry) { bool Database::list(std::vector& entries) { std::string sql = "SELECT hash, labeltags, metadata FROM objects;"; sqlite3_stmt* stmt; - + if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { return false; } @@ -249,7 +249,35 @@ bool Database::list(std::vector& entries) { entry.hash = reinterpret_cast(sqlite3_column_text(stmt, 0)); std::string labeltags_str = reinterpret_cast(sqlite3_column_text(stmt, 1)); std::string metadata_str = reinterpret_cast(sqlite3_column_text(stmt, 2)); - + + entry.labeltags = nlohmann::json::parse(labeltags_str).get>(); + entry.metadata = nlohmann::json::parse(metadata_str); + entries.push_back(entry); + } + + sqlite3_finalize(stmt); + return true; +} + +bool Database::list_by_label(const std::string& label, std::vector& entries) { + // Find all entries that have at least one labeltag starting with "label:" + std::string sql = "SELECT hash, labeltags, metadata FROM objects WHERE labeltags LIKE ?;"; + sqlite3_stmt* stmt; + + if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { + return false; + } + + std::string pattern = "%\"" + label + ":%"; + sqlite3_bind_text(stmt, 1, pattern.c_str(), -1, SQLITE_STATIC); + + entries.clear(); + while (sqlite3_step(stmt) == SQLITE_ROW) { + dbEntry entry; + entry.hash = reinterpret_cast(sqlite3_column_text(stmt, 0)); + std::string labeltags_str = reinterpret_cast(sqlite3_column_text(stmt, 1)); + std::string metadata_str = reinterpret_cast(sqlite3_column_text(stmt, 2)); + entry.labeltags = nlohmann::json::parse(labeltags_str).get>(); entry.metadata = nlohmann::json::parse(metadata_str); entries.push_back(entry); diff --git a/src/database.hpp b/src/database.hpp index f8a2e59..032e786 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -25,6 +25,7 @@ class Database { bool remove_by_hash(const std::string& hash); bool get(const std::string& hash_or_labeltag, dbEntry& entry); bool list(std::vector& entries); + bool list_by_label(const std::string& label, std::vector& entries); bool update_or_insert(const dbEntry& entry); private: std::filesystem::path path_; diff --git a/src/server.cpp b/src/server.cpp index a1d7b4b..9fe7634 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -6,6 +6,7 @@ #include // For seeding random number generator #include // For getAllKeys #include // For litecask values +#include // For handle_delete_old_objects #include // For std::runtime_error #include @@ -476,6 +477,94 @@ void Server::handle_delete_object(const drogon::HttpRequestPtr& req, std::functi callback(resp); } +void Server::handle_delete_old_objects(const drogon::HttpRequestPtr& req, std::function&& callback) { + auto resp = drogon::HttpResponse::newHttpResponse(); + + std::map params; + if (!validate_write_request(req, resp, {"label"}, params)) { + add_security_headers(resp); + callback(resp); + return; + } + + std::string label = params["label"]; + + // Get all entries for this label + std::vector entries; + if (!db_->list_by_label(label, entries)) { + resp->setStatusCode(drogon::k500InternalServerError); + nlohmann::json response = {{"result", "error"}, {"error", "Failed to list objects for label: " + label}}; + resp->setBody(response.dump()); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + add_security_headers(resp); + callback(resp); + return; + } + + if (entries.empty()) { + nlohmann::json response = {{"result", "success"}, {"deleted", 0}, {"message", "No objects found for label: " + label}}; + resp->setBody(response.dump()); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + add_security_headers(resp); + callback(resp); + return; + } + + // Find hashes that have at least one tag containing 'latest' for this label + std::set hashes_to_keep; + for (const auto& entry : entries) { + for (const auto& labeltag : entry.labeltags) { + // Check if this labeltag belongs to our label and contains 'latest' + if (labeltag.rfind(label + ":", 0) == 0) { + std::string tag = labeltag.substr(label.length() + 1); + if (tag.find("latest") != std::string::npos) { + hashes_to_keep.insert(entry.hash); + break; + } + } + } + } + + // Delete entries that don't have a 'latest' tag + int deleted_count = 0; + std::vector errors; + + for (const auto& entry : entries) { + if (hashes_to_keep.find(entry.hash) != hashes_to_keep.end()) { + continue; // Skip entries with 'latest' tags + } + + // Remove from database + if (!db_->remove_by_hash(entry.hash)) { + errors.push_back("Failed to remove database entry for hash: " + entry.hash); + continue; + } + + // Remove the file + std::filesystem::path file_path = config_.object_store_path / entry.hash; + if (std::filesystem::exists(file_path) && std::filesystem::is_regular_file(file_path)) { + try { + std::filesystem::remove(file_path); + } catch (const std::filesystem::filesystem_error& e) { + errors.push_back("Failed to delete file for hash " + entry.hash + ": " + e.what()); + } + } + + deleted_count++; + } + + nlohmann::json response; + if (errors.empty()) { + response = {{"result", "success"}, {"deleted", deleted_count}}; + } else { + response = {{"result", "partial"}, {"deleted", deleted_count}, {"errors", errors}}; + } + resp->setBody(response.dump()); + resp->setContentTypeCode(drogon::CT_APPLICATION_JSON); + add_security_headers(resp); + callback(resp); +} + void Server::handle_exists(const drogon::HttpRequestPtr& req, std::function&& callback, const std::string& key) { nlohmann::json response; dbEntry entry; diff --git a/src/server.hpp b/src/server.hpp index 54d612a..7387931 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -47,6 +47,7 @@ public: void handle_get_directory(const drogon::HttpRequestPtr& req, std::function&& callback); void handle_get_metadata(const drogon::HttpRequestPtr& req, std::function&& callback, const std::string& key); void handle_delete_object(const drogon::HttpRequestPtr& req, std::function&& callback); + void handle_delete_old_objects(const drogon::HttpRequestPtr& req, std::function&& callback); void handle_exists(const drogon::HttpRequestPtr& req, std::function&& callback, const std::string& key); void handle_cors_preflight(const drogon::HttpRequestPtr& req, std::function&& callback); void add_cors_headers(const drogon::HttpRequestPtr& req, const drogon::HttpResponsePtr& res);