diff --git a/CMakeLists.txt b/CMakeLists.txt index afd434e..47c01ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,30 +7,35 @@ project(${project_name}) set(CMAKE_CXX_STANDARD 11) add_library(${project_name}-lib - src/App.cpp - src/AppComponent.hpp - src/ServiceComponent.hpp - src/SwaggerComponent.hpp - src/controller/UserController.cpp src/controller/UserController.hpp - src/db/Database.cpp - src/db/Database.hpp + src/db/UserDb.hpp src/dto/ConfigDto.hpp - src/dto/ErrorDto.hpp + src/dto/PageDto.hpp + src/dto/StatusDto.hpp src/dto/UserDto.hpp + src/service/UserService.cpp + src/service/UserService.hpp + src/AppComponent.hpp + src/DatabaseComponent.hpp + src/ErrorHandler.cpp + src/ErrorHandler.hpp + src/ServiceComponent.hpp + src/SwaggerComponent.hpp ) target_include_directories(${project_name}-lib PUBLIC src) ## link libs -find_package(oatpp 0.19.9 REQUIRED) -find_package(oatpp-swagger 0.19.9 REQUIRED) +find_package(oatpp 1.3.0 REQUIRED) +find_package(oatpp-swagger 1.3.0 REQUIRED) +find_package(oatpp-postgresql 1.3.0 REQUIRED) target_link_libraries(${project_name}-lib PUBLIC oatpp::oatpp PUBLIC oatpp::oatpp-test PUBLIC oatpp::oatpp-swagger + PUBLIC oatpp::oatpp-postgresql ) add_definitions( @@ -39,35 +44,12 @@ add_definitions( # Path to config file # -DCONFIG_PATH="${CMAKE_CURRENT_LIST_DIR}/resources/config.json" -) - -################################################################# -## link postgresql client -include(FindPkgConfig) - -set(ENV{PKG_CONFIG_PATH} "/usr/local/pgsql/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") ## change this if needed - -pkg_check_modules(PKG_PQ REQUIRED libpq) - -message("PKG_PQ_INCLUDE_DIRS=${PKG_PQ_INCLUDE_DIRS}") -message("PKG_PQ_LIBRARY_DIRS=${PKG_PQ_LIBRARY_DIRS}") -message("PKG_PQ_LIBRARIES=${PKG_PQ_LIBRARIES}") - -target_include_directories(${project_name}-lib - PUBLIC ${PKG_PQ_INCLUDE_DIRS} -) - -link_directories( - ${PKG_PQ_LIBRARY_DIRS} -) - -target_link_libraries(${project_name}-lib - PUBLIC ${PKG_PQ_LIBRARIES} + ## Path to database migration scripts + -DDATABASE_MIGRATIONS="${CMAKE_CURRENT_SOURCE_DIR}/sql" ) ################################################################# - ## add executables add_executable(${project_name}-exe diff --git a/README.md b/README.md index 835605c..d5caf61 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # example-postgresql [![Build Status](https://dev.azure.com/lganzzzo/lganzzzo/_apis/build/status/oatpp.example-postgresql?branchName=master)](https://dev.azure.com/lganzzzo/lganzzzo/_build/latest?definitionId=17&branchName=master) -Example of a production grade entity service storing information in PostgreSQL. With Swagger-UI and configuration profiles. -*Libpq is used to communicate with PostgreSQL database.* -*Dockerfile and docker-compose.yaml files included.* +A complete example of a "CRUD" service (UserService) built with Oat++ and using oatpp ORM with PostgreSQL. -See more: + +In this example: + +- How to create CRUD endpoints. +- How to use [oatpp ORM](https://oatpp.io/docs/components/orm/) - PostgreSQL example. +- How to document API with Swagger-UI and OpenApi 3.0.0. + +More about Oat++: - [Oat++ Website](https://oatpp.io/) - [Oat++ Github Repository](https://github.com/oatpp/oatpp) @@ -12,7 +17,11 @@ See more: ## Overview -This project is using [oatpp](https://github.com/oatpp/oatpp) and [oatpp-swagger](https://github.com/oatpp/oatpp-swagger) modules. +This project is using the following oatpp modules: + +- [oatpp](https://github.com/oatpp/oatpp) +- [oatpp-swagger](https://github.com/oatpp/oatpp-swagger) +- [oatpp-postgresql](https://github.com/oatpp/oatpp-postgresql) ### Project layout @@ -21,11 +30,13 @@ This project is using [oatpp](https://github.com/oatpp/oatpp) and [oatpp-swagger |- src/ | | | |- controller/ // Folder containing Controller where all endpoints are declared -| |- db/ // Database class is here +| |- db/ // Folder containing the database client | |- dto/ // DTOs are declared here +| |- service/ // Service business logic classes (UserService) | |- ServiceComponent.hpp // Service configuration (port, ObjectMapper, Database) | |- SwaggerComponent.hpp // Configuration for swagger-ui | |- AppComponent.hpp // Service configuration is loaded here +| |- DatabaseComponent.hpp // Database config | |- App.cpp // main() is here | |- test/ // test folder @@ -43,12 +54,12 @@ This project is using [oatpp](https://github.com/oatpp/oatpp) and [oatpp-swagger **Requires** -- libpq installed. To install libpq: - - On Mac `$ brew install libpq` +- This example also requires the PostgreSQL package installed. - On Alpine `$ apk add postgresql-dev` - - On Ubuntu - goto [Install PostgreSQL Client From Sources](#install-postgresql-client-from-sources) + - On Ubuntu `$ apt-get install postgresql-server-dev-all` -- `oatpp` and `oatpp-swagger` modules installed. You may run `utility/install-oatpp-modules.sh` + For more info see [oatpp-postgresql/README.md](https://github.com/oatpp/oatpp-postgresql/blob/master/README.md) +- `oatpp`, `oatpp-swagger` and `oatpp-postgresql` modules installed. You may run `utility/install-oatpp-modules.sh` script to install required oatpp modules. ``` @@ -81,29 +92,3 @@ $ docker-compose up Go to [http://localhost:8000/swagger/ui](http://localhost:8000/swagger/ui) to try endpoints. -## Install PostgreSQL Client From Sources - -- Download sources from [https://www.postgresql.org/ftp/source/](https://www.postgresql.org/ftp/source/) - ``` - $ wget https://ftp.postgresql.org/pub/source/v11.1/postgresql-11.1.tar.gz - ``` - -- Untar - ``` - $ tar -xvzf postgresql-11.1.tar.gz - ``` - -- CD to postgresql-11.1, configure, make: - ``` - $ cd postgresql-11.1 - $ ./configure - $ make - ``` -- Install PostgreSQL client-only - *For this particular example we don't need full PostgreSQL installation.* - ``` - $ make -C src/include install - $ make -C src/interfaces install - ``` - -For more information see [PostgreSQL installation guide](https://www.postgresql.org/docs/11/install-procedure.html#INSTALL) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e485078..bef3a10 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,28 +4,17 @@ # https://aka.ms/yaml jobs: - - job: ubuntu_16_04 - displayName: 'Build - Ubuntu 16.04' + - job: ubuntu_20_04 + displayName: 'Build - Ubuntu 20.04' continueOnError: false pool: - vmImage: 'Ubuntu 16.04' - container: - image: lganzzzo/ubuntu-cmake-postgresql:latest + vmImage: 'ubuntu-20.04' workspace: clean: all steps: - script: | - sudo ./install-oatpp-modules.sh - displayName: 'install oatpp modules' - workingDirectory: utility + docker-compose -f ci.compose.yaml build + displayName: 'Compose build' - script: | - mkdir build - - script: | - cmake .. - sudo make - displayName: 'CMake' - workingDirectory: build - - script: | - make test ARGS="-V" - displayName: 'Test' - workingDirectory: build + docker-compose -f ci.compose.yaml run test + displayName: 'Compose run' \ No newline at end of file diff --git a/ci.compose.yaml b/ci.compose.yaml new file mode 100644 index 0000000..429c5ab --- /dev/null +++ b/ci.compose.yaml @@ -0,0 +1,21 @@ +version: '3' + +services: + db: + image: postgres + restart: always + environment: + POSTGRES_PASSWORD: db-pass + ports: + - 5432:5432 + + test: + build: + context: . + dockerfile: ci.dockerfile + ports: + - "8000:8000" + depends_on: + - db + environment: + CONFIG_PROFILE: local-docker diff --git a/ci.dockerfile b/ci.dockerfile new file mode 100644 index 0000000..cd730db --- /dev/null +++ b/ci.dockerfile @@ -0,0 +1,18 @@ +FROM lganzzzo/alpine-cmake:latest + +RUN apk add postgresql-dev + +ADD . /service + +WORKDIR /service/utility + +RUN ./install-oatpp-modules.sh + +WORKDIR /service/build + +RUN cmake .. +RUN make + +EXPOSE 8000 8000 + +ENTRYPOINT ["./example-postgresql-test"] diff --git a/docker-compose.yaml b/docker-compose.yaml index 2263d27..8857bcd 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,11 +6,14 @@ services: restart: always environment: POSTGRES_PASSWORD: db-pass - example-postgresql: + ports: + - 5432:5432 + + service: build: . ports: - "8000:8000" - links: - - "db" + depends_on: + - db environment: CONFIG_PROFILE: local-docker diff --git a/resources/config.json b/resources/config.json index 228473c..c8ee385 100644 --- a/resources/config.json +++ b/resources/config.json @@ -1,18 +1,14 @@ { "dev": { + "host": "0.0.0.0", "port": 8000, "swaggerHost": "localhost:8000", - "dbHost": "localhost", - "dbUser": "postgres", - "dbPass": "db-pass", - "dbName": "postgres" + "dbConnectionString": "postgresql://postgres:db-pass@localhost:5432/postgres" }, "local-docker": { + "host": "0.0.0.0", "port": 8000, "swaggerHost": "localhost:8000", - "dbHost": "db", - "dbUser": "postgres", - "dbPass": "db-pass", - "dbName": "postgres" + "dbConnectionString": "postgresql://postgres:db-pass@db:5432/postgres" } } diff --git a/sql/001_init.sql b/sql/001_init.sql new file mode 100644 index 0000000..d40cdef --- /dev/null +++ b/sql/001_init.sql @@ -0,0 +1,15 @@ + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE TABLE IF NOT EXISTS AppUser ( + id varchar (256) PRIMARY KEY, + username varchar (256) NOT NULL, + email varchar (256) NOT NULL, + password varchar (256) NOT NULL, + role varchar (256) NULL, + CONSTRAINT UK_APPUSER_USERNAME UNIQUE (username), + CONSTRAINT UK_APPUSER_EMAIL UNIQUE (email) +); + +INSERT INTO AppUser +(id, username, email, password, role) VALUES (uuid_generate_v4(), 'admin', 'admin@domain.com', 'admin', 'ROLE_ADMIN'); diff --git a/src/App.cpp b/src/App.cpp index 8360a24..ab35356 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -1,52 +1,36 @@ -// -// main.cpp -// web-starter-project -// -// Created by Leonid on 2/12/18. -// Copyright © 2018 oatpp. All rights reserved. -// -#include "./controller/UserController.hpp" -#include "./AppComponent.hpp" -#include "./ServiceComponent.hpp" -#include "./SwaggerComponent.hpp" - -#include "oatpp/network/server/Server.hpp" +#include "controller/UserController.hpp" +#include "AppComponent.hpp" +#include "DatabaseComponent.hpp" +#include "ServiceComponent.hpp" +#include "SwaggerComponent.hpp" #include "oatpp-swagger/Controller.hpp" -#include +#include "oatpp/network/Server.hpp" +#include -/** - * run() method. - * 1) set Environment components. - * 2) add ApiController's endpoints to router - * 3) run server - */ void run(const oatpp::base::CommandLineArguments& args) { AppComponent appComponent(args); ServiceComponent serviceComponent; SwaggerComponent swaggerComponent; + DatabaseComponent databaseComponent; /* create ApiControllers and add endpoints to router */ auto router = serviceComponent.httpRouter.getObject(); - auto docEndpoints = oatpp::swagger::Controller::Endpoints::createShared(); + oatpp::web::server::api::Endpoints docEndpoints; - auto userController = UserController::createShared(); - userController->addEndpointsToRouter(router); + docEndpoints.append(router->addController(UserController::createShared())->getEndpoints()); - docEndpoints->pushBackAll(userController->getEndpoints()); // Add userController to swagger - - auto swaggerController = oatpp::swagger::Controller::createShared(docEndpoints); - swaggerController->addEndpointsToRouter(router); + router->addController(oatpp::swagger::Controller::createShared(docEndpoints)); /* create server */ - oatpp::network::server::Server server(serviceComponent.serverConnectionProvider.getObject(), - serviceComponent.serverConnectionHandler.getObject()); + oatpp::network::Server server(serviceComponent.serverConnectionProvider.getObject(), + serviceComponent.serverConnectionHandler.getObject()); OATPP_LOGD("Server", "Running on port %s...", serviceComponent.serverConnectionProvider.getObject()->getProperty("port").toString()->c_str()); @@ -54,21 +38,12 @@ void run(const oatpp::base::CommandLineArguments& args) { } -/** - * main - */ int main(int argc, const char * argv[]) { oatpp::base::Environment::init(); run(oatpp::base::CommandLineArguments(argc, argv)); - /* Print how much objects were created during app running, and what have left-probably leaked */ - /* Disable object counting for release builds using '-D OATPP_DISABLE_ENV_OBJECT_COUNTERS' flag for better performance */ - std::cout << "\nEnvironment:\n"; - std::cout << "objectsCount = " << oatpp::base::Environment::getObjectsCount() << "\n"; - std::cout << "objectsCreated = " << oatpp::base::Environment::getObjectsCreated() << "\n\n"; - oatpp::base::Environment::destroy(); return 0; diff --git a/src/AppComponent.hpp b/src/AppComponent.hpp index a311fed..bbfd3fa 100644 --- a/src/AppComponent.hpp +++ b/src/AppComponent.hpp @@ -1,10 +1,3 @@ -// -// AppComponent.hpp -// file-service -// -// Created by Leonid on 8/13/18. -// Copyright © 2018 oatpp. All rights reserved. -// #ifndef AppComponent_hpp #define AppComponent_hpp @@ -30,15 +23,15 @@ class AppComponent { /** * This should be configured through config-server ex. Consul */ - OATPP_CREATE_COMPONENT(ConfigDto::ObjectWrapper, config)([this] { + OATPP_CREATE_COMPONENT(oatpp::Object, config)([this] { const char* configPath = CONFIG_PATH; auto objectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared(); - oatpp::String configText = oatpp::base::StrBuffer::loadFromFile(configPath); + oatpp::String configText = oatpp::String::loadFromFile(configPath); if (configText) { - auto profiles = objectMapper->readFromString>(configText); + auto profiles = objectMapper->readFromString>>(configText); const char *profileArg = std::getenv("CONFIG_PROFILE"); // first read from env variable if (profileArg == nullptr) { @@ -47,7 +40,7 @@ class AppComponent { OATPP_LOGD("Server", "Loading configuration profile '%s'", profileArg); - auto profile = profiles->get(profileArg, nullptr); + auto profile = profiles.getValueByKey(profileArg, nullptr); if(!profile) { throw std::runtime_error("No configuration profile found. Server won't run."); } diff --git a/src/DatabaseComponent.hpp b/src/DatabaseComponent.hpp new file mode 100644 index 0000000..562a875 --- /dev/null +++ b/src/DatabaseComponent.hpp @@ -0,0 +1,37 @@ + +#ifndef EXAMPLE_POSTGRESQL_DATABASECOMPONENT_HPP +#define EXAMPLE_POSTGRESQL_DATABASECOMPONENT_HPP + +#include "db/UserDb.hpp" +#include "dto/ConfigDto.hpp" + +class DatabaseComponent { +public: + + /** + * Create database client + */ + OATPP_CREATE_COMPONENT(std::shared_ptr, userDb)([] { + + OATPP_COMPONENT(oatpp::Object, config); // Get config component + + /* Create database-specific ConnectionProvider */ + auto connectionProvider = std::make_shared(config->dbConnectionString); + + /* Create database-specific ConnectionPool */ + auto connectionPool = oatpp::postgresql::ConnectionPool::createShared(connectionProvider, + 10 /* max-connections */, + std::chrono::seconds(5) /* connection TTL */); + + /* Create database-specific Executor */ + auto executor = std::make_shared(connectionPool); + + /* Create MyClient database client */ + return std::make_shared(executor); + + }()); + + +}; + +#endif //EXAMPLE_POSTGRESQL_DATABASECOMPONENT_HPP diff --git a/src/ErrorHandler.cpp b/src/ErrorHandler.cpp new file mode 100644 index 0000000..b07cff3 --- /dev/null +++ b/src/ErrorHandler.cpp @@ -0,0 +1,24 @@ + +#include "ErrorHandler.hpp" + +ErrorHandler::ErrorHandler(const std::shared_ptr& objectMapper) + : m_objectMapper(objectMapper) +{} + +std::shared_ptr +ErrorHandler::handleError(const Status& status, const oatpp::String& message, const Headers& headers) { + + auto error = StatusDto::createShared(); + error->status = "ERROR"; + error->code = status.code; + error->message = message; + + auto response = ResponseFactory::createResponse(status, error, m_objectMapper); + + for(const auto& pair : headers.getAll()) { + response->putHeader(pair.first.toString(), pair.second.toString()); + } + + return response; + +} \ No newline at end of file diff --git a/src/ErrorHandler.hpp b/src/ErrorHandler.hpp new file mode 100644 index 0000000..2af8deb --- /dev/null +++ b/src/ErrorHandler.hpp @@ -0,0 +1,27 @@ + +#ifndef EXAMPLE_POSTGRESQL_ERRORHANDLER_HPP +#define EXAMPLE_POSTGRESQL_ERRORHANDLER_HPP + +#include "dto/StatusDto.hpp" + +#include "oatpp/web/server/handler/ErrorHandler.hpp" +#include "oatpp/web/protocol/http/outgoing/ResponseFactory.hpp" + +class ErrorHandler : public oatpp::web::server::handler::ErrorHandler { +private: + typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse; + typedef oatpp::web::protocol::http::Status Status; + typedef oatpp::web::protocol::http::outgoing::ResponseFactory ResponseFactory; +private: + std::shared_ptr m_objectMapper; +public: + + ErrorHandler(const std::shared_ptr& objectMapper); + + std::shared_ptr + handleError(const Status& status, const oatpp::String& message, const Headers& headers) override; + +}; + + +#endif //EXAMPLE_POSTGRESQL_ERRORHANDLER_HPP diff --git a/src/ServiceComponent.hpp b/src/ServiceComponent.hpp index ddf42ea..92e3123 100644 --- a/src/ServiceComponent.hpp +++ b/src/ServiceComponent.hpp @@ -1,78 +1,36 @@ -// -// AppComponent.hpp -// oatpp-web-starter -// -// Created by Leonid on 3/2/18. -// Copyright © 2018 lganzzzo. All rights reserved. -// #ifndef ServiceComponent_hpp #define ServiceComponent_hpp #include "dto/ConfigDto.hpp" - -#include "db/Database.hpp" +#include "ErrorHandler.hpp" #include "oatpp/web/server/HttpConnectionHandler.hpp" #include "oatpp/web/server/HttpRouter.hpp" -#include "oatpp/network/server/SimpleTCPConnectionProvider.hpp" +#include "oatpp/network/tcp/server/ConnectionProvider.hpp" #include "oatpp/parser/json/mapping/ObjectMapper.hpp" - #include "oatpp/core/macro/component.hpp" class ServiceComponent { -public: - - class ErrorHandler : public oatpp::web::server::handler::ErrorHandler { - public: - typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse; - typedef oatpp::web::protocol::http::Status Status; - typedef oatpp::web::protocol::http::outgoing::ResponseFactory ResponseFactory; - private: - std::shared_ptr m_objectMapper; - public: - - ErrorHandler(const std::shared_ptr& objectMapper) - : m_objectMapper(objectMapper) - {} - - OATPP_COMPONENT(std::shared_ptr, objectMapper); - - - std::shared_ptr handleError(const Status& status, const oatpp::String& message, const Headers& headers) override { - - auto error = ErrorDto::createShared(); - error->code = 500; - error->error = "Unhandled Error"; - error->message = message; - - auto response = ResponseFactory::createResponse(Status::CODE_500, error, m_objectMapper.get()); - - for(auto& pair : headers) { - response->putHeader(pair.first, pair.second); - } - - return response; - } - - }; - public: /** * Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API */ OATPP_CREATE_COMPONENT(std::shared_ptr, apiObjectMapper)([] { - return oatpp::parser::json::mapping::ObjectMapper::createShared(); + auto mapper = oatpp::parser::json::mapping::ObjectMapper::createShared(); + mapper->getSerializer()->getConfig()->useBeautifier = true; + mapper->getSerializer()->getConfig()->escapeFlags = 0; + return mapper; }()); /** * Create ConnectionProvider component which listens on the port */ OATPP_CREATE_COMPONENT(std::shared_ptr, serverConnectionProvider)([] { - OATPP_COMPONENT(ConfigDto::ObjectWrapper, config); // Get config component - return oatpp::network::server::SimpleTCPConnectionProvider::createShared(config->port); + OATPP_COMPONENT(oatpp::Object, config); // Get config component + return oatpp::network::tcp::server::ConnectionProvider::createShared({"0.0.0.0", config->port, oatpp::network::Address::IP_4}); }()); /** @@ -85,21 +43,14 @@ class ServiceComponent { /** * Create ConnectionHandler component which uses Router component to route requests */ - OATPP_CREATE_COMPONENT(std::shared_ptr, serverConnectionHandler)([] { + OATPP_CREATE_COMPONENT(std::shared_ptr, serverConnectionHandler)([] { OATPP_COMPONENT(std::shared_ptr, router); // get Router component OATPP_COMPONENT(std::shared_ptr, objectMapper); // get ObjectMapper component + auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router); connectionHandler->setErrorHandler(std::make_shared(objectMapper)); return connectionHandler; }()); - - OATPP_CREATE_COMPONENT(std::shared_ptr, database)([] { - OATPP_COMPONENT(ConfigDto::ObjectWrapper, config); // Get config component - auto db = std::make_shared(config->dbHost, config->dbUser, config->dbPass, config->dbName); - db->connect(); - db->init(); - return db; - }()); }; diff --git a/src/SwaggerComponent.hpp b/src/SwaggerComponent.hpp index a8ede88..21cfba6 100644 --- a/src/SwaggerComponent.hpp +++ b/src/SwaggerComponent.hpp @@ -1,10 +1,3 @@ -// -// SwaggerComponent.hpp -// file-service -// -// Created by Leonid on 8/13/18. -// Copyright © 2018 oatpp. All rights reserved. -// #ifndef SwaggerComponent_hpp #define SwaggerComponent_hpp @@ -23,7 +16,7 @@ class SwaggerComponent { * General API docs info */ OATPP_CREATE_COMPONENT(std::shared_ptr, swaggerDocumentInfo)([] { - OATPP_COMPONENT(ConfigDto::ObjectWrapper, config); // Get config component + OATPP_COMPONENT(oatpp::Object, config); // Get config component oatpp::swagger::DocumentInfo::Builder builder; diff --git a/src/controller/UserController.cpp b/src/controller/UserController.cpp deleted file mode 100644 index 2bc9568..0000000 --- a/src/controller/UserController.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// -// UserController.cpp -// user-service -// -// Created by Leonid on 12/21/18. -// Copyright © 2018 lganzzzo. All rights reserved. -// - -#include "UserController.hpp" - -#include - -void UserController::assertUid(const oatpp::String& uid) { - - OATPP_ASSERT_HTTP(uid && uid->getSize() > 0, Status::CODE_400, "UID can't be empty"); - - for(v_int32 i = 0; i < uid->getSize(); i++) { - v_char8 a = uid->getData()[i]; - bool validChar = (a >= 'a' && a <= 'z') || (a >= '0' && a <= '9') || a == '-'; - if(!validChar) { - throw std::runtime_error("Valid characters for UID are: [a-z], [0-9], and symbols: -"); - } - } - -} - -void UserController::assertLogin(const oatpp::String& login) { - - OATPP_ASSERT_HTTP(login && login->getSize() > 0, Status::CODE_400, "Login can't be empty"); - - for(v_int32 i = 0; i < login->getSize(); i++) { - v_char8 a = login->getData()[i]; - bool validChar = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || (a >= '0' && a <= '9'); - if(!validChar) { - throw std::runtime_error("Valid characters for login are: [a-z], [A-Z], [0-9]"); - } - } - -} - -void UserController::assertEmail(const oatpp::String& email) { - - OATPP_ASSERT_HTTP(email && email->getSize() > 0, Status::CODE_400, "Email can't be empty"); - - for(v_int32 i = 0; i < email->getSize(); i++) { - v_char8 a = email->getData()[i]; - bool validChar = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || (a >= '0' && a <= '9') || a == '@' || a == '.' || a == '_' || a == '-'; - if(!validChar) { - throw std::runtime_error("Valid characters for email are: [a-z], [A-Z], [0-9], and symbols: @._-"); - } - } - -} - -void UserController::assertPassword(const oatpp::String& password) { - - OATPP_ASSERT_HTTP(password && password->getSize() > 0, Status::CODE_400, "Password can't be empty"); - - for(v_int32 i = 0; i < password->getSize(); i++) { - v_char8 a = password->getData()[i]; - bool validChar = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || (a >= '0' && a <= '9') || a == '!' || a == '.' || a == '_' || a == '-'; - if(!validChar) { - throw std::runtime_error("Valid characters for password are: [a-z], [A-Z], [0-9], and symbols: !._-"); - } - } - -} - diff --git a/src/controller/UserController.hpp b/src/controller/UserController.hpp index 55d4d9c..33108e4 100644 --- a/src/controller/UserController.hpp +++ b/src/controller/UserController.hpp @@ -1,126 +1,115 @@ -// -// UserController.hpp -// web-starter-project -// -// Created by Leonid on 2/12/18. -// Copyright © 2018 oatpp. All rights reserved. -// #ifndef UserController_hpp #define UserController_hpp -#include "db/Database.hpp" - -#include "dto/ErrorDto.hpp" -#include "dto/ConfigDto.hpp" - -#include "oatpp-swagger/Types.hpp" +#include "service/UserService.hpp" #include "oatpp/web/server/api/ApiController.hpp" #include "oatpp/parser/json/mapping/ObjectMapper.hpp" #include "oatpp/core/macro/codegen.hpp" -#include "oatpp/core/macro/component.hpp" -#include -#include +#include OATPP_CODEGEN_BEGIN(ApiController) //<- Begin Codegen /** - * EXAMPLE ApiController - * Basic examples of howto create ENDPOINTs - * More details on oatpp.io + * User REST controller. */ class UserController : public oatpp::web::server::api::ApiController { -protected: +public: UserController(const std::shared_ptr& objectMapper) : oatpp::web::server::api::ApiController(objectMapper) {} private: - OATPP_COMPONENT(ConfigDto::ObjectWrapper, config); // Inject config - OATPP_COMPONENT(std::shared_ptr, database); // Inject database -private: - void assertUid(const oatpp::String& uid); - void assertLogin(const oatpp::String& login); - void assertEmail(const oatpp::String& email); - void assertPassword(const oatpp::String& password); + UserService m_userService; // Create user service. public: - - /** - * Inject @objectMapper component here as default parameter - * Do not return bare Controllable* object! use shared_ptr! - */ - static std::shared_ptr createShared(OATPP_COMPONENT(std::shared_ptr, objectMapper)){ - return std::shared_ptr(new UserController(objectMapper)); + + static std::shared_ptr createShared( + OATPP_COMPONENT(std::shared_ptr, objectMapper) // Inject objectMapper component here as default parameter + ){ + return std::make_shared(objectMapper); } - /** - * Begin ENDPOINTs generation ('ApiController' codegen) - */ -#include OATPP_CODEGEN_BEGIN(ApiController) - ENDPOINT_INFO(createUser) { info->summary = "Create new User"; - info->addConsumes("application/json"); - info->addResponse(Status::CODE_200, "application/json"); - info->addResponse(Status::CODE_500, "application/json"); + + info->addConsumes>("application/json"); + + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_500, "application/json"); + info->addResponse>(Status::CODE_500, "application/json"); } - ENDPOINT("POST", "/users", createUser, - BODY_DTO(UserDto::ObjectWrapper, user)) { - assertLogin(user->login); - assertEmail(user->email); - assertPassword(user->password); - auto createdUser = database->createUser(user); - OATPP_ASSERT_HTTP(createdUser, Status::CODE_500, "User was not created"); - return createDtoResponse(Status::CODE_200, createdUser); + ENDPOINT("POST", "users", createUser, + BODY_DTO(Object, userDto)) + { + return createDtoResponse(Status::CODE_200, m_userService.createUser(userDto)); } - ENDPOINT_INFO(getUserByUid) { - info->summary = "Get user by UID"; - info->addResponse(Status::CODE_200, "application/json"); - info->addResponse(Status::CODE_500, "application/json"); + ENDPOINT_INFO(putUser) { + info->summary = "Update User by userId"; + + info->addConsumes>("application/json"); + + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_404, "application/json"); + info->addResponse>(Status::CODE_500, "application/json"); + + info->pathParams["userId"].description = "User Identifier"; } - ENDPOINT("GET", "/users/uid/{userId}", getUserByUid, - PATH(String, userId)) { - assertUid(userId); - auto user = database->getUserByUid(userId); - OATPP_ASSERT_HTTP(user, Status::CODE_404, "User not found"); - return createDtoResponse(Status::CODE_200, user); + ENDPOINT("PUT", "users/{userId}", putUser, + PATH(String, userId), + BODY_DTO(Object, userDto)) + { + userDto->id = userId; + return createDtoResponse(Status::CODE_200, m_userService.updateUser(userDto)); } - ENDPOINT_INFO(getUserByLogin) { - info->summary = "Get user by Login"; - info->addResponse(Status::CODE_200, "application/json"); - info->addResponse(Status::CODE_500, "application/json"); + ENDPOINT_INFO(getUserById) { + info->summary = "Get one User by userId"; + + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_404, "application/json"); + info->addResponse>(Status::CODE_500, "application/json"); + + info->pathParams["userId"].description = "User Identifier"; } - ENDPOINT("GET", "/users/login/{login}", getUserByLogin, - PATH(String, login)) { - assertLogin(login); - auto user = database->getUserByLogin(login); - OATPP_ASSERT_HTTP(user, Status::CODE_404, "User not found"); - return createDtoResponse(Status::CODE_200, user); + ENDPOINT("GET", "users/{userId}", getUserById, + PATH(String, userId)) + { + return createDtoResponse(Status::CODE_200, m_userService.getUserById(userId)); } - ENDPOINT_INFO(getUserByEmail) { - info->summary = "Get user by Email"; - info->addResponse(Status::CODE_200, "application/json"); - info->addResponse(Status::CODE_500, "application/json"); + ENDPOINT_INFO(getUsers) { + info->summary = "get all stored users"; + + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_500, "application/json"); } - ENDPOINT("GET", "/users/email/{email}", getUserByEmail, - PATH(String, email)) { - assertEmail(email); - auto user = database->getUserByEmail(email); - OATPP_ASSERT_HTTP(user, Status::CODE_404, "User not found"); - return createDtoResponse(Status::CODE_200, user); + ENDPOINT("GET", "users/offset/{offset}/limit/{limit}", getUsers, + PATH(UInt32, offset), + PATH(UInt32, limit)) + { + return createDtoResponse(Status::CODE_200, m_userService.getAllUsers(offset, limit)); } - // TODO Insert Your endpoints here !!! - /** - * Finish ENDPOINTs generation ('ApiController' codegen) - */ -#include OATPP_CODEGEN_END(ApiController) + ENDPOINT_INFO(deleteUser) { + info->summary = "Delete User by userId"; + + info->addResponse>(Status::CODE_200, "application/json"); + info->addResponse>(Status::CODE_500, "application/json"); + + info->pathParams["userId"].description = "User Identifier"; + } + ENDPOINT("DELETE", "users/{userId}", deleteUser, + PATH(String, userId)) + { + return createDtoResponse(Status::CODE_200, m_userService.deleteUserById(userId)); + } + }; +#include OATPP_CODEGEN_BEGIN(ApiController) //<- End Codegen + #endif /* UserController_hpp */ diff --git a/src/db/Database.cpp b/src/db/Database.cpp deleted file mode 100644 index 40791ae..0000000 --- a/src/db/Database.cpp +++ /dev/null @@ -1,179 +0,0 @@ -// -// Database.cpp -// user-service -// -// Created by Leonid on 12/20/18. -// Copyright © 2018 lganzzzo. All rights reserved. -// - -#include "Database.hpp" - -#include "oatpp/core/data/stream/ChunkedBuffer.hpp" -#include "oatpp/core/utils/ConversionUtils.hpp" - -const char* Database::DDL = -"CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";" -"\r\n" -"CREATE TABLE IF NOT EXISTS EXAMPLE_USER (" -"id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY," -"userId varchar (256) NOT NULL," -"login varchar (256) NOT NULL," -"password varchar (256) NOT NULL," -"email varchar (256) NOT NULL," -"emailConfirmation varchar (256) NULL," -"deletedTimestamp timestamp NULL," -"CONSTRAINT UK_EXAMPLE_USER_USERID UNIQUE (userId)," -"CONSTRAINT UK_EXAMPLE_USER_LOGIN UNIQUE (login)," -"CONSTRAINT UK_EXAMPLE_USER_EMAIL UNIQUE (email)" -");" -; - -const char* Database::SQL_INSERT_USER = -"INSERT INTO EXAMPLE_USER " -"(userId, login, password, email) VALUES " -"(uuid_generate_v4(), $1::varchar, $2::varchar, $3::varchar) " -"RETURNING *;"; - -const char* Database::SQL_SELECT_USER_BY_USERID = "SELECT * FROM EXAMPLE_USER WHERE deletedTimestamp is NULL AND userId=$1::varchar"; -const char* Database::SQL_SELECT_USER_BY_LOGIN = "SELECT * FROM EXAMPLE_USER WHERE deletedTimestamp is NULL AND login=$1::varchar"; -const char* Database::SQL_SELECT_USER_BY_EMAIL = "SELECT * FROM EXAMPLE_USER WHERE deletedTimestamp is NULL AND email=$1::varchar"; - -Database::Database(const oatpp::String& dbHost, - const oatpp::String& dbUser, - const oatpp::String& dbPassword, - const oatpp::String& dbName) - : m_connection(nullptr) -{ - oatpp::data::stream::ChunkedBuffer stream; - stream << "host=" << dbHost << " user=" << dbUser << " password=" << dbPassword << " dbname=" << dbName; - m_connectionString = stream.toString(); -} - -Database::~Database() { - disconnect(); -} - -void Database::connect() { - if(m_connection != nullptr) { - PQfinish(m_connection); - } - m_connection = PQconnectdb(m_connectionString->c_str()); - if(PQstatus(m_connection) == CONNECTION_BAD) { - OATPP_LOGD("Database", "Connection to database failed: %s\n", PQerrorMessage(m_connection)); - PQfinish(m_connection); - m_connection = nullptr; - } -} - -void Database::disconnect() { - if(m_connection != nullptr) { - PQfinish(m_connection); - m_connection = nullptr; - } -} - -void Database::tryReconnect() { - if(m_connection == nullptr) { - connect(); - } -} - -UserDto::ObjectWrapper Database::readUserFromResult(const ResultWrapper& result, v_int32 index) { - - auto cuserId = PQfnumber(result.get(), "userId"); - auto clogin = PQfnumber(result.get(), "login"); - auto cpassword = PQfnumber(result.get(), "password"); - auto cemail = PQfnumber(result.get(), "email"); - - auto user = UserDto::createShared(); - user->userId = PQgetvalue(result.get(), index, cuserId); - user->login = PQgetvalue(result.get(), index, clogin); - user->password = PQgetvalue(result.get(), index, cpassword); - user->email = PQgetvalue(result.get(), index, cemail); - - return user; - -} - -void Database::init() { - ResultWrapper result = PQexec(m_connection, DDL); - auto status = PQresultStatus(result.get()); - if (status != PGRES_COMMAND_OK) { - OATPP_LOGD("Database", "execute init DDL command failed: %s", PQerrorMessage(m_connection)); - if(status == PGRES_FATAL_ERROR) { - disconnect(); - } - } -} - -bool Database::checkResultOrThrow(const ResultWrapper& result) { - auto status = PQresultStatus(result.get()); - if (status == PGRES_TUPLES_OK) { - return true; - } else { - oatpp::String message = "[Database]: Error - " + oatpp::String(PQerrorMessage(m_connection)); - if(status == PGRES_FATAL_ERROR) { - disconnect(); - } - throw std::runtime_error(message->std_str()); - } -} - -UserDto::ObjectWrapper Database::createUser(const UserDto::ObjectWrapper& user) { - std::lock_guard lock(m_mutex); - tryReconnect(); - UserDto::ObjectWrapper createdUser; - const v_int32 paramsNumber = 3; - const char* params[paramsNumber] = {user->login->c_str(), user->password->c_str(), user->email->c_str()}; - ResultWrapper result = PQexecParams(m_connection, SQL_INSERT_USER, paramsNumber, nullptr, params, nullptr, nullptr, 0); - if (checkResultOrThrow(result)) { - if(PQntuples(result.get()) == 1) { - createdUser = readUserFromResult(result, 0); - } - } - return createdUser; -} - -UserDto::ObjectWrapper Database::getUserByUid(const oatpp::String& uid) { - std::lock_guard lock(m_mutex); - tryReconnect(); - UserDto::ObjectWrapper user; - const char* params[1] = {uid->c_str()}; - ResultWrapper result = PQexecParams(m_connection, SQL_SELECT_USER_BY_USERID, 1, nullptr, params, nullptr, nullptr, 0); - if (checkResultOrThrow(result)) { - if(PQntuples(result.get()) == 1) { - user = readUserFromResult(result, 0); - } - } - return user; -} - -UserDto::ObjectWrapper Database::getUserByLogin(const oatpp::String& login) { - std::lock_guard lock(m_mutex); - tryReconnect(); - UserDto::ObjectWrapper user; - const char* params[1] = {login->c_str()}; - ResultWrapper result = PQexecParams(m_connection, SQL_SELECT_USER_BY_LOGIN, 1, nullptr, params, nullptr, nullptr, 0); - if (checkResultOrThrow(result)) { - if(PQntuples(result.get()) == 1) { - user = readUserFromResult(result, 0); - } - } - return user; -} - -UserDto::ObjectWrapper Database::getUserByEmail(const oatpp::String& email) { - std::lock_guard lock(m_mutex); - tryReconnect(); - UserDto::ObjectWrapper user; - const char* params[1] = {email->c_str()}; - ResultWrapper result = PQexecParams(m_connection, SQL_SELECT_USER_BY_EMAIL, 1, nullptr, params, nullptr, nullptr, 0); - if (checkResultOrThrow(result)) { - if(PQntuples(result.get()) == 1) { - user = readUserFromResult(result, 0); - } - } - return user; -} - - diff --git a/src/db/Database.hpp b/src/db/Database.hpp deleted file mode 100644 index 30fa865..0000000 --- a/src/db/Database.hpp +++ /dev/null @@ -1,79 +0,0 @@ -// -// Database.hpp -// user-service -// -// Created by Leonid on 12/20/18. -// Copyright © 2018 lganzzzo. All rights reserved. -// - -#ifndef Database_hpp -#define Database_hpp - -#include "dto/UserDto.hpp" - -#include "oatpp/core/Types.hpp" - -#include -#include - -class Database { -private: - static const char* DDL; - static const char* SQL_INSERT_USER; - static const char* SQL_SELECT_USER_BY_USERID; - static const char* SQL_SELECT_USER_BY_LOGIN; - static const char* SQL_SELECT_USER_BY_EMAIL; -private: - - class ResultWrapper { - private: - PGresult* m_result; - public: - - ResultWrapper(PGresult* result) - : m_result(result) - {} - - ~ResultWrapper() { - if(m_result != nullptr) { - PQclear(m_result); - m_result = nullptr; - } - } - - PGresult* get() const { - return m_result; - } - - }; -private: - PGconn* m_connection; - oatpp::String m_connectionString; - std::mutex m_mutex; -private: - void tryReconnect(); - UserDto::ObjectWrapper readUserFromResult(const ResultWrapper& result, v_int32 index); - bool checkResultOrThrow(const ResultWrapper& result); -public: - void connect(); - void disconnect(); -public: - - Database(const oatpp::String& dbHost, - const oatpp::String& dbUser, - const oatpp::String& dbPassword, - const oatpp::String& dbName); - - ~Database(); - - void init(); - - UserDto::ObjectWrapper createUser(const UserDto::ObjectWrapper& user); - - UserDto::ObjectWrapper getUserByUid(const oatpp::String& uid); - UserDto::ObjectWrapper getUserByLogin(const oatpp::String& login); - UserDto::ObjectWrapper getUserByEmail(const oatpp::String& email); - -}; - -#endif /* Database_hpp */ diff --git a/src/db/UserDb.hpp b/src/db/UserDb.hpp new file mode 100644 index 0000000..751baa3 --- /dev/null +++ b/src/db/UserDb.hpp @@ -0,0 +1,71 @@ + +#ifndef EXAMPLE_POSTGRESQL_USERDB_HPP +#define EXAMPLE_POSTGRESQL_USERDB_HPP + +#include "dto/UserDto.hpp" +#include "oatpp-postgresql/orm.hpp" + +#include OATPP_CODEGEN_BEGIN(DbClient) //<- Begin Codegen + +/** + * UserDb client definitions. + */ +class UserDb : public oatpp::orm::DbClient { +public: + + UserDb(const std::shared_ptr& executor) + : oatpp::orm::DbClient(executor) + { + + oatpp::orm::SchemaMigration migration(executor); + migration.addFile(1 /* start from version 1 */, DATABASE_MIGRATIONS "/001_init.sql"); + // TODO - Add more migrations here. + migration.migrate(); // <-- run migrations. This guy will throw on error. + + auto version = executor->getSchemaVersion(); + OATPP_LOGD("UserDb", "Migration - OK. Version=%d.", version); + + } + + QUERY(createUser, + "INSERT INTO AppUser" + "(id, username, email, password, role) VALUES " + "(uuid_generate_v4(), :user.username, :user.email, :user.password, :user.role)" + "RETURNING *;", + PREPARE(true), // user prepared statement! + PARAM(oatpp::Object, user)) + + QUERY(updateUser, + "UPDATE AppUser " + "SET " + " username=:user.username, " + " email=:user.email, " + " password=:user.password, " + " role=:user.role " + "WHERE " + " id=:user.id " + "RETURNING *;", + PREPARE(true), //<-- user prepared statement! + PARAM(oatpp::Object, user)) + + QUERY(getUserById, + "SELECT * FROM AppUser WHERE id=:id;", + PREPARE(true), //<-- user prepared statement! + PARAM(oatpp::String, id)) + + QUERY(getAllUsers, + "SELECT * FROM AppUser LIMIT :limit OFFSET :offset;", + PREPARE(true), //<-- user prepared statement! + PARAM(oatpp::UInt32, offset), + PARAM(oatpp::UInt32, limit)) + + QUERY(deleteUserById, + "DELETE FROM AppUser WHERE id=:id;", + PREPARE(true), //<-- user prepared statement! + PARAM(oatpp::String, id)) + +}; + +#include OATPP_CODEGEN_END(DbClient) //<- End Codegen + +#endif //EXAMPLE_POSTGRESQL_USERDB_HPP diff --git a/src/dto/ConfigDto.hpp b/src/dto/ConfigDto.hpp index a791fc4..8ccb53b 100644 --- a/src/dto/ConfigDto.hpp +++ b/src/dto/ConfigDto.hpp @@ -1,29 +1,20 @@ -// -// ConfigDto.hpp -// file-service -// -// Created by Leonid on 8/12/18. -// Copyright © 2018 oatpp. All rights reserved. -// #ifndef ConfigDto_hpp #define ConfigDto_hpp -#include "oatpp/core/data/mapping/type/Object.hpp" +#include "oatpp/core/Types.hpp" #include "oatpp/core/macro/codegen.hpp" #include OATPP_CODEGEN_BEGIN(DTO) -class ConfigDto : public oatpp::data::mapping::type::Object { +class ConfigDto : public oatpp::DTO { - DTO_INIT(ConfigDto, Object) - - DTO_FIELD(Int32, port); + DTO_INIT(ConfigDto, DTO) + + DTO_FIELD(String, host); + DTO_FIELD(UInt16, port); DTO_FIELD(String, swaggerHost); - DTO_FIELD(String, dbHost); - DTO_FIELD(String, dbUser); - DTO_FIELD(String, dbPass); - DTO_FIELD(String, dbName); + DTO_FIELD(String, dbConnectionString); }; diff --git a/src/dto/ErrorDto.hpp b/src/dto/ErrorDto.hpp deleted file mode 100644 index 3b7d42c..0000000 --- a/src/dto/ErrorDto.hpp +++ /dev/null @@ -1,30 +0,0 @@ -// -// ErrorDto.hpp -// file-service -// -// Created by Leonid on 8/13/18. -// Copyright © 2018 oatpp. All rights reserved. -// - -#ifndef ErrorDto_hpp -#define ErrorDto_hpp - -#include "oatpp/core/data/mapping/type/Object.hpp" -#include "oatpp/core/macro/codegen.hpp" - -#include OATPP_CODEGEN_BEGIN(DTO) - -class ErrorDto : public oatpp::data::mapping::type::Object { - - DTO_INIT(ErrorDto, Object) - - DTO_FIELD(String, service) = "example-postgresql: user-service"; - DTO_FIELD(Int32, code); - DTO_FIELD(String, error); - DTO_FIELD(String, message); - -}; - -#include OATPP_CODEGEN_END(DTO) - -#endif /* ErrorDto_hpp */ diff --git a/src/dto/PageDto.hpp b/src/dto/PageDto.hpp new file mode 100644 index 0000000..361e88c --- /dev/null +++ b/src/dto/PageDto.hpp @@ -0,0 +1,29 @@ + +#ifndef EXAMPLE_POSTGRESQL_PAGEDTO_HPP +#define EXAMPLE_POSTGRESQL_PAGEDTO_HPP + +#include "UserDto.hpp" + +#include OATPP_CODEGEN_BEGIN(DTO) + +template +class PageDto : public oatpp::DTO { + + DTO_INIT(PageDto, DTO) + + DTO_FIELD(UInt32, offset); + DTO_FIELD(UInt32, limit); + DTO_FIELD(UInt32, count); + DTO_FIELD(Vector, items); + +}; + +class UsersPageDto : public PageDto> { + + DTO_INIT(UsersPageDto, PageDto>) + +}; + +#include OATPP_CODEGEN_END(DTO) + +#endif //EXAMPLE_POSTGRESQL_PAGEDTO_HPP diff --git a/src/dto/StatusDto.hpp b/src/dto/StatusDto.hpp new file mode 100644 index 0000000..fd8eb6e --- /dev/null +++ b/src/dto/StatusDto.hpp @@ -0,0 +1,33 @@ + +#ifndef EXAMPLE_POSTGRESQL_STATUSDTO_HPP +#define EXAMPLE_POSTGRESQL_STATUSDTO_HPP + +#include "oatpp/core/macro/codegen.hpp" +#include "oatpp/core/Types.hpp" + +#include OATPP_CODEGEN_BEGIN(DTO) + +class StatusDto : public oatpp::DTO { + + DTO_INIT(StatusDto, DTO) + + DTO_FIELD_INFO(status) { + info->description = "Short status text"; + } + DTO_FIELD(String, status); + + DTO_FIELD_INFO(code) { + info->description = "Status code"; + } + DTO_FIELD(Int32, code); + + DTO_FIELD_INFO(message) { + info->description = "Verbose message"; + } + DTO_FIELD(String, message); + +}; + +#include OATPP_CODEGEN_END(DTO) + +#endif //EXAMPLE_POSTGRESQL_STATUSDTO_HPP diff --git a/src/dto/UserDto.hpp b/src/dto/UserDto.hpp index 9d5d18b..0b46a55 100644 --- a/src/dto/UserDto.hpp +++ b/src/dto/UserDto.hpp @@ -1,28 +1,26 @@ -// -// UserDto.hpp -// crud -// -// Created by Leonid on 3/13/18. -// Copyright © 2018 oatpp. All rights reserved. -// - #ifndef UserDto_hpp #define UserDto_hpp -#include "oatpp/core/data/mapping/type/Object.hpp" #include "oatpp/core/macro/codegen.hpp" +#include "oatpp/core/Types.hpp" #include OATPP_CODEGEN_BEGIN(DTO) -class UserDto : public oatpp::data::mapping::type::Object { - - DTO_INIT(UserDto, Object) - - DTO_FIELD(String, userId); - DTO_FIELD(String, login); - DTO_FIELD(String, password); - DTO_FIELD(String, email); +ENUM(Role, v_int32, + VALUE(GUEST, 0, "ROLE_GUEST"), + VALUE(ADMIN, 1, "ROLE_ADMIN") +) + +class UserDto : public oatpp::DTO { + DTO_INIT(UserDto, DTO) + + DTO_FIELD(String, id); + DTO_FIELD(String, userName, "username"); + DTO_FIELD(String, email, "email"); + DTO_FIELD(String, password, "password"); + DTO_FIELD(Enum::AsString, role, "role"); + }; #include OATPP_CODEGEN_END(DTO) diff --git a/src/service/UserService.cpp b/src/service/UserService.cpp new file mode 100644 index 0000000..e35dc9c --- /dev/null +++ b/src/service/UserService.cpp @@ -0,0 +1,69 @@ + +#include "UserService.hpp" + +oatpp::Object UserService::createUser(const oatpp::Object& dto) { + + auto dbResult = m_database->createUser(dto); + OATPP_ASSERT_HTTP(dbResult->isSuccess(), Status::CODE_500, dbResult->getErrorMessage()); + + auto result = dbResult->fetch>>(); + OATPP_ASSERT_HTTP(result->size() == 1, Status::CODE_500, "Unknown error"); + return result[0]; + +} + +oatpp::Object UserService::updateUser(const oatpp::Object& dto) { + + auto dbResult = m_database->updateUser(dto); + OATPP_ASSERT_HTTP(dbResult->isSuccess(), Status::CODE_500, dbResult->getErrorMessage()); + auto result = dbResult->fetch>>(); + OATPP_ASSERT_HTTP(result->size() == 1, Status::CODE_500, "Unknown error"); + return result[0]; + +} + +oatpp::Object UserService::getUserById(const oatpp::String& id) { + + auto dbResult = m_database->getUserById(id); + OATPP_ASSERT_HTTP(dbResult->isSuccess(), Status::CODE_500, dbResult->getErrorMessage()); + OATPP_ASSERT_HTTP(dbResult->hasMoreToFetch(), Status::CODE_404, "User not found"); + + auto result = dbResult->fetch>>(); + OATPP_ASSERT_HTTP(result->size() == 1, Status::CODE_500, "Unknown error"); + + return result[0]; + +} + +oatpp::Object>> UserService::getAllUsers(const oatpp::UInt32& offset, const oatpp::UInt32& limit) { + + oatpp::UInt32 countToFetch = limit; + + if(limit > 10) { + countToFetch = 10; + } + + auto dbResult = m_database->getAllUsers(offset, countToFetch); + OATPP_ASSERT_HTTP(dbResult->isSuccess(), Status::CODE_500, dbResult->getErrorMessage()); + + auto items = dbResult->fetch>>(); + + auto page = PageDto>::createShared(); + page->offset = offset; + page->limit = countToFetch; + page->count = items->size(); + page->items = items; + + return page; + +} + +oatpp::Object UserService::deleteUserById(const oatpp::String& userId) { + auto dbResult = m_database->deleteUserById(userId); + OATPP_ASSERT_HTTP(dbResult->isSuccess(), Status::CODE_500, dbResult->getErrorMessage()); + auto status = StatusDto::createShared(); + status->status = "OK"; + status->code = 200; + status->message = "User was successfully deleted"; + return status; +} \ No newline at end of file diff --git a/src/service/UserService.hpp b/src/service/UserService.hpp new file mode 100644 index 0000000..ab6dd43 --- /dev/null +++ b/src/service/UserService.hpp @@ -0,0 +1,27 @@ + +#ifndef EXAMPLE_POSTGRESQL_USERSERVICE_HPP +#define EXAMPLE_POSTGRESQL_USERSERVICE_HPP + +#include "db/UserDb.hpp" +#include "dto/PageDto.hpp" +#include "dto/StatusDto.hpp" + +#include "oatpp/web/protocol/http/Http.hpp" +#include "oatpp/core/macro/component.hpp" + +class UserService { +private: + typedef oatpp::web::protocol::http::Status Status; +private: + OATPP_COMPONENT(std::shared_ptr, m_database); // Inject database component +public: + + oatpp::Object createUser(const oatpp::Object& dto); + oatpp::Object updateUser(const oatpp::Object& dto); + oatpp::Object getUserById(const oatpp::String& id); + oatpp::Object>> getAllUsers(const oatpp::UInt32& offset, const oatpp::UInt32& limit); + oatpp::Object deleteUserById(const oatpp::String& id); + +}; + +#endif //EXAMPLE_POSTGRESQL_USERSERVICE_HPP diff --git a/utility/install-oatpp-modules.sh b/utility/install-oatpp-modules.sh index 66a91f2..26291ff 100755 --- a/utility/install-oatpp-modules.sh +++ b/utility/install-oatpp-modules.sh @@ -1,30 +1,30 @@ #!/bin/sh +BUILD_TYPE=$1 + +if [ -z "$BUILD_TYPE" ]; then + BUILD_TYPE="Debug" +fi + rm -rf tmp mkdir tmp cd tmp ########################################################## -## install oatpp +## install oatpp module -MODULE_NAME="oatpp" +function install_module () { -git clone --depth=1 https://github.com/oatpp/$MODULE_NAME +BUILD_TYPE=$1 +MODULE_NAME=$2 +NPROC=$(nproc) -cd $MODULE_NAME -mkdir build -cd build - -cmake .. -make install +if [ -z "$NPROC" ]; then + NPROC=1 +fi -cd ../../ - -########################################################## -## install oatpp-swagger - -MODULE_NAME="oatpp-swagger" +echo "\n\nINSTALLING MODULE '$MODULE_NAME' ($BUILD_TYPE) using $NPROC threads ...\n\n" git clone --depth=1 https://github.com/oatpp/$MODULE_NAME @@ -32,11 +32,18 @@ cd $MODULE_NAME mkdir build cd build -cmake .. -make install +cmake -DOATPP_DISABLE_ENV_OBJECT_COUNTERS=ON -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DOATPP_BUILD_TESTS=OFF .. +make install -j $NPROC cd ../../ -cd ../ +} + +########################################################## + +install_module $BUILD_TYPE oatpp +install_module $BUILD_TYPE oatpp-swagger +install_module $BUILD_TYPE oatpp-postgresql -rm -rf tmp \ No newline at end of file +cd ../ +rm -rf tmp