diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..76246c6
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+build
+**/CMakeCache.txt
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..752ea4b
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @charankamarapu
diff --git a/.gitmodules b/.gitmodules
index f4b271e..497bb62 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,3 +10,9 @@
[submodule "third_party/libbcrypt"]
path = third_party/libbcrypt
url = https://github.com/trusch/libbcrypt
+[submodule "third_party/gtest"]
+ path = third_party/gtest
+ url = https://github.com/google/googletest
+[submodule "third_party/gmock"]
+ path = third_party/gmock
+ url = https://github.com/google/googlemock
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 44a3e69..c0929ed 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,83 +1,170 @@
+# cmake_minimum_required(VERSION 3.5)
+# project(org_chart CXX)
+
+# include(CheckIncludeFileCXX)
+
+# check_include_file_cxx(any HAS_ANY)
+# check_include_file_cxx(string_view HAS_STRING_VIEW)
+# check_include_file_cxx(coroutine HAS_COROUTINE)
+# if (NOT "${CMAKE_CXX_STANDARD}" STREQUAL "")
+# # Do nothing
+# elseif (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE)
+# set(CMAKE_CXX_STANDARD 20)
+# elseif (HAS_ANY AND HAS_STRING_VIEW)
+# set(CMAKE_CXX_STANDARD 17)
+# else ()
+# set(CMAKE_CXX_STANDARD 14)
+# endif ()
+
+# set(CMAKE_CXX_STANDARD_REQUIRED ON)
+# set(CMAKE_CXX_EXTENSIONS OFF)
+
+# add_executable(${PROJECT_NAME} main.cc)
+
+# # Add these lines for coverage
+# if (COVERAGE MATCHES "ON") # Or any build type you use for coverage
+# message(STATUS "Enabling code coverage flags")
+# target_compile_options(${PROJECT_NAME} PRIVATE --coverage)
+# target_link_options(${PROJECT_NAME} PRIVATE --coverage)
+# endif()
+
+# # ##############################################################################
+# # https://github.com/drogonframework/drogon
+# add_subdirectory(third_party/drogon)
+# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
+
+# # https://github.com/Thalhammer/jwt-cpp
+# add_subdirectory(third_party/jwt-cpp)
+# target_link_libraries(${PROJECT_NAME} PRIVATE jwt-cpp)
+
+# # https://github.com/trusch/libbcrypt
+# add_subdirectory(third_party/libbcrypt)
+# target_link_libraries(${PROJECT_NAME} PRIVATE bcrypt)
+
+# add_subdirectory(third_party/gtest)
+# target_link_libraries(${PROJECT_NAME} PRIVATE gtest gtest_main)
+
+# # and comment out the following lines
+# find_package(Drogon CONFIG REQUIRED)
+# target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
+
+# # ##############################################################################
+
+# if (CMAKE_CXX_STANDARD LESS 17)
+# # With C++14, use boost to support any, string_view and filesystem
+# message(STATUS "use c++14")
+# find_package(Boost 1.61.0 REQUIRED)
+# target_link_libraries(${PROJECT_NAME} PUBLIC Boost::boost)
+# elseif (CMAKE_CXX_STANDARD LESS 20)
+# message(STATUS "use c++17")
+# else ()
+# message(STATUS "use c++20")
+# endif ()
+
+# aux_source_directory(controllers CTL_SRC)
+# aux_source_directory(filters FILTER_SRC)
+# aux_source_directory(plugins PLUGIN_SRC)
+# aux_source_directory(models MODEL_SRC)
+# aux_source_directory(utils UTIL_SRC)
+
+# target_include_directories(${PROJECT_NAME}
+# PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+# ${CMAKE_CURRENT_SOURCE_DIR}/models)
+# target_sources(${PROJECT_NAME}
+# PRIVATE
+# ${SRC_DIR}
+# ${CTL_SRC}
+# ${FILTER_SRC}
+# ${PLUGIN_SRC}
+# ${MODEL_SRC}
+# ${UTIL_SRC})
+# # ##############################################################################
+# # uncomment the following line for dynamically loading views
+# # set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS ON)
+
+# # ##############################################################################
+
+# add_subdirectory(test)
+
+# # add_executable(${PROJECT_NAME}_test test/test_main.cc)
+
+# # target_link_libraries(${PROJECT_NAME}_test PRIVATE drogon)
+
+# # ParseAndAddDrogonTests(${PROJECT_NAME}_test)
+# # ===================================================================
+# # AFL++ Fuzzer Target (add this entire block to the end of the file)
+# # ===================================================================
+# option(BUILD_FUZZER "Build the AFL++ fuzzer harness" ON)
+
+# if(BUILD_FUZZER)
+# message(STATUS "Fuzzer build is enabled.")
+
+# # Force the use of the AFL++ compiler, overriding any external settings.
+# set(CMAKE_CXX_COMPILER "afl-clang-fast++" CACHE FILEPATH "AFL++ C++ compiler" FORCE)
+# set(CMAKE_C_COMPILER "afl-clang-fast" CACHE FILEPATH "AFL++ C compiler" FORCE)
+
+# # Define the fuzzer executable
+# add_executable(fuzz_harness harness.cpp)
+
+# # Link the fuzzer against your entire API project.
+# # This automatically handles ALL dependencies, source files, and libraries.
+# target_link_libraries(fuzz_harness PRIVATE org_chart)
+
+# # Add the FUZZING_BUILD definition and the fuzzer runtime linker flag
+# target_compile_definitions(fuzz_harness PRIVATE FUZZING_BUILD)
+# target_link_libraries(fuzz_harness PRIVATE -fsanitize=fuzzer)
+
+# message(STATUS "Configured fuzzer target. To build, run from your build directory: make fuzz_harness")
+# endif()
cmake_minimum_required(VERSION 3.5)
project(org_chart CXX)
-include(CheckIncludeFileCXX)
-
-check_include_file_cxx(any HAS_ANY)
-check_include_file_cxx(string_view HAS_STRING_VIEW)
-check_include_file_cxx(coroutine HAS_COROUTINE)
-if (NOT "${CMAKE_CXX_STANDARD}" STREQUAL "")
- # Do nothing
-elseif (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE)
- set(CMAKE_CXX_STANDARD 20)
-elseif (HAS_ANY AND HAS_STRING_VIEW)
- set(CMAKE_CXX_STANDARD 17)
-else ()
- set(CMAKE_CXX_STANDARD 14)
-endif ()
-
+set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
-add_executable(${PROJECT_NAME} main.cc)
-
-# ##############################################################################
-# https://github.com/drogonframework/drogon
add_subdirectory(third_party/drogon)
-target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
-
-# https://github.com/Thalhammer/jwt-cpp
add_subdirectory(third_party/jwt-cpp)
-target_link_libraries(${PROJECT_NAME} PRIVATE jwt-cpp)
-
-# https://github.com/trusch/libbcrypt
add_subdirectory(third_party/libbcrypt)
-target_link_libraries(${PROJECT_NAME} PRIVATE bcrypt)
-
-# and comment out the following lines
-find_package(Drogon CONFIG REQUIRED)
-target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
-
-# ##############################################################################
-
-if (CMAKE_CXX_STANDARD LESS 17)
- # With C++14, use boost to support any, string_view and filesystem
- message(STATUS "use c++14")
- find_package(Boost 1.61.0 REQUIRED)
- target_link_libraries(${PROJECT_NAME} PUBLIC Boost::boost)
-elseif (CMAKE_CXX_STANDARD LESS 20)
- message(STATUS "use c++17")
-else ()
- message(STATUS "use c++20")
-endif ()
+add_subdirectory(third_party/jsoncpp)
aux_source_directory(controllers CTL_SRC)
-aux_source_directory(filters FILTER_SRC)
-aux_source_directory(plugins PLUGIN_SRC)
aux_source_directory(models MODEL_SRC)
aux_source_directory(utils UTIL_SRC)
-target_include_directories(${PROJECT_NAME}
- PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/models)
-target_sources(${PROJECT_NAME}
- PRIVATE
- ${SRC_DIR}
- ${CTL_SRC}
- ${FILTER_SRC}
- ${PLUGIN_SRC}
- ${MODEL_SRC}
- ${UTIL_SRC})
-# ##############################################################################
-# uncomment the following line for dynamically loading views
-# set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS ON)
+add_library(org_chart_lib STATIC
+ ${CTL_SRC}
+ ${MODEL_SRC}
+ ${UTIL_SRC}
+)
+
+target_link_libraries(org_chart_lib PRIVATE
+ drogon
+ jwt-cpp
+ bcrypt
+ jsoncpp_lib
+)
+target_include_directories(org_chart_lib PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}
+)
+add_executable(${PROJECT_NAME} main.cc)
+target_link_libraries(${PROJECT_NAME} PRIVATE org_chart_lib)
-# ##############################################################################
+option(BUILD_FUZZER "Build the AFL++ fuzzer harness" ON)
-add_subdirectory(test)
+if(BUILD_FUZZER)
+ message(STATUS "Fuzzer build is enabled.")
+ add_executable(fuzz_harness harness.cpp)
-# add_executable(${PROJECT_NAME}_test test/test_main.cc)
+ target_link_libraries(fuzz_harness PRIVATE org_chart_lib)
-# target_link_libraries(${PROJECT_NAME}_test PRIVATE drogon)
+ target_include_directories(fuzz_harness PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/drogon/lib/inc
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/drogon/orm_lib/inc
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/jsoncpp/include
+ )
+ target_link_options(fuzz_harness PRIVATE -fsanitize=fuzzer)
-# ParseAndAddDrogonTests(${PROJECT_NAME}_test)
+ target_compile_definitions(fuzz_harness PRIVATE FUZZING_BUILD)
+ message(STATUS "Configured fuzzer target. To build, run: make fuzz_harness")
+endif()
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a7d2cc0
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,61 @@
+# Start with the base Ubuntu image
+FROM ubuntu:22.04
+
+# Set the timezone
+ENV TZ=UTC
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+# Install necessary dependencies
+RUN apt-get update -yqq \
+ && apt-get install -yqq --no-install-recommends \
+ software-properties-common \
+ sudo curl wget cmake make pkg-config locales git \
+ gcc-11 g++-11 openssl libssl-dev libjsoncpp-dev uuid-dev \
+ zlib1g-dev libc-ares-dev postgresql-server-dev-all \
+ libmariadb-dev libsqlite3-dev libhiredis-dev \
+ && rm -rf /var/lib/apt/lists/* \
+ && locale-gen en_US.UTF-8
+
+# Set environment variables for localization
+ENV LANG=en_US.UTF-8 \
+ LANGUAGE=en_US:en \
+ LC_ALL=en_US.UTF-8 \
+ CC=gcc-11 \
+ CXX=g++-11 \
+ AR=gcc-ar-11 \
+ RANLIB=gcc-ranlib-11 \
+ IROOT=/install
+
+# Clone Drogon repository
+ENV DROGON_ROOT="$IROOT/drogon"
+RUN git clone --depth 1 --recurse-submodules \
+ https://github.com/drogonframework/drogon $DROGON_ROOT # β submodules pulled
+
+WORKDIR $DROGON_ROOT
+RUN mkdir build && cd build && \
+ cmake .. -DCMAKE_BUILD_TYPE=Release \
+ -DMYSQL_CLIENT=ON \
+ -DPOSTGRESQL_CLIENT=OFF \
+ && make -j$(nproc) && make install
+
+# Build Drogon
+RUN ./build.sh
+
+WORKDIR /
+
+# Copy source code for your application (from the local directory)
+COPY . /app
+
+WORKDIR /app
+
+# Install build tools for the app
+RUN apt-get update && apt-get install -y cmake g++ git
+
+# Pull submodules for your application
+RUN git submodule update --init --recursive
+
+# Create build directory and build the project
+RUN mkdir -p /app/build && cd /app/build && cmake .. && make -j$(nproc)
+
+# Set CMD to the actual binary
+CMD ["./build/org_chart"]
diff --git a/ReadMe.md b/ReadMe.md
index e16ea63..a9713fb 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -1,189 +1,347 @@
-# Org Chart Api
-
-Restful API built using
-[Drogon](https://github.com/drogonframework/drogon).
-Routes are protected using JWT for token-based authorization.
-
-Endpoints
----------
-
-### Persons
-| Method | URI | Action |
-|------------|---------------------------------------|-------------------------------------------|
-| `GET` | `/persons?limit={}&offset={}&sort_field={}&sort_order={}` | `Retrieve all persons` |
-| `GET` | `/persons/{id}` | `Retrieve person` |
-| `GET` | `/persons/{id}/reports` | `Retrieve person direct reports` |
-| `POST` | `/persons` | `Create person` |
-| `PUT` | `/persons/{id}` | `Update person` |
-| `DELETE` | `/persons/{id} ` | `Delete person` |
-
-### Departments
-| Method | URI | Action |
-|------------|---------------------------------------|-------------------------------------------|
-| `GET` | `/departments?limit={}&offset={}&sort_field={}&sort_order={}` | `Retrieve all departments` |
-| `GET` | `/departments/{id}` | `Retrieve department` |
-| `GET` | `/departments/{id}/persons` | `Retrieve department persons` |
-| `POST` | `/departments` | `Create department` |
-| `PUT` | `/departments/{id}` | `Update department` |
-| `DELETE` | `/departments/{id}` | `Delete department` |
-
-### Jobs
-| Method | URI | Action |
-|------------|---------------------------------------|-------------------------------------------|
-| `GET` | `/jobs?limit={}&offset={}&sort_fields={}&sort_order={}` | `Retrieve all jobs` |
-| `GET` | `/jobs/{id}` | `Retrieve job` |
-| `GET` | `/jobs/{id}/persons` | `Retrieve job persons` |
-| `POST` | `/jobs` | `Create job` |
-| `PUT` | `/jobs/{id}` | `Update job` |
-| `DELETE` | `/jobs/{id}` | `Delete job` |
-
-### Auth
-| Method | URI | Action |
-|------------|---------------------------------------|-------------------------------------------|
-| `POST` | `/auth/register` | `Register user and obtain JWT token` |
-| `POST` | `/auth/login` | `Login User ` |
-
-How to build the project
+# Org Chart API
+
+## π Index
+
+1. [Overview](#overview)
+2. [Endpoints](#-endpoints)
+ - [Persons](#persons)
+ - [Departments](#-departments)
+ - [Jobs](#-jobs)
+ - [Auth](#-auth)
+3. [Getting Started](#-two-ways-to-get-started)
+ - [Using Docker](#1-using-docker)
+ - [Manual Setup](#2-manual-setup-for-those-who-prefer-to-run-the-project-locally)
+ - [Install Dependencies](#-install-dependencies)
+ - [Drogon Installation](#-drogon-installation)
+ - [Database Setup](#database-setup)
+ - [Build the Project](#build-the-project)
+4. [UT and Coverage](#-ut-and-coverage)
+5. [Usage Guide](#-usage-guide)
+6. [keploy Integration and Coverage](#keploy-integration-api-testing-and-coverage)
+
+## Overview
+
+A **RESTful API** built with [Drogon](https://github.com/drogonframework/drogon), a high-performance C++ framework. This API is designed to manage organizational structures, including persons, departments, and job roles.
+
+π **All routes are protected using JWT for token-based authentication**.
+
+## π Endpoints
+
+### π§ Persons
+
+| Method | URI | Action |
+| -------- | --------------------------------------------------------- | ------------------------- |
+| `GET` | `/persons?limit={}&offset={}&sort_field={}&sort_order={}` | Retrieve all persons |
+| `GET` | `/persons/{id}` | Retrieve a single person |
+| `GET` | `/persons/{id}/reports` | Retrieve direct reports |
+| `POST` | `/persons` | Create a new person |
+| `PUT` | `/persons/{id}` | Update a person's details |
+| `DELETE` | `/persons/{id}` | Delete a person |
+
---
-### Installation
-See drogon documentation [here](https://github.com/an-tao/drogon/wiki/ENG-02-Installation#System-Requirements)!
-
-### Verify Installation
-Confirm the database development environment using `drogon_ctl -v`:
-```
- _ [0/365]
- __| |_ __ ___ __ _ ___ _ __
- / _` | '__/ _ \ / _` |/ _ \| '_ \
-| (_| | | | (_) | (_| | (_) | | | |
- \__,_|_| \___/ \__, |\___/|_| |_|
- |___/
-
-A utility for drogon
-Version: 1.7.5
-Git commit: fc68b8c92c8c202d8cc58d83629d6e8c8701fc47
-Compilation:
- Compiler: /Library/Developer/CommandLineTools/usr/bin/c++
- Compiler ID: AppleClang
- Compilation flags: -std=c++17 -I/usr/local/include
-Libraries:
- postgresql: yes (batch mode: no)
- mariadb: yes
- sqlite3: yes
- openssl: yes
- brotli: yes
- boost: no
- hiredis: no
- c-ares: yes
-```
-
-### Setup Database
-Start a postgres server.
-`docker run --name pg -e POSTGRES_PASSWORD=password -d -p 5433:5432 postgres`
-
-
-Log into postgres using `psql` to create a `org_chart` database.
-`psql 'postgresql://postgres:password@127.0.0.1:5433/org_chart'`
-
-Create and seed the tables.
-`psql 'postgresql://postgres:password@127.0.0.1:5433/org_chart' -f scripts/create_db.sql`
-`psql 'postgresql://postgres:password@127.0.0.1:5433/org_chart' -f scripts/seed_db.sql`
-
-### Build
-```
-git clone https://github.com/maikeulb/orgChartApi
-git submodule update --init
-mkdir build
-cd build
+
+### π’ Departments
+
+| Method | URI | Action |
+| -------- | ------------------------------------------------------------- | --------------------------- |
+| `GET` | `/departments?limit={}&offset={}&sort_field={}&sort_order={}` | Retrieve all departments |
+| `GET` | `/departments/{id}` | Retrieve a department |
+| `GET` | `/departments/{id}/persons` | Retrieve department members |
+| `POST` | `/departments` | Create a department |
+| `PUT` | `/departments/{id}` | Update department info |
+| `DELETE` | `/departments/{id}` | Delete a department |
+
+---
+
+### πΌ Jobs
+
+| Method | URI | Action |
+| -------- | ------------------------------------------------------- | ----------------------------- |
+| `GET` | `/jobs?limit={}&offset={}&sort_fields={}&sort_order={}` | Retrieve all job roles |
+| `GET` | `/jobs/{id}` | Retrieve a job role |
+| `GET` | `/jobs/{id}/persons` | Retrieve people in a job role |
+| `POST` | `/jobs` | Create a job role |
+| `PUT` | `/jobs/{id}` | Update job role |
+| `DELETE` | `/jobs/{id}` | Delete a job role |
+
+---
+
+### π Auth
+
+| Method | URI | Action |
+| ------ | ---------------- | ----------------------------------- |
+| `POST` | `/auth/register` | Register a user and get a JWT token |
+| `POST` | `/auth/login` | Login and receive a JWT token |
+
+---
+
+## π¦ Two Ways to Get Started
+
+There are two ways to run the project:
+
+### 1. Using Docker
+
+**in `config.json` file change the host from `127.0.0.1` to db**
+
+```bash
+docker compose up
+```
+
+Docker simplifies the setup process and ensures all dependencies are handled automatically.
+
+### 2. **Manual Setup** (For those who prefer to run the project locally)
+
+### π₯ Install Dependencies
+
+```bash
+sudo apt-get update -yqq \
+ && sudo apt-get install -yqq --no-install-recommends \
+ software-properties-common \
+ curl wget cmake make pkg-config locales git \
+ gcc-11 g++-11 openssl libssl-dev libjsoncpp-dev uuid-dev \
+ zlib1g-dev libc-ares-dev postgresql-server-dev-all \
+ libmariadb-dev libsqlite3-dev libhiredis-dev \
+ && sudo rm -rf /var/lib/apt/lists/*
+```
+
+### π Drogon Installation
+
+```bash
+DROGON_ROOT="$HOME/drogon"
+```
+
+```bash
+git clone --depth 1 --recurse-submodules https://github.com/drogonframework/drogon $DROGON_ROOT
+```
+
+```bash
+cd $HOME/drogon
+```
+
+```bash
+mkdir build && cd build
+```
+
+```bash
+cmake .. -DCMAKE_BUILD_TYPE=Release -DUSE_MYSQL=ON
+```
+
+```bash
+make -j$(nproc) && sudo make install
+```
+
+```bash
+drogon_ctl -v
+```
+
+### Database Setup
+
+```bash
+navigate to orgchartAPI repo folder
+```
+
+```bash
+docker run --name db \
+ -e MYSQL_ROOT_PASSWORD=password \
+ -e MYSQL_DATABASE=org_chart \
+ -e MYSQL_USER=org \
+ -e MYSQL_PASSWORD=password \
+ -p 3306:3306 \
+ -d mysql:8.3 \
+ --default-authentication-plugin=mysql_native_password
+```
+
+```bash
+sudo apt install default-mysql-client
+```
+
+```bash
+mysql -h127.0.0.1 -P3306 -uorg -ppassword org_chart < scripts/create_db.sql
+mysql -h127.0.0.1 -P3306 -uorg -ppassword org_chart < scripts/seed_db.sql
+```
+
+### Build the Project
+
+```bash
+git submodule update --init --recursive
+```
+
+```bash
+mkdir build && cd build
+```
+
+```bash
cmake ..
+```
+
+```bash
make
```
-### Run
-Make the necessary database changes to `config.json` and run the project `./org_chart`
-
-Usage
----------------
-1. register user
-`http post localhost:3000/auth/register username="admin" password="password"`
-```
-{
- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE2NDU4MzE2MDcsImlhdCI6MTY0NTgzMTYwNywiaXNzIjoiYXV0aDAiLCJ1c2VyX2lkIjoiMCJ9.8PyNKVTlY6Qy81kXrCXTSD2XRxSKHLxmIELqEmOyFoU",
- "username": "admin"
-}
-```
-
-2. login user and obtain token (can also obtain token after registering)
-`http post localhost:3000/auth/login username="admin" password="password"`
-```
-{
- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE2NDU4MzE2MDcsImlhdCI6MTY0NTgzMTYwNywiaXNzIjoiYXV0aDAiLCJ1c2VyX2lkIjoiMCJ9.8PyNKVTlY6Qy81kXrCXTSD2XRxSKHLxmIELqEmOyFoU",
- "username": "admin"
-}
-```
-
-3. access resource using token
-`http --auth-type=bearer --auth="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE2NDU4MzE2MzYsImlhdCI6MTY0NTgzMTYzNiwiaXNzIjoiYXV0aDAiLCJ1c2VyX2lkIjoiMyJ9.x84yaRyC8sxjfRqeBC9AJW4NUAA2nhDexFUh3lImF50" get localhost:3000/persons offset=1 limit=25 sort_field=id sort_order=asc`
-```
-[
- {
- "department": {
- "id": 1,
- "name": "Product"
- },
- "first_name": "Gary",
- "hire_date": "2018-04-07 01:00:00",
- "id": 2,
- "job": {
- "id": 2,
- "title": "M1"
- },
- "last_name": "Reed",
- "manager": {
- "id": 1,
- "full_name": "Sabryna Peers",
- }
- },
- {
- "department": {
- "id": 1,
- "name": "Product"
- },
- "first_name": "Madonna",
- "hire_date": "2018-03-08",
- "id": 3,
- "job": {
- "id": 2,
- "title": "M1"
- },
- "last_name": "Axl",
- "manager": {
- "id": 1,
- "full_name": "Sabryna Peers",
- }
- },
- {
- "department": {
- "id": 1,
- "name": "Product"
- },
- "first_name": "Marcia",
- "hire_date": "2020-01-11",
- "id": 4,
- "job": {
- "id": 4,
- "title": "E5"
- },
- "last_name": "Stuart",
- "manager": {
- "id": 2,
- "full_name": "Gary Reed",
- }
- },
-...
-]
-```
-
-### Troubleshooting
-* Ensure that openssl is installed correctly (check `drogon_ctl -v`) and point cmake to the correct directory.
- `cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl`
-* If you're using a LSP, export `compile_commands.json`
- `cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DCMAKE_EXPORT_COMPILE_COMMANDS=`
+
+```bash
+./org_chart
+```
+
+## π§ͺ UT and Coverage
+
+There are already some unit tests in the repository. Hereβs how you can run them and generate a coverage report:
+
+1. Install `gcovr`
+
+ ```bash
+ sudo apt install gcovr
+ ```
+
+2. Navigate to the orgChartAPI Repository
+
+3. Build the Project with Coverage Enabled
+ Follow the [Build the Project](#build-the-project) steps, but **replace**:
+
+ ```bash
+ cmake ..
+ ```
+
+ with
+
+ ```bash
+ cmake -DCOVERAGE=ON ..
+ ```
+
+4. Run the Unit Tests
+
+ ```bash
+ ./test/org_chart_test
+ ```
+
+5. Navigate to the orgChartAPI Repository
+
+6. Generate the Coverage Report
+ ```bash
+ mkdir -p coverage
+ gcovr -r . --html --html-details -o coverage/coverage.html
+ ```
+
+Open `coverage/coverage.html` in your browser to view the coverage report.
+
+## π‘ Usage Guide
+
+Use the `postman.json` for postman collection and try the requests
+
+## Keploy Integration (API Testing and Coverage)
+
+Integrate [Keploy](https://keploy.io) to automatically record, replay, and generate coverage for your API tests.
+
+---
+
+### 1. Install Keploy
+
+**Open Source:**
+
+```bash
+curl --silent -O -L https://keploy.io/install.sh && source install.sh
+```
+
+**Enterprise:**
+
+```bash
+curl --silent -O -L https://keploy.io/ent/install.sh && source install.sh
+```
+
+---
+
+### 2. Run Application in Record Mode
+
+**If using Docker Compose:**
+
+```bash
+keploy record -c "docker compose up" --container-name "drogon_app"
+```
+
+**Or, if running manually:**
+
+```bash
+keploy record -c "./org_chart"
+```
+
+---
+
+### 3. Hit and Record API Requests
+
+Example (Register a new user):
+
+```bash
+curl --location 'http://localhost:3000/auth/register' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "username": "admin3adwes2",
+ "password": "password"
+ }'
+```
+
+---
+
+### 4. Stop Keploy and Run in Test Mode
+
+**With Docker Compose:**
+
+```bash
+keploy test -c "docker compose up" --container-name "drogon_app"
+```
+
+**Or, manually:**
+
+```bash
+keploy test -c "./org_chart"
+```
+
+---
+
+### 5. View Coverage Report
+
+Coverage will be **automatically saved** if the build was done with the `-DCOVERAGE=ON` flag during CMake.
+
+for combined coverage you can do something like this
+
+1. After you ran the uts save the coverage to a json
+
+ ```bash
+ gcovr -r . --json ut.json
+ ```
+
+2. remove the saved coverage object files
+
+ ```bash
+ find . -name "*.gcda" -delete
+ ```
+
+3. After you ran keploy test save the coverage to another json
+
+ ```bash
+ gcovr -r . --json it.json
+ ```
+
+4. Generate report for ut
+
+ ```bash
+ gcovr --add-tracefile ut.json --html --html-details -o coverage-ut/coverage.html
+ ```
+
+5. Generate report for it
+
+ ```bash
+ gcovr --add-tracefile it.json --html --html-details -o coverage-it/coverage.html
+ ```
+
+6. generate report for combined
+
+ ```bash
+ gcovr merge --add-tracefile ut.json --add-tracefile it.json --json -o merged.json
+ ```
+
+ ```bash
+ gcovr --add-tracefile merged.json --html --html-details -o coverage-combined/coverage.html
+ ```
+
+---
+
+For more, see [Keploy Docs](https://docs.keploy.io/).
diff --git a/config.json b/config.json
index 5fe1536..136b324 100644
--- a/config.json
+++ b/config.json
@@ -3,80 +3,39 @@
{
"listeners": [
{
- //address: Ip address,0.0.0.0 by default
"address": "0.0.0.0",
- //port: Port number
"port": 3000,
- //https: If true, use https for security,false by default
"https": false
}
],
"db_clients": [
{
- //name: Name of the client,'default' by default
//"name":"",
//rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
- "rdbms": "postgresql",
- //filename: Sqlite3 db file name
+ "rdbms": "mysql", // β¬
οΈ switched to mysql
//"filename":"",
- //host: Server address,localhost by default
"host": "127.0.0.1",
- //port: Server port, 5432 by default
- "port": 5433,
- //dbname: Database name
+ //port: Server port, 3306 for MySQL
+ "port": 3306, // β¬
οΈ MySQL port
"dbname": "org_chart",
- //user: 'postgres' by default
- "user": "postgres",
- //passwd: '' by default
+ //user: root by default; use the app-specific user we created
+ "user": "org", // β¬
οΈ MySQL user
"passwd": "password",
- //is_fast: false by default, if it is true, the client is faster but user can't call
- //any synchronous interface of it.
"is_fast": false,
- //client_encoding: The character set used by the client. it is empty string by default which
- //means use the default character set.
//"client_encoding": "",
- //number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
- //connections per IO thread, otherwise it is the total number of all connections.
"number_of_connections": 1,
- //timeout: -1.0 by default, in seconds, the timeout for executing a SQL query.
- //zero or negative value means no timeout.
"timeout": -1.0
}
],
"app": {
- //number_of_threads: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
- //is the number of CPU cores
"number_of_threads": 1,
- //enable_session: False by default
"enable_session": false,
"session_timeout": 0,
- //document_root: Root path of HTTP document, defaut path is ./
"document_root": "./",
- //home_page: Set the HTML file of the home page, the default value is "index.html"
- //If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
- //to the request for "/".
"home_page": "index.html",
- //use_implicit_page: enable implicit pages if true, true by default
"use_implicit_page": true,
- //implicit_page: Set the file which would the server access in a directory that a user accessed.
- //For example, by default, http://localhost/a-directory resolves to http://localhost/a-directory/index.html.
"implicit_page": "index.html",
- //static_file_headers: Headers for static files
- /*"static_file_headers": [
- {
- "name": "field-name",
- "value": "field-value"
- }
- ],*/
- //upload_path: The path to save the uploaded file. "uploads" by default.
- //If the path isn't prefixed with /, ./ or ../,
- //it is relative path of document_root path
"upload_path": "uploads",
- /* file_types:
- * HTTP download file types,The file types supported by drogon
- * by default are "html", "js", "css", "xml", "xsl", "txt", "svg",
- * "ttf", "otf", "woff2", "woff" , "eot", "png", "jpg", "jpeg",
- * "gif", "bmp", "ico", "icns", etc. */
"file_types": [
"gif",
"png",
@@ -91,151 +50,58 @@
"cur",
"xml"
],
- //locations: An array of locations of static files for GET requests.
"locations": [
{
- //uri_prefix: The URI prefix of the location prefixed with "/", the default value is "" that disables the location.
//"uri_prefix": "/.well-known/acme-challenge/",
- //default_content_type: The default content type of the static files without
- //an extension. empty string by default.
"default_content_type": "text/plain",
- //alias: The location in file system, if it is prefixed with "/", it
- //presents an absolute path, otherwise it presents a relative path to
- //the document_root path.
- //The default value is "" which means use the document root path as the location base path.
"alias": "",
- //is_case_sensitive: indicates whether the URI prefix is case sensitive.
"is_case_sensitive": false,
- //allow_all: true by default. If it is set to false, only static files with a valid extension can be accessed.
"allow_all": true,
- //is_recursive: true by default. If it is set to false, files in sub directories can't be accessed.
"is_recursive": true,
- //filters: string array, the filters applied to the location.
"filters": []
}
],
- //max_connections: maximum number of connections, 100000 by default
"max_connections": 100000,
- //max_connections_per_ip: maximum number of connections per clinet, 0 by default which means no limit
"max_connections_per_ip": 0,
- //Load_dynamic_views: False by default, when set to true, drogon
- //compiles and loads dynamically "CSP View Files" in directories defined
- //by "dynamic_views_path"
"load_dynamic_views": false,
- //dynamic_views_path: If the path isn't prefixed with /, ./ or ../,
- //it is relative path of document_root path
"dynamic_views_path": [
"./views"
],
- //dynamic_views_output_path: Default by an empty string which means the output path of source
- //files is the path where the csp files locate. If the path isn't prefixed with /, it is relative
- //path of the current working directory.
"dynamic_views_output_path": "",
- //enable_unicode_escaping_in_json: true by default, enable unicode escaping in json.
"enable_unicode_escaping_in_json": true,
- //float_precision_in_json: set precision of float number in json.
"float_precision_in_json": {
- //precision: 0 by default, 0 means use the default precision of the jsoncpp lib.
"precision": 0,
- //precision_type: must be "significant" or "decimal", defaults to "significant" that means
- //setting max number of significant digits in string, "decimal" means setting max number of
- //digits after "." in string
"precision_type": "significant"
},
- //log: Set log output, drogon output logs to stdout by default
"log": {
- //log_path: Log file path,empty by default,in which case,logs are output to the stdout
- //"log_path": "./",
- //logfile_base_name: Log file base name,empty by default which means drogon names logfile as
- //drogon.log ...
"logfile_base_name": "",
- //log_size_limit: 100000000 bytes by default,
- //When the log file size reaches "log_size_limit", the log file is switched.
"log_size_limit": 100000000,
- //log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
- //The TRACE level is only valid when built in DEBUG mode.
"log_level": "DEBUG"
},
- //run_as_daemon: False by default
"run_as_daemon": false,
- //handle_sig_term: True by default
"handle_sig_term": true,
- //relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
"relaunch_on_error": false,
- //use_sendfile: True by default, if true, the program
- //uses sendfile() system-call to send static files to clients;
"use_sendfile": true,
- //use_gzip: True by default, use gzip to compress the response body's content;
"use_gzip": true,
- //use_brotli: False by default, use brotli to compress the response body's content;
"use_brotli": false,
- //static_files_cache_time: 5 (seconds) by default, the time in which the static file response is cached,
- //0 means cache forever, the negative value means no cache
"static_files_cache_time": 5,
- //simple_controllers_map: Used to configure mapping from path to simple controller
- // "simple_controllers_map": [
- // {
- // "path": "/path/name",
- // "controller": "controllerClassName",
- // "http_methods": [
- // "get",
- // "post"
- // ],
- // "filters": [
- // "FilterClassName"
- // ]
- // }
- // ],
- //idle_connection_timeout: Defaults to 60 seconds, the lifetime
- //of the connection without read or write
"idle_connection_timeout": 60,
- //server_header_field: Set the 'Server' header field in each response sent by drogon,
- //empty string by default with which the 'Server' header field is set to "Server: drogon/version string\r\n"
"server_header_field": "",
- //enable_server_header: Set true to force drogon to add a 'Server' header to each HTTP response. The default
- //value is true.
"enable_server_header": true,
- //enable_date_header: Set true to force drogon to add a 'Date' header to each HTTP response. The default
- //value is true.
"enable_date_header": true,
- //keepalive_requests: Set the maximum number of requests that can be served through one keep-alive connection.
- //After the maximum number of requests are made, the connection is closed.
- //The default value of 0 means no limit.
"keepalive_requests": 0,
- //pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer.
- //After the maximum number of requests are made, the connection is closed.
- //The default value of 0 means no limit.
"pipelining_requests": 0,
- //gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
- //file with the extension ".gz" in the same path and send the compressed file to the client.
- //The default value of gzip_static is true.
"gzip_static": true,
- //br_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
- //file with the extension ".br" in the same path and send the compressed file to the client.
- //The default value of br_static is true.
"br_static": true,
- //client_max_body_size: Set the maximum body size of HTTP requests received by drogon. The default value is "1M".
- //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_body_size": "1M",
- //max_memory_body_size: Set the maximum body size in memory of HTTP requests received by drogon. The default value is "64K" bytes.
- //If the body size of a HTTP request exceeds this limit, the body is stored to a temporary file for processing.
- //Setting it to "" means no limit.
"client_max_memory_body_size": "64K",
- //client_max_websocket_message_size: Set the maximum size of messages sent by WebSocket client. The default value is "128K".
- //One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_websocket_message_size": "128K",
- //reuse_port: Defaults to false, users can run multiple processes listening on the same port at the same time.
"reuse_port": false
},
- //plugins: Define all plugins running in the application
"plugins": [
{
- //name: The class name of the plugin
//"name": "drogon::plugin::SecureSSLRedirector",
- //dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
- //config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
- //It can be commented out
"config": {
"ssl_redirect_exempt": [
".*\\.jpg"
@@ -244,22 +110,16 @@
}
},
{
- //name: The class name of the plugin
//"name": "JwtPlugin"
- //dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
- //config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
- //It can be commented out
"config": {
- "jwt-secret":"secret",
- "jwt-sessionTime":3600
+ "jwt-secret": "secret",
+ "jwt-sessionTime": 3600
}
}
-
],
- //custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
"custom_config": {
- "jwt-secret":"secret",
- "jwt-sessionTime":3600
+ "jwt-secret": "secret",
+ "jwt-sessionTime": 3600
}
}
diff --git a/controllers/AuthController.cc b/controllers/AuthController.cc
index c1b1952..bd5d16e 100644
--- a/controllers/AuthController.cc
+++ b/controllers/AuthController.cc
@@ -5,23 +5,38 @@
using namespace drogon::orm;
using namespace drogon_model::org_chart;
-namespace drogon {
- template<>
- inline User fromRequest(const HttpRequest &req) {
+namespace drogon
+{
+ template <>
+ inline User fromRequest(const HttpRequest &req)
+ {
auto jsonPtr = req.getJsonObject();
- auto json = *jsonPtr;
- auto user = User(json);
- return user;
+ if (jsonPtr)
+ {
+ return User(*jsonPtr);
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (reader.parse(std::string(req.body()), json))
+ { // <-- FIXED
+ return User(json);
+ }
+
+ return User(Json::Value{});
}
}
-void AuthController::registerUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const {
+void AuthController::registerUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const
+{
LOG_DEBUG << "registerUser";
- try {
+ try
+ {
auto dbClientPtr = drogon::app().getDbClient();
Mapper mp(dbClientPtr);
- if (!areFieldsValid(pUser)) {
+ if (!areFieldsValid(pUser))
+ {
Json::Value ret{};
ret["error"] = "missing fields";
auto resp = HttpResponse::newHttpJsonResponse(ret);
@@ -30,7 +45,8 @@ void AuthController::registerUser(const HttpRequestPtr &req, std::functionsetStatusCode(HttpStatusCode::k201Created);
callback(resp);
- } catch (const DrogonDbException & e) {
+ }
+ catch (const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
Json::Value ret{};
ret["error"] = "database error";
@@ -58,13 +76,16 @@ void AuthController::registerUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const {
+void AuthController::loginUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const
+{
LOG_DEBUG << "loginUser";
- try {
+ try
+ {
auto dbClientPtr = drogon::app().getDbClient();
Mapper mp(dbClientPtr);
- if (!areFieldsValid(pUser)) {
+ if (!areFieldsValid(pUser))
+ {
Json::Value ret{};
ret["error"] = "missing fields";
auto resp = HttpResponse::newHttpJsonResponse(ret);
@@ -74,7 +95,8 @@ void AuthController::loginUser(const HttpRequestPtr &req, std::function &mp) const {
+bool AuthController::isUserAvailable(const User &user, Mapper &mp) const
+{
auto criteria = Criteria(User::Cols::_username, CompareOperator::EQ, user.getValueOfUsername());
return mp.findFutureBy(criteria).get().empty();
}
-bool AuthController::isPasswordValid(const std::string &text, const std::string &hash) const {
+bool AuthController::isPasswordValid(const std::string &text, const std::string &hash) const
+{
return BCrypt::validatePassword(text, hash);
}
-AuthController::UserWithToken::UserWithToken(const User &user) {
+AuthController::UserWithToken::UserWithToken(const User &user)
+{
auto *jwtPtr = drogon::app().getPlugin();
auto jwt = jwtPtr->init();
token = jwt.encode("user_id", user.getValueOfId());
username = user.getValueOfUsername();
}
-Json::Value AuthController::UserWithToken::toJson() {
+Json::Value AuthController::UserWithToken::toJson()
+{
Json::Value ret{};
ret["username"] = username;
ret["token"] = token;
return ret;
}
+
+void AuthController::deregisterUser(
+ const HttpRequestPtr &req,
+ std::function &&callback,
+ User && /*pUser*/) const // accept it, but ignore it.
+{
+ LOG_DEBUG << "deregisterUser";
+
+ const auto jsonPtr = req->getJsonObject();
+ if (!jsonPtr || !jsonPtr->isMember("username") || !(*jsonPtr)["username"].isString())
+ {
+ auto resp = HttpResponse::newHttpJsonResponse({{"error", "missing or invalid 'username'"}});
+ resp->setStatusCode(HttpStatusCode::k400BadRequest);
+ return callback(resp);
+ }
+
+ std::string username = (*jsonPtr)["username"].asString();
+ if (username.empty() || username.size() > 128)
+ {
+ auto resp = HttpResponse::newHttpJsonResponse({{"error", "invalid 'username' length"}});
+ resp->setStatusCode(HttpStatusCode::k400BadRequest);
+ return callback(resp);
+ }
+
+ auto cbPtr = std::make_shared>(
+ std::move(callback));
+ auto client = drogon::app().getDbClient();
+ Mapper mp(client);
+
+ mp.deleteBy(
+ Criteria(User::Cols::_username, CompareOperator::EQ, username),
+ [cbPtr](std::size_t count)
+ {
+ Json::Value body;
+ HttpStatusCode code;
+ if (count == 0)
+ {
+ body["error"] = "user not found";
+ code = HttpStatusCode::k404NotFound;
+ }
+ else
+ {
+ body["message"] = "user deregistered successfully";
+ code = HttpStatusCode::k200OK;
+ }
+ auto resp = HttpResponse::newHttpJsonResponse(body);
+ resp->setStatusCode(code);
+ (*cbPtr)(resp);
+ },
+ [cbPtr](const DrogonDbException &e)
+ {
+ LOG_ERROR << e.base().what();
+ auto resp = HttpResponse::newHttpJsonResponse({{"error", "database error"}});
+ resp->setStatusCode(HttpStatusCode::k500InternalServerError);
+ (*cbPtr)(resp);
+ });
+}
diff --git a/controllers/AuthController.h b/controllers/AuthController.h
index f74c66f..861360a 100644
--- a/controllers/AuthController.h
+++ b/controllers/AuthController.h
@@ -8,26 +8,30 @@ using namespace drogon;
using namespace drogon::orm;
using namespace drogon_model::org_chart;
-class AuthController : public drogon::HttpController {
- public:
- METHOD_LIST_BEGIN
- ADD_METHOD_TO(AuthController::registerUser, "/auth/register", Post);
- ADD_METHOD_TO(AuthController::loginUser, "/auth/login", Post);
- METHOD_LIST_END
+class AuthController : public drogon::HttpController
+{
+public:
+ METHOD_LIST_BEGIN
+ ADD_METHOD_TO(AuthController::registerUser, "/auth/register", Post);
+ ADD_METHOD_TO(AuthController::deregisterUser, "/auth/deregister", Post);
+ ADD_METHOD_TO(AuthController::loginUser, "/auth/login", Post);
+ METHOD_LIST_END
- void registerUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const;
- void loginUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const;
+ void registerUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const;
+ void loginUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const;
+ void deregisterUser(const HttpRequestPtr &req, std::function &&callback, User &&pUser) const;
- private:
- struct UserWithToken {
- std::string username;
- std::string password;
- std::string token;
- explicit UserWithToken(const User &user);
- Json::Value toJson();
- };
+private:
+ struct UserWithToken
+ {
+ std::string username;
+ std::string password;
+ std::string token;
+ explicit UserWithToken(const User &user);
+ Json::Value toJson();
+ };
- bool areFieldsValid(const User &user) const;
- bool isUserAvailable(const User &user, Mapper &mp) const;
- bool isPasswordValid(const std::string &text, const std::string &hash) const;
+ bool areFieldsValid(const User &user) const;
+ bool isUserAvailable(const User &user, Mapper &mp) const;
+ bool isPasswordValid(const std::string &text, const std::string &hash) const;
};
diff --git a/controllers/DepartmentsController.cc b/controllers/DepartmentsController.cc
index 0dfeff0..a9e8c70 100644
--- a/controllers/DepartmentsController.cc
+++ b/controllers/DepartmentsController.cc
@@ -9,17 +9,30 @@
using namespace drogon::orm;
using namespace drogon_model::org_chart;
-namespace drogon {
- template<>
- inline Department fromRequest(const HttpRequest &req) {
+namespace drogon
+{
+ template <>
+ inline Department fromRequest(const HttpRequest &req)
+ {
auto jsonPtr = req.getJsonObject();
- auto json = *jsonPtr;
- auto department = Department(json);
- return department;
+ if (jsonPtr)
+ {
+ return Department(*jsonPtr);
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (reader.parse(std::string(req.body()), json))
+ { // Safe conversion!
+ return Department(json);
+ }
+
+ return Department(Json::Value{});
}
-} // namespace drogon
+}
-void DepartmentsController::get(const HttpRequestPtr &req, std::function &&callback) const {
+void DepartmentsController::get(const HttpRequestPtr &req, std::function &&callback) const
+{
LOG_DEBUG << "get";
auto offset = req->getOptionalParameter("offset").value_or(0);
auto limit = req->getOptionalParameter("limit").value_or(25);
@@ -30,42 +43,44 @@ void DepartmentsController::get(const HttpRequestPtr &req, std::function>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
Mapper mp(dbClientPtr);
- mp.orderBy(sortField, sortOrderEnum).offset(offset).limit(limit).findAll(
- [callbackPtr](const std::vector &departments) {
+ mp.orderBy(sortField, sortOrderEnum).offset(offset).limit(limit).findAll([callbackPtr](const std::vector &departments)
+ {
Json::Value ret{};
for (auto d : departments) {
ret.append(d.toJson());
}
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k200OK);
- (*callbackPtr)(resp);
- },
- [callbackPtr](const DrogonDbException &e) {
+ (*callbackPtr)(resp); }, [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
- (*callbackPtr)(resp);
- });
+ (*callbackPtr)(resp); });
}
-void DepartmentsController::getOne(const HttpRequestPtr &req, std::function &&callback, int departmentId) const {
- LOG_DEBUG << "getOne departmentId: "<< departmentId;
+void DepartmentsController::getOne(const HttpRequestPtr &req, std::function &&callback, int departmentId) const
+{
+ LOG_DEBUG << "getOne departmentId: " << departmentId;
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
Mapper mp(dbClientPtr);
mp.findByPrimaryKey(
departmentId,
- [callbackPtr](const Department &department) {
+ [callbackPtr](const Department &department)
+ {
Json::Value ret{};
ret = department.toJson();
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k201Created);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
const drogon::orm::UnexpectedRows *s = dynamic_cast(&e.base());
- if(s) {
+ if (s)
+ {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
(*callbackPtr)(resp);
@@ -75,10 +90,11 @@ void DepartmentsController::getOne(const HttpRequestPtr &req, std::functionsetStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void DepartmentsController::createOne(const HttpRequestPtr &req, std::function &&callback, Department &&pDepartment) const {
+void DepartmentsController::createOne(const HttpRequestPtr &req, std::function &&callback, Department &&pDepartment) const
+{
LOG_DEBUG << "createOne";
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
@@ -86,39 +102,47 @@ void DepartmentsController::createOne(const HttpRequestPtr &req, std::function mp(dbClientPtr);
mp.insert(
pDepartment,
- [callbackPtr](const Department &department) {
+ [callbackPtr](const Department &department)
+ {
Json::Value ret{};
ret = department.toJson();
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k201Created);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void DepartmentsController::updateOne(const HttpRequestPtr &req, std::function &&callback, int departmentId, Department &&pDepartmentDetails) const {
+void DepartmentsController::updateOne(const HttpRequestPtr &req, std::function &&callback, int departmentId, Department &&pDepartmentDetails) const
+{
LOG_DEBUG << "updateOne departmentId: " << departmentId;
auto dbClientPtr = drogon::app().getDbClient();
// blocking IO
Mapper mp(dbClientPtr);
Department department;
- try {
+ try
+ {
department = mp.findFutureByPrimaryKey(departmentId).get();
- } catch (const DrogonDbException & e) {
+ }
+ catch (const DrogonDbException &e)
+ {
Json::Value ret{};
ret["error"] = "resource not found";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k404NotFound);
callback(resp);
+ return;
}
- if (pDepartmentDetails.getName() != nullptr) {
+ if (pDepartmentDetails.getName() != nullptr)
+ {
department.setName(pDepartmentDetails.getValueOfName());
}
@@ -137,11 +161,11 @@ void DepartmentsController::updateOne(const HttpRequestPtr &req, std::functionsetStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- }
- );
+ });
}
-void DepartmentsController::deleteOne(const HttpRequestPtr &req, std::function &&callback, int departmentId) const {
+void DepartmentsController::deleteOne(const HttpRequestPtr &req, std::function &&callback, int departmentId) const
+{
LOG_DEBUG << "deleteOne departmentId: ";
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
@@ -149,39 +173,46 @@ void DepartmentsController::deleteOne(const HttpRequestPtr &req, std::function mp(dbClientPtr);
mp.deleteBy(
Criteria(Department::Cols::_id, CompareOperator::EQ, departmentId),
- [callbackPtr](const std::size_t count) {
+ [callbackPtr](const std::size_t count)
+ {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(HttpStatusCode::k204NoContent);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void DepartmentsController::getDepartmentPersons(const HttpRequestPtr &req, std::function &&callback, int departmentId) const {
- LOG_DEBUG << "getDepartmentPersons departmentId: "<< departmentId;
+void DepartmentsController::getDepartmentPersons(const HttpRequestPtr &req, std::function &&callback, int departmentId) const
+{
+ LOG_DEBUG << "getDepartmentPersons departmentId: " << departmentId;
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
// blocking IO
Mapper mp(dbClientPtr);
Department department;
- try {
+ try
+ {
department = mp.findFutureByPrimaryKey(departmentId).get();
- } catch (const DrogonDbException & e) {
+ }
+ catch (const DrogonDbException &e)
+ {
Json::Value ret{};
ret["error"] = "resource not found";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k404NotFound);
callback(resp);
+ return;
}
- department.getPersons(dbClientPtr,
- [callbackPtr](const std::vector persons) {
+ department.getPersons(dbClientPtr, [callbackPtr](const std::vector persons)
+ {
if (persons.empty()) {
Json::Value ret{};
ret["error"] = "resource not found";
@@ -196,14 +227,12 @@ void DepartmentsController::getDepartmentPersons(const HttpRequestPtr &req, std:
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k200OK);
(*callbackPtr)(resp);
- }
- },
- [callbackPtr](const DrogonDbException &e) {
+ } }, [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
Json::Value ret{};
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
- (*callbackPtr)(resp);
- });
+ (*callbackPtr)(resp); });
}
diff --git a/controllers/DepartmentsController.h b/controllers/DepartmentsController.h
index 9fc7ef4..ca17307 100644
--- a/controllers/DepartmentsController.h
+++ b/controllers/DepartmentsController.h
@@ -6,21 +6,27 @@
using namespace drogon;
using namespace drogon_model::org_chart;
-class DepartmentsController : public drogon::HttpController {
- public:
- METHOD_LIST_BEGIN
- ADD_METHOD_TO(DepartmentsController::get, "/departments", Get);
- ADD_METHOD_TO(DepartmentsController::getOne, "/departments/{1}", Get);
- ADD_METHOD_TO(DepartmentsController::createOne, "/departments", Post, "LoginFilter");
- ADD_METHOD_TO(DepartmentsController::updateOne, "/departments/{1}", Put, "LoginFilter");
- ADD_METHOD_TO(DepartmentsController::deleteOne, "/departments/{1}", Delete, "LoginFilter");
- ADD_METHOD_TO(DepartmentsController::getDepartmentPersons, "/departments/{1}/persons", Get, "LoginFilter");
- METHOD_LIST_END
+class DepartmentsController : public drogon::HttpController
+{
+public:
+ #ifndef FUZZING_BUILD
- void get(const HttpRequestPtr& req, std::function &&callback) const;
- void getOne(const HttpRequestPtr& req, std::function &&callback, int pDepartmentId) const;
- void createOne(const HttpRequestPtr &req, std::function &&callback, Department &&pDepartment) const;
- void updateOne(const HttpRequestPtr &req, std::function &&callback, int pDepartmentId, Department &&pDepartment) const;
- void deleteOne(const HttpRequestPtr &req, std::function &&callback, int pDepartmentId) const;
- void getDepartmentPersons(const HttpRequestPtr &req, std::function &&callback, int departmentId) const;
+ METHOD_LIST_BEGIN
+ METHOD_LIST_END
+ #else
+ METHOD_LIST_BEGIN
+ ADD_METHOD_TO(DepartmentsController::get, "/departments", Get, "LoginFilter");
+ ADD_METHOD_TO(DepartmentsController::getOne, "/departments/{1}", Get, "LoginFilter");
+ ADD_METHOD_TO(DepartmentsController::createOne, "/departments", Post, "LoginFilter");
+ ADD_METHOD_TO(DepartmentsController::updateOne, "/departments/{1}", Put, "LoginFilter");
+ ADD_METHOD_TO(DepartmentsController::deleteOne, "/departments/{1}", Delete, "LoginFilter");
+ ADD_METHOD_TO(DepartmentsController::getDepartmentPersons, "/departments/{1}/persons", Get, "LoginFilter");
+ METHOD_LIST_END
+ #endif
+ void get(const HttpRequestPtr &req, std::function &&callback) const;
+ void getOne(const HttpRequestPtr &req, std::function &&callback, int pDepartmentId) const;
+ void createOne(const HttpRequestPtr &req, std::function &&callback, Department &&pDepartment) const;
+ void updateOne(const HttpRequestPtr &req, std::function &&callback, int pDepartmentId, Department &&pDepartment) const;
+ void deleteOne(const HttpRequestPtr &req, std::function &&callback, int pDepartmentId) const;
+ void getDepartmentPersons(const HttpRequestPtr &req, std::function &&callback, int departmentId) const;
};
diff --git a/controllers/JobsController.cc b/controllers/JobsController.cc
index 88e65e4..c853de1 100644
--- a/controllers/JobsController.cc
+++ b/controllers/JobsController.cc
@@ -9,16 +9,38 @@
using namespace drogon::orm;
using namespace drogon_model::org_chart;
-namespace drogon {
- template<>
- inline Job fromRequest(const HttpRequest &req) {
- auto json = req.getJsonObject();
- auto job = Job(*json);
- return job;
+JobsController::JobsController()
+{
+ dbClient_ = drogon::app().getDbClient();
+}
+
+JobsController::JobsController(std::shared_ptr dbClient)
+ : dbClient_(std::move(dbClient)) {}
+
+namespace drogon
+{
+ template <>
+ inline Job fromRequest(const HttpRequest &req)
+ {
+ auto jsonPtr = req.getJsonObject();
+ if (jsonPtr)
+ {
+ return Job(*jsonPtr);
+ }
+
+ Json::Value json;
+ Json::Reader reader;
+ if (reader.parse(std::string(req.body()), json))
+ {
+ return Job(json);
+ }
+
+ return Job(Json::Value{});
}
}
-void JobsController::get(const HttpRequestPtr &req, std::function &&callback) const {
+void JobsController::get(const HttpRequestPtr &req, std::function &&callback) const
+{
LOG_DEBUG << "get";
auto offset = req->getOptionalParameter("offset").value_or(0);
auto limit = req->getOptionalParameter("limit").value_or(25);
@@ -27,44 +49,45 @@ void JobsController::get(const HttpRequestPtr &req, std::function>(std::move(callback));
- auto dbClientPtr = drogon::app().getDbClient();
+ auto dbClientPtr = dbClient_;
Mapper mp(dbClientPtr);
- mp.orderBy(sortField, sortOrderEnum).offset(offset).limit(limit).findAll(
- [callbackPtr](const std::vector &jobs) {
+ mp.orderBy(sortField, sortOrderEnum).offset(offset).limit(limit).findAll([callbackPtr](const std::vector &jobs)
+ {
Json::Value ret{};
for (auto j : jobs) {
ret.append(j.toJson());
}
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k200OK);
- (*callbackPtr)(resp);
- },
- [callbackPtr](const DrogonDbException &e) {
+ (*callbackPtr)(resp); }, [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
- (*callbackPtr)(resp);
- });
+ (*callbackPtr)(resp); });
}
-void JobsController::getOne(const HttpRequestPtr &req, std::function &&callback, int jobId) const {
- LOG_DEBUG << "getOne jobId: "<< jobId;
+void JobsController::getOne(const HttpRequestPtr &req, std::function &&callback, int jobId) const
+{
+ LOG_DEBUG << "getOne jobId: " << jobId;
auto callbackPtr = std::make_shared>(std::move(callback));
- auto dbClientPtr = drogon::app().getDbClient();
-
+ auto dbClientPtr = dbClient_;
Mapper mp(dbClientPtr);
mp.findByPrimaryKey(
jobId,
- [callbackPtr](const Job &job) {
+ [callbackPtr](const Job &job)
+ {
Json::Value ret{};
ret = job.toJson();
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k201Created);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
const drogon::orm::UnexpectedRows *s = dynamic_cast(&e.base());
- if(s) {
+ if (s)
+ {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
(*callbackPtr)(resp);
@@ -74,60 +97,68 @@ void JobsController::getOne(const HttpRequestPtr &req, std::functionsetStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void JobsController::createOne(const HttpRequestPtr &req, std::function &&callback, Job &&pJob) const {
+void JobsController::createOne(const HttpRequestPtr &req, std::function &&callback, Job &&pJob) const
+{
LOG_DEBUG << "createOne";
auto callbackPtr = std::make_shared>(std::move(callback));
- auto dbClientPtr = drogon::app().getDbClient();
-
+ auto dbClientPtr = dbClient_;
Mapper mp(dbClientPtr);
mp.insert(
pJob,
- [callbackPtr](const Job &job) {
+ [callbackPtr](const Job &job)
+ {
Json::Value ret{};
ret = job.toJson();
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k201Created);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void JobsController::updateOne(const HttpRequestPtr &req, std::function &&callback, int jobId, Job &&pJobDetails) const {
+void JobsController::updateOne(const HttpRequestPtr &req, std::function &&callback, int jobId, Job &&pJobDetails) const
+{
LOG_DEBUG << "updateOne jobId: " << jobId;
auto jsonPtr = req->jsonObject();
- if (!jsonPtr) {
- Json::Value ret{};
- ret["error"]="No json object is found in the request";
- auto resp = HttpResponse::newHttpResponse();
- resp->setStatusCode(HttpStatusCode::k400BadRequest);
- callback(resp);
- return;
+ if (!jsonPtr)
+ {
+ Json::Value ret{};
+ ret["error"] = "No json object is found in the request";
+ auto resp = HttpResponse::newHttpResponse();
+ resp->setStatusCode(HttpStatusCode::k400BadRequest);
+ callback(resp);
+ return;
}
- auto dbClientPtr = drogon::app().getDbClient();
-
+ auto dbClientPtr = dbClient_;
// blocking IO
Mapper mp(dbClientPtr);
Job job;
- try {
+ try
+ {
job = mp.findFutureByPrimaryKey(jobId).get();
- } catch (const DrogonDbException & e) {
+ }
+ catch (const DrogonDbException &e)
+ {
Json::Value ret{};
ret["error"] = "resource not found";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k404NotFound);
callback(resp);
+ return;
}
- if (pJobDetails.getTitle() != nullptr) {
+ if (pJobDetails.getTitle() != nullptr)
+ {
job.setTitle(pJobDetails.getValueOfTitle());
}
@@ -146,51 +177,56 @@ void JobsController::updateOne(const HttpRequestPtr &req, std::functionsetStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- }
- );
+ });
}
-void JobsController::deleteOne(const HttpRequestPtr &req, std::function &&callback, int jobId) const {
+void JobsController::deleteOne(const HttpRequestPtr &req, std::function &&callback, int jobId) const
+{
LOG_DEBUG << "deleteOne jobId: ";
auto callbackPtr = std::make_shared>(std::move(callback));
- auto dbClientPtr = drogon::app().getDbClient();
-
+ auto dbClientPtr = dbClient_;
Mapper mp(dbClientPtr);
mp.deleteBy(
Criteria(Job::Cols::_id, CompareOperator::EQ, jobId),
- [callbackPtr](const std::size_t count) {
+ [callbackPtr](const std::size_t count)
+ {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(HttpStatusCode::k204NoContent);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void JobsController::getJobPersons(const HttpRequestPtr &req, std::function &&callback, int jobId) const {
- LOG_DEBUG << "getJobPersons jobId: "<< jobId;
+void JobsController::getJobPersons(const HttpRequestPtr &req, std::function &&callback, int jobId) const
+{
+ LOG_DEBUG << "getJobPersons jobId: " << jobId;
auto callbackPtr = std::make_shared>(std::move(callback));
- auto dbClientPtr = drogon::app().getDbClient();
-
+ auto dbClientPtr = dbClient_;
// blocking IO
Mapper mp(dbClientPtr);
Job job;
- try {
+ try
+ {
job = mp.findFutureByPrimaryKey(jobId).get();
- } catch (const DrogonDbException & e) {
+ }
+ catch (const DrogonDbException &e)
+ {
Json::Value ret{};
ret["error"] = "resource not found";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k404NotFound);
callback(resp);
+ return;
}
- job.getPersons(dbClientPtr,
- [callbackPtr](const std::vector persons) {
+ job.getPersons(dbClientPtr, [callbackPtr](const std::vector persons)
+ {
if (persons.empty()) {
Json::Value ret{};
ret["error"] = "resource not found";
@@ -205,14 +241,12 @@ void JobsController::getJobPersons(const HttpRequestPtr &req, std::functionsetStatusCode(HttpStatusCode::k200OK);
(*callbackPtr)(resp);
- }
- },
- [callbackPtr](const DrogonDbException &e) {
+ } }, [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
Json::Value ret{};
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
- (*callbackPtr)(resp);
- });
+ (*callbackPtr)(resp); });
}
diff --git a/controllers/JobsController.h b/controllers/JobsController.h
index 5116f3f..adebf14 100644
--- a/controllers/JobsController.h
+++ b/controllers/JobsController.h
@@ -2,25 +2,35 @@
#include
#include "../models/Job.h"
+#include
+#include
using namespace drogon;
using namespace drogon_model::org_chart;
+using namespace drogon::orm;
-class JobsController : public drogon::HttpController {
- public:
- METHOD_LIST_BEGIN
- ADD_METHOD_TO(JobsController::get, "/jobs", Get, "LoginFilter");
- ADD_METHOD_TO(JobsController::getOne, "/jobs/{1}", Get, "LoginFilter");
- ADD_METHOD_TO(JobsController::createOne, "/jobs", Post, "LoginFilter");
- ADD_METHOD_TO(JobsController::updateOne, "/jobs/{1}", Put, "LoginFilter");
- ADD_METHOD_TO(JobsController::deleteOne, "/jobs/{1}", Delete, "LoginFilter");
- ADD_METHOD_TO(JobsController::getJobPersons, "/jobs/{1}/persons", Get, "LoginFilter");
- METHOD_LIST_END
+class JobsController : public drogon::HttpController
+{
+public:
+ JobsController(); // Default constructor, defined in .cc
+ explicit JobsController(std::shared_ptr dbClient);
- void get(const HttpRequestPtr& req, std::function &&callback) const;
- void getOne(const HttpRequestPtr& req, std::function &&callback, int pJobId) const;
- void createOne(const HttpRequestPtr &req, std::function &&callback, Job &&pJob) const;
- void updateOne(const HttpRequestPtr &req, std::function &&callback, int pJobId, Job &&pJob) const;
- void deleteOne(const HttpRequestPtr &req, std::function &&callback, int pJobId) const;
- void getJobPersons(const HttpRequestPtr &req, std::function &&callback, int jobId) const;
+ METHOD_LIST_BEGIN
+ ADD_METHOD_TO(JobsController::get, "/jobs", Get, "LoginFilter");
+ ADD_METHOD_TO(JobsController::getOne, "/jobs/{1}", Get, "LoginFilter");
+ ADD_METHOD_TO(JobsController::createOne, "/jobs", Post, "LoginFilter");
+ ADD_METHOD_TO(JobsController::updateOne, "/jobs/{1}", Put, "LoginFilter");
+ ADD_METHOD_TO(JobsController::deleteOne, "/jobs/{1}", Delete, "LoginFilter");
+ ADD_METHOD_TO(JobsController::getJobPersons, "/jobs/{1}/persons", Get, "LoginFilter");
+ METHOD_LIST_END
+
+ void get(const HttpRequestPtr &req, std::function &&callback) const;
+ void getOne(const HttpRequestPtr &req, std::function &&callback, int pJobId) const;
+ void createOne(const HttpRequestPtr &req, std::function &&callback, Job &&pJob) const;
+ void updateOne(const HttpRequestPtr &req, std::function &&callback, int pJobId, Job &&pJob) const;
+ void deleteOne(const HttpRequestPtr &req, std::function &&callback, int pJobId) const;
+ void getJobPersons(const HttpRequestPtr &req, std::function &&callback, int jobId) const;
+
+private:
+ std::shared_ptr dbClient_;
};
diff --git a/controllers/PersonsController.cc b/controllers/PersonsController.cc
index e8d3e89..36b24aa 100644
--- a/controllers/PersonsController.cc
+++ b/controllers/PersonsController.cc
@@ -8,20 +8,43 @@
using namespace drogon::orm;
using namespace drogon_model::org_chart;
-namespace drogon {
- template<>
- inline Person fromRequest(const HttpRequest &req) {
+namespace drogon
+{
+ template <>
+ inline Person fromRequest(const HttpRequest &req)
+ {
+ // Try Content-Type: application/json first
auto jsonPtr = req.getJsonObject();
- auto json = *jsonPtr;
- if (json["department_id"]) json["department_id"] = std::stoi(json["department_id"].asString());
- if (json["manager_id"]) json["manager_id"] = std::stoi(json["manager_id"].asString());
- if (json["job_id"]) json["job_id"] = std::stoi(json["job_id"].asString());
- auto person = Person(json);
- return person;
+ Json::Value json;
+ if (jsonPtr)
+ {
+ json = *jsonPtr;
+ }
+ else
+ {
+ // Fallback: parse raw body as JSON
+ Json::Reader reader;
+ if (!reader.parse(std::string(req.body()), json))
+ {
+ // Parsing failed, return default Person
+ return Person(Json::Value{});
+ }
+ }
+
+ // Safely coerce string fields to int if present
+ if (json.isMember("department_id") && json["department_id"].isString())
+ json["department_id"] = std::stoi(json["department_id"].asString());
+ if (json.isMember("manager_id") && json["manager_id"].isString())
+ json["manager_id"] = std::stoi(json["manager_id"].asString());
+ if (json.isMember("job_id") && json["job_id"].isString())
+ json["job_id"] = std::stoi(json["job_id"].asString());
+
+ return Person(json);
}
-} // namespace drogon
+} // namespace drogon
-void PersonsController::get(const HttpRequestPtr &req, std::function &&callback) const {
+void PersonsController::get(const HttpRequestPtr &req, std::function &&callback) const
+{
LOG_DEBUG << "get";
auto sort_field = req->getOptionalParameter("sort_field").value_or("id");
auto sort_order = req->getOptionalParameter("sort_order").value_or("asc");
@@ -37,48 +60,50 @@ void PersonsController::get(const HttpRequestPtr &req, std::function> [callbackPtr](const Result &result)
- {
- if (result.empty()) {
- auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("resource not found"));
- resp->setStatusCode(HttpStatusCode::k404NotFound);
- (*callbackPtr)(resp);
- return;
- }
-
- Json::Value ret{};
- for (auto row : result) {
- PersonInfo personInfo{row};
- PersonDetails personDetails{personInfo};
- ret.append(personDetails.toJson());
- }
-
- auto resp = HttpResponse::newHttpJsonResponse(ret);
- resp->setStatusCode(HttpStatusCode::k200OK);
- (*callbackPtr)(resp);
- }
- >> [callbackPtr](const DrogonDbException &e)
- {
- LOG_ERROR << e.base().what();
- auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
- resp->setStatusCode(HttpStatusCode::k500InternalServerError);
- (*callbackPtr)(resp);
- };
+ << limit
+ << offset >>
+ [callbackPtr](const Result &result)
+ {
+ if (result.empty())
+ {
+ auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("resource not found"));
+ resp->setStatusCode(HttpStatusCode::k404NotFound);
+ (*callbackPtr)(resp);
+ return;
+ }
+
+ Json::Value ret{};
+ for (auto row : result)
+ {
+ PersonInfo personInfo{row};
+ PersonDetails personDetails{personInfo};
+ ret.append(personDetails.toJson());
+ }
+
+ auto resp = HttpResponse::newHttpJsonResponse(ret);
+ resp->setStatusCode(HttpStatusCode::k200OK);
+ (*callbackPtr)(resp);
+ } >> [callbackPtr](const DrogonDbException &e)
+ {
+ LOG_ERROR << e.base().what();
+ auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
+ resp->setStatusCode(HttpStatusCode::k500InternalServerError);
+ (*callbackPtr)(resp);
+ };
}
-void PersonsController::getOne(const HttpRequestPtr &req, std::function &&callback, int personId) const {
- LOG_DEBUG << "getOne personId: "<< personId;
+void PersonsController::getOne(const HttpRequestPtr &req, std::function &&callback, int personId) const
+{
+ LOG_DEBUG << "getOne personId: " << personId;
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
@@ -89,94 +114,157 @@ void PersonsController::getOne(const HttpRequestPtr &req, std::function> [callbackPtr](const Result &result)
- {
- if (result.empty()) {
- auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("resource not found"));
- resp->setStatusCode(HttpStatusCode::k404NotFound);
- (*callbackPtr)(resp);
- return;
- }
-
- auto row = result[0];
- PersonInfo personInfo{row};
- PersonDetails personDetails{personInfo};
-
- Json::Value ret = personDetails.toJson();
- auto resp = HttpResponse::newHttpJsonResponse(ret);
- resp->setStatusCode(HttpStatusCode::k200OK);
- (*callbackPtr)(resp);
- }
- >> [callbackPtr](const DrogonDbException &e)
- {
- LOG_ERROR << e.base().what();
- auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
- resp->setStatusCode(HttpStatusCode::k500InternalServerError);
- (*callbackPtr)(resp);
- };
+ << personId >>
+ [callbackPtr](const Result &result)
+ {
+ if (result.empty())
+ {
+ auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("resource not found"));
+ resp->setStatusCode(HttpStatusCode::k404NotFound);
+ (*callbackPtr)(resp);
+ return;
+ }
+
+ auto row = result[0];
+ PersonInfo personInfo{row};
+ PersonDetails personDetails{personInfo};
+
+ Json::Value ret = personDetails.toJson();
+ auto resp = HttpResponse::newHttpJsonResponse(ret);
+ resp->setStatusCode(HttpStatusCode::k200OK);
+ (*callbackPtr)(resp);
+ } >> [callbackPtr](const DrogonDbException &e)
+ {
+ LOG_ERROR << e.base().what();
+ auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
+ resp->setStatusCode(HttpStatusCode::k500InternalServerError);
+ (*callbackPtr)(resp);
+ };
}
-void PersonsController::createOne(const HttpRequestPtr &req, std::function &&callback, Person &&pPerson) const {
+void PersonsController::createOne(const HttpRequestPtr &req, std::function &&callback, Person &&pPerson) const
+{
LOG_DEBUG << "createOne";
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
+ auto errResp = [&](const std::string &msg, int code = 400)
+ {
+ auto resp = HttpResponse::newHttpJsonResponse(makeErrResp(msg));
+ resp->setStatusCode(static_cast(code));
+ (*callbackPtr)(resp);
+ };
+
+ /* ---------- VALIDATE REQUIRED FIELDS ---------- */
+ if (!pPerson.getLastName() || pPerson.getLastName()->empty())
+ return errResp("last_name is compulsory");
+
+ if (!pPerson.getFirstName() || pPerson.getFirstName()->empty())
+ return errResp("first_name is compulsory");
+
+ if (!pPerson.getHireDate())
+ return errResp("hire_date is compulsory");
+
+ if (!pPerson.getDepartmentId())
+ return errResp("department_id is compulsory");
+ if (!rowExists(dbClientPtr, "department", pPerson.getValueOfDepartmentId()))
+ return errResp("department_id is invalid", 422);
+
+ if (!pPerson.getJobId())
+ return errResp("job_id is compulsory");
+ if (!rowExists(dbClientPtr, "job", pPerson.getValueOfJobId()))
+ return errResp("job_id is invalid", 422);
+
+ /* ---------- MANAGER (optional) ---------- */
+ if (pPerson.getManagerId() && // provided
+ !rowExists(dbClientPtr, "person", pPerson.getValueOfManagerId()))
+ return errResp("manager_id is invalid", 422);
+
+ if (personNameExists(dbClientPtr,
+ *pPerson.getFirstName(),
+ *pPerson.getLastName()))
+ return errResp("person with the same first_name and last_name already exists");
+
Mapper mp(dbClientPtr);
mp.insert(
pPerson,
- [callbackPtr](const Person &person) {
+ [callbackPtr](const Person &person)
+ {
Json::Value ret{};
ret = person.toJson();
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k201Created);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void PersonsController::updateOne(const HttpRequestPtr &req, std::function &&callback, int personId, Person &&pPerson) const {
+void PersonsController::updateOne(const HttpRequestPtr &req, std::function &&callback, int personId, Person &&pPerson) const
+{
LOG_DEBUG << "updateOne personId: " << personId;
auto dbClientPtr = drogon::app().getDbClient();
// blocking IO
Mapper mp(dbClientPtr);
Person person;
- try {
+ try
+ {
person = mp.findFutureByPrimaryKey(personId).get();
- } catch (const DrogonDbException & e) {
+ }
+ catch (const DrogonDbException &e)
+ {
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("resource not found"));
resp->setStatusCode(HttpStatusCode::k404NotFound);
callback(resp);
return;
}
- if (pPerson.getJobId() != nullptr) {
- person.setJobId(pPerson.getValueOfJobId());
+ auto callbackPtr = std::make_shared>(std::move(callback));
+
+ auto errResp = [&](const std::string &msg, int code = 400)
+ {
+ auto resp = HttpResponse::newHttpJsonResponse(makeErrResp(msg));
+ resp->setStatusCode(static_cast(code));
+ (*callbackPtr)(resp);
+ };
+
+ if (pPerson.getJobId() != nullptr)
+ {
+ if (!rowExists(dbClientPtr, "job", pPerson.getValueOfJobId()))
+ return errResp("job_id is invalid", 422);
+ person.setJobId(pPerson.getValueOfJobId());
}
- if (pPerson.getManagerId() != nullptr) {
- person.setManagerId(pPerson.getValueOfManagerId());
+ if (pPerson.getManagerId() != nullptr)
+ {
+ if (!rowExists(dbClientPtr, "person", pPerson.getValueOfManagerId()))
+ return errResp("manager_id is invalid", 422);
+ person.setManagerId(pPerson.getValueOfManagerId());
}
- if (pPerson.getDepartmentId() != nullptr) {
- person.setDepartmentId(pPerson.getValueOfDepartmentId());
+ if (pPerson.getDepartmentId() != nullptr)
+ {
+ if (!rowExists(dbClientPtr, "department", pPerson.getValueOfDepartmentId()))
+ return errResp("department_id is invalid", 422);
+ person.setDepartmentId(pPerson.getValueOfDepartmentId());
}
- if (pPerson.getFirstName() != nullptr) {
- person.setFirstName(pPerson.getValueOfFirstName());
+ if (pPerson.getFirstName() != nullptr)
+ {
+ person.setFirstName(pPerson.getValueOfFirstName());
}
- if (pPerson.getLastName() != nullptr) {
- person.setLastName(pPerson.getValueOfLastName());
+ if (pPerson.getLastName() != nullptr)
+ {
+ person.setLastName(pPerson.getValueOfLastName());
}
- auto callbackPtr = std::make_shared>(std::move(callback));
mp.update(
person,
[callbackPtr](const std::size_t count)
@@ -191,11 +279,11 @@ void PersonsController::updateOne(const HttpRequestPtr &req, std::functionsetStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- }
- );
+ });
}
-void PersonsController::deleteOne(const HttpRequestPtr &req, std::function &&callback, int personId) const {
+void PersonsController::deleteOne(const HttpRequestPtr &req, std::function &&callback, int personId) const
+{
LOG_DEBUG << "deleteOne personId: ";
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
@@ -203,37 +291,44 @@ void PersonsController::deleteOne(const HttpRequestPtr &req, std::function mp(dbClientPtr);
mp.deleteBy(
Criteria(Person::Cols::_id, CompareOperator::EQ, personId),
- [callbackPtr](const std::size_t count) {
+ [callbackPtr](const std::size_t count)
+ {
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(HttpStatusCode::k204NoContent);
(*callbackPtr)(resp);
},
- [callbackPtr](const DrogonDbException &e) {
+ [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
(*callbackPtr)(resp);
- });
+ });
}
-void PersonsController::getDirectReports(const HttpRequestPtr &req, std::function &&callback, int personId) const {
- LOG_DEBUG << "getDirectReports personId: "<< personId;
+void PersonsController::getDirectReports(const HttpRequestPtr &req, std::function &&callback, int personId) const
+{
+ LOG_DEBUG << "getDirectReports personId: " << personId;
auto callbackPtr = std::make_shared>(std::move(callback));
auto dbClientPtr = drogon::app().getDbClient();
// blocking IO
Mapper mp(dbClientPtr);
Person department;
- try {
+ try
+ {
department = mp.findFutureByPrimaryKey(personId).get();
- } catch (const DrogonDbException & e) {
+ }
+ catch (const DrogonDbException &e)
+ {
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("resource not found"));
resp->setStatusCode(HttpStatusCode::k404NotFound);
callback(resp);
+ return;
}
- department.getPersons(dbClientPtr,
- [callbackPtr](const std::vector persons) {
+ department.getPersons(dbClientPtr, [callbackPtr](const std::vector persons)
+ {
if (persons.empty()) {
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("resource not found"));
resp->setStatusCode(HttpStatusCode::k404NotFound);
@@ -246,17 +341,16 @@ void PersonsController::getDirectReports(const HttpRequestPtr &req, std::functio
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(HttpStatusCode::k200OK);
(*callbackPtr)(resp);
- }
- },
- [callbackPtr](const DrogonDbException &e) {
+ } }, [callbackPtr](const DrogonDbException &e)
+ {
LOG_ERROR << e.base().what();
auto resp = HttpResponse::newHttpJsonResponse(makeErrResp("database error"));
resp->setStatusCode(HttpStatusCode::k500InternalServerError);
- (*callbackPtr)(resp);
- });
+ (*callbackPtr)(resp); });
}
-PersonsController::PersonDetails::PersonDetails(const PersonInfo &personInfo) {
+PersonsController::PersonDetails::PersonDetails(const PersonInfo &personInfo)
+{
id = personInfo.getValueOfId();
first_name = personInfo.getValueOfFirstName();
last_name = personInfo.getValueOfLastName();
@@ -275,7 +369,8 @@ PersonsController::PersonDetails::PersonDetails(const PersonInfo &personInfo) {
this->job = jobJson;
}
-auto PersonsController::PersonDetails::toJson() -> Json::Value {
+auto PersonsController::PersonDetails::toJson() -> Json::Value
+{
Json::Value ret{};
ret["id"] = id;
ret["first_name"] = first_name;
diff --git a/controllers/PersonsController.h b/controllers/PersonsController.h
index c5a4cb5..772e32b 100644
--- a/controllers/PersonsController.h
+++ b/controllers/PersonsController.h
@@ -8,35 +8,37 @@
using namespace drogon;
using namespace drogon_model::org_chart;
-class PersonsController : public drogon::HttpController {
- public:
- METHOD_LIST_BEGIN
- ADD_METHOD_TO(PersonsController::get, "/persons", Get);
- ADD_METHOD_TO(PersonsController::getOne, "/persons/{1}", Get);
- ADD_METHOD_TO(PersonsController::createOne, "/persons", Post);
- ADD_METHOD_TO(PersonsController::updateOne, "/persons/{1}", Put);
- ADD_METHOD_TO(PersonsController::deleteOne, "/persons/{1}", Delete);
- ADD_METHOD_TO(PersonsController::getDirectReports, "/persons/{1}/reports", Get);
- METHOD_LIST_END
+class PersonsController : public drogon::HttpController
+{
+public:
+ METHOD_LIST_BEGIN
+ ADD_METHOD_TO(PersonsController::get, "/persons", Get, "LoginFilter");
+ ADD_METHOD_TO(PersonsController::getOne, "/persons/{1}", Get, "LoginFilter");
+ ADD_METHOD_TO(PersonsController::createOne, "/persons", Post, "LoginFilter");
+ ADD_METHOD_TO(PersonsController::updateOne, "/persons/{1}", Put, "LoginFilter");
+ ADD_METHOD_TO(PersonsController::deleteOne, "/persons/{1}", Delete, "LoginFilter");
+ ADD_METHOD_TO(PersonsController::getDirectReports, "/persons/{1}/reports", Get, "LoginFilter");
+ METHOD_LIST_END
- void get(const HttpRequestPtr& req, std::function &&callback) const;
- void getOne(const HttpRequestPtr& req, std::function &&callback, int pPersonId) const;
- void createOne(const HttpRequestPtr &req, std::function &&callback, Person &&pPerson) const;
- void updateOne(const HttpRequestPtr &req, std::function &&callback, int pPersonId, Person &&pPerson) const;
- void deleteOne(const HttpRequestPtr &req, std::function &&callback, int pPersonId) const;
- void getDirectReports(const HttpRequestPtr &req, std::function &&callback, int pPersonId) const;
+ void get(const HttpRequestPtr &req, std::function &&callback) const;
+ void getOne(const HttpRequestPtr &req, std::function &&callback, int pPersonId) const;
+ void createOne(const HttpRequestPtr &req, std::function &&callback, Person &&pPerson) const;
+ void updateOne(const HttpRequestPtr &req, std::function &&callback, int pPersonId, Person &&pPerson) const;
+ void deleteOne(const HttpRequestPtr &req, std::function &&callback, int pPersonId) const;
+ void getDirectReports(const HttpRequestPtr &req, std::function &&callback, int pPersonId) const;
- private:
- struct PersonDetails {
- int id;
- std::string first_name;
- std::string last_name;
- trantor::Date hire_date;
- Json::Value manager;
- Json::Value department;
- Json::Value job;
- PersonDetails() {}
- explicit PersonDetails(const PersonInfo &personInfo);
- Json::Value toJson();
- };
+private:
+ struct PersonDetails
+ {
+ int id;
+ std::string first_name;
+ std::string last_name;
+ trantor::Date hire_date;
+ Json::Value manager;
+ Json::Value department;
+ Json::Value job;
+ PersonDetails() {}
+ explicit PersonDetails(const PersonInfo &personInfo);
+ Json::Value toJson();
+ };
};
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..366129e
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,50 @@
+version: '3.8'
+
+services:
+ db:
+ image: mysql:8.3 # or any 8.x you prefer
+ container_name: mysql
+ restart: always
+ environment:
+ # root account (use strong pw in prod)
+ MYSQL_ROOT_PASSWORD: password
+ # an app-level DB + user the app can connect with
+ MYSQL_DATABASE: org_chart
+ MYSQL_USER: org
+ MYSQL_PASSWORD: password
+ ports:
+ - "3307:3306" # host:container
+ volumes:
+ - mysql_data:/var/lib/mysql
+ # every *.sql in this dir runs exactly once at first boot
+ - ./scripts:/docker-entrypoint-initdb.d
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-ppassword"]
+ interval: 5s
+ timeout: 3s
+ retries: 5
+
+ app:
+ build: .
+ container_name: drogon_app
+ ports:
+ - "3000:3000"
+ depends_on:
+ db:
+ condition: service_healthy
+ volumes:
+ - .:/app
+ - /app/build
+ environment:
+ - DB_HOST=db
+ - DB_PORT=3306
+ - DB_USER=org
+ - DB_PASSWORD=password
+ - DB_NAME=org_chart
+ # optional hint for many frameworks/ORMS
+ - DB_DRIVER=mysql
+ working_dir: /app/build
+ command: ["./org_chart"]
+
+volumes:
+ mysql_data:
diff --git a/harness.cpp b/harness.cpp
new file mode 100644
index 0000000..e80cec1
--- /dev/null
+++ b/harness.cpp
@@ -0,0 +1,65 @@
+#include "controllers/DepartmentsController.h"
+#include
+#include
+#include
+#include
+
+// This is the main entry point for the fuzzer
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+ static bool is_initialized = []() -> bool {
+ try {
+ drogon::app().disableSession();
+ drogon::app().setLogLevel(trantor::Logger::kError);
+ drogon::app().loadConfigFile("config.json");
+ std::cout << "Drogon configured for fuzzing with in-memory SQLite database." << std::endl;
+
+ } catch (const std::exception& e) {
+ // If this fails, the harness is misconfigured.
+ std::cerr << "!!! HARNESS INITIALIZATION FAILED: " << e.what() << std::endl;
+ abort();
+ }
+ return true;
+ }();
+ // --- End of Initialization ---
+
+ if (Size < 1) {
+ return 0;
+ }
+
+ DepartmentsController controller;
+ uint8_t selector = Data[0];
+ const uint8_t* payload = Data + 1;
+ size_t payload_size = Size - 1;
+
+ try {
+ switch (selector % 2) {
+ case 0: { // Fuzz createOne
+ if (payload_size < 2) break;
+ Json::Value json_body;
+ Json::Reader reader;
+ std::string input_string(reinterpret_cast(payload), payload_size);
+ if (reader.parse(input_string, json_body)) {
+ Department department_to_create(json_body);
+ auto req = drogon::HttpRequest::newHttpJsonRequest(json_body);
+ auto cb = [](const drogon::HttpResponsePtr &){};
+ controller.createOne(req, std::move(cb), std::move(department_to_create));
+ }
+ break;
+ }
+ case 1: { // Fuzz getOne
+ if (payload_size == 0) break;
+ int departmentId = 0;
+ size_t bytes_to_copy = std::min(payload_size, sizeof(int));
+ memcpy(&departmentId, payload, bytes_to_copy);
+ auto req = drogon::HttpRequest::newHttpRequest();
+ auto cb = [](const drogon::HttpResponsePtr &){};
+ controller.getOne(req, std::move(cb), departmentId);
+ break;
+ }
+ }
+ } catch (const std::exception &e) {
+ // Correctly catch exceptions from business logic.
+ }
+
+ return 0;
+}
diff --git a/models/Department.cc b/models/Department.cc
index 27e3eb0..83513e8 100644
--- a/models/Department.cc
+++ b/models/Department.cc
@@ -21,8 +21,8 @@ const bool Department::hasPrimaryKey = true;
const std::string Department::tableName = "department";
const std::vector Department::metaData_={
-{"id","int32_t","integer",4,1,1,1},
-{"name","std::string","character varying",50,0,0,1}
+{"id","uint64_t","bigint unsigned",8,1,1,1},
+{"name","std::string","varchar(50)",50,0,0,1}
};
const std::string &Department::getColumnName(size_t index) noexcept(false)
{
@@ -35,7 +35,7 @@ Department::Department(const Row &r, const ssize_t indexOffset) noexcept
{
if(!r["id"].isNull())
{
- id_=std::make_shared(r["id"].as());
+ id_=std::make_shared(r["id"].as());
}
if(!r["name"].isNull())
{
@@ -54,7 +54,7 @@ Department::Department(const Row &r, const ssize_t indexOffset) noexcept
index = offset + 0;
if(!r[index].isNull())
{
- id_=std::make_shared(r[index].as());
+ id_=std::make_shared(r[index].as());
}
index = offset + 1;
if(!r[index].isNull())
@@ -77,7 +77,7 @@ Department::Department(const Json::Value &pJson, const std::vector
dirtyFlag_[0] = true;
if(!pJson[pMasqueradingVector[0]].isNull())
{
- id_=std::make_shared((int32_t)pJson[pMasqueradingVector[0]].asInt64());
+ id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
}
}
if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
@@ -97,7 +97,7 @@ Department::Department(const Json::Value &pJson) noexcept(false)
dirtyFlag_[0]=true;
if(!pJson["id"].isNull())
{
- id_=std::make_shared((int32_t)pJson["id"].asInt64());
+ id_=std::make_shared((uint64_t)pJson["id"].asUInt64());
}
}
if(pJson.isMember("name"))
@@ -122,7 +122,7 @@ void Department::updateByMasqueradedJson(const Json::Value &pJson,
{
if(!pJson[pMasqueradingVector[0]].isNull())
{
- id_=std::make_shared((int32_t)pJson[pMasqueradingVector[0]].asInt64());
+ id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
}
}
if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
@@ -141,7 +141,7 @@ void Department::updateByJson(const Json::Value &pJson) noexcept(false)
{
if(!pJson["id"].isNull())
{
- id_=std::make_shared((int32_t)pJson["id"].asInt64());
+ id_=std::make_shared((uint64_t)pJson["id"].asUInt64());
}
}
if(pJson.isMember("name"))
@@ -154,20 +154,20 @@ void Department::updateByJson(const Json::Value &pJson) noexcept(false)
}
}
-const int32_t &Department::getValueOfId() const noexcept
+const uint64_t &Department::getValueOfId() const noexcept
{
- const static int32_t defaultValue = int32_t();
+ static const uint64_t defaultValue = uint64_t();
if(id_)
return *id_;
return defaultValue;
}
-const std::shared_ptr &Department::getId() const noexcept
+const std::shared_ptr &Department::getId() const noexcept
{
return id_;
}
-void Department::setId(const int32_t &pId) noexcept
+void Department::setId(const uint64_t &pId) noexcept
{
- id_ = std::make_shared(pId);
+ id_ = std::make_shared(pId);
dirtyFlag_[0] = true;
}
const typename Department::PrimaryKeyType & Department::getPrimaryKey() const
@@ -178,7 +178,7 @@ const typename Department::PrimaryKeyType & Department::getPrimaryKey() const
const std::string &Department::getValueOfName() const noexcept
{
- const static std::string defaultValue = std::string();
+ static const std::string defaultValue = std::string();
if(name_)
return *name_;
return defaultValue;
@@ -200,6 +200,7 @@ void Department::setName(std::string &&pName) noexcept
void Department::updateId(const uint64_t id)
{
+ id_ = std::make_shared(id);
}
const std::vector &Department::insertColumns() noexcept
@@ -254,7 +255,7 @@ Json::Value Department::toJson() const
Json::Value ret;
if(getId())
{
- ret["id"]=getValueOfId();
+ ret["id"]=(Json::UInt64)getValueOfId();
}
else
{
@@ -271,6 +272,11 @@ Json::Value Department::toJson() const
return ret;
}
+std::string Department::toString() const
+{
+ return toJson().toStyledString();
+}
+
Json::Value Department::toMasqueradedJson(
const std::vector &pMasqueradingVector) const
{
@@ -281,7 +287,7 @@ Json::Value Department::toMasqueradedJson(
{
if(getId())
{
- ret[pMasqueradingVector[0]]=getValueOfId();
+ ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId();
}
else
{
@@ -304,7 +310,7 @@ Json::Value Department::toMasqueradedJson(
LOG_ERROR << "Masquerade failed";
if(getId())
{
- ret["id"]=getValueOfId();
+ ret["id"]=(Json::UInt64)getValueOfId();
}
else
{
@@ -450,7 +456,7 @@ bool Department::validJsonOfField(size_t index,
err="The automatic primary key cannot be set";
return false;
}
- if(!pJson.isInt())
+ if(!pJson.isUInt64())
{
err="Type error in the "+fieldName+" field";
return false;
@@ -467,8 +473,7 @@ bool Department::validJsonOfField(size_t index,
err="Type error in the "+fieldName+" field";
return false;
}
- // asString().length() creates a string object, is there any better way to validate the length?
- if(pJson.isString() && pJson.asString().length() > 50)
+ if(pJson.isString() && std::strlen(pJson.asCString()) > 50)
{
err="String length exceeds limit for the " +
fieldName +
@@ -480,15 +485,32 @@ bool Department::validJsonOfField(size_t index,
default:
err="Internal error in the server";
return false;
- break;
}
return true;
}
+std::vector Department::getPersons(const DbClientPtr &clientPtr) const {
+ static const std::string sql = "select * from person where department_id = ?";
+ Result r(nullptr);
+ {
+ auto binder = *clientPtr << sql;
+ binder << *id_ << Mode::Blocking >>
+ [&r](const Result &result) { r = result; };
+ binder.exec();
+ }
+ std::vector ret;
+ ret.reserve(r.size());
+ for (auto const &row : r)
+ {
+ ret.emplace_back(Person(row));
+ }
+ return ret;
+}
+
void Department::getPersons(const DbClientPtr &clientPtr,
const std::function)> &rcb,
const ExceptionCallback &ecb) const
{
- const static std::string sql = "select * from person where department_id = $1";
+ static const std::string sql = "select * from person where department_id = ?";
*clientPtr << sql
<< *id_
>> [rcb = std::move(rcb)](const Result &r){
diff --git a/models/Department.h b/models/Department.h
index 9e82611..9ce6535 100644
--- a/models/Department.h
+++ b/models/Department.h
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#ifdef __cpp_impl_coroutine
#include
#endif
@@ -18,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -47,11 +49,11 @@ class Department
static const std::string _name;
};
- const static int primaryKeyNumber;
- const static std::string tableName;
- const static bool hasPrimaryKey;
- const static std::string primaryKeyName;
- using PrimaryKeyType = int32_t;
+ static const int primaryKeyNumber;
+ static const std::string tableName;
+ static const bool hasPrimaryKey;
+ static const std::string primaryKeyName;
+ using PrimaryKeyType = uint64_t;
const PrimaryKeyType &getPrimaryKey() const;
/**
@@ -98,11 +100,11 @@ class Department
/** For column id */
///Get the value of the column id, returns the default value if the column is null
- const int32_t &getValueOfId() const noexcept;
+ const uint64_t &getValueOfId() const noexcept;
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
- const std::shared_ptr &getId() const noexcept;
+ const std::shared_ptr &getId() const noexcept;
///Set the value of the column id
- void setId(const int32_t &pId) noexcept;
+ void setId(const uint64_t &pId) noexcept;
/** For column name */
///Get the value of the column name, returns the default value if the column is null
@@ -118,13 +120,19 @@ class Department
static const std::string &getColumnName(size_t index) noexcept(false);
Json::Value toJson() const;
+ std::string toString() const;
Json::Value toMasqueradedJson(const std::vector &pMasqueradingVector) const;
/// Relationship interfaces
+ std::vector getPersons(const drogon::orm::DbClientPtr &clientPtr) const;
void getPersons(const drogon::orm::DbClientPtr &clientPtr,
const std::function)> &rcb,
const drogon::orm::ExceptionCallback &ecb) const;
private:
friend drogon::orm::Mapper;
+ friend drogon::orm::BaseBuilder;
+ friend drogon::orm::BaseBuilder;
+ friend drogon::orm::BaseBuilder;
+ friend drogon::orm::BaseBuilder;
#ifdef __cpp_impl_coroutine
friend drogon::orm::CoroMapper;
#endif
@@ -134,7 +142,7 @@ class Department
void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
///For mysql or sqlite3
void updateId(const uint64_t id);
- std::shared_ptr id_;
+ std::shared_ptr id_;
std::shared_ptr name_;
struct MetaData
{
@@ -151,13 +159,13 @@ class Department
public:
static const std::string &sqlForFindingByPrimaryKey()
{
- static const std::string sql="select * from " + tableName + " where id = $1";
+ static const std::string sql="select * from " + tableName + " where id = ?";
return sql;
}
static const std::string &sqlForDeletingByPrimaryKey()
{
- static const std::string sql="delete from " + tableName + " where id = $1";
+ static const std::string sql="delete from " + tableName + " where id = ?";
return sql;
}
std::string sqlForInserting(bool &needSelection) const
@@ -181,27 +189,17 @@ class Department
else
sql += ") values (";
- int placeholder=1;
- char placeholderStr[64];
- size_t n=0;
sql +="default,";
if(dirtyFlag_[1])
{
- n = sprintf(placeholderStr,"$%d,",placeholder++);
- sql.append(placeholderStr, n);
+ sql.append("?,");
+
}
if(parametersCount > 0)
{
sql.resize(sql.length() - 1);
}
- if(needSelection)
- {
- sql.append(") returning *");
- }
- else
- {
- sql.append(1, ')');
- }
+ sql.append(1, ')');
LOG_TRACE << sql;
return sql;
}
diff --git a/models/Job.cc b/models/Job.cc
index ac059fa..98a318f 100644
--- a/models/Job.cc
+++ b/models/Job.cc
@@ -21,8 +21,8 @@ const bool Job::hasPrimaryKey = true;
const std::string Job::tableName = "job";
const std::vector Job::metaData_={
-{"id","int32_t","integer",4,1,1,1},
-{"title","std::string","character varying",50,0,0,1}
+{"id","uint64_t","bigint unsigned",8,1,1,1},
+{"title","std::string","varchar(50)",50,0,0,1}
};
const std::string &Job::getColumnName(size_t index) noexcept(false)
{
@@ -35,7 +35,7 @@ Job::Job(const Row &r, const ssize_t indexOffset) noexcept
{
if(!r["id"].isNull())
{
- id_=std::make_shared(r["id"].as());
+ id_=std::make_shared(r["id"].as());
}
if(!r["title"].isNull())
{
@@ -54,7 +54,7 @@ Job::Job(const Row &r, const ssize_t indexOffset) noexcept
index = offset + 0;
if(!r[index].isNull())
{
- id_=std::make_shared(r[index].as());
+ id_=std::make_shared(r[index].as());
}
index = offset + 1;
if(!r[index].isNull())
@@ -77,7 +77,7 @@ Job::Job(const Json::Value &pJson, const std::vector &pMasquerading
dirtyFlag_[0] = true;
if(!pJson[pMasqueradingVector[0]].isNull())
{
- id_=std::make_shared((int32_t)pJson[pMasqueradingVector[0]].asInt64());
+ id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
}
}
if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
@@ -97,7 +97,7 @@ Job::Job(const Json::Value &pJson) noexcept(false)
dirtyFlag_[0]=true;
if(!pJson["id"].isNull())
{
- id_=std::make_shared((int32_t)pJson["id"].asInt64());
+ id_=std::make_shared((uint64_t)pJson["id"].asUInt64());
}
}
if(pJson.isMember("title"))
@@ -122,7 +122,7 @@ void Job::updateByMasqueradedJson(const Json::Value &pJson,
{
if(!pJson[pMasqueradingVector[0]].isNull())
{
- id_=std::make_shared((int32_t)pJson[pMasqueradingVector[0]].asInt64());
+ id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64());
}
}
if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1]))
@@ -141,7 +141,7 @@ void Job::updateByJson(const Json::Value &pJson) noexcept(false)
{
if(!pJson["id"].isNull())
{
- id_=std::make_shared((int32_t)pJson["id"].asInt64());
+ id_=std::make_shared((uint64_t)pJson["id"].asUInt64());
}
}
if(pJson.isMember("title"))
@@ -154,20 +154,20 @@ void Job::updateByJson(const Json::Value &pJson) noexcept(false)
}
}
-const int32_t &Job::getValueOfId() const noexcept
+const uint64_t &Job::getValueOfId() const noexcept
{
- const static int32_t defaultValue = int32_t();
+ static const uint64_t defaultValue = uint64_t();
if(id_)
return *id_;
return defaultValue;
}
-const std::shared_ptr &Job::getId() const noexcept
+const std::shared_ptr &Job::getId() const noexcept
{
return id_;
}
-void Job::setId(const int32_t &pId) noexcept
+void Job::setId(const uint64_t &pId) noexcept
{
- id_ = std::make_shared(pId);
+ id_ = std::make_shared(pId);
dirtyFlag_[0] = true;
}
const typename Job::PrimaryKeyType & Job::getPrimaryKey() const
@@ -178,7 +178,7 @@ const typename Job::PrimaryKeyType & Job::getPrimaryKey() const
const std::string &Job::getValueOfTitle() const noexcept
{
- const static std::string defaultValue = std::string();
+ static const std::string defaultValue = std::string();
if(title_)
return *title_;
return defaultValue;
@@ -200,6 +200,7 @@ void Job::setTitle(std::string &&pTitle) noexcept
void Job::updateId(const uint64_t id)
{
+ id_ = std::make_shared(id);
}
const std::vector &Job::insertColumns() noexcept
@@ -254,7 +255,7 @@ Json::Value Job::toJson() const
Json::Value ret;
if(getId())
{
- ret["id"]=getValueOfId();
+ ret["id"]=(Json::UInt64)getValueOfId();
}
else
{
@@ -271,6 +272,11 @@ Json::Value Job::toJson() const
return ret;
}
+std::string Job::toString() const
+{
+ return toJson().toStyledString();
+}
+
Json::Value Job::toMasqueradedJson(
const std::vector &pMasqueradingVector) const
{
@@ -281,7 +287,7 @@ Json::Value Job::toMasqueradedJson(
{
if(getId())
{
- ret[pMasqueradingVector[0]]=getValueOfId();
+ ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId();
}
else
{
@@ -304,7 +310,7 @@ Json::Value Job::toMasqueradedJson(
LOG_ERROR << "Masquerade failed";
if(getId())
{
- ret["id"]=getValueOfId();
+ ret["id"]=(Json::UInt64)getValueOfId();
}
else
{
@@ -450,7 +456,7 @@ bool Job::validJsonOfField(size_t index,
err="The automatic primary key cannot be set";
return false;
}
- if(!pJson.isInt())
+ if(!pJson.isUInt64())
{
err="Type error in the "+fieldName+" field";
return false;
@@ -467,8 +473,7 @@ bool Job::validJsonOfField(size_t index,
err="Type error in the "+fieldName+" field";
return false;
}
- // asString().length() creates a string object, is there any better way to validate the length?
- if(pJson.isString() && pJson.asString().length() > 50)
+ if(pJson.isString() && std::strlen(pJson.asCString()) > 50)
{
err="String length exceeds limit for the " +
fieldName +
@@ -480,15 +485,32 @@ bool Job::validJsonOfField(size_t index,
default:
err="Internal error in the server";
return false;
- break;
}
return true;
}
+std::vector Job::getPersons(const DbClientPtr &clientPtr) const {
+ static const std::string sql = "select * from person where job_id = ?";
+ Result r(nullptr);
+ {
+ auto binder = *clientPtr << sql;
+ binder << *id_ << Mode::Blocking >>
+ [&r](const Result &result) { r = result; };
+ binder.exec();
+ }
+ std::vector ret;
+ ret.reserve(r.size());
+ for (auto const &row : r)
+ {
+ ret.emplace_back(Person(row));
+ }
+ return ret;
+}
+
void Job::getPersons(const DbClientPtr &clientPtr,
const std::function)> &rcb,
const ExceptionCallback &ecb) const
{
- const static std::string sql = "select * from person where job_id = $1";
+ static const std::string sql = "select * from person where job_id = ?";
*clientPtr << sql
<< *id_
>> [rcb = std::move(rcb)](const Result &r){
diff --git a/models/Job.h b/models/Job.h
index 821ca99..756595e 100644
--- a/models/Job.h
+++ b/models/Job.h
@@ -11,6 +11,7 @@
#include
#include