From f9fee188e8b319425381e0c0506b12d153ea0ecb Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 29 May 2025 22:26:48 +1200 Subject: [PATCH] :-'Generic Commit' --- README.md | 8 +- src/database.cpp | 33 +++---- src/database.hpp | 3 +- src/server.cpp | 217 +++++++++++++++---------------------------- src/server.hpp | 2 + src/welcome_page.cpp | 13 ++- test.sh | 8 +- 7 files changed, 109 insertions(+), 175 deletions(-) diff --git a/README.md b/README.md index 20f18da..edc9bd7 100644 --- a/README.md +++ b/README.md @@ -123,14 +123,14 @@ curl -X PUT \ ### Get a File ``` -GET /object/{hash} -GET /object/{label}:{tag} +GET /{hash} +GET /{label}:{tag} ``` Example: ```bash -curl http://localhost:8080/object/abc123 -curl http://localhost:8080/object/test:latest +curl http://localhost:8080/abc123 > abc123.txt +curl http://localhost:8080/test:latest > test.sh ``` ### Check if a File Exists diff --git a/src/database.cpp b/src/database.cpp index cc75584..62d7f4d 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -154,19 +154,19 @@ Database::~Database() { } } -bool Database::remove(const std::string& labeltag) { - std::string sql = "DELETE FROM objects WHERE labeltag = ?;"; - sqlite3_stmt* stmt; +// bool Database::remove(const std::string& labeltag) { +// std::string sql = "DELETE FROM objects WHERE labeltag = ?;"; +// sqlite3_stmt* stmt; - if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { - return false; - } +// if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { +// return false; +// } - sqlite3_bind_text(stmt, 1, labeltag.c_str(), -1, SQLITE_STATIC); - bool success = sqlite3_step(stmt) == SQLITE_DONE; - sqlite3_finalize(stmt); - return success; -} +// sqlite3_bind_text(stmt, 1, labeltag.c_str(), -1, SQLITE_STATIC); +// bool success = sqlite3_step(stmt) == SQLITE_DONE; +// sqlite3_finalize(stmt); +// return success; +// } bool Database::remove_by_hash(const std::string& hash) { std::string sql = "DELETE FROM objects WHERE hash = ?;"; @@ -182,9 +182,9 @@ bool Database::remove_by_hash(const std::string& hash) { return success; } -bool Database::get(const std::string& key, dbEntry& entry) { +bool Database::get(const std::string& hash_or_labeltag, dbEntry& entry) { std::string sql; - if (key.find(':') != std::string::npos) { + if (hash_or_labeltag.find(':') != std::string::npos) { // Query by label:tag - search for exact match in the JSON array sql = "SELECT hash, labeltags, metadata FROM objects WHERE json_array_length(labeltags) > 0 AND EXISTS (SELECT 1 FROM json_each(labeltags) WHERE value = ?);"; } else { @@ -197,12 +197,7 @@ bool Database::get(const std::string& key, dbEntry& entry) { return false; } - if (key.find(':') != std::string::npos) { - // For label:tag queries, bind the exact label:tag string - sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_STATIC); - } else { - sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_STATIC); - } + sqlite3_bind_text(stmt, 1, hash_or_labeltag.c_str(), -1, SQLITE_STATIC); if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); diff --git a/src/database.hpp b/src/database.hpp index 2cddb04..654f4a1 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -22,9 +22,8 @@ class Database { Database(const std::filesystem::path& path); ~Database(); - bool remove(const std::string& hash); bool remove_by_hash(const std::string& hash); - bool get(const std::string& hash, dbEntry& entry); + bool get(const std::string& hash_or_labeltag, dbEntry& entry); bool list(std::vector& entries); bool update_or_insert(const dbEntry& entry); private: diff --git a/src/server.cpp b/src/server.cpp index 013cdb3..9d33d97 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -154,8 +154,13 @@ void Server::setup_routes() { // Welcome page - server_.Get("/", [](const httplib::Request&, httplib::Response& res) { - res.set_content(welcome_page(), "text/html"); + server_.Get("/(.*)", [this](const httplib::Request& req, httplib::Response& res) { + if (req.path == "/") { + res.set_content(welcome_page(), "text/html"); + return; + } + // if the path is not /, then it's a hash or label:tag + handle_get_object(req, res); }); server_.Get("/index.html", [](const httplib::Request&, httplib::Response& res) { @@ -172,6 +177,12 @@ void Server::setup_routes() { handle_get_hash(req, res); }); + // Get version for label:tag + server_.Get("/version/(.*)", [this](const httplib::Request& req, httplib::Response& res) { + handle_get_version(req, res); + }); + + // Check if object exists by hash or label:tag server_.Get("/exists/(.*)", [this](const httplib::Request& req, httplib::Response& res) { handle_exists(req, res); @@ -267,45 +278,18 @@ void Server::handle_get_object(const httplib::Request& req, httplib::Response& r // first check if the key matches. dbEntry entry; - if (db_->get(key, entry)) { - // got it! - hash_str = entry.hash; - } - - if (hash_str.empty()) { + if (!db_->get(key, entry)) { res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Object hash could not be determined"}}; + nlohmann::json response = {{"result", "error"}, {"error", "Couldn't find: " + key}}; res.set_content(response.dump(), "application/json"); return; } - // check valid hash. - uint64_t hash_value; - try { - hash_value = std::stoull(hash_str); - } catch (const std::invalid_argument& e) { - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Invalid hash: " + hash_str}}; - res.set_content(response.dump(), "application/json"); - return; - } - - std::stringstream oss; - oss << hash_value; - if (oss.str() != hash_str) { - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Invalid hash: " + hash_str}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // hash is valid, safe to look up! - // Construct the file path using the hash string - std::filesystem::path file_path = config_.object_store_path / oss.str(); + std::filesystem::path file_path = config_.object_store_path / entry.hash; if (!std::filesystem::exists(file_path) || !std::filesystem::is_regular_file(file_path)) { res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Object file not found for hash: " + hash_str}}; + nlohmann::json response = {{"result", "error"}, {"error", "Hash recognised, but object missing for: " + entry.hash}}; res.set_content(response.dump(), "application/json"); return; } @@ -357,68 +341,23 @@ void Server::handle_get_metadata(const httplib::Request& req, httplib::Response& // Check if the key is a label:tag format dbEntry entry; + nlohmann::json response; + if (db_->get(key, entry)) { // Got it from label:tag, use the hash - hash_str = entry.hash; - } - - // If we couldn't determine a hash - if (hash_str.empty()) { - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Metadata not found for: " + key}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Try to interpret the key as a hash directly - uint64_t hash_value; - try { - hash_value = std::stoull(hash_str); - } catch (const std::invalid_argument& e) { - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Invalid hash: " + hash_str}}; - res.set_content(response.dump(), "application/json"); - return; - } - - std::stringstream oss; - oss << hash_value; - if (oss.str() != hash_str) { - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Invalid hash: " + hash_str}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Hash is valid, look up the metadata - std::vector entries; - if (!db_->list(entries)) { - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Failed to retrieve metadata"}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Find the entry with matching hash - for (const auto& e : entries) { - if (e.hash == hash_str) { - try { - nlohmann::json response = {{"result", "success"}, {"metadata", e.metadata}}; - res.set_content(response.dump(), "application/json"); - return; - } catch (const nlohmann::json::exception& e) { - std::cerr << "Error serializing metadata for hash " << hash_str << ": " << e.what() << std::endl; - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Internal server error: Failed to serialize metadata"}}; - res.set_content(response.dump(), "application/json"); - return; - } + try { + response = {{"result", "success"}, {"metadata", entry.metadata}}; + } catch (const nlohmann::json::exception& e) { + std::cerr << "Error serializing metadata for hash " << hash_str << ": " << e.what() << std::endl; + res.status = 500; + response = {{"result", "error"}, {"error", "Internal server error: Failed to serialize metadata"}}; } } - - // No entry found with this hash - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Metadata not found for hash: " + hash_str}}; + else + { + res.status = 404; + response = {{"result", "error"}, {"error", "Invalid hash: " + hash_str}}; + } res.set_content(response.dump(), "application/json"); } @@ -431,22 +370,24 @@ std::pair Server::parse_labeltag(const std::string& la } void Server::handle_delete_object(const httplib::Request& req, httplib::Response& res) { - std::map params; - if (!validate_write_request(req, res, {"hash"}, params)) { - return; - } + dbEntry entry; - // Validate that the hash exists as a file - std::filesystem::path file_path = config_.object_store_path / params["hash"]; - if (!std::filesystem::exists(file_path) || !std::filesystem::is_regular_file(file_path)) { - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Object not found for hash: " + params["hash"]}}; - res.set_content(response.dump(), "application/json"); - return; - } + { + std::map params; + if (!validate_write_request(req, res, {"hash"}, params)) { + return; + } + + if (!db_->get(params["hash"], entry)) { + res.status = 404; + nlohmann::json response = {{"result", "error"}, {"error", "Object not found for: " + params["hash"]}}; + res.set_content(response.dump(), "application/json"); + return; + } + } // we only use sanitised data from here on out. // Remove all tags that reference this hash - if (!db_->remove_by_hash(params["hash"])) { + if (!db_->remove_by_hash(entry.hash)) { res.status = 500; nlohmann::json response = {{"result", "error"}, {"error", "Failed to remove some or all associated tags"}}; res.set_content(response.dump(), "application/json"); @@ -454,14 +395,17 @@ void Server::handle_delete_object(const httplib::Request& req, httplib::Response } // Remove the file - try { - std::filesystem::remove(file_path); - } catch (const std::filesystem::filesystem_error& e) { - std::cerr << "Error deleting object file: " << e.what() << std::endl; - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Failed to delete object file: " + std::string(e.what())}}; - res.set_content(response.dump(), "application/json"); - return; + 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) { + std::cerr << "Error deleting object file: " << e.what() << std::endl; + res.status = 500; + nlohmann::json response = {{"result", "error"}, {"error", "Failed to delete object file: " + std::string(e.what())}}; + res.set_content(response.dump(), "application/json"); + return; + } } nlohmann::json response = {{"result", "success"}}; @@ -469,46 +413,35 @@ void Server::handle_delete_object(const httplib::Request& req, httplib::Response } void Server::handle_exists(const httplib::Request& req, httplib::Response& res) { + const auto& key = req.matches[1].str(); + nlohmann::json response; + dbEntry entry; + if (db_->get(key, entry)) { + response = {{"result", "success"}, {"exists", true}}; + } else { + response = {{"result", "success"}, {"exists", false}}; + } + res.set_content(response.dump(), "application/json"); +} + +void Server::handle_get_version(const httplib::Request& req, httplib::Response& res) { const auto& key = req.matches[1].str(); std::string hash_str = key; // Check if the key is a label:tag format dbEntry entry; - if (db_->get(key, entry)) { - // Got it from label:tag, use the hash - hash_str = entry.hash; - } - - // If we couldn't determine a hash - if (hash_str.empty()) { - nlohmann::json response = {{"result", "success"}, {"exists", false}}; + if (!db_->get(key, entry)) { + nlohmann::json response = {{"result", "failed"}, {"error", "Failed to get version for: " + key}}; res.set_content(response.dump(), "application/json"); return; } - // Try to interpret the key as a hash directly - uint64_t hash_value; - try { - hash_value = std::stoull(hash_str); - } catch (const std::invalid_argument& e) { - nlohmann::json response = {{"result", "success"}, {"exists", false}}; - res.set_content(response.dump(), "application/json"); - return; + nlohmann::json response; + if (entry.metadata.contains("version")) { + response = {{"result", "success"}, {"version", entry.metadata["version"]}}; + } else { + response = {{"result", "failed"}, {"error", "No version found for: " + key}}; } - - std::stringstream oss; - oss << hash_value; - if (oss.str() != hash_str) { - nlohmann::json response = {{"result", "success"}, {"exists", false}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Hash is valid, check if the file exists - std::filesystem::path file_path = config_.object_store_path / oss.str(); - bool exists = std::filesystem::exists(file_path) && std::filesystem::is_regular_file(file_path); - - nlohmann::json response = {{"result", "success"}, {"exists", exists}}; res.set_content(response.dump(), "application/json"); } diff --git a/src/server.hpp b/src/server.hpp index b281970..b9dc1b2 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -27,6 +27,8 @@ public: ServerConfig config_; std::unique_ptr db_; + void handle_get_version(const httplib::Request& req, httplib::Response& res); + private: void setup_routes(); void handle_get_object(const httplib::Request& req, httplib::Response& res); diff --git a/src/welcome_page.cpp b/src/welcome_page.cpp index 2cb9da0..241d1b6 100644 --- a/src/welcome_page.cpp +++ b/src/welcome_page.cpp @@ -157,6 +157,11 @@ std::string welcome_page() {

Operations

    +
  • + GET + /{label:tag|hash} + Retrieve an object +
  • GET /hash/label:tag @@ -164,13 +169,13 @@ std::string welcome_page() {
  • GET - /exists/{label:tag|hash} - Check if an object exists + /version/label:tag + Get version for a label:tag
  • GET - /object/{label:tag|hash} - Retrieve an object + /exists/{label:tag|hash} + Check if an object exists
  • GET diff --git a/test.sh b/test.sh index d093725..d9054e1 100755 --- a/test.sh +++ b/test.sh @@ -117,13 +117,13 @@ echo "md5sum of ${SCRIPT_DIR}/${SCRIPT_NAME} is ${MD5SUM}" # download the object echo "downloading ${OBJECT_HASH} to ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1" -if ! curl -s "${HOSTURL}/object/${OBJECT_HASH}" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1"; then +if ! curl -s "${HOSTURL}/${OBJECT_HASH}" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded1"; then die "failed to download ${OBJECT_HASH}" fi # download the object again via the label:tag echo "downloading ${BASE_TAG}:test1 to ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded2" -if ! curl -s "${HOSTURL}/object/${BASE_TAG}:test1" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded2"; then +if ! curl -s "${HOSTURL}/${BASE_TAG}:test1" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded2"; then die "failed to download ${BASE_TAG}:test1" fi @@ -181,7 +181,7 @@ fi # download via the label:tag echo "downloading ${LABELTAG} to ${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3" -if ! curl -s "${HOSTURL}/object/${LABELTAG}" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3"; then +if ! curl -s "${HOSTURL}/${LABELTAG}" -o "${SCRIPT_DIR}/${SCRIPT_NAME}.downloaded3"; then die "failed to download ${LABELTAG}" fi @@ -205,7 +205,7 @@ fi # verify the object is deleted echo "verifying ${OBJECT_HASH} is deleted" -DELETE_RESPONSE=$(curl -s "${HOSTURL}/object/${OBJECT_HASH}") +DELETE_RESPONSE=$(curl -s "${HOSTURL}/${OBJECT_HASH}") if ! echo "${DELETE_RESPONSE}" | jq -r '.result' | grep -q 'error'; then die "failed to verify ${OBJECT_HASH} is deleted" fi