From b7226ddf5f3448717d6e5f5d77b039a8abfaf2bc Mon Sep 17 00:00:00 2001 From: sabudilovskiy Date: Sat, 8 Apr 2023 16:22:30 +0300 Subject: [PATCH] Magic convert (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) Docker workflow выключен из-за того, что в Dockerfile userver используется g++ 9.4, который сегфолтится 2) Добавлены автостоп ранов и очистка старых ранов 3) Пофикшен CMake проекта: инклуды прокидываются по таргетам 4) Добавлены шаблонные парсеры, сериализаторы для json/http --- .clang-format | 16 ++- .gen/objs.txt | 15 +- .gen/unittest.txt | 1 + .github/workflows/ci.yml | 31 +++-- .github/workflows/delete_old_runs.yml | 57 ++++++++ .github/workflows/docker.yaml | 44 ------ .gitmodules | 2 +- CMakeLists.txt | 7 +- Makefile | 3 +- README.md | 4 + benchs/hello_benchmark.cpp | 26 ++-- dependencies/ubuntu-22.04.md | 43 ++++++ service/main.cpp | 118 ++++++++-------- .../controllers/token_controller.cpp | 43 +++--- .../controllers/token_controller.hpp | 25 ++-- .../controllers/token_controller_fwd.cpp | 3 +- .../controllers/token_controller_fwd.hpp | 2 +- .../controllers/user_controller.cpp | 39 +++--- .../controllers/user_controller.hpp | 21 +-- .../controllers/user_controller_fwd.cpp | 2 +- .../controllers/user_controller_fwd.hpp | 2 +- src/http/handler_parsed.hpp | 100 ++++++++------ src/http/legacy_handler_parsed.hpp | 63 +++++++++ src/models/auth_token/serialize.hpp | 8 +- src/models/auth_token/type.hpp | 2 +- src/models/user/postgre.hpp | 8 +- src/models/user/type.hpp | 13 +- src/models/user_type/parse.cpp | 41 +++--- src/models/user_type/parse.hpp | 1 + src/models/user_type/postgre.hpp | 24 ++-- src/utils/constexpr_string.hpp | 40 ++++++ src/utils/convert/base.hpp | 87 ++++++++++++ .../detail/parse/converter_http_request.hpp | 117 ++++++++++++++++ .../convert/detail/parse/converter_json.hpp | 49 +++++++ .../serialize/converter_http_response.hpp | 129 ++++++++++++++++++ .../detail/serialize/converter_json.hpp | 52 +++++++ src/utils/convert/http_request_parse.hpp | 21 +++ src/utils/convert/http_response_base.hpp | 27 ++++ src/utils/convert/http_response_serialize.hpp | 15 ++ src/utils/convert/json_parse.hpp | 26 ++++ src/utils/convert/json_serialize.hpp | 25 ++++ src/utils/json_type.cpp | 42 +++--- src/utils/json_type.hpp | 1 + src/utils/meta.hpp | 5 + src/views/hello/view.cpp | 91 ++++++------ src/views/hello/view.hpp | 1 - src/views/login/Request.cpp | 16 --- src/views/login/Request.hpp | 18 ++- src/views/login/Responses.hpp | 30 ++++ src/views/login/view.cpp | 74 +++++----- src/views/login/view.hpp | 2 +- src/views/register/Request.cpp | 11 +- src/views/register/Request.hpp | 4 +- src/views/register/view.cpp | 86 ++++++------ src/views/register/view.hpp | 2 +- utests/convert_test.cpp | 50 +++++++ utests/hello_test.cpp | 21 +-- vsu_timetable.code-workspace | 5 +- 58 files changed, 1342 insertions(+), 469 deletions(-) create mode 100644 .github/workflows/delete_old_runs.yml delete mode 100644 .github/workflows/docker.yaml create mode 100644 dependencies/ubuntu-22.04.md create mode 100644 src/http/legacy_handler_parsed.hpp create mode 100644 src/utils/constexpr_string.hpp create mode 100644 src/utils/convert/base.hpp create mode 100644 src/utils/convert/detail/parse/converter_http_request.hpp create mode 100644 src/utils/convert/detail/parse/converter_json.hpp create mode 100644 src/utils/convert/detail/serialize/converter_http_response.hpp create mode 100644 src/utils/convert/detail/serialize/converter_json.hpp create mode 100644 src/utils/convert/http_request_parse.hpp create mode 100644 src/utils/convert/http_response_base.hpp create mode 100644 src/utils/convert/http_response_serialize.hpp create mode 100644 src/utils/convert/json_parse.hpp create mode 100644 src/utils/convert/json_serialize.hpp create mode 100644 src/utils/meta.hpp delete mode 100644 src/views/login/Request.cpp create mode 100644 src/views/login/Responses.hpp create mode 100644 utests/convert_test.cpp diff --git a/.clang-format b/.clang-format index 276eaf0a..ba25f56b 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,15 @@ -BasedOnStyle: google +# Описание настроек форматирования для clang-format 14 + +BasedOnStyle: Google +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +ColumnLimit: 80 +PointerAlignment: Left +SpaceAfterCStyleCast: true DerivePointerAlignment: false -IncludeBlocks: Preserve +AlignTrailingComments: true \ No newline at end of file diff --git a/.gen/objs.txt b/.gen/objs.txt index 35bcec7c..c95778e0 100644 --- a/.gen/objs.txt +++ b/.gen/objs.txt @@ -4,12 +4,24 @@ src/views/register/Request.hpp src/views/register/Request.cpp src/views/login/view.hpp src/views/login/view.cpp +src/views/login/Responses.hpp src/views/login/Request.hpp -src/views/login/Request.cpp src/views/hello/view.hpp src/views/hello/view.cpp +src/utils/meta.hpp src/utils/json_type.hpp src/utils/json_type.cpp +src/utils/convert/json_serialize.hpp +src/utils/convert/json_parse.hpp +src/utils/convert/http_response_serialize.hpp +src/utils/convert/http_response_base.hpp +src/utils/convert/http_request_parse.hpp +src/utils/convert/detail/serialize/converter_json.hpp +src/utils/convert/detail/serialize/converter_http_response.hpp +src/utils/convert/detail/parse/converter_json.hpp +src/utils/convert/detail/parse/converter_http_request.hpp +src/utils/convert/base.hpp +src/utils/constexpr_string.hpp src/utils/component_list_fwd.hpp src/models/user_type/type.hpp src/models/user_type/postgre.hpp @@ -19,6 +31,7 @@ src/models/user/type.hpp src/models/user/postgre.hpp src/models/auth_token/type.hpp src/models/auth_token/serialize.hpp +src/http/legacy_handler_parsed.hpp src/http/handler_parsed.hpp src/components/controllers/user_controller_fwd.hpp src/components/controllers/user_controller_fwd.cpp diff --git a/.gen/unittest.txt b/.gen/unittest.txt index 72ba6f85..cf65d423 100644 --- a/.gen/unittest.txt +++ b/.gen/unittest.txt @@ -1 +1,2 @@ utests/hello_test.cpp +utests/convert_test.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ede11c3..c2c131a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,22 +15,37 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 make: test-debug - info: g++-9 + test-debug + info: g++-11 + test-debug - - os: ubuntu-20.04 + - os: ubuntu-22.04 make: test-release - info: g++-9 + test-release + info: g++-11 + test-release name: '${{matrix.os}}: ${{matrix.info}}' runs-on: ${{matrix.os}} steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 - uses: actions/checkout@v2 with: submodules: true - + - name: add Postgres package repository + run: | + sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null + - name: Install packages + run: | + sudo apt update + sudo apt install --allow-downgrades -y pep8 $(cat dependencies/${{matrix.os}}.md | tr '\n' ' ') + sudo apt install clang-format-11 + sudo pip install $(cat dependencies/python.md) | tr '\n' ' ' + - name: install g++11 + run: | + sudo apt install g++-11 + export CXX=/usr/bin/g++-11 - name: Check format sources run: | make check-format @@ -47,12 +62,6 @@ jobs: ${{matrix.os}} ${{matrix.info}} ccache-dir ${{github.ref}} run-' ${{matrix.os}} ${{matrix.info}} ccache- - - name: Install packages - run: | - sudo apt update - sudo apt install --allow-downgrades -y pep8 $(cat third_party/userver/scripts/docs/en/deps/${{matrix.os}}.md | tr '\n' ' ') - sudo pip install $(cat dependencies/python.md) | tr '\n' ' ' - - name: Setup ccache run: | ccache -M 2.0GB diff --git a/.github/workflows/delete_old_runs.yml b/.github/workflows/delete_old_runs.yml new file mode 100644 index 00000000..24a8bcea --- /dev/null +++ b/.github/workflows/delete_old_runs.yml @@ -0,0 +1,57 @@ +name: Delete old workflow runs +on: + workflow_dispatch: + inputs: + days: + description: 'Number of days.' + required: true + default: 0 + minimum_runs: + description: 'The minimum runs to keep for each workflow.' + required: true + default: 0 + delete_workflow_pattern: + description: 'The name or filename of the workflow. if not set then it will target all workflows.' + required: false + delete_workflow_by_state_pattern: + description: 'Remove workflow by state: active, deleted, disabled_fork, disabled_inactivity, disabled_manually' + required: true + default: "All" + type: choice + options: + - "All" + - active + - deleted + - disabled_inactivity + - disabled_manually + delete_run_by_conclusion_pattern: + description: 'Remove workflow by conclusion: action_required, cancelled, failure, skipped, success' + required: true + default: failure + type: choice + options: + - "All" + - action_required + - cancelled + - failure + - skipped + - success + dry_run: + description: 'Only log actions, do not perform any delete operations.' + required: false + +jobs: + del_runs: + runs-on: ubuntu-latest + steps: + - name: Delete workflow runs + uses: Mattraks/delete-workflow-runs@v2 + with: + token: ${{ github.token }} + repository: ${{ github.repository }} + retain_days: ${{ github.event.inputs.days }} + keep_minimum_runs: ${{ github.event.inputs.minimum_runs }} + delete_workflow_pattern: ${{ github.event.inputs.delete_workflow_pattern }} + delete_workflow_by_state_pattern: ${{ github.event.inputs.delete_workflow_by_state_pattern }} + delete_run_by_conclusion_pattern: ${{ github.event.inputs.delete_run_by_conclusion_pattern }} + dry_run: ${{ github.event.inputs.dry_run }} \ No newline at end of file diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml deleted file mode 100644 index 5cc1678b..00000000 --- a/.github/workflows/docker.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: Docker build - -'on': - pull_request: - push: - branches: - - master - - develop - - feature/** - -jobs: - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Check format sources - run: | - make check-format - - name: Check git status - run: | - make gen - make check-git-status - - name: Reuse ccache directory - uses: actions/cache@v2 - with: - path: .ccache - key: 'ccache-dir-${{github.ref}}_run-${{github.run_number}}' - restore-keys: | - ccache-dir-${{github.ref}}_run- - ccache- - - - name: Setup ccache - run: docker-compose run --rm timetable_vsu_backend-container bash -c 'ccache -M 2.0GB && ccache -s' - - - name: Cmake - run: make docker-cmake-release - - - name: Build - run: make docker-build-release - - - name: Run tests - run: make docker-test-release diff --git a/.gitmodules b/.gitmodules index 4d25646e..88c93c76 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "third_party/userver"] path = third_party/userver - url = https://github.com/userver-framework/userver.git + url = https://github.com/userver-framework/userver.git \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 22fc24f5..2803f50a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,13 @@ cmake_minimum_required(VERSION 3.12) set(CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 20) - project(timetable_vsu_backend CXX) +message("Compiler version: ${CMAKE_CXX_COMPILER_VERSION}") include(third_party/userver/cmake/SetupEnvironment.cmake) include(GNUInstallDirs) add_subdirectory(third_party/userver) - #make gen #source lists are generated in the .gen folder file(STRINGS ${PROJECT_SOURCE_DIR}/.gen/benchs.txt benchs_src) @@ -20,11 +19,13 @@ file(STRINGS ${PROJECT_SOURCE_DIR}/.gen/unittest.txt unittest_src) add_library(${PROJECT_NAME}_objs OBJECT ${objs_src} ) +target_include_directories(${PROJECT_NAME}_objs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) target_link_libraries(${PROJECT_NAME}_objs PUBLIC userver-core userver-postgresql) # The Service add_executable(${PROJECT_NAME} ${service_src}) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/service) target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_objs) @@ -32,6 +33,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_objs) add_executable(${PROJECT_NAME}_unittest ${unittest_src} ) +target_include_directories(${PROJECT_NAME}_unittest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/utests) target_link_libraries(${PROJECT_NAME}_unittest PRIVATE ${PROJECT_NAME}_objs userver-utest) add_google_tests(${PROJECT_NAME}_unittest) @@ -40,6 +42,7 @@ add_google_tests(${PROJECT_NAME}_unittest) add_executable(${PROJECT_NAME}_benchmark ${benchs_src} ) +target_include_directories(${PROJECT_NAME}_benchmark PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/benchs) target_link_libraries(${PROJECT_NAME}_benchmark PRIVATE ${PROJECT_NAME}_objs userver-ubench) add_google_benchmark_tests(${PROJECT_NAME}_benchmark) diff --git a/Makefile b/Makefile index 55710192..df7733e6 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CMAKE_DEBUG_FLAGS ?= -DUSERVER_SANITIZE='addr ub' CMAKE_RELEASE_FLAGS ?= CMAKE_OS_FLAGS ?= -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 -DUSERVER_FEATURE_REDIS_HI_MALLOC=1 NPROCS ?= $(shell nproc) -CLANG_FORMAT ?= clang-format +CLANG_FORMAT ?= clang-format-11 # NOTE: use Makefile.local for customization -include Makefile.local @@ -99,6 +99,7 @@ check-git-status: .PHONY: check-format-cpp check-format-cpp: + @$(CLANG_FORMAT) --version @find benchs -name '*pp' -type f | xargs $(CLANG_FORMAT) -i --dry-run --Werror @find service -name '*pp' -type f | xargs $(CLANG_FORMAT) -i --dry-run --Werror @find src -name '*pp' -type f | xargs $(CLANG_FORMAT) -i --dry-run --Werror diff --git a/README.md b/README.md index 67e4887f..17bf46f4 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,10 @@ Makefile contains typicaly useful targets for development: Edit `Makefile.local` to change the default configuration and build options. +## Known issues + +### Clangd doesn't working with project +Use `ln -s build_debug/compile_commands.json compile_commands.json` ## License diff --git a/benchs/hello_benchmark.cpp b/benchs/hello_benchmark.cpp index b2d61f05..1d684066 100644 --- a/benchs/hello_benchmark.cpp +++ b/benchs/hello_benchmark.cpp @@ -1,24 +1,24 @@ -#include "../src/views/hello/view.hpp" +#include #include // for std::uint64_t #include // for std::size #include - -#include #include +#include "views/hello/view.hpp" + void HelloBenchmark(benchmark::State& state) { - userver::engine::RunStandalone([&] { - constexpr std::string_view kNames[] = {"userver", "is", "awesome", "!"}; - std::uint64_t i = 0; + userver::engine::RunStandalone([&] { + constexpr std::string_view kNames[] = {"userver", "is", "awesome", "!"}; + std::uint64_t i = 0; - for (auto _ : state) { - const auto name = kNames[i++ % std::size(kNames)]; - auto result = service_template::SayHelloTo( - name, service_template::UserType::kKnown); - benchmark::DoNotOptimize(result); - } - }); + for (auto _ : state) { + const auto name = kNames[i++ % std::size(kNames)]; + auto result = service_template::SayHelloTo( + name, service_template::UserType::kKnown); + benchmark::DoNotOptimize(result); + } + }); } BENCHMARK(HelloBenchmark); diff --git a/dependencies/ubuntu-22.04.md b/dependencies/ubuntu-22.04.md new file mode 100644 index 00000000..aff1b779 --- /dev/null +++ b/dependencies/ubuntu-22.04.md @@ -0,0 +1,43 @@ +cmake +libboost1.74-dev +libboost-program-options1.74-dev +libboost-filesystem1.74-dev +libboost-locale1.74-dev +libboost-regex1.74-dev +libboost-iostreams1.74-dev +libev-dev +zlib1g-dev +libcurl4-openssl-dev +libcrypto++-dev +libyaml-cpp-dev +libssl-dev +libfmt-dev +libcctz-dev +libhttp-parser-dev +libjemalloc-dev +libmongoc-dev +libbson-dev +libldap2-dev +libpq-dev +postgresql-server-dev-15 +libkrb5-dev +libhiredis-dev +libgrpc-dev +libgrpc++-dev +libgrpc++1 +protobuf-compiler-grpc +libprotoc-dev +python3-dev +python3-protobuf +python3-jinja2 +python3-virtualenv +python3-voluptuous +libc-ares-dev +libspdlog-dev +libbenchmark-dev +libgmock-dev +libgtest-dev +ccache +git +postgresql-15 +redis-server diff --git a/service/main.cpp b/service/main.cpp index acde29c0..4f7a8697 100644 --- a/service/main.cpp +++ b/service/main.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include #include #include @@ -5,80 +8,77 @@ #include #include #include - -#include -#include -#include #include -#include "../src/components/controllers/token_controller_fwd.hpp" -#include "../src/components/controllers/user_controller_fwd.hpp" -#include "../src/views/hello/view.hpp" -#include "../src/views/login/view.hpp" -#include "../src/views/register/view.hpp" + +#include "components/controllers/token_controller_fwd.hpp" +#include "components/controllers/user_controller_fwd.hpp" #include "userver/formats/json/value.hpp" #include "userver/logging/log.hpp" #include "userver/storages/postgres/component.hpp" +#include "views/hello/view.hpp" +#include "views/login/view.hpp" +#include "views/register/view.hpp" namespace fs = std::filesystem; void LogAllFiles(fs::path path) { - std::stringstream buffer; - LOG_ERROR() << path; - for (const auto& entry : fs::directory_iterator(path)) - buffer << entry.path() << ' '; - LOG_ERROR() << buffer.str(); + std::stringstream buffer; + LOG_ERROR() << path; + for (const auto& entry : fs::directory_iterator(path)) + buffer << entry.path() << ' '; + LOG_ERROR() << buffer.str(); } void LogCommands(int argc, char** argv) { - std::stringstream buffer; - if (argc == 0) { - LOG_ERROR() << "No commands"; - } - for (int i = 0; i < argc; i++) { - buffer << argv[i] << ' '; - } - LOG_ERROR() << fmt::format("Commands is {}", buffer.str()); + std::stringstream buffer; + if (argc == 0) { + LOG_ERROR() << "No commands"; + } + for (int i = 0; i < argc; i++) { + buffer << argv[i] << ' '; + } + LOG_ERROR() << fmt::format("Commands is {}", buffer.str()); } void LogFile(std::string_view path) { - std::string path_str(path); - std::ifstream file(path_str); - std::string temp; - if (!file.is_open()) { - std::stringstream buffer; - buffer << strerror(errno); - LOG_ERROR() << fmt::format("{} ifstream fail: {} ", path, buffer.str()); - } else if (file.eof()) { - LOG_ERROR() << fmt::format("{} is empty", path); - } - while (std::getline(file, temp)) { - LOG_ERROR() << temp; - } - file.close(); + std::string path_str(path); + std::ifstream file(path_str); + std::string temp; + if (!file.is_open()) { + std::stringstream buffer; + buffer << strerror(errno); + LOG_ERROR() << fmt::format("{} ifstream fail: {} ", path, buffer.str()); + } else if (file.eof()) { + LOG_ERROR() << fmt::format("{} is empty", path); + } + while (std::getline(file, temp)) { + LOG_ERROR() << temp; + } + file.close(); } int main(int argc, char* argv[]) { - // LogAllFiles(fs::current_path()); - // LogAllFiles("/home/user/.local/etc/timetable_vsu_backend/"); - // LogCommands(argc, argv); - // //кто это? - // LogFile("/usr/local/etc/timetable_vsu_backend/config_dev.yaml"); - // LogFile("/home/user/.local/etc/timetable_vsu_backend/config_vars_docker.yaml"); - // LogFile("config_vars.docker.yaml"); - using namespace timetable_vsu_backend::components; - using namespace timetable_vsu_backend::views; - auto component_list = - userver::components::MinimalServerComponentList() - .Append() - .Append() - .Append() - .Append() - .Append("postgres-db-1") - .Append(); - service_template::AppendHello(component_list); - timetable_vsu_backend::views::login::Append(component_list); - timetable_vsu_backend::views::register_::Append(component_list); - AppendUserController(component_list); - AppendTokenController(component_list); - return userver::utils::DaemonMain(argc, argv, component_list); + // LogAllFiles(fs::current_path()); + // LogAllFiles("/home/user/.local/etc/timetable_vsu_backend/"); + // LogCommands(argc, argv); + // //кто это? + // LogFile("/usr/local/etc/timetable_vsu_backend/config_dev.yaml"); + // LogFile("/home/user/.local/etc/timetable_vsu_backend/config_vars_docker.yaml"); + // LogFile("config_vars.docker.yaml"); + using namespace timetable_vsu_backend::components; + using namespace timetable_vsu_backend::views; + auto component_list = + userver::components::MinimalServerComponentList() + .Append() + .Append() + .Append() + .Append() + .Append("postgres-db-1") + .Append(); + service_template::AppendHello(component_list); + timetable_vsu_backend::views::login::Append(component_list); + timetable_vsu_backend::views::register_::Append(component_list); + AppendUserController(component_list); + AppendTokenController(component_list); + return userver::utils::DaemonMain(argc, argv, component_list); } diff --git a/src/components/controllers/token_controller.cpp b/src/components/controllers/token_controller.cpp index 084cf963..f98985c6 100644 --- a/src/components/controllers/token_controller.cpp +++ b/src/components/controllers/token_controller.cpp @@ -1,12 +1,15 @@ #include "token_controller.hpp" + #include + #include #include #include #include #include -#include "../../models/user/postgre.hpp" -#include "../../models/user_type/postgre.hpp" + +#include "models/user/postgre.hpp" +#include "models/user_type/postgre.hpp" #include "userver/components/component_context.hpp" #include "userver/logging/log.hpp" #include "userver/storages/postgres/cluster_types.hpp" @@ -32,29 +35,31 @@ TokenController::TokenController( : LoggableComponentBase(config, context), pg_cluster_( context.FindComponent("postgres-db-1") - .GetCluster()) {} + .GetCluster()) { +} std::optional TokenController::GetById( std::string_view id) const { - auto result = pg_cluster_->Execute( - userver::storages::postgres::ClusterHostType::kMaster, qGetUserByTokenId, - id, userver::utils::datetime::Now()); - if (result.IsEmpty()) { - return std::nullopt; - } - return result.AsSingleRow(userver::storages::postgres::kRowTag); + auto result = pg_cluster_->Execute( + userver::storages::postgres::ClusterHostType::kMaster, + qGetUserByTokenId, id, userver::utils::datetime::Now()); + if (result.IsEmpty()) { + return std::nullopt; + } + return result.AsSingleRow( + userver::storages::postgres::kRowTag); } std::optional TokenController::CreateNew( const models::User::Id& id, const std::chrono::system_clock::time_point& time) const { - LOG_DEBUG() << fmt::format("Try to create new token, user_id: {}", - boost::uuids::to_string(id.GetUnderlying())); - auto result = pg_cluster_->Execute( - userver::storages::postgres::ClusterHostType::kMaster, qAddToken, id, - time); - if (result.IsEmpty()) { - return {}; - } - return result.AsSingleRow(); + LOG_DEBUG() << fmt::format("Try to create new token, user_id: {}", + boost::uuids::to_string(id.GetUnderlying())); + auto result = pg_cluster_->Execute( + userver::storages::postgres::ClusterHostType::kMaster, qAddToken, id, + time); + if (result.IsEmpty()) { + return {}; + } + return result.AsSingleRow(); } } // namespace timetable_vsu_backend::components \ No newline at end of file diff --git a/src/components/controllers/token_controller.hpp b/src/components/controllers/token_controller.hpp index f9852f1a..624e481e 100644 --- a/src/components/controllers/token_controller.hpp +++ b/src/components/controllers/token_controller.hpp @@ -3,22 +3,23 @@ #include #include #include -#include "../../models/user/type.hpp" + +#include "models/user/type.hpp" namespace timetable_vsu_backend::components { class TokenController : public userver::components::LoggableComponentBase { - public: - using userver::components::LoggableComponentBase::LoggableComponentBase; - static constexpr inline std::string_view kName = "token_controller"; - std::optional GetById(std::string_view id) const; - std::optional CreateNew( - const models::User::Id& id, - const std::chrono::system_clock::time_point& time) const; - TokenController(const userver::components::ComponentConfig& config, - const userver::components::ComponentContext& context); + public: + using userver::components::LoggableComponentBase::LoggableComponentBase; + static constexpr inline std::string_view kName = "token_controller"; + std::optional GetById(std::string_view id) const; + std::optional CreateNew( + const models::User::Id& id, + const std::chrono::system_clock::time_point& time) const; + TokenController(const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context); - protected: - userver::storages::postgres::ClusterPtr pg_cluster_; + protected: + userver::storages::postgres::ClusterPtr pg_cluster_; }; } // namespace timetable_vsu_backend::components \ No newline at end of file diff --git a/src/components/controllers/token_controller_fwd.cpp b/src/components/controllers/token_controller_fwd.cpp index 7bc7e00a..4ab1fbe8 100644 --- a/src/components/controllers/token_controller_fwd.cpp +++ b/src/components/controllers/token_controller_fwd.cpp @@ -1,9 +1,10 @@ #include "token_controller_fwd.hpp" + #include "token_controller.hpp" #include "userver/components/component_list.hpp" namespace timetable_vsu_backend::components { void AppendTokenController(userver::components::ComponentList& component_list) { - component_list.Append(); + component_list.Append(); } } // namespace timetable_vsu_backend::components \ No newline at end of file diff --git a/src/components/controllers/token_controller_fwd.hpp b/src/components/controllers/token_controller_fwd.hpp index 7f3b65e3..529335e7 100644 --- a/src/components/controllers/token_controller_fwd.hpp +++ b/src/components/controllers/token_controller_fwd.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../../utils/component_list_fwd.hpp" +#include "utils/component_list_fwd.hpp" namespace timetable_vsu_backend::components { class TokenController; void AppendTokenController(userver::components::ComponentList& component_list); diff --git a/src/components/controllers/user_controller.cpp b/src/components/controllers/user_controller.cpp index 8f39ec22..c43d027b 100644 --- a/src/components/controllers/user_controller.cpp +++ b/src/components/controllers/user_controller.cpp @@ -1,11 +1,14 @@ #include "user_controller.hpp" + #include + #include #include #include #include -#include "../../models/user/postgre.hpp" -#include "../../models/user_type/postgre.hpp" + +#include "models/user/postgre.hpp" +#include "models/user_type/postgre.hpp" #include "userver/components/component_context.hpp" #include "userver/logging/log.hpp" #include "userver/storages/postgres/cluster_types.hpp" @@ -28,26 +31,28 @@ UserController::UserController( : LoggableComponentBase(config, context), pg_cluster_( context.FindComponent("postgres-db-1") - .GetCluster()) {} + .GetCluster()) { +} std::optional UserController::GetByLogin( std::string_view login) const { - auto result = pg_cluster_->Execute( - userver::storages::postgres::ClusterHostType::kMaster, qGetUserByLogin, - login); - if (result.IsEmpty()) { - return std::nullopt; - } - return result.AsSingleRow(userver::storages::postgres::kRowTag); + auto result = pg_cluster_->Execute( + userver::storages::postgres::ClusterHostType::kMaster, qGetUserByLogin, + login); + if (result.IsEmpty()) { + return std::nullopt; + } + return result.AsSingleRow( + userver::storages::postgres::kRowTag); } std::optional UserController::TryToAdd( const models::User& user) const { - auto result = pg_cluster_->Execute( - userver::storages::postgres::ClusterHostType::kMaster, qAddUser, - user.login, user.password, user.user_type); - if (result.IsEmpty()) { - return {}; - } - return result.AsSingleRow(); + auto result = pg_cluster_->Execute( + userver::storages::postgres::ClusterHostType::kMaster, qAddUser, + user.login, user.password, user.user_type); + if (result.IsEmpty()) { + return {}; + } + return result.AsSingleRow(); } } // namespace timetable_vsu_backend::components \ No newline at end of file diff --git a/src/components/controllers/user_controller.hpp b/src/components/controllers/user_controller.hpp index 50ce43d3..603e375a 100644 --- a/src/components/controllers/user_controller.hpp +++ b/src/components/controllers/user_controller.hpp @@ -2,20 +2,21 @@ #include #include #include -#include "../../models/user/type.hpp" + +#include "models/user/type.hpp" namespace timetable_vsu_backend::components { class UserController : public userver::components::LoggableComponentBase { - public: - using userver::components::LoggableComponentBase::LoggableComponentBase; - static constexpr inline std::string_view kName = "user_controller"; - std::optional GetByLogin(std::string_view login) const; - std::optional TryToAdd(const models::User& user) const; - UserController(const userver::components::ComponentConfig& config, - const userver::components::ComponentContext& context); + public: + using userver::components::LoggableComponentBase::LoggableComponentBase; + static constexpr inline std::string_view kName = "user_controller"; + std::optional GetByLogin(std::string_view login) const; + std::optional TryToAdd(const models::User& user) const; + UserController(const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context); - protected: - userver::storages::postgres::ClusterPtr pg_cluster_; + protected: + userver::storages::postgres::ClusterPtr pg_cluster_; }; } // namespace timetable_vsu_backend::components \ No newline at end of file diff --git a/src/components/controllers/user_controller_fwd.cpp b/src/components/controllers/user_controller_fwd.cpp index 54e7b574..c74e6bea 100644 --- a/src/components/controllers/user_controller_fwd.cpp +++ b/src/components/controllers/user_controller_fwd.cpp @@ -4,6 +4,6 @@ namespace timetable_vsu_backend::components { void AppendUserController(userver::components::ComponentList& component_list) { - component_list.Append(); + component_list.Append(); } } // namespace timetable_vsu_backend::components \ No newline at end of file diff --git a/src/components/controllers/user_controller_fwd.hpp b/src/components/controllers/user_controller_fwd.hpp index d6f56a94..d85175a5 100644 --- a/src/components/controllers/user_controller_fwd.hpp +++ b/src/components/controllers/user_controller_fwd.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../../utils/component_list_fwd.hpp" +#include "utils/component_list_fwd.hpp" namespace timetable_vsu_backend::components { class UserController; void AppendUserController(userver::components::ComponentList& component_list); diff --git a/src/http/handler_parsed.hpp b/src/http/handler_parsed.hpp index 31da57bb..14309e1a 100644 --- a/src/http/handler_parsed.hpp +++ b/src/http/handler_parsed.hpp @@ -1,59 +1,77 @@ #pragma once #include #include + #include #include #include #include +#include + #include "userver/formats/json/value.hpp" #include "userver/formats/json/value_builder.hpp" #include "userver/server/http/http_status.hpp" +#include "utils/convert/http_response_base.hpp" namespace timetable_vsu_backend::http { -template +template class HandlerParsed : public userver::server::handlers::HttpHandlerBase { - public: - using HttpHandlerBase::HttpHandlerBase; - using HttpStatus = userver::server::http::HttpStatus; - HandlerParsed(const userver::components::ComponentConfig& config, - const userver::components::ComponentContext& context) - : HttpHandlerBase(config, context) {} - virtual Response Handle( - Request&& request, - userver::server::http::HttpResponse& response) const = 0; - static std::optional ParseUserRequest( - const userver::server::http::HttpRequest& raw_request) { - std::optional request; - try { - request = Parse(raw_request, userver::formats::parse::To{}); - return request; - } catch (std::exception& exc) { - LOG_DEBUG() << fmt::format( - "Can't parse user request, return 400. Error: {}", exc.what()); - return std::nullopt; + public: + using Response = std::variant; + using HttpHandlerBase::HttpHandlerBase; + HandlerParsed(const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context) + : HttpHandlerBase(config, context) { + } + virtual Response Handle(Request&& request) const = 0; + static std::optional ParseUserRequest( + const userver::server::http::HttpRequest& raw_request) { + std::optional request; + try { + request = + Parse(raw_request, userver::formats::parse::To{}); + return request; + } catch (std::exception& exc) { + LOG_DEBUG() << fmt::format( + "Can't parse user request, return 400. Error: {}", exc.what()); + return std::nullopt; + } } - } - std::string HandleRequestThrow( - const userver::server::http::HttpRequest& raw_request, - userver::server::request::RequestContext& /*context*/) const override { - auto& http_response = raw_request.GetHttpResponse(); - auto request = ParseUserRequest(raw_request); - if (!request) { - http_response.SetStatus(HttpStatus::kBadRequest); - return {}; + + private: + using HttpStatus = userver::server::http::HttpStatus; + + template + static std::string SerializeResponse( + SomeResponse& some_response, + const userver::server::http::HttpRequest& raw_request) { + std::string body; + timetable_vsu_backend::utils::convert::HttpResponse convert_response{ + raw_request.GetHttpResponse(), body}; + Serialize(some_response, convert_response); + return body; } - try { - auto response = Handle(std::move(*request), http_response); - userver::formats::json::Value json = Serialize( - response, - userver::formats::serialize::To{}); - return userver::formats::json::ToString(json); - } catch (std::exception& exc) { - http_response.SetStatus(HttpStatus::kInternalServerError); - LOG_ERROR() << fmt::format("Unexpected error while handle request: {}", - exc.what()); - return {}; + std::string HandleRequestThrow( + const userver::server::http::HttpRequest& raw_request, + userver::server::request::RequestContext& /*context*/) const override { + auto& http_response = raw_request.GetHttpResponse(); + auto request = ParseUserRequest(raw_request); + if (!request) { + http_response.SetStatus(HttpStatus::kBadRequest); + return {}; + } + try { + auto response = Handle(std::move(*request)); + auto visiter = [&raw_request](auto& value) { + return SerializeResponse(value, raw_request); + }; + return std::visit(visiter, response); + } catch (std::exception& exc) { + http_response.SetStatus(HttpStatus::kInternalServerError); + LOG_ERROR() << fmt::format( + "Unexpected error while handle request: {}", exc.what()); + return {}; + } } - } }; } // namespace timetable_vsu_backend::http \ No newline at end of file diff --git a/src/http/legacy_handler_parsed.hpp b/src/http/legacy_handler_parsed.hpp new file mode 100644 index 00000000..e6e0bd20 --- /dev/null +++ b/src/http/legacy_handler_parsed.hpp @@ -0,0 +1,63 @@ +#pragma once +#include +#include + +#include +#include +#include +#include + +#include "userver/formats/json/value.hpp" +#include "userver/formats/json/value_builder.hpp" +#include "userver/server/http/http_status.hpp" + +namespace timetable_vsu_backend::http { +template +class LegacyHandlerParsed : public userver::server::handlers::HttpHandlerBase { + public: + using HttpHandlerBase::HttpHandlerBase; + using HttpStatus = userver::server::http::HttpStatus; + LegacyHandlerParsed(const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context) + : HttpHandlerBase(config, context) { + } + virtual Response Handle( + Request&& request, + userver::server::http::HttpResponse& response) const = 0; + static std::optional ParseUserRequest( + const userver::server::http::HttpRequest& raw_request) { + std::optional request; + try { + request = + Parse(raw_request, userver::formats::parse::To{}); + return request; + } catch (std::exception& exc) { + LOG_DEBUG() << fmt::format( + "Can't parse user request, return 400. Error: {}", exc.what()); + return std::nullopt; + } + } + std::string HandleRequestThrow( + const userver::server::http::HttpRequest& raw_request, + userver::server::request::RequestContext& /*context*/) const override { + auto& http_response = raw_request.GetHttpResponse(); + auto request = ParseUserRequest(raw_request); + if (!request) { + http_response.SetStatus(HttpStatus::kBadRequest); + return {}; + } + try { + auto response = Handle(std::move(*request), http_response); + userver::formats::json::Value json = + Serialize(response, userver::formats::serialize::To< + userver::formats::json::Value>{}); + return userver::formats::json::ToString(json); + } catch (std::exception& exc) { + http_response.SetStatus(HttpStatus::kInternalServerError); + LOG_ERROR() << fmt::format( + "Unexpected error while handle request: {}", exc.what()); + return {}; + } + } +}; +} // namespace timetable_vsu_backend::http \ No newline at end of file diff --git a/src/models/auth_token/serialize.hpp b/src/models/auth_token/serialize.hpp index d6df6d22..7a47931a 100644 --- a/src/models/auth_token/serialize.hpp +++ b/src/models/auth_token/serialize.hpp @@ -1,7 +1,9 @@ #pragma once #include + #include #include + #include "type.hpp" #include "userver/formats/json/value.hpp" #include "userver/formats/json/value_builder.hpp" @@ -11,8 +13,8 @@ namespace timetable_vsu_backend::models { inline userver::formats::json::Value Serialize( const AuthToken& token, userver::formats::serialize::To) { - userver::formats::json::ValueBuilder json; - json["token"] = boost::uuids::to_string(token.id); - return json.ExtractValue(); + userver::formats::json::ValueBuilder json; + json["token"] = boost::uuids::to_string(token.id); + return json.ExtractValue(); } } // namespace timetable_vsu_backend::models \ No newline at end of file diff --git a/src/models/auth_token/type.hpp b/src/models/auth_token/type.hpp index 8d16c3c2..315fd2a2 100644 --- a/src/models/auth_token/type.hpp +++ b/src/models/auth_token/type.hpp @@ -3,6 +3,6 @@ namespace timetable_vsu_backend::models { struct AuthToken { - boost::uuids::uuid id; + boost::uuids::uuid id; }; } // namespace timetable_vsu_backend::models \ No newline at end of file diff --git a/src/models/user/postgre.hpp b/src/models/user/postgre.hpp index 0085513d..34286e48 100644 --- a/src/models/user/postgre.hpp +++ b/src/models/user/postgre.hpp @@ -1,8 +1,8 @@ #pragma once -#include "type.hpp" - #include #include + +#include "type.hpp" #include "userver/utils/trivial_map.hpp" namespace userver::storages::postgres::io { @@ -10,7 +10,7 @@ namespace userver::storages::postgres::io { using namespace timetable_vsu_backend::models; template <> struct CppToUserPg { - static constexpr userver::storages::postgres::DBTypeName postgres_name = - "uuid"; + static constexpr userver::storages::postgres::DBTypeName postgres_name = + "uuid"; }; } // namespace userver::storages::postgres::io \ No newline at end of file diff --git a/src/models/user/type.hpp b/src/models/user/type.hpp index 84b5a196..15565b8d 100644 --- a/src/models/user/type.hpp +++ b/src/models/user/type.hpp @@ -1,14 +1,15 @@ #pragma once #include #include -#include "../user_type/type.hpp" + +#include "models/user_type/type.hpp" namespace timetable_vsu_backend::models { struct User { - using Id = userver::utils::StrongTypedef; - Id id; - std::string login; - std::string password; - UserType user_type; + using Id = userver::utils::StrongTypedef; + Id id; + std::string login; + std::string password; + UserType user_type; }; } // namespace timetable_vsu_backend::models \ No newline at end of file diff --git a/src/models/user_type/parse.cpp b/src/models/user_type/parse.cpp index 483d81c3..b09d242a 100644 --- a/src/models/user_type/parse.cpp +++ b/src/models/user_type/parse.cpp @@ -1,32 +1,35 @@ #include "parse.hpp" + #include + #include #include -#include "../../utils/json_type.hpp" + #include "type.hpp" +#include "utils/json_type.hpp" namespace timetable_vsu_backend::models { UserType Parse(std::string_view str, userver::formats::parse::To) { - if (str == "user") { - return UserType::kUser; - } else if (str == "root") { - return UserType::kRoot; - } else if (str == "admin") { - return UserType::kAdmin; - } else if (str == "teacher") { - return UserType::kTeacher; - } else - throw std::runtime_error( - fmt::format("Fail parse UserType, get unexpected value: {}", str)); + if (str == "user") { + return UserType::kUser; + } else if (str == "root") { + return UserType::kRoot; + } else if (str == "admin") { + return UserType::kAdmin; + } else if (str == "teacher") { + return UserType::kTeacher; + } else + throw std::runtime_error( + fmt::format("Fail parse UserType, get unexpected value: {}", str)); } UserType Parse(const userver::formats::json::Value& value, userver::formats::parse::To) { - if (!value.IsString()) { - throw std::runtime_error(fmt::format("Expected string type, but got: {}", - utils::GetType(value))); - } - auto raw_value = value.As(); - return Parse(std::string_view{raw_value}, - userver::formats::parse::To{}); + if (!value.IsString()) { + throw std::runtime_error(fmt::format( + "Expected string type, but got: {}", utils::GetType(value))); + } + auto raw_value = value.As(); + return Parse(std::string_view{raw_value}, + userver::formats::parse::To{}); } } // namespace timetable_vsu_backend::models \ No newline at end of file diff --git a/src/models/user_type/parse.hpp b/src/models/user_type/parse.hpp index 6f404f1a..459b762d 100644 --- a/src/models/user_type/parse.hpp +++ b/src/models/user_type/parse.hpp @@ -1,6 +1,7 @@ #pragma once #include #include + #include "type.hpp" #include "userver/formats/json/value.hpp" diff --git a/src/models/user_type/postgre.hpp b/src/models/user_type/postgre.hpp index bb5872ab..70f28f5c 100644 --- a/src/models/user_type/postgre.hpp +++ b/src/models/user_type/postgre.hpp @@ -1,23 +1,23 @@ #pragma once -#include "type.hpp" - #include #include + +#include "type.hpp" #include "userver/utils/trivial_map.hpp" namespace userver::storages::postgres::io { using timetable_vsu_backend::models::UserType; template <> struct CppToUserPg : EnumMappingBase { - static constexpr userver::storages::postgres::DBTypeName postgres_name = - "vsu_timetable.usertype"; - static constexpr userver::utils::TrivialBiMap enumerators = - [](auto selector) { - return selector() - .Case("user", UserType::kUser) - .Case("admin", UserType::kAdmin) - .Case("root", UserType::kRoot) - .Case("teacher", UserType::kTeacher); - }; + static constexpr userver::storages::postgres::DBTypeName postgres_name = + "vsu_timetable.usertype"; + static constexpr userver::utils::TrivialBiMap enumerators = + [](auto selector) { + return selector() + .Case("user", UserType::kUser) + .Case("admin", UserType::kAdmin) + .Case("root", UserType::kRoot) + .Case("teacher", UserType::kTeacher); + }; }; } // namespace userver::storages::postgres::io \ No newline at end of file diff --git a/src/utils/constexpr_string.hpp b/src/utils/constexpr_string.hpp new file mode 100644 index 00000000..b3127c8e --- /dev/null +++ b/src/utils/constexpr_string.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include + +namespace timetable_vsu_backend::utils { + +template +struct ConstexprString { + std::array contents{}; + + constexpr ConstexprString() noexcept = default; + + constexpr ConstexprString(const char (&str)[Size]) noexcept { + std::copy_n(str, Size, begin(contents)); + } + + constexpr operator std::string_view() const { + return AsStringView(); + } + + constexpr std::string_view AsStringView() const { + return {contents.begin(), contents.begin() + Size - 1}; + } + + constexpr auto c_str() const -> const char* { + return contents.data(); + } + friend constexpr bool operator==(const ConstexprString& string, + const char* rhs) { + std::string_view right{rhs}; + return string.AsStringView() == right; + } + friend constexpr bool operator==(const char* lhs, + const ConstexprString& string) { + return string == lhs; + } +}; +} // namespace timetable_vsu_backend::utils \ No newline at end of file diff --git a/src/utils/convert/base.hpp b/src/utils/convert/base.hpp new file mode 100644 index 00000000..da87ed3a --- /dev/null +++ b/src/utils/convert/base.hpp @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +// #include "utlis/convert/constexpr_string.hpp" +#include "utils/constexpr_string.hpp" +#include "utils/meta.hpp" + +namespace timetable_vsu_backend::utils::convert { +//В данный момент поддерживаются только конвертация всех полей +enum struct PolicyFields { ConvertAll }; + +//тип тела в запросе. Empty body = body игнорируеттся +enum struct TypeOfBody { Empty, Json }; + +enum struct RequestParse { + Unspecified, //данное поле в запросах будет парсится из body + Query, + Header, + Cookie +}; + +template +struct BaseProperty { + using value_type = T; + constexpr auto static kName = name; + constexpr auto static kRequestParse = request_parse; + + template + value_type& operator=(Arg&& arg) { + value = std::forward(arg); + return value; + } + value_type& operator()() { + return value; + } + const value_type& operator()() const { + return value; + } + value_type value; +}; + +template +using Property = BaseProperty; + +template +using QueryProperty = BaseProperty; + +template +using HeaderProperty = BaseProperty; + +template +using CookieProperty = BaseProperty; + +template +concept IsProperty = std::is_class_v&& + std::is_same_v, T>; + +template +concept IsQueryProperty = std::is_class_v&& + std::is_same_v, T>; + +template +concept IsHeaderProperty = std::is_class_v&& + std::is_same_v, T>; + +template +concept IsCookieProperty = std::is_class_v&& + std::is_same_v, T>; + +template +concept IsAnyProperty = std::is_class_v&& std::is_same_v< + BaseProperty, T>; + +template +concept HasTypeOfBody = requires { + { T::kTypeOfBody } + ->std::convertible_to; + requires IsConstexpr; +}; + +template +concept IsConvertAll = requires { + requires T::kPolicyFields == PolicyFields::ConvertAll; +}; +} // namespace timetable_vsu_backend::utils::convert \ No newline at end of file diff --git a/src/utils/convert/detail/parse/converter_http_request.hpp b/src/utils/convert/detail/parse/converter_http_request.hpp new file mode 100644 index 00000000..f6c2dc99 --- /dev/null +++ b/src/utils/convert/detail/parse/converter_http_request.hpp @@ -0,0 +1,117 @@ +#pragma once +#include +#include + +#include "userver/formats/json/value.hpp" +#include "userver/server/http/http_request.hpp" +#include "utils/convert/base.hpp" + +namespace timetable_vsu_backend::utils::convert::detail::parse { +template +struct ConverterHttpRequest { + static void Parse(T& t, const userver::server::http::HttpRequest& value) { + auto tuple = GetTuple(t); + static_assert(HasTypeOfBody, "Type must have kTypeOfBody"); + auto body = GetBody(value); + ParseTuple(value, body, tuple, IndexSequence{}); + } + + protected: + //тип для обозначения пустого тела + struct EmptyBody {}; + using ValueType = T; + using TupleType = + decltype(boost::pfr::structure_tie(std::declval())); + static constexpr std::size_t kSize = std::tuple_size::value; + using IndexSequence = std::make_index_sequence; + static TupleType GetTuple(ValueType& v) { + return boost::pfr::structure_tie(v); + } + + //проверяем, что каждое поле кортежа является Property + template + static constexpr void check_properties(const std::tuple&) { + static_assert((IsAnyProperty> && ...), + "Not all properties satisfy IsProperty concept"); + } + //невозможно распарсить поле из тела, если оно пустое + template + static void ParseField(const userver::server::http::HttpRequest&, + const EmptyBody&, Field&) { + auto bad = []() { + static_assert(flag, + "Found property from body, but body marked empty"); + }; + } + //парсим поле из json тела + template + static void ParseField(const userver::server::http::HttpRequest&, + const userver::formats::json::Value& body, + Field& field) { + static constexpr std::string_view kName = Field::kName; + field = body[kName].template As(); + } + //парсим поле из query + template + static void ParseField(const userver::server::http::HttpRequest& value, + const userver::formats::json::Value&, Field& field) { + static constexpr std::string_view kName = Field::kName; + using FieldValue = typename Field::value_type; + std::string temp{kName}; + auto& query_value = value.GetArg(temp); + if constexpr (std::is_same_v) { + field = Field{query_value}; + } else { + field = + Parse(query_value, userver::formats::parse::To{}); + } + } + //парсим поле из cookie + template + static void ParseField(const userver::server::http::HttpRequest& value, + const userver::formats::json::Value&, Field& field) { + static constexpr std::string_view kName = Field::kName; + using FieldValue = typename Field::value_type; + std::string temp{kName}; + auto& cookie_value = value.GetCookie(temp); + if constexpr (std::is_same_v) { + field = Field{cookie_value}; + } else { + field = + Parse(cookie_value, userver::formats::parse::To{}); + } + } + //парсим поле из header + template + static void ParseField(const userver::server::http::HttpRequest& value, + const userver::formats::json::Value&, Field& field) { + static constexpr std::string_view kName = Field::kName; + using FieldValue = typename Field::value_type; + std::string temp{kName}; + auto& header_value = value.GetHeader(temp); + if constexpr (std::is_same_v) { + field = Field{header_value}; + } else { + field = + Parse(header_value, userver::formats::parse::To{}); + } + } + //Паттерн-матчинг относительно типа поля + template + static auto GetBody(const userver::server::http::HttpRequest& value) { + if constexpr (type_of_body == TypeOfBody::Json) { + return userver::formats::json::FromString(value.RequestBody()); + } else { + return EmptyBody{}; + } + } + template + static void ParseTuple(const userver::server::http::HttpRequest& value, + const Body& body, Tuple&& tuple, + std::index_sequence) { + check_properties(tuple); + //парсим все поля + (ParseField(value, body, std::get(tuple)), ...); + } +}; +} // namespace timetable_vsu_backend::utils::convert::detail::parse \ No newline at end of file diff --git a/src/utils/convert/detail/parse/converter_json.hpp b/src/utils/convert/detail/parse/converter_json.hpp new file mode 100644 index 00000000..5f25fe54 --- /dev/null +++ b/src/utils/convert/detail/parse/converter_json.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include + +#include "utils/convert/base.hpp" + +namespace timetable_vsu_backend::utils::convert::detail::parse { +template +struct ConverterJson { + static void Parse(T& t, const userver::formats::json::Value& value) { + auto tuple = GetTuple(t); + ParseTuple(value, std::move(tuple), IndexSequence{}); + } + + protected: + using ValueType = T; + //кортеж ссылок на изначальную структуру + using TupleType = + decltype(boost::pfr::structure_tie(std::declval())); + //количество полей + static constexpr std::size_t size = std::tuple_size::value; + //последовательность индексов для обхода полей + using IndexSequence = std::make_index_sequence; + //создаем кортеж ссылок на изначальную структуру + static TupleType GetTuple(ValueType& v) { + return boost::pfr::structure_tie(v); + } + //для парса поля достаем соответствующее поле JSON по названию из Property + template + static void ParseField(const userver::formats::json::Value& value, + Field& field) { + static constexpr std::string_view kName = Field::kName; + field = value[kName].template As(); + } + //проверяем, что каждое поле кортежа является Property + template + static constexpr void check_properties(const std::tuple&) { + static_assert((IsProperty> && ...), + "Not all properties satisfy IsProperty concept"); + } + template + static void ParseTuple(const userver::formats::json::Value& value, + TupleType&& tuple, std::index_sequence) { + check_properties(tuple); + //парсим каждое поле + (ParseField(value, std::get(tuple)), ...); + } +}; +} // namespace timetable_vsu_backend::utils::convert::detail::parse \ No newline at end of file diff --git a/src/utils/convert/detail/serialize/converter_http_response.hpp b/src/utils/convert/detail/serialize/converter_http_response.hpp new file mode 100644 index 00000000..915aa2ac --- /dev/null +++ b/src/utils/convert/detail/serialize/converter_http_response.hpp @@ -0,0 +1,129 @@ +#pragma once +#include +#include + +#include "userver/formats/json/value_builder.hpp" +#include "userver/server/http/http_response.hpp" +#include "utils/convert/base.hpp" +#include "utils/convert/http_response_base.hpp" + +namespace timetable_vsu_backend::utils::convert::detail::serialize { +template +struct ConverterHttpResponse { + static void Serialize(const T& t, HttpResponse& response) { + auto tuple = GetTuple(t); + static_assert(HasTypeOfBody, "Type must have kTypeOfBody"); + static_assert(HasStatusCode, "Type must have kStatusCode"); + auto body = GetBody(); + SerializeTuple(response, body, tuple, IndexSequence{}); + SetStatus(response); + TranslateBody(response, body); + } + + protected: + struct EmptyBody {}; + static void SetStatus(HttpResponse& response) { + response().SetStatus(T::kStatusCode); + } + static void TranslateBody(HttpResponse& response, EmptyBody&) { + response.body = ""; + } + static void TranslateBody(HttpResponse& response, + userver::formats::json::ValueBuilder& body) { + response.body = ToString(body.ExtractValue()); + } + using ValueType = T; + //кортеж константных ссылок на поля изначальной структуры + using ConstTupleType = + decltype(boost::pfr::structure_tie(std::declval())); + //количество полей структуры + static constexpr std::size_t size = std::tuple_size::value; + //последовательность индексов для обхода полей + using IndexSequence = std::make_index_sequence; + //создаем кортеж ссылок на поля изначальной структуры + static ConstTupleType GetTuple(const ValueType& v) { + return boost::pfr::structure_tie(v); + } + + //проверяем, что каждое поле кортежа является Property + template + static constexpr void check_properties(const std::tuple&) { + static_assert((IsAnyProperty> && ...), + "Not all properties satisfy IsProperty concept"); + } + //невозможно распарсить поле в тело, если оно пустое + template + static void SerializeField(HttpResponse&, const EmptyBody&, const Field&) { + auto bad = []() { + static_assert(flag, + "Found property from body, but body marked empty"); + }; + } + //сериализуем поле в json тело + template + static void SerializeField(HttpResponse&, + userver::formats::json::ValueBuilder& body, + const Field& field) { + static constexpr std::string_view kName = Field::kName; + std::string temp{kName}; + body[temp] = field(); + } + //сериализуем поле в query + template + static void SerializeField(HttpResponse&, + userver::formats::json::ValueBuilder&, + const Field&) { + auto bad = []() { + static_assert( + flag, + "A query property was found, which is invalid because " + "response does not have a query"); + }; + } + //сериализуем поле в cookie + template + static void SerializeField(HttpResponse& response, + userver::formats::json::ValueBuilder&, + const Field& field) { + static constexpr std::string_view kName = Field::kName; + std::string temp{kName}; + std::string field_value = + Serialize(field(), userver::formats::serialize::To{}); + //на самом деле там триллион параметров, для куки нужен отдельный + //все-таки проперти и отдельная обработка + userver::server::http::Cookie cookie{std::move(temp), + std::move(field_value)}; + response().SetCookie(std::move(cookie)); + } + //сериализуем поле в header + template + static void SerializeField(HttpResponse& response, + userver::formats::json::ValueBuilder&, + const Field& field) { + static constexpr std::string_view kName = Field::kName; + std::string temp{kName}; + std::string field_value = + Serialize(field(), userver::formats::serialize::To{}); + response().SetHeader(std::move(temp), std::move(field_value)); + } + //Паттерн-матчинг относительно типа поля + template + static auto GetBody() { + if constexpr (type_of_body == TypeOfBody::Json) { + //было бы неплохо поддерживать не только возврат как объекта, но + //пока так + return userver::formats::json::ValueBuilder( + userver::formats::json::Type::kObject); + } else { + return EmptyBody{}; + } + } + template + static void SerializeTuple(HttpResponse& response, Body& body, + Tuple&& tuple, std::index_sequence) { + check_properties(tuple); + //сериализуем все поля + (SerializeField(response, body, std::get(tuple)), ...); + } +}; +} // namespace timetable_vsu_backend::utils::convert::detail::serialize \ No newline at end of file diff --git a/src/utils/convert/detail/serialize/converter_json.hpp b/src/utils/convert/detail/serialize/converter_json.hpp new file mode 100644 index 00000000..01f616b0 --- /dev/null +++ b/src/utils/convert/detail/serialize/converter_json.hpp @@ -0,0 +1,52 @@ +#pragma once +#include + +#include "userver/formats/json/value_builder.hpp" +#include "utils/convert/base.hpp" + +namespace timetable_vsu_backend::utils::convert::detail::serialize { +template +struct ConverterJson { + static void Serialize(const T& t, + userver::formats::json::ValueBuilder& value) { + auto tuple = GetTuple(t); + SerializeTuple(value, std::move(tuple), IndexSequence{}); + } + + protected: + using ValueType = T; + //кортеж константных ссылок на поля изначальной структуры + using ConstTupleType = + decltype(boost::pfr::structure_tie(std::declval())); + //количество полей структуры + static constexpr std::size_t size = std::tuple_size::value; + //последовательность индексов для обхода полей + using IndexSequence = std::make_index_sequence; + //создаем кортеж ссылок на поля изначальной структуры + static ConstTupleType GetTuple(const ValueType& v) { + return boost::pfr::structure_tie(v); + } + template + static void SerializeField(userver::formats::json::ValueBuilder& value, + const Field& field) { + constexpr std::string_view kName = Field::kName; + std::string temp{kName}; + value.EmplaceNocheck(temp, field); + } + //проверка, что все поля являются Property + template + static constexpr void check_properties(const std::tuple&) { + static_assert((IsProperty> && ...), + "Not all properties satisfy IsProperty concept"); + } + + template + static void SerializeTuple(userver::formats::json::ValueBuilder& value, + ConstTupleType&& tuple, + std::index_sequence) { + check_properties(tuple); + //сериализуем все поля при помощи индексов + (SerializeField(value, std::get(tuple)), ...); + } +}; +} // namespace timetable_vsu_backend::utils::convert::detail::serialize \ No newline at end of file diff --git a/src/utils/convert/http_request_parse.hpp b/src/utils/convert/http_request_parse.hpp new file mode 100644 index 00000000..fe61a091 --- /dev/null +++ b/src/utils/convert/http_request_parse.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "detail/parse/converter_http_request.hpp" + +namespace timetable_vsu_backend::utils::convert { +//данынй концепт лишь активирует перегрузки, но не проверяет все требования для +//типа +template +concept HttpRequestParsable = IsConvertAll&& HasTypeOfBody; +} // namespace timetable_vsu_backend::utils::convert + +namespace userver::formats::parse { + +template +T Parse(const userver::server::http::HttpRequest& value, + formats::parse::To) { + T t; + timetable_vsu_backend::utils::convert::detail::parse::ConverterHttpRequest< + T>::Parse(t, value); + return t; +} +} // namespace userver::formats::parse \ No newline at end of file diff --git a/src/utils/convert/http_response_base.hpp b/src/utils/convert/http_response_base.hpp new file mode 100644 index 00000000..e55e8306 --- /dev/null +++ b/src/utils/convert/http_response_base.hpp @@ -0,0 +1,27 @@ +#pragma once +#include + +#include "utils/meta.hpp" + +namespace userver::server::http { +class HttpResponse; +} + +namespace timetable_vsu_backend::utils::convert { +struct HttpResponse { + userver::server::http::HttpResponse& response; + std::string& body; + userver::server::http::HttpResponse& operator()() { + return response; + } + userver::server::http::HttpResponse& operator()() const { + return response; + } +}; +template +concept HasStatusCode = requires { + { T::kStatusCode } + ->std::convertible_to; + requires IsConstexpr; +}; +} // namespace timetable_vsu_backend::utils::convert \ No newline at end of file diff --git a/src/utils/convert/http_response_serialize.hpp b/src/utils/convert/http_response_serialize.hpp new file mode 100644 index 00000000..57410b94 --- /dev/null +++ b/src/utils/convert/http_response_serialize.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "detail/serialize/converter_http_response.hpp" + +namespace timetable_vsu_backend::utils::convert { +//данынй концепт лишь активирует перегрузки, но не проверяет все требования для +//типа +template +concept HttpResponseSeriazable = + IsConvertAll&& HasTypeOfBody&& HasStatusCode; + +template +void Serialize(const T& t, HttpResponse& response) { + detail::serialize::ConverterHttpResponse::Serialize(t, response); +} +} // namespace timetable_vsu_backend::utils::convert \ No newline at end of file diff --git a/src/utils/convert/json_parse.hpp b/src/utils/convert/json_parse.hpp new file mode 100644 index 00000000..15e47891 --- /dev/null +++ b/src/utils/convert/json_parse.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "detail/parse/converter_json.hpp" + +namespace timetable_vsu_backend::utils::convert { +//данынй концепт лишь активирует перегрузки, но не проверяет все требования для +//типа +template +concept JsonParsable = IsConvertAll; +} // namespace timetable_vsu_backend::utils::convert + +namespace userver::formats::parse { + +template +T Parse(const json::Value& value, To) { + return T{value.As()}; +} + +template +T Parse(const json::Value& value, To) { + T t; + timetable_vsu_backend::utils::convert::detail::parse::ConverterJson< + T>::Parse(t, value); + return t; +} + +} // namespace userver::formats::parse \ No newline at end of file diff --git a/src/utils/convert/json_serialize.hpp b/src/utils/convert/json_serialize.hpp new file mode 100644 index 00000000..f8746d96 --- /dev/null +++ b/src/utils/convert/json_serialize.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "detail/serialize/converter_json.hpp" + +namespace timetable_vsu_backend::utils::convert { +//данынй концепт лишь активирует перегрузки, но не проверяет все требования для +//типа +template +concept JsonSeriazable = IsConvertAll; +} // namespace timetable_vsu_backend::utils::convert + +namespace userver::formats::serialize { + +template +json::Value Serialize(const T& t, To) { + return json::ValueBuilder(t.value).ExtractValue(); +} + +template +json::Value Serialize(const T& t, To) { + json::ValueBuilder json; + timetable_vsu_backend::utils::convert::detail::serialize::ConverterJson< + T>::Serialize(t, json); + return json.ExtractValue(); +} +} // namespace userver::formats::serialize \ No newline at end of file diff --git a/src/utils/json_type.cpp b/src/utils/json_type.cpp index 1cb1d38e..767bfa9a 100644 --- a/src/utils/json_type.cpp +++ b/src/utils/json_type.cpp @@ -1,28 +1,30 @@ #include "json_type.hpp" + #include + #include "userver/formats/json/impl/types.hpp" namespace timetable_vsu_backend::utils { std::string_view GetType(const userver::formats::json::Value& value) { - if (value.IsNull()) { - return "Null"; - } else if (value.IsArray()) { - return "Array"; - } else if (value.IsBool()) { - return "Bool"; - } else if (value.IsDouble()) { - return "Double"; - } else if (value.IsInt()) { - return "Int"; - } else if (value.IsInt64()) { - return "Int64"; - } else if (value.IsUInt64()) { - return "UInt64"; - } else if (value.IsObject()) { - return "Object"; - } else if (value.IsString()) { - return "String"; - } else - return "Unknown"; + if (value.IsNull()) { + return "Null"; + } else if (value.IsArray()) { + return "Array"; + } else if (value.IsBool()) { + return "Bool"; + } else if (value.IsDouble()) { + return "Double"; + } else if (value.IsInt()) { + return "Int"; + } else if (value.IsInt64()) { + return "Int64"; + } else if (value.IsUInt64()) { + return "UInt64"; + } else if (value.IsObject()) { + return "Object"; + } else if (value.IsString()) { + return "String"; + } else + return "Unknown"; } } // namespace timetable_vsu_backend::utils \ No newline at end of file diff --git a/src/utils/json_type.hpp b/src/utils/json_type.hpp index 437dc427..f052409d 100644 --- a/src/utils/json_type.hpp +++ b/src/utils/json_type.hpp @@ -1,6 +1,7 @@ #include #pragma once #include + #include "userver/formats/json/value.hpp" namespace timetable_vsu_backend::utils { diff --git a/src/utils/meta.hpp b/src/utils/meta.hpp new file mode 100644 index 00000000..4f8989fd --- /dev/null +++ b/src/utils/meta.hpp @@ -0,0 +1,5 @@ +#pragma once +template +concept IsConstexpr = requires { + requires true; +}; diff --git a/src/views/hello/view.cpp b/src/views/hello/view.cpp index 13aa92d9..d429964b 100644 --- a/src/views/hello/view.cpp +++ b/src/views/hello/view.cpp @@ -13,62 +13,63 @@ namespace service_template { namespace { class Hello final : public userver::server::handlers::HttpHandlerBase { - public: - static constexpr std::string_view kName = "handler-hello"; - - Hello(const userver::components::ComponentConfig& config, - const userver::components::ComponentContext& component_context) - : HttpHandlerBase(config, component_context), - pg_cluster_( - component_context - .FindComponent("postgres-db-1") - .GetCluster()) {} - - std::string HandleRequestThrow( - const userver::server::http::HttpRequest& request, - userver::server::request::RequestContext&) const override { - const auto& name = request.GetArg("name"); - - auto user_type = UserType::kFirstTime; - if (!name.empty()) { - auto result = pg_cluster_->Execute( - userver::storages::postgres::ClusterHostType::kMaster, - "INSERT INTO hello_schema.users(name, count) VALUES($1, 1) " - "ON CONFLICT (name) " - "DO UPDATE SET count = users.count + 1 " - "RETURNING users.count", - name); - - if (result.AsSingleRow() > 1) { - user_type = UserType::kKnown; - } + public: + static constexpr std::string_view kName = "handler-hello"; + + Hello(const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& component_context) + : HttpHandlerBase(config, component_context), + pg_cluster_( + component_context + .FindComponent("postgres-db-1") + .GetCluster()) { } - return service_template::SayHelloTo(name, user_type); - } + std::string HandleRequestThrow( + const userver::server::http::HttpRequest& request, + userver::server::request::RequestContext&) const override { + const auto& name = request.GetArg("name"); + + auto user_type = UserType::kFirstTime; + if (!name.empty()) { + auto result = pg_cluster_->Execute( + userver::storages::postgres::ClusterHostType::kMaster, + "INSERT INTO hello_schema.users(name, count) VALUES($1, 1) " + "ON CONFLICT (name) " + "DO UPDATE SET count = users.count + 1 " + "RETURNING users.count", + name); + + if (result.AsSingleRow() > 1) { + user_type = UserType::kKnown; + } + } + + return service_template::SayHelloTo(name, user_type); + } - userver::storages::postgres::ClusterPtr pg_cluster_; + userver::storages::postgres::ClusterPtr pg_cluster_; }; } // namespace std::string SayHelloTo(std::string_view name, UserType type) { - if (name.empty()) { - name = "unknown user"; - } - - switch (type) { - case UserType::kFirstTime: - return fmt::format("Hello, {}!\n", name); - case UserType::kKnown: - return fmt::format("Hi again, {}!\n", name); - } - - UASSERT(false); + if (name.empty()) { + name = "unknown user"; + } + + switch (type) { + case UserType::kFirstTime: + return fmt::format("Hello, {}!\n", name); + case UserType::kKnown: + return fmt::format("Hi again, {}!\n", name); + } + + UASSERT(false); } void AppendHello(userver::components::ComponentList& component_list) { - component_list.Append(); + component_list.Append(); } } // namespace service_template \ No newline at end of file diff --git a/src/views/hello/view.hpp b/src/views/hello/view.hpp index bb631b4d..ef51c616 100644 --- a/src/views/hello/view.hpp +++ b/src/views/hello/view.hpp @@ -2,7 +2,6 @@ #include #include - #include namespace service_template { diff --git a/src/views/login/Request.cpp b/src/views/login/Request.cpp deleted file mode 100644 index e841d4c0..00000000 --- a/src/views/login/Request.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "Request.hpp" -#include -#include "userver/formats/json.hpp" -#include "userver/formats/json/value_builder.hpp" -#include "userver/formats/yaml/serialize.hpp" -#include "userver/logging/log.hpp" -#include "userver/server/http/http_request.hpp" - -namespace timetable_vsu_backend::views::login { -Request Parse(const userver::server::http::HttpRequest& value, - userver::formats::parse::To) { - auto body = userver::formats::json::FromString(value.RequestBody()); - return Request{body["login"].As(), - body["password"].As()}; -} -} // namespace timetable_vsu_backend::views::login \ No newline at end of file diff --git a/src/views/login/Request.hpp b/src/views/login/Request.hpp index 80b6031a..7793b6e0 100644 --- a/src/views/login/Request.hpp +++ b/src/views/login/Request.hpp @@ -2,15 +2,19 @@ #include #include -namespace userver::server::http { -class HttpRequest; -} +#include "userver/formats/common/meta.hpp" +#include "userver/server/http/http_request.hpp" +#include "utils/convert/base.hpp" +#include "utils/convert/http_request_parse.hpp" +#include "utils/convert/json_parse.hpp" namespace timetable_vsu_backend::views::login { +using namespace utils::convert; struct Request { - std::string login; - std::string password; + Property login; + Property password; + static constexpr TypeOfBody kTypeOfBody = TypeOfBody::Json; + static constexpr PolicyFields kPolicyFields = PolicyFields::ConvertAll; }; -Request Parse(const userver::server::http::HttpRequest& value, - userver::formats::parse::To); +static_assert(HasTypeOfBody); } // namespace timetable_vsu_backend::views::login \ No newline at end of file diff --git a/src/views/login/Responses.hpp b/src/views/login/Responses.hpp new file mode 100644 index 00000000..4a928fcd --- /dev/null +++ b/src/views/login/Responses.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include + +#include "utils/convert/base.hpp" +#include "utils/convert/http_response_serialize.hpp" +#include "utils/convert/json_parse.hpp" + +namespace timetable_vsu_backend::views::login { +using namespace utils::convert; +struct Response200 { + Property id; + static constexpr TypeOfBody kTypeOfBody = TypeOfBody::Json; + static constexpr PolicyFields kPolicyFields = PolicyFields::ConvertAll; + static constexpr userver::server::http::HttpStatus kStatusCode = + userver::server::http::HttpStatus::kOk; +}; +struct Response401 { + static constexpr TypeOfBody kTypeOfBody = TypeOfBody::Empty; + static constexpr PolicyFields kPolicyFields = PolicyFields::ConvertAll; + static constexpr userver::server::http::HttpStatus kStatusCode = + userver::server::http::HttpStatus::kUnauthorized; +}; +struct Response500 { + static constexpr TypeOfBody kTypeOfBody = TypeOfBody::Empty; + static constexpr PolicyFields kPolicyFields = PolicyFields::ConvertAll; + static constexpr userver::server::http::HttpStatus kStatusCode = + userver::server::http::HttpStatus::kInternalServerError; +}; +} // namespace timetable_vsu_backend::views::login \ No newline at end of file diff --git a/src/views/login/view.cpp b/src/views/login/view.cpp index b351b491..aac576cb 100644 --- a/src/views/login/view.cpp +++ b/src/views/login/view.cpp @@ -1,59 +1,59 @@ #include "view.hpp" + #include #include #include -#include "../../components/controllers/token_controller.hpp" -#include "../../components/controllers/user_controller.hpp" -#include "../../http/handler_parsed.hpp" -#include "../../models/auth_token/serialize.hpp" + #include "Request.hpp" +#include "components/controllers/token_controller.hpp" +#include "components/controllers/user_controller.hpp" +#include "http/handler_parsed.hpp" +#include "models/auth_token/serialize.hpp" #include "userver/formats/parse/to.hpp" #include "userver/storages/postgres/component.hpp" #include "userver/utils/datetime.hpp" +#include "views/login/Responses.hpp" namespace timetable_vsu_backend::views::login { namespace { -using Response = models::AuthToken; -class LoginHandler final : public http::HandlerParsed { - public: - static constexpr std::string_view kName = "handler-login"; - using http::HandlerParsed::HandlerParsed; - LoginHandler(const userver::components::ComponentConfig& config, - const userver::components::ComponentContext& context) - : HandlerParsed(config, context), - user_controller(context.FindComponent()), - token_controller(context.FindComponent()) { - } - - Response Handle( - Request&& request, - userver::server::http::HttpResponse& http_response) const override { - auto user = user_controller.GetByLogin(request.login); - if (!user || user->password != request.password) { - http_response.SetStatus(HttpStatus::kUnauthorized); - return {}; +class LoginHandler final + : public http::HandlerParsed { + public: + static constexpr std::string_view kName = "handler-login"; + LoginHandler(const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context) + : HandlerParsed(config, context), + user_controller(context.FindComponent()), + token_controller( + context.FindComponent()) { } - auto id = token_controller.CreateNew( - user->id, userver::utils::datetime::Now() + std::chrono::hours(24)); - if (!id) { - LOG_WARNING() << fmt::format( - "Failed to create token for user, id: {}", - boost::uuids::to_string(user->id.GetUnderlying())); - http_response.SetStatus(HttpStatus::kInternalServerError); - return {}; + + Response Handle(Request&& request) const override { + auto user = user_controller.GetByLogin(request.login()); + if (!user || user->password != request.password()) { + return Response401{}; + } + auto id = token_controller.CreateNew( + user->id, userver::utils::datetime::Now() + std::chrono::hours(24)); + if (!id) { + LOG_WARNING() << fmt::format( + "Failed to create token for user, id: {}", + boost::uuids::to_string(user->id.GetUnderlying())); + return Response500{}; + } + return Response200{*id}; } - return {*id}; - } - private: - const components::UserController& user_controller; - const components::TokenController& token_controller; + private: + const components::UserController& user_controller; + const components::TokenController& token_controller; }; } // namespace void Append(userver::components::ComponentList& component_list) { - component_list.Append(); + component_list.Append(); } } // namespace timetable_vsu_backend::views::login diff --git a/src/views/login/view.hpp b/src/views/login/view.hpp index fad559a5..2f940d01 100644 --- a/src/views/login/view.hpp +++ b/src/views/login/view.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../../utils/component_list_fwd.hpp" +#include "utils/component_list_fwd.hpp" namespace timetable_vsu_backend::views::login { void Append(userver::components::ComponentList& component_list); diff --git a/src/views/register/Request.cpp b/src/views/register/Request.cpp index 14d8b722..ca4be265 100644 --- a/src/views/register/Request.cpp +++ b/src/views/register/Request.cpp @@ -1,16 +1,17 @@ #include "Request.hpp" -#include "../../utils/json_type.hpp" + #include "userver/formats/json.hpp" #include "userver/formats/json/value_builder.hpp" #include "userver/logging/log.hpp" #include "userver/server/http/http_request.hpp" +#include "utils/json_type.hpp" namespace timetable_vsu_backend::views::register_ { Request Parse(const userver::server::http::HttpRequest& value, userver::formats::parse::To) { - auto body = userver::formats::json::FromString(value.RequestBody()); - auto login = body["login"].As(); - auto password = body["password"].As(); - return Request{std::move(login), std::move(password)}; + auto body = userver::formats::json::FromString(value.RequestBody()); + auto login = body["login"].As(); + auto password = body["password"].As(); + return Request{std::move(login), std::move(password)}; } } // namespace timetable_vsu_backend::views::register_ \ No newline at end of file diff --git a/src/views/register/Request.hpp b/src/views/register/Request.hpp index 262d178f..40364d70 100644 --- a/src/views/register/Request.hpp +++ b/src/views/register/Request.hpp @@ -8,8 +8,8 @@ class HttpRequest; namespace timetable_vsu_backend::views::register_ { struct Request { - std::string login; - std::string password; + std::string login; + std::string password; }; Request Parse(const userver::server::http::HttpRequest& value, userver::formats::parse::To); diff --git a/src/views/register/view.cpp b/src/views/register/view.cpp index bad447d3..7ce80129 100644 --- a/src/views/register/view.cpp +++ b/src/views/register/view.cpp @@ -1,15 +1,18 @@ #include "view.hpp" + #include + #include #include #include #include #include -#include "../../components/controllers/token_controller.hpp" -#include "../../components/controllers/user_controller.hpp" -#include "../../http/handler_parsed.hpp" -#include "../../models/auth_token/serialize.hpp" + #include "Request.hpp" +#include "components/controllers/token_controller.hpp" +#include "components/controllers/user_controller.hpp" +#include "http/legacy_handler_parsed.hpp" +#include "models/auth_token/serialize.hpp" #include "userver/formats/parse/to.hpp" #include "userver/logging/log.hpp" #include "userver/storages/postgres/component.hpp" @@ -21,49 +24,52 @@ namespace timetable_vsu_backend::views::register_ { namespace { using Response = models::AuthToken; -class RegisterHandler final : public http::HandlerParsed { - public: - static constexpr std::string_view kName = "handler-register"; - using http::HandlerParsed::HandlerParsed; - RegisterHandler(const userver::components::ComponentConfig& config, - const userver::components::ComponentContext& context) - : HandlerParsed(config, context), - user_controller(context.FindComponent()), - token_controller(context.FindComponent()) { - } - - Response Handle( - Request&& request, - userver::server::http::HttpResponse& http_response) const override { - models::User user{models::User::Id{}, request.login, request.password, - models::UserType::kUser}; - auto user_id = user_controller.TryToAdd(user); - if (!user_id) { - LOG_DEBUG() << fmt::format("Cannot create user, login: {}", user.login); - http_response.SetStatus(HttpStatus::kBadRequest); - return {}; +class RegisterHandler final + : public http::LegacyHandlerParsed { + public: + static constexpr std::string_view kName = "handler-register"; + using http::LegacyHandlerParsed::LegacyHandlerParsed; + RegisterHandler(const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context) + : LegacyHandlerParsed(config, context), + user_controller(context.FindComponent()), + token_controller( + context.FindComponent()) { } - user.id = models::User::Id{std::move(*user_id)}; - auto id = token_controller.CreateNew( - user.id, userver::utils::datetime::Now() + std::chrono::hours(24)); - if (!id) { - LOG_WARNING() << fmt::format( - "Failed to create token for user, id: {}", - boost::uuids::to_string(user.id.GetUnderlying())); - http_response.SetStatus(HttpStatus::kInternalServerError); - return {}; + + Response Handle( + Request&& request, + userver::server::http::HttpResponse& http_response) const override { + models::User user{models::User::Id{}, request.login, request.password, + models::UserType::kUser}; + auto user_id = user_controller.TryToAdd(user); + if (!user_id) { + LOG_DEBUG() << fmt::format("Cannot create user, login: {}", + user.login); + http_response.SetStatus(HttpStatus::kBadRequest); + return {}; + } + user.id = models::User::Id{std::move(*user_id)}; + auto id = token_controller.CreateNew( + user.id, userver::utils::datetime::Now() + std::chrono::hours(24)); + if (!id) { + LOG_WARNING() << fmt::format( + "Failed to create token for user, id: {}", + boost::uuids::to_string(user.id.GetUnderlying())); + http_response.SetStatus(HttpStatus::kInternalServerError); + return {}; + } + return {*id}; } - return {*id}; - } - private: - const components::UserController& user_controller; - const components::TokenController& token_controller; + private: + const components::UserController& user_controller; + const components::TokenController& token_controller; }; } // namespace void Append(userver::components::ComponentList& component_list) { - component_list.Append(); + component_list.Append(); } } // namespace timetable_vsu_backend::views::register_ diff --git a/src/views/register/view.hpp b/src/views/register/view.hpp index 34078204..76049f5d 100644 --- a/src/views/register/view.hpp +++ b/src/views/register/view.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../../utils/component_list_fwd.hpp" +#include "utils/component_list_fwd.hpp" namespace timetable_vsu_backend::views::register_ { void Append(userver::components::ComponentList& component_list); diff --git a/utests/convert_test.cpp b/utests/convert_test.cpp new file mode 100644 index 00000000..38dbb429 --- /dev/null +++ b/utests/convert_test.cpp @@ -0,0 +1,50 @@ +#include + +#include +#include +#include +#include + +#include "userver/formats/common/meta.hpp" +#include "userver/formats/json/value.hpp" +#include "userver/formats/json/value_builder.hpp" +#include "userver/logging/log.hpp" +#include "userver/server/http/http_request.hpp" +#include "utils/convert/base.hpp" +#include "utils/convert/json_parse.hpp" +#include "utils/convert/json_serialize.hpp" + +namespace magic = timetable_vsu_backend::utils::convert; +struct TestStruct { + static constexpr auto kPolicyFields = magic::PolicyFields::ConvertAll; + magic::Property Login; + magic::Property Password; +}; +static_assert(magic::IsProperty); + +UTEST(TestConvert, BasicJsonToStruct) { + static_assert(magic::IsConvertAll); + static_assert( + userver::formats::common::impl::kHasParse); + userver::formats::json::ValueBuilder builder; + builder["login"] = "test_login"; + builder["password"] = "test_password"; + auto json = builder.ExtractValue(); + auto test_struct = json.As(); + EXPECT_EQ(test_struct.Login(), "test_login"); + EXPECT_EQ(test_struct.Password(), "test_password"); +} + +UTEST(TestConvert, BasicStructToJson) { + static_assert(magic::IsConvertAll); + static_assert(userver::formats::common::impl::kHasSerialize< + userver::formats::json::Value, TestStruct>); + TestStruct test_struct; + test_struct.Login() = "some_login"; + test_struct.Password() = "some_password"; + auto json = + userver::formats::json::ValueBuilder(test_struct).ExtractValue(); + EXPECT_EQ(json["login"].As(), "some_login"); + EXPECT_EQ(json["password"].As(), "some_password"); +} \ No newline at end of file diff --git a/utests/hello_test.cpp b/utests/hello_test.cpp index c3cf58be..14aeeae7 100644 --- a/utests/hello_test.cpp +++ b/utests/hello_test.cpp @@ -1,15 +1,16 @@ #include #include -#include "../src/views/hello/view.hpp" + +#include "views/hello/view.hpp" UTEST(SayHelloTo, Basic) { - EXPECT_EQ(service_template::SayHelloTo("Developer", - service_template::UserType::kKnown), - "Hi again, Developer!\n"); - EXPECT_EQ( - service_template::SayHelloTo("", service_template::UserType::kFirstTime), - "Hello, unknown user!\n"); - EXPECT_EQ(service_template::SayHelloTo( - "Developer", service_template::UserType::kFirstTime), - "Hello, Developer!\n"); + EXPECT_EQ(service_template::SayHelloTo("Developer", + service_template::UserType::kKnown), + "Hi again, Developer!\n"); + EXPECT_EQ(service_template::SayHelloTo( + "", service_template::UserType::kFirstTime), + "Hello, unknown user!\n"); + EXPECT_EQ(service_template::SayHelloTo( + "Developer", service_template::UserType::kFirstTime), + "Hello, Developer!\n"); } diff --git a/vsu_timetable.code-workspace b/vsu_timetable.code-workspace index a29499af..af0a1a58 100644 --- a/vsu_timetable.code-workspace +++ b/vsu_timetable.code-workspace @@ -21,8 +21,9 @@ "third_party": true, "build*": true, ".*" : true, - "dependencies" : true + "dependencies" : true, + "compile_commands.json" : true }, - "clang-tidy.buildPath": "../build_debug" + "clang-tidy.buildPath": "build_debug" } }