diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 40a1ba8..70a1384 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -6,19 +6,33 @@ cmake_path(SET issues_proto ${CMAKE_CURRENT_SOURCE_DIR}/reasy/api/v1/issues.prot cmake_path(SET issues_header ${CMAKE_CURRENT_BINARY_DIR}/reasy/api/v1/issues.pb.h) cmake_path(SET issues_source ${CMAKE_CURRENT_BINARY_DIR}/reasy/api/v1/issues.pb.cc) +cmake_path(SET projects_proto ${CMAKE_CURRENT_SOURCE_DIR}/reasy/api/v1/projects.proto) +cmake_path(SET projects_header ${CMAKE_CURRENT_BINARY_DIR}/reasy/api/v1/projects.pb.h) +cmake_path(SET projects_source ${CMAKE_CURRENT_BINARY_DIR}/reasy/api/v1/projects.pb.cc) + +cmake_path(SET workspaces_proto ${CMAKE_CURRENT_SOURCE_DIR}/reasy/api/v1/workspaces.proto) +cmake_path(SET workspaces_header ${CMAKE_CURRENT_BINARY_DIR}/reasy/api/v1/workspaces.pb.h) +cmake_path(SET workspaces_source ${CMAKE_CURRENT_BINARY_DIR}/reasy/api/v1/workspaces.pb.cc) + set(protos ${users_proto} ${issues_proto} + ${projects_proto} + ${workspaces_proto} ) set(headers ${users_header} ${issues_header} + ${projects_header} + ${workspaces_header} ) set(sources ${users_source} ${issues_source} + ${projects_source} + ${workspaces_source} ) add_custom_command( diff --git a/proto/reasy/api/v1/issues.proto b/proto/reasy/api/v1/issues.proto index 196203b..11d180b 100644 --- a/proto/reasy/api/v1/issues.proto +++ b/proto/reasy/api/v1/issues.proto @@ -9,7 +9,7 @@ service issues { rpc Get(IssuesGetRequest) returns (IssuesGetResponse) {} rpc Create(IssuesCreateRequest) returns (reasy.core.Empty) {} rpc Assign(IssuesAssignRequest) returns (reasy.core.Empty) {} - rpc RemoveAssignee(reasy.core.Empty) returns (reasy.core.Empty) {} + rpc RemoveAssignee(IssuesRemoveAssigneeRequest) returns (reasy.core.Empty) {} rpc AddToProject(IssuesAddToProjectRequest) returns (reasy.core.Empty) {} rpc ChangePriority(IssuesChangePriorityRequest) returns (reasy.core.Empty) {} rpc ChangeStatus(IssuesChangeStatusRequest) returns (reasy.core.Empty) {} @@ -71,6 +71,10 @@ message IssuesAssignRequest { string assignee_id = 2; } +message IssuesRemoveAssigneeRequest { + string issue_id = 1; +} + message IssuesAddToProjectRequest { string issue_id = 1; string project_id = 2; diff --git a/proto/reasy/api/v1/projects.proto b/proto/reasy/api/v1/projects.proto new file mode 100644 index 0000000..2e24fdf --- /dev/null +++ b/proto/reasy/api/v1/projects.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package reasy.api.v1; + +import "google/protobuf/struct.proto"; +import "reasy/core.proto"; + +service projects { + rpc GetAll(reasy.core.Empty) returns (ProjectsGetAllResponse) {} + rpc Get(ProjectsGetRequest) returns (Project) {} + rpc Create(ProjectsCreateRequest) returns (Project) {} + rpc ChangeName(ProjectsChangeNameRequest) returns (reasy.core.Empty) {} + rpc Delete(ProjectsDeleteRequest) returns (reasy.core.Empty) {} +} + +message Project { + string id = 1; + string workspace_id = 2; + string name = 3; +} + +message ProjectsGetRequest { + string id = 1; +} + +message ProjectsCreateRequest { + string workspace_id = 1; + string name = 2; +} + +message ProjectsChangeNameRequest { + string id = 1; + string name = 2; +} + +message ProjectsDeleteRequest { + string id = 1; +} + +message ProjectsGetAllResponse { + repeated Project projects = 1; +} \ No newline at end of file diff --git a/proto/reasy/api/v1/users.proto b/proto/reasy/api/v1/users.proto index 619a9c4..f4bd585 100644 --- a/proto/reasy/api/v1/users.proto +++ b/proto/reasy/api/v1/users.proto @@ -5,8 +5,12 @@ package reasy.api.v1; import "google/protobuf/struct.proto"; service users { - rpc GetAll(UsersGetAllRequest) returns (UsersGetAllResponse) {} - rpc Get(Empty) returns (User) {} + rpc GetAll(Empty) returns (UsersGetAllResponse) {} + rpc Get(UsersGetRequest) returns (User) {} + rpc Create(UsersCreateRequest) returns (User) {} + rpc ChangeName(UsersChangeNameRequest) returns (Empty) {} + rpc ChangeUsername(UsersChangeUsernameRequest) returns (Empty) {} + rpc Delete(UsersDeleteRequest) returns (Empty) {} } message Empty {} @@ -18,13 +22,31 @@ message User { string email = 4; } -message UsersGetAllRequest { - optional string segment = 1; +message UsersGetRequest { + string id = 1; +} + +message UsersCreateRequest { + string name = 1; + string username = 2; + string email = 3; +} + +message UsersChangeNameRequest { + string id = 1; + string new_name = 2; +} + +message UsersChangeUsernameRequest { + string id = 1; + string new_username = 2; +} + +message UsersDeleteRequest { + string id = 1; } message UsersGetAllResponse { repeated User users = 1; - - optional string cursor = 2; } diff --git a/proto/reasy/api/v1/workspaces.proto b/proto/reasy/api/v1/workspaces.proto new file mode 100644 index 0000000..0a89b2d --- /dev/null +++ b/proto/reasy/api/v1/workspaces.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package reasy.api.v1; + +import "google/protobuf/struct.proto"; +import "reasy/core.proto"; + +service workspaces { + rpc GetAll(reasy.core.Empty) returns (WorkspacesGetAllResponse) {} + rpc Get(WorkspacesGetRequest) returns (Workspace) {} + rpc Create(WorkspacesCreateRequest) returns(Workspace) {} + rpc ChangeAdmin(WorkspacesChangeAdminRequest) returns (reasy.core.Empty) {} + rpc ChangeName(WorkspacesChangeNameRequest) returns (reasy.core.Empty) {} + rpc Delete(WorkspacesDeleteRequest) returns (reasy.core.Empty) {} +} + +message Workspace { + string id = 1; + string admin_id = 2; + string name = 3; +} + +message WorkspacesGetRequest { + string id = 1; +} + +message WorkspacesCreateRequest { + string admin_id = 1; + string name = 2; +} + +message WorkspacesChangeAdminRequest { + string id = 1; + string new_admin_id = 2; +} + +message WorkspacesChangeNameRequest { + string id = 1; + string new_name = 2; +} + +message WorkspacesDeleteRequest { + string id = 1; +} + +message WorkspacesGetAllResponse { + repeated Workspace workspaces = 1; +} \ No newline at end of file diff --git a/requests/issues.http b/requests/issues.http new file mode 100644 index 0000000..c806930 --- /dev/null +++ b/requests/issues.http @@ -0,0 +1,28 @@ + +### +GRPC localhost:8080/reasy.api.v1.issues/Get + +{ + "issue_id": "cp9k0kjjuspjbfubl0s0" +} + +### +GRPC localhost:8080/reasy.api.v1.issues/Create + +{ + "project_id": "cc0a2mn6i1e6brmdbip0", + "creator_id": "cc0aar76i1e6jr6no620", + "name": "Issue #2", + "description": "hello world plz", + "status": 1, + "priority": 2, + "assignee_id": "cc0a2mn6i1e6brmdbip0" +} + +### +GRPC localhost:8080/reasy.api.v1.issues/ChangePriority + +{ + "issue_id": "cp9k0kjjuspjbfubl0s0", + "new_priority": 3 +} \ No newline at end of file diff --git a/requests/projects.http b/requests/projects.http new file mode 100644 index 0000000..b3c84e5 --- /dev/null +++ b/requests/projects.http @@ -0,0 +1,35 @@ +@url = localhost:8080/reasy.api.v1.projects +@id = "cp9nk6bjuspm2rts0la0" + +### +GRPC {{url}}/GetAll + +### +GRPC {{url}}/Get + +{ + "id": {{id}} +} + +### +GRPC {{url}}/Create + +{ + "workspace_id": "workspace1", + "name": "hello world" +} + +### +GRPC {{url}}/ChangeName + +{ + "id": {{id}}, + "name": "workspace2 edit" +} + +### +GRPC {{url}}/Delete + +{ + "id": {{id}} +} \ No newline at end of file diff --git a/requests/users.http b/requests/users.http new file mode 100644 index 0000000..4b4805c --- /dev/null +++ b/requests/users.http @@ -0,0 +1,45 @@ +@url = localhost:8080/reasy.api.v1.users +@id = "cp9m3jjjuspkirgm5ds0" + +### +GRPC {{url}}/GetAll + +### +GRPC {{url}}/Get + +{ + "id": {{id}} +} + +### +GRPC {{url}}/Create + +{ + "name": "Reezy 2", + "username": "@rizesql2_", + "email": "mail2+new@mail.com" +} + +### +GRPC {{url}}/ChangeName + +{ + "id": {{id}}, + "new_name": "new Reezy" +} + +### +GRPC {{url}}/ChangeUsername + +{ + "id": {{id}}, + "new_username": "new @rizesql_" +} + +### +GRPC {{url}}/Delete + +{ + "id": {{id}} +} + diff --git a/requests/workspaces.http b/requests/workspaces.http new file mode 100644 index 0000000..a97f0f9 --- /dev/null +++ b/requests/workspaces.http @@ -0,0 +1,43 @@ +@url = localhost:8080/reasy.api.v1.workspaces +@id = "" + +### +GRPC {{url}}/GetAll + +### +GRPC {{url}}/Get + +{ + "id": {{id}} +} + +### +GRPC {{url}}/Create + +{ + "admin_id": "", + "name": "" +} + +### +GRPC {{url}}/ChangeAdmin + +{ + "id": "", + "new_admin_id": "" +} + +### +GRPC {{url}}/ChangeName + +{ + "id": "", + "new_name": "" +} + +### +GRPC {{url}}/Delete + +{ + "id": {{id}} +} \ No newline at end of file diff --git a/src/api/v1.h b/src/api/v1.h index cd4645f..e57935e 100644 --- a/src/api/v1.h +++ b/src/api/v1.h @@ -3,8 +3,11 @@ #include "wrapper.h" #include "users_service.h" #include "issues_service.h" +#include "projects_service.h" namespace api::v1 { using issues = Wrapper; + using projects = Wrapper; using users = Wrapper; -} \ No newline at end of file + using workspaces = Wrapper; +} diff --git a/src/issues/CMakeLists.txt b/src/issues/CMakeLists.txt index e5f3f55..d0d2aba 100644 --- a/src/issues/CMakeLists.txt +++ b/src/issues/CMakeLists.txt @@ -21,4 +21,4 @@ target_sources(issues target_link_libraries(issues PRIVATE ${PROJECT_NAME}::core PRIVATE ${PROJECT_NAME}::libproto -) \ No newline at end of file +) diff --git a/src/issues/issues_service.cpp b/src/issues/issues_service.cpp index 3c2450f..3d3cfb1 100644 --- a/src/issues/issues_service.cpp +++ b/src/issues/issues_service.cpp @@ -86,20 +86,53 @@ namespace api::issues { } template<> - rpcAssign::result_type Rpc::call(grpcxx::context &ctx, rpcAssign::request_type const &res) { - return {grpcxx::status::code_t::unimplemented, std::nullopt}; + rpcAssign::result_type Rpc::call(grpcxx::context &ctx, rpcAssign::request_type const &req) { + auto evt = issue::events::AssignedTo{ + .new_assignee_id = user::id(req.assignee_id()), + .metadata = {.created_at=sc::now()} + }; + nlohmann::json j = evt; + + db_ << "begin;"; + db_ << issues_queries::insert_event << xid::next().string() << req.issue_id() << 1 << "issue::AssignedTo" + << j.dump() << xid::next().string() << sc::now().time_since_epoch().count(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok, std::nullopt}; } template<> rpcRemoveAssignee::result_type Rpc::call( grpcxx::context &, - rpcRemoveAssignee::request_type const &) { - return {grpcxx::status::code_t::unimplemented, std::nullopt}; + rpcRemoveAssignee::request_type const &req) { + auto evt = issue::events::RemovedAssignee{ + .metadata = {.created_at=sc::now()} + }; + nlohmann::json j = evt; + + db_ << "begin;"; + db_ << issues_queries::insert_event << xid::next().string() << req.issue_id() << 1 << "issue::RemovedAssignee" + << j.dump() << xid::next().string() << sc::now().time_since_epoch().count(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok, std::nullopt}; } template<> - rpcAddToProject::result_type Rpc::call(grpcxx::context &, rpcAddToProject::request_type const &) { - return {grpcxx::status::code_t::unimplemented, std::nullopt}; + rpcAddToProject::result_type + Rpc::call(grpcxx::context &, rpcAddToProject::request_type const &req) { + auto evt = issue::events::AddedToProject{ + .project_id = project::id(req.project_id()), + .metadata = {.created_at=sc::now()} + }; + nlohmann::json j = evt; + + db_ << "begin;"; + db_ << issues_queries::insert_event << xid::next().string() << req.issue_id() << 1 << "issue::AddedToProject" + << j.dump() << xid::next().string() << sc::now().time_since_epoch().count(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok, std::nullopt}; } template<> @@ -121,8 +154,20 @@ namespace api::issues { } template<> - rpcChangeStatus::result_type Rpc::call(grpcxx::context &, rpcChangeStatus::request_type const &) { - return {grpcxx::status::code_t::unimplemented, std::nullopt}; + rpcChangeStatus::result_type + Rpc::call(grpcxx::context &, rpcChangeStatus::request_type const &req) { + auto evt = issue::events::ChangedStatus{ + .new_status = Status::from(req.status()).value_or(Status::todo), + .metadata = {.created_at=sc::now()} + }; + nlohmann::json j = evt; + + db_ << "begin;"; + db_ << issues_queries::insert_event << xid::next().string() << req.issue_id() << 1 << "issue::ChangedStatus" + << j.dump() << xid::next().string() << sc::now().time_since_epoch().count(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok, std::nullopt}; } google::rpc::Status Rpc::exception() noexcept { diff --git a/src/main.cpp b/src/main.cpp index 693ff7c..04d8845 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,9 +14,15 @@ int main() { auto issues = api::v1::issues(db.connection()); server.add(issues.service()); + auto projects = api::v1::projects(db.connection()); + server.add(projects.service()); + auto users = api::v1::users(db.connection()); server.add(users.service()); + auto workspaces = api::v1::workspaces(db.connection()); + server.add(workspaces.service()); + try { spdlog::info("Server started at {}", log::label("127.0.0.1:8080")); server.run("127.0.0.1", 8080); diff --git a/src/projects/CMakeLists.txt b/src/projects/CMakeLists.txt index 10f87df..4f1c9a3 100644 --- a/src/projects/CMakeLists.txt +++ b/src/projects/CMakeLists.txt @@ -1,13 +1,17 @@ add_library(projects) target_sources(projects - PRIVATE project.cpp + PRIVATE + projects_service.cpp + project.cpp PUBLIC FILE_SET headers TYPE HEADERS FILES + projects_service.h project.h ) target_link_libraries(projects PRIVATE ${PROJECT_NAME}::core -) \ No newline at end of file + PUBLIC ${PROJECT_NAME}::libproto +) diff --git a/src/projects/projects_service.cpp b/src/projects/projects_service.cpp new file mode 100644 index 0000000..ef4603e --- /dev/null +++ b/src/projects/projects_service.cpp @@ -0,0 +1,162 @@ +#include "projects_service.h" +#include "project.h" +#include "google/rpc/code.pb.h" + +struct projects_queries { + constexpr static std::string_view get_all = R"( + select id, workspace_id, name + from projects; + )"; + + constexpr static std::string_view get = R"( + select id, workspace_id, name + from projects + where id = ?; + )"; + + constexpr static std::string_view exists = R"( + select count(*) + from projects + where id = ?; + )"; + + constexpr static std::string_view check_unique = R"( + select id + from projects + where name = ? and workspace_id = ?; + )"; + + constexpr static std::string_view create = R"( + insert into projects(id, workspace_id, name) + values (?, ?, ?); + )"; + + constexpr static std::string_view change_name = R"( + update projects set name = ? + where id = ?; + )"; + + constexpr static std::string_view delete_project = R"( + delete from projects + where id = ?; + )"; +}; + +namespace api::projects { + Rpc::Rpc(sqlite::database db) : rpc_t(), db_{std::move(db)} {} + + template<> + rpcGetAll::result_type Rpc::call(grpcxx::context &, rpcGetAll::request_type const &req) { + auto response = reasy::api::v1::ProjectsGetAllResponse(); + + for (auto &&row: db_ << projects_queries::get_all) { + map(row, response.add_projects()); + } + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcGet::result_type Rpc::call(grpcxx::context &, rpcGet::request_type const &req) { + if (req.id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto response = reasy::api::v1::Project(); + auto found = false; + for (auto row: db_ << projects_queries::get << req.id()) { + map(row, &response); + found = true; + break; + } + + if (!found) { + return {grpcxx::status::code_t::not_found}; + } + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcCreate::result_type Rpc::call(grpcxx::context &, rpcCreate::request_type const &req) { + if (req.name().empty() || req.workspace_id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto id = project::id(); + for (auto &&row: db_ << projects_queries::check_unique << req.name() << req.workspace_id()) { + return {grpcxx::status::code_t::already_exists}; + } + + db_ << "begin;"; + db_ << projects_queries::create << id.val().string() << req.workspace_id() << req.name(); + db_ << "commit;"; + + auto response = reasy::api::v1::Project(); + response.set_id(id.val().string()); + response.set_workspace_id(req.workspace_id()); + response.set_name(req.name()); + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcChangeName::result_type Rpc::call(grpcxx::context &, rpcChangeName::request_type const &req) { + if (req.id().empty() || req.name().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto rows_count = 0; + db_ << projects_queries::exists << req.id() >> rows_count; + + if (rows_count == 0) { + return {grpcxx::status::code_t::not_found}; + } + + db_ << "begin;"; + db_ << projects_queries::change_name << req.name() << req.id(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok}; + } + + template<> + rpcDelete::result_type Rpc::call(grpcxx::context &, rpcDelete::request_type const &req) { + if (req.id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto rows_count = 0; + db_ << projects_queries::exists << req.id() >> rows_count; + + if (rows_count == 0) { + return {grpcxx::status::code_t::not_found}; + } + + db_ << "begin;"; + db_ << projects_queries::delete_project << req.id(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok}; + } + + google::rpc::Status Rpc::exception() noexcept { + google::rpc::Status status; + status.set_code(google::rpc::UNKNOWN); + + try { + std::rethrow_exception(std::current_exception()); + } catch (std::exception const &e) { + status.set_code(google::rpc::INTERNAL); + status.set_message(e.what()); + } + + return status; + } + + void Rpc::map(row_t const &from, reasy::api::v1::Project *const to) noexcept { + to->set_id(get<0>(from)); + to->set_workspace_id(get<1>(from)); + to->set_name(get<2>(from)); + } +} \ No newline at end of file diff --git a/src/projects/projects_service.h b/src/projects/projects_service.h new file mode 100644 index 0000000..979b33a --- /dev/null +++ b/src/projects/projects_service.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "reasy/api/v1/projects.grpcxx.pb.h" +#include "api/rpc.h" + +namespace api::projects { + using namespace reasy::api::v1::projects; + using row_t = std::tuple; + + class Rpc : public rpc_t { + private: + sqlite::database db_; + + static void map(row_t const &from, reasy::api::v1::Project *to) noexcept; + + public: + explicit Rpc(sqlite::database db); + + ~Rpc() override = default; + + using service_t = Service; + + template + T::result_type call(grpcxx::context &, T::request_type const &) { + return {grpcxx::status::code_t::unimplemented, std::nullopt}; + } + + template<> + rpcGetAll::result_type call(grpcxx::context &, rpcGetAll::request_type const &); + + template<> + rpcGet::result_type call(grpcxx::context &, rpcGet::request_type const &); + + template<> + rpcCreate::result_type call(grpcxx::context &, rpcCreate::request_type const &); + + template<> + rpcChangeName::result_type call(grpcxx::context &, rpcChangeName::request_type const &); + + template<> + rpcDelete::result_type call(grpcxx::context &, rpcDelete::request_type const &); + + google::rpc::Status exception() noexcept override; + }; +} \ No newline at end of file diff --git a/src/users/users_service.cpp b/src/users/users_service.cpp index b1cd045..a8de641 100644 --- a/src/users/users_service.cpp +++ b/src/users/users_service.cpp @@ -1,4 +1,5 @@ #include +#include #include "users_service.h" #include "user.h" @@ -6,6 +7,35 @@ struct users_queries { constexpr static std::string_view get_all = R"( select id, name, username, email from users; )"; + + constexpr static std::string_view get = R"( + select id, name, username, email + from users + where id = ?; + )"; + + constexpr static std::string_view get_by_email = R"( + select id + from users + where email = ?; + )"; + + constexpr static std::string_view create = R"( + insert into users (id, name, username, email) + values (?, ?, ?, ?); + )"; + + constexpr static std::string_view update_name = R"( + update users set name = ? where id = ?; + )"; + + constexpr static std::string_view update_username = R"( + update users set username = ? where id = ?; + )"; + + constexpr static std::string_view delete_user = R"( + delete from users where id = ?; + )"; }; namespace api::users { @@ -24,12 +54,125 @@ namespace api::users { template<> rpcGet::result_type Rpc::call(grpcxx::context &ctx, rpcGet::request_type const &req) { - auto user = User(user::id(), "rizesql", "@rizesql_", "mail@mail.com"); + if (req.id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto response = reasy::api::v1::User(); + auto rows_count = 0uz; + for (row_t row: db_ << users_queries::get << req.id()) { + map(row, &response); + ++rows_count; + break; + } + + if (rows_count == 0) { + return {grpcxx::status::code_t::not_found}; + } + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcCreate::result_type Rpc::call(grpcxx::context &ctx, rpcCreate::request_type const &req) { + if (req.name().empty() || req.username().empty() || req.email().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto id = user::id(); + for (row_t row: db_ << users_queries::get_by_email << req.email()) { + return {grpcxx::status::code_t::already_exists}; + } + + try { + db_ << "begin;"; + db_ << users_queries::create << id.val().string() << req.name() << req.username() << req.email(); + db_ << "commit;"; + } catch (sqlite::sqlite_exception const &e) { + spdlog::error("{}", e.errstr()); + return {grpcxx::status::code_t::already_exists}; + } + + auto response = reasy::api::v1::User(); + response.set_id(id.val().string()); + response.set_name(req.name()); + response.set_username(req.username()); + response.set_email(req.email()); + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcChangeName::result_type Rpc::call(grpcxx::context &ctx, rpcChangeName::request_type const &req) { + if (req.id().empty() || req.new_name().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto found = false; + for (row_t row: db_ << users_queries::get << req.id()) { + found = true; + break; + } + + if (!found) { + return {grpcxx::status::code_t::not_found}; + } + + db_ << "begin;"; + db_ << users_queries::update_name << req.new_name() << req.id(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok}; + } + + template<> + rpcChangeUsername::result_type Rpc::call( + grpcxx::context &ctx, + rpcChangeUsername::request_type const &req) { + if (req.id().empty() || req.new_username().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto found = false; + for (row_t row: db_ << users_queries::get << req.id()) { + found = true; + break; + } + + if (!found) { + return {grpcxx::status::code_t::not_found}; + } + + db_ << "begin;"; + db_ << users_queries::update_username << req.new_username() << req.id(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok}; + } + + template<> + rpcDelete::result_type Rpc::call( + grpcxx::context &ctx, + rpcDelete::request_type const &req) { + if (req.id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto found = false; + for (row_t row: db_ << users_queries::get << req.id()) { + found = true; + break; + } + + if (!found) { + return {grpcxx::status::code_t::not_found}; + } - rpcGet::response_type res; - res.set_id(user.id().val()); + db_ << "begin;"; + db_ << users_queries::delete_user << req.id(); + db_ << "commit;"; - return {grpcxx::status::code_t::ok, res}; + return {grpcxx::status::code_t::ok}; } google::rpc::Status Rpc::exception() noexcept { diff --git a/src/users/users_service.h b/src/users/users_service.h index 74a0de7..2f23ac1 100644 --- a/src/users/users_service.h +++ b/src/users/users_service.h @@ -33,6 +33,22 @@ namespace api::users { template<> rpcGet::result_type call(grpcxx::context &ctx, rpcGet::request_type const &req); + template<> + rpcCreate::result_type call(grpcxx::context &ctx, rpcCreate::request_type const &req); + + template<> + rpcChangeName::result_type call(grpcxx::context &ctx, rpcChangeName::request_type const &req); + + template<> + rpcChangeUsername::result_type call( + grpcxx::context &ctx, + rpcChangeUsername::request_type const &req); + + template<> + rpcDelete::result_type call( + grpcxx::context &ctx, + rpcDelete::request_type const &req); + google::rpc::Status exception() noexcept override; }; } diff --git a/src/workspaces/CMakeLists.txt b/src/workspaces/CMakeLists.txt index 5ef9186..fa186a9 100644 --- a/src/workspaces/CMakeLists.txt +++ b/src/workspaces/CMakeLists.txt @@ -1,13 +1,17 @@ add_library(workspaces) target_sources(workspaces - PRIVATE workspace.cpp + PRIVATE + workspaces_service.cpp + workspace.cpp PUBLIC FILE_SET headers TYPE HEADERS FILES + workspaces_service.h workspace.h ) target_link_libraries(workspaces PRIVATE ${PROJECT_NAME}::core -) \ No newline at end of file + PUBLIC ${PROJECT_NAME}::libproto +) diff --git a/src/workspaces/workspaces_service.cpp b/src/workspaces/workspaces_service.cpp new file mode 100644 index 0000000..35a268b --- /dev/null +++ b/src/workspaces/workspaces_service.cpp @@ -0,0 +1,173 @@ +#include "workspaces_service.h" +#include "workspace.h" + +struct workspaces_queries { + constexpr static std::string_view get_all = R"( + select id, admin_id, name from workspaces; + )"; + + constexpr static std::string_view get = R"( + select id, admin_id, name + from workspaces + where id = ?; + )"; + + constexpr static std::string_view exists = R"( + select count(*) + from workspaces + where id = ?; + )"; + + constexpr static std::string_view check_unique = R"( + select id + from workspaces + where name = ?; + )"; + + constexpr static std::string_view create = R"( + insert into workspaces(id, admin_id, name) + values(?, ?, ?); + )"; + + constexpr static std::string_view change_admin_id = R"( + update workspaces + set admin_id = ? + where id = ?; + )"; + + constexpr static std::string_view change_name = R"( + update workspaces + set name = ? + where id = ?; + )"; + + constexpr static std::string_view delete_workspace = R"( + delete from workspaces + where id = ?; + )"; +}; + +namespace api::workspaces { + Rpc::Rpc(sqlite::database db) : rpc_t(), db_{std::move(db)} {} + + template<> + rpcGetAll::result_type Rpc::call(grpcxx::context &, rpcGetAll::request_type const &) { + auto response = reasy::api::v1::WorkspacesGetAllResponse(); + + for (auto &&row: db_ << workspaces_queries::get_all) { + map(row, response.add_workspaces()); + } + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcGet::result_type Rpc::call(grpcxx::context &, rpcGet::request_type const &req) { + if (req.id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto response = reasy::api::v1::Workspace(); + auto found = false; + for (auto row: db_ << workspaces_queries::get << req.id()) { + map(row, &response); + found = true; + break; + } + + if (!found) { + return {grpcxx::status::code_t::not_found}; + } + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcCreate::result_type Rpc::call(grpcxx::context &, rpcCreate::request_type const &req) { + if (req.name().empty() || req.admin_id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto id = workspace::id(); + for (auto &&row: db_ << workspaces_queries::check_unique << req.name()) { + return {grpcxx::status::code_t::already_exists}; + } + + db_ << "begin;"; + db_ << workspaces_queries::create << id.val().string() << req.admin_id() << req.name(); + db_ << "commit;"; + + auto response = reasy::api::v1::Workspace(); + response.set_id(id.val().string()); + response.set_admin_id(req.admin_id()); + response.set_name(req.name()); + + return {grpcxx::status::code_t::ok, response}; + } + + template<> + rpcChangeName::result_type Rpc::call(grpcxx::context &, rpcChangeName::request_type const &req) { + if (req.id().empty() || req.new_name().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto rows_count = 0; + db_ << workspaces_queries::exists << req.id() >> rows_count; + + if (rows_count == 0) { + return {grpcxx::status::code_t::not_found}; + } + + db_ << "begin;"; + db_ << workspaces_queries::change_name << req.new_name() << req.id(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok}; + } + + template<> + rpcChangeAdmin::result_type Rpc::call(grpcxx::context &, rpcChangeAdmin::request_type const &req) { + if (req.id().empty() || req.new_admin_id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto rows_count = 0; + db_ << workspaces_queries::exists << req.id() >> rows_count; + + if (rows_count == 0) { + return {grpcxx::status::code_t::not_found}; + } + + db_ << "begin;"; + db_ << workspaces_queries::change_admin_id << req.new_admin_id() << req.id(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok}; + } + + template<> + rpcDelete::result_type Rpc::call(grpcxx::context &, rpcDelete::request_type const &req) { + if (req.id().empty()) { + return {grpcxx::status::code_t::invalid_argument}; + } + + auto rows_count = 0; + db_ << workspaces_queries::exists << req.id() >> rows_count; + + if (rows_count == 0) { + return {grpcxx::status::code_t::not_found}; + } + + db_ << "begin;"; + db_ << workspaces_queries::delete_workspace << req.id(); + db_ << "commit;"; + + return {grpcxx::status::code_t::ok}; + } + + void Rpc::map(const api::workspaces::row_t &from, reasy::api::v1::Workspace *to) noexcept { + to->set_id(get<0>(from)); + to->set_admin_id(get<1>(from)); + to->set_name(get<2>(from)); + } +} \ No newline at end of file diff --git a/src/workspaces/workspaces_service.h b/src/workspaces/workspaces_service.h new file mode 100644 index 0000000..22a76ea --- /dev/null +++ b/src/workspaces/workspaces_service.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include "reasy/api/v1/workspaces.grpcxx.pb.h" +#include "api/rpc.h" + +namespace api::workspaces { + using namespace reasy::api::v1::workspaces; + using row_t = std::tuple; + + class Rpc : public rpc_t { + private: + sqlite::database db_; + + static void map(row_t const &from, reasy::api::v1::Workspace *to) noexcept; + + public: + explicit Rpc(sqlite::database db); + + ~Rpc() override = default; + + using service_t = Service; + + template + T::result_type call(grpcxx::context &, T::request_type const &) { + return {grpcxx::status::code_t::unimplemented, std::nullopt}; + } + + template<> + rpcGetAll::result_type call(grpcxx::context &, rpcGetAll::request_type const &); + + template<> + rpcGet::result_type call(grpcxx::context &, rpcGet::request_type const &); + + template<> + rpcCreate::result_type call(grpcxx::context &, rpcCreate::request_type const &); + + template<> + rpcChangeName::result_type call(grpcxx::context &, rpcChangeName::request_type const &); + + template<> + rpcChangeAdmin::result_type call(grpcxx::context &, rpcChangeAdmin::request_type const &); + + template<> + rpcDelete::result_type call(grpcxx::context &, rpcDelete::request_type const &); + }; +} \ No newline at end of file