diff --git a/dbal/cpp/src/client.cpp b/dbal/cpp/src/client.cpp index b0fb0e1ae..68c7bf564 100644 --- a/dbal/cpp/src/client.cpp +++ b/dbal/cpp/src/client.cpp @@ -50,6 +50,14 @@ Result Client::batchDeleteUsers(const std::vector& ids) { return entities::user::batchDelete(getStore(), ids); } +Result> Client::searchUsers(const std::string& query, int limit) { + return entities::user::search(getStore(), query, limit); +} + +Result Client::countUsers(const std::optional& role) { + return entities::user::count(getStore(), role); +} + Result Client::setCredential(const CreateCredentialInput& input) { return entities::credential::set(getStore(), input); } diff --git a/dbal/cpp/tests/unit/client_test.cpp b/dbal/cpp/tests/unit/client_test.cpp index c47e98935..70087d078 100644 --- a/dbal/cpp/tests/unit/client_test.cpp +++ b/dbal/cpp/tests/unit/client_test.cpp @@ -211,6 +211,75 @@ void test_credential_validation() { std::cout << " ✓ Empty password hash rejected" << std::endl; } +void test_user_search() { + std::cout << "Testing user search..." << std::endl; + + dbal::ClientConfig config; + config.adapter = "sqlite"; + config.database_url = ":memory:"; + dbal::Client client(config); + + dbal::CreateUserInput user1; + user1.username = "search_alpha"; + user1.email = "alpha@example.com"; + auto result1 = client.createUser(user1); + assert(result1.isOk()); + + dbal::CreateUserInput user2; + user2.username = "search_beta"; + user2.email = "beta@examples.com"; + auto result2 = client.createUser(user2); + assert(result2.isOk()); + + auto found = client.searchUsers("search", 10); + assert(found.isOk()); + assert(found.value().size() >= 2); + std::cout << " ✓ Search matched multiple users" << std::endl; + + auto caseInsensitive = client.searchUsers("SEARCH_BETA", 10); + assert(caseInsensitive.isOk()); + assert(caseInsensitive.value().size() == 1); + assert(caseInsensitive.value()[0].username == "search_beta"); + std::cout << " ✓ Search is case-insensitive" << std::endl; + + auto limited = client.searchUsers("search", 1); + assert(limited.isOk()); + assert(limited.value().size() == 1); + std::cout << " ✓ Search respects limit" << std::endl; +} + +void test_user_count() { + std::cout << "Testing user count..." << std::endl; + + dbal::ClientConfig config; + config.adapter = "sqlite"; + config.database_url = ":memory:"; + dbal::Client client(config); + + dbal::CreateUserInput user; + user.username = "count_user"; + user.email = "count@example.com"; + auto result = client.createUser(user); + assert(result.isOk()); + + dbal::CreateUserInput admin; + admin.username = "count_admin"; + admin.email = "count_admin@example.com"; + admin.role = dbal::UserRole::Admin; + auto adminResult = client.createUser(admin); + assert(adminResult.isOk()); + + auto totalCount = client.countUsers(); + assert(totalCount.isOk()); + assert(totalCount.value() >= 2); + std::cout << " ✓ Total user count matches" << std::endl; + + auto adminCount = client.countUsers(dbal::UserRole::Admin); + assert(adminCount.isOk()); + assert(adminCount.value() >= 1); + std::cout << " ✓ Admin count matches" << std::endl; +} + void test_get_user() { std::cout << "Testing get user..." << std::endl; diff --git a/frontends/cli/CMakeLists.txt b/frontends/cli/CMakeLists.txt new file mode 100644 index 000000000..44c123e04 --- /dev/null +++ b/frontends/cli/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.27) +project(metabuilder_cli VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +include(${CMAKE_BINARY_DIR}/conan_toolchain.cmake OPTIONAL) + +add_executable(metabuilder-cli + src/main.cpp + src/commands/command_dispatch.cpp + src/utils/http_client.cpp +) + +find_package(cpr CONFIG REQUIRED) + +target_link_libraries(metabuilder-cli PRIVATE cpr::cpr) +target_compile_features(metabuilder-cli PRIVATE cxx_std_20) +target_include_directories(metabuilder-cli PRIVATE src) + +install(TARGETS metabuilder-cli + RUNTIME DESTINATION bin +) diff --git a/frontends/cli/src/utils/http_client.h b/frontends/cli/src/utils/http_client.h new file mode 100644 index 000000000..ed2a81f40 --- /dev/null +++ b/frontends/cli/src/utils/http_client.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +class HttpClient { +public: + explicit HttpClient(std::string base_url); + + cpr::Response get(const std::string &path) const; + cpr::Response post(const std::string &path, + const std::string &body, + const std::string &content_type = "application/json") const; + + [[nodiscard]] const std::string &base_url() const noexcept; + +private: + std::string base_url_; +};