From 187f1a250d72d93d1d4a6fb8c4095244b6c2e738 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 20 Jul 2025 15:06:51 +1200 Subject: [PATCH] docs: Update 2 files --- .kiro/steering/structure.md | 2 +- getpkg/src/ServerManager.cpp | 353 +++++++++++++++++++++++++++++++++++ getpkg/src/ServerManager.hpp | 53 ++++++ gp/gp | 13 +- 4 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 getpkg/src/ServerManager.cpp create mode 100644 getpkg/src/ServerManager.hpp diff --git a/.kiro/steering/structure.md b/.kiro/steering/structure.md index 7b7f80c..84a8f7d 100644 --- a/.kiro/steering/structure.md +++ b/.kiro/steering/structure.md @@ -69,4 +69,4 @@ ## Configuration Files - **.gitignore**: Standard ignore patterns for build artifacts - **.vscode/**: VS Code workspace settings -- **CMakeLists.txt**: Follows standard template with PROJECT_NAME parameter \ No newline at end of file +- **CMakeLists.txt**: Follows standard template with PROJECT_NAME parameter for the name of the project diff --git a/getpkg/src/ServerManager.cpp b/getpkg/src/ServerManager.cpp new file mode 100644 index 0000000..0e535f8 --- /dev/null +++ b/getpkg/src/ServerManager.cpp @@ -0,0 +1,353 @@ +#include "ServerManager.hpp" +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +// ServerConfig implementation +json ServerConfig::toJson() const { + return json{ + {"url", url}, + {"name", name}, + {"default", isDefault}, + {"writeToken", writeToken}, + {"added", addedDate} + }; +} + +ServerConfig ServerConfig::fromJson(const json& j) { + ServerConfig config; + config.url = j.value("url", ""); + config.name = j.value("name", ""); + config.isDefault = j.value("default", false); + config.writeToken = j.value("writeToken", ""); + config.addedDate = j.value("added", ""); + return config; +} + +// ServerManager implementation +ServerManager::ServerManager() { + const char* home = getenv("HOME"); + if (home) { + configPath_ = std::filesystem::path(home) / ".config" / "getpkg" / "servers.json"; + } +} + +bool ServerManager::addServer(const std::string& serverUrl, const std::string& writeToken) { + if (!validateServerUrl(serverUrl)) { + std::cerr << "Invalid server URL: " << serverUrl << std::endl; + return false; + } + + // Check if server already exists + if (findServer(serverUrl) != nullptr) { + std::cerr << "Server already exists: " << serverUrl << std::endl; + return false; + } + + // Check if server is reachable + if (!isServerReachable(serverUrl)) { + std::cerr << "Warning: Server may not be reachable: " << serverUrl << std::endl; + // Continue anyway - server might be temporarily down + } + + ServerConfig config; + config.url = serverUrl; + config.name = serverUrl; // Use URL as default name + config.isDefault = servers_.empty(); // First server becomes default + config.writeToken = writeToken; + config.addedDate = getCurrentTimestamp(); + + servers_.push_back(config); + + return saveConfiguration(); +} + +bool ServerManager::removeServer(const std::string& serverUrl) { + auto it = std::find_if(servers_.begin(), servers_.end(), + [&serverUrl](const ServerConfig& config) { + return config.url == serverUrl; + }); + + if (it == servers_.end()) { + std::cerr << "Server not found: " << serverUrl << std::endl; + return false; + } + + // Don't allow removing the last server + if (servers_.size() == 1) { + std::cerr << "Cannot remove the last server. Add another server first." << std::endl; + return false; + } + + bool wasDefault = it->isDefault; + servers_.erase(it); + + // If we removed the default server, make the first remaining server default + if (wasDefault && !servers_.empty()) { + servers_[0].isDefault = true; + } + + return saveConfiguration(); +} + +std::vector ServerManager::getServers() const { + std::vector urls; + for (const auto& server : servers_) { + urls.push_back(server.url); + } + return urls; +} + +std::string ServerManager::getDefaultServer() const { + for (const auto& server : servers_) { + if (server.isDefault) { + return server.url; + } + } + + // If no default is set, return the first server + if (!servers_.empty()) { + return servers_[0].url; + } + + return "getpkg.xyz"; // Fallback to original default +} + +std::string ServerManager::getDefaultPublishServer() const { + // Return first server with a write token + for (const auto& server : servers_) { + if (!server.writeToken.empty()) { + return server.url; + } + } + + // If no server has a token, return the default server + return getDefaultServer(); +} + +bool ServerManager::setWriteToken(const std::string& serverUrl, const std::string& token) { + ServerConfig* server = findServer(serverUrl); + if (server == nullptr) { + std::cerr << "Server not found: " << serverUrl << std::endl; + return false; + } + + server->writeToken = token; + return saveConfiguration(); +} + +std::string ServerManager::getWriteToken(const std::string& serverUrl) const { + const ServerConfig* server = findServer(serverUrl); + if (server != nullptr) { + return server->writeToken; + } + return ""; +} + +bool ServerManager::hasWriteToken(const std::string& serverUrl) const { + const ServerConfig* server = findServer(serverUrl); + return server != nullptr && !server->writeToken.empty(); +} + +std::vector ServerManager::getServersWithTokens() const { + std::vector serversWithTokens; + for (const auto& server : servers_) { + if (!server.writeToken.empty()) { + serversWithTokens.push_back(server.url); + } + } + return serversWithTokens; +} + +bool ServerManager::loadConfiguration() { + if (!std::filesystem::exists(configPath_)) { + ensureDefaultConfiguration(); + return true; + } + + try { + std::ifstream file(configPath_); + if (!file.is_open()) { + std::cerr << "Failed to open server configuration file: " << configPath_ << std::endl; + ensureDefaultConfiguration(); + return true; + } + + json config; + file >> config; + + if (!config.contains("servers") || !config["servers"].is_array()) { + std::cerr << "Invalid server configuration format" << std::endl; + ensureDefaultConfiguration(); + return true; + } + + servers_.clear(); + for (const auto& serverJson : config["servers"]) { + try { + servers_.push_back(ServerConfig::fromJson(serverJson)); + } catch (const std::exception& e) { + std::cerr << "Warning: Skipping invalid server config: " << e.what() << std::endl; + } + } + + // Ensure we have at least one server + if (servers_.empty()) { + ensureDefaultConfiguration(); + } + + return true; + } catch (const std::exception& e) { + std::cerr << "Error loading server configuration: " << e.what() << std::endl; + ensureDefaultConfiguration(); + return true; + } +} + +bool ServerManager::saveConfiguration() { + try { + // Ensure directory exists + std::filesystem::create_directories(configPath_.parent_path()); + + json config; + config["version"] = "1.0"; + config["lastUpdated"] = getCurrentTimestamp(); + + json serversArray = json::array(); + for (const auto& server : servers_) { + serversArray.push_back(server.toJson()); + } + config["servers"] = serversArray; + + std::ofstream file(configPath_); + if (!file.is_open()) { + std::cerr << "Failed to open server configuration file for writing: " << configPath_ << std::endl; + return false; + } + + file << config.dump(2); + return file.good(); + } catch (const std::exception& e) { + std::cerr << "Error saving server configuration: " << e.what() << std::endl; + return false; + } +} + +void ServerManager::ensureDefaultConfiguration() { + servers_.clear(); + + ServerConfig defaultServer; + defaultServer.url = "getpkg.xyz"; + defaultServer.name = "Official getpkg Registry"; + defaultServer.isDefault = true; + defaultServer.writeToken = ""; + defaultServer.addedDate = getCurrentTimestamp(); + + servers_.push_back(defaultServer); + + saveConfiguration(); +} + +bool ServerManager::migrateFromLegacy() { + const char* home = getenv("HOME"); + if (!home) { + return false; + } + + std::filesystem::path legacyTokenPath = std::filesystem::path(home) / ".config" / "getpkg.xyz" / "write_token.txt"; + + if (std::filesystem::exists(legacyTokenPath)) { + try { + std::ifstream tokenFile(legacyTokenPath); + std::string token; + std::getline(tokenFile, token); + + if (!token.empty()) { + // Set the token for getpkg.xyz server + setWriteToken("getpkg.xyz", token); + + // Optionally remove the legacy token file + // std::filesystem::remove(legacyTokenPath); + + std::cout << "Migrated legacy write token for getpkg.xyz" << std::endl; + return true; + } + } catch (const std::exception& e) { + std::cerr << "Warning: Failed to migrate legacy token: " << e.what() << std::endl; + } + } + + return false; +} + +bool ServerManager::validateServerUrl(const std::string& url) const { + if (url.empty() || url.length() > 253) { // DNS name length limit + return false; + } + + // Basic URL validation - should be a valid hostname or IP + // Allow formats like: example.com, sub.example.com, 192.168.1.1, localhost + std::regex urlPattern(R"(^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$)"); + + if (!std::regex_match(url, urlPattern)) { + return false; + } + + // Additional checks + if (url.find("..") != std::string::npos) { + return false; + } + + if (url.front() == '.' || url.back() == '.') { + return false; + } + + return true; +} + +bool ServerManager::isServerReachable(const std::string& url) const { + try { + std::string testUrl = "https://" + url + "/"; + + auto response = cpr::Head(cpr::Url{testUrl}, + cpr::Timeout{5000}, // 5 seconds + cpr::VerifySsl{true}); + + // Accept any response that indicates the server is reachable + // (200, 404, 403, etc. - as long as we get a response) + return response.status_code > 0; + } catch (const std::exception& e) { + return false; + } +} + +ServerConfig* ServerManager::findServer(const std::string& url) { + auto it = std::find_if(servers_.begin(), servers_.end(), + [&url](const ServerConfig& config) { + return config.url == url; + }); + return (it != servers_.end()) ? &(*it) : nullptr; +} + +const ServerConfig* ServerManager::findServer(const std::string& url) const { + auto it = std::find_if(servers_.begin(), servers_.end(), + [&url](const ServerConfig& config) { + return config.url == url; + }); + return (it != servers_.end()) ? &(*it) : nullptr; +} + +std::string ServerManager::getCurrentTimestamp() const { + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + + std::stringstream ss; + ss << std::put_time(std::gmtime(&time_t), "%Y-%m-%dT%H:%M:%SZ"); + return ss.str(); +} \ No newline at end of file diff --git a/getpkg/src/ServerManager.hpp b/getpkg/src/ServerManager.hpp new file mode 100644 index 0000000..49bfcdb --- /dev/null +++ b/getpkg/src/ServerManager.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include + +struct ServerConfig { + std::string url; + std::string name; + bool isDefault = false; + std::string writeToken; + std::string addedDate; + + // JSON serialization + nlohmann::json toJson() const; + static ServerConfig fromJson(const nlohmann::json& j); +}; + +class ServerManager { +public: + ServerManager(); + + // Server management + bool addServer(const std::string& serverUrl, const std::string& writeToken = ""); + bool removeServer(const std::string& serverUrl); + std::vector getServers() const; + std::string getDefaultServer() const; + std::string getDefaultPublishServer() const; // First server with write token + + // Token management + bool setWriteToken(const std::string& serverUrl, const std::string& token); + std::string getWriteToken(const std::string& serverUrl) const; + bool hasWriteToken(const std::string& serverUrl) const; + std::vector getServersWithTokens() const; + + // Configuration + bool loadConfiguration(); + bool saveConfiguration(); + void ensureDefaultConfiguration(); + + // Migration + bool migrateFromLegacy(); + +private: + std::vector servers_; + std::filesystem::path configPath_; + + bool validateServerUrl(const std::string& url) const; + bool isServerReachable(const std::string& url) const; + ServerConfig* findServer(const std::string& url); + const ServerConfig* findServer(const std::string& url) const; + std::string getCurrentTimestamp() const; +}; \ No newline at end of file diff --git a/gp/gp b/gp/gp index 785c65b..9b2af2f 100755 --- a/gp/gp +++ b/gp/gp @@ -202,12 +202,23 @@ generate_commit_message() { echo "$message" } -# Function to check if we're in a git repository +# Function to check if we're in a git repository and change to repo root check_git_repo() { if ! git rev-parse --git-dir >/dev/null 2>&1; then print_error "Not in a git repository" exit 1 fi + + # Change to the git repository root to ensure we operate on the entire repo + local git_root + git_root=$(git rev-parse --show-toplevel) + if [ "$PWD" != "$git_root" ]; then + print_info "Changing to git repository root: $git_root" + cd "$git_root" || { + print_error "Failed to change to git repository root" + exit 1 + } + fi } # Function to check for uncommitted changes and unpushed commits