AUTH SERVER
- set CMAke project - add Drogon submodule - add json and Catch2 submodule - add health check endpoint -RAII SqliteDb - Verify DB and HTTP some work
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: '-2'
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: 'true'
|
||||
AlignConsecutiveDeclarations: 'true'
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: 'true'
|
||||
AlignTrailingComments: 'true'
|
||||
AllowAllConstructorInitializersOnNextLine: 'false'
|
||||
AllowAllParametersOfDeclarationOnNextLine: 'true'
|
||||
AllowShortBlocksOnASingleLine: 'false'
|
||||
AllowShortCaseLabelsOnASingleLine: 'true'
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: Inline
|
||||
AllowShortLoopsOnASingleLine: 'false'
|
||||
AllowShortCompoundRequirementOnASingleLine: 'true'
|
||||
AlwaysBreakTemplateDeclarations: 'Yes'
|
||||
BinPackArguments: 'true'
|
||||
BinPackParameters: 'true'
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: 'true'
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeComma
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
ColumnLimit: '100'
|
||||
CompactNamespaces: 'false'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
|
||||
Cpp11BracedListStyle: 'false'
|
||||
FixNamespaceComments: 'true'
|
||||
IncludeBlocks: Regroup
|
||||
IndentCaseLabels: 'true'
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentWidth: '2'
|
||||
IndentWrappedFunctionNames: 'false'
|
||||
KeepEmptyLinesAtTheStartOfBlocks: 'false'
|
||||
Language: Cpp
|
||||
MaxEmptyLinesToKeep: '1'
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SortIncludes: 'true'
|
||||
SortUsingDeclarations: 'true'
|
||||
SpaceAfterCStyleCast: 'false'
|
||||
SpaceAfterLogicalNot: 'false'
|
||||
SpaceAfterTemplateKeyword: 'true'
|
||||
SpaceBeforeAssignmentOperators: 'true'
|
||||
SpaceBeforeCpp11BracedList: 'false'
|
||||
SpaceBeforeCtorInitializerColon: 'false'
|
||||
SpaceBeforeInheritanceColon: 'false'
|
||||
SpaceBeforeParens: Never
|
||||
SpaceInEmptyParentheses: 'false'
|
||||
SpacesInAngles: 'false'
|
||||
SpacesInContainerLiterals: 'false'
|
||||
SpacesInParentheses: 'false'
|
||||
Standard: Cpp11
|
||||
TabWidth: '2'
|
||||
UseTab: Never
|
||||
@@ -0,0 +1,9 @@
|
||||
[submodule "submodules/drogon"]
|
||||
path = submodules/drogon
|
||||
url = https://github.com/drogonframework/drogon.git
|
||||
[submodule "submodules/json"]
|
||||
path = submodules/json
|
||||
url = https://github.com/nlohmann/json.git
|
||||
[submodule "submodules/Catch2"]
|
||||
path = submodules/Catch2
|
||||
url = https://github.com/catchorg/Catch2.git
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run auth_service",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/auth_service",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"stopAtEntry": false,
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"preLaunchTask": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build",
|
||||
"build",
|
||||
"--target",
|
||||
"auth_service"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "run",
|
||||
"type": "shell",
|
||||
"command": "./build/auth_service",
|
||||
"dependsOn": "build",
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"type": "shell",
|
||||
"command": "ctest",
|
||||
"args": [
|
||||
"--test-dir",
|
||||
"build",
|
||||
"--output-on-failure"
|
||||
],
|
||||
"dependsOn": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
project(auth_service VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
|
||||
find_package(SQLite3 REQUIRED)
|
||||
|
||||
add_subdirectory(submodules/drogon)
|
||||
add_subdirectory(submodules/json)
|
||||
add_subdirectory(submodules/Catch2)
|
||||
|
||||
add_executable(auth_service
|
||||
src/main.cpp
|
||||
src/application/application.cpp
|
||||
src/http/controllers/health_controller.cpp
|
||||
src/db/sqllite_db.cpp
|
||||
src/db/statement.cpp
|
||||
)
|
||||
|
||||
target_include_directories(auth_service
|
||||
PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${PROJECT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_compile_options(auth_service PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
)
|
||||
|
||||
target_link_libraries(auth_service PRIVATE
|
||||
drogon
|
||||
SQLite::SQLite3
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
|
||||
add_executable(auth_service_tests
|
||||
tests/smoke_test.cpp
|
||||
)
|
||||
|
||||
target_include_directories(auth_service_tests
|
||||
PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${PROJECT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_compile_options(auth_service_tests PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
)
|
||||
|
||||
target_link_libraries(auth_service_tests PRIVATE
|
||||
Catch2::Catch2WithMain
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
|
||||
include(${PROJECT_SOURCE_DIR}/submodules/Catch2/extras/Catch.cmake)
|
||||
catch_discover_tests(auth_service_tests)
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "application/application.hpp"
|
||||
// #include <nlohmann/json.hpp>
|
||||
#include "db/sqllite_db.hpp"
|
||||
#include "http/controllers/health_controller.hpp"
|
||||
|
||||
#include <drogon/drogon.h>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
namespace {
|
||||
void register_routes() {
|
||||
static HealthController health_controller;
|
||||
drogon::app().registerHandler(
|
||||
"/health",
|
||||
[](const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||
health_controller.handle(req, std::move(callback));
|
||||
},
|
||||
{ drogon::Get });
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Application::Application(Settings settings)
|
||||
: settings_(std::move(settings)) {}
|
||||
|
||||
int Application::run() const {
|
||||
SqlliteDb db(settings_.db_path);
|
||||
db.initialize();
|
||||
|
||||
// {
|
||||
// db.execute("CREATE TABLE IF NOT EXISTS healthcheck_tmp ("
|
||||
// "id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
// "name TEXT NOT NULL"
|
||||
// ");");
|
||||
|
||||
// {
|
||||
// auto insert_stmt = db.prepare("INSERT INTO healthcheck_tmp(name) VALUES(?1);");
|
||||
// insert_stmt.bind_text(1, "boot");
|
||||
// insert_stmt.execute();
|
||||
// }
|
||||
|
||||
// {
|
||||
// auto select_stmt =
|
||||
// db.prepare("SELECT id, name FROM healthcheck_tmp ORDER BY id DESC LIMIT 1;");
|
||||
|
||||
// if(select_stmt.step()) {
|
||||
// const auto id = select_stmt.column_int64(0);
|
||||
// const auto name = select_stmt.column_text(1);
|
||||
|
||||
// std::cout << "id=" << id << ", value=" << name << '\n';
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
register_routes();
|
||||
drogon::app().addListener(settings_.host, settings_.port);
|
||||
printInfo();
|
||||
drogon::app().run();
|
||||
return 0;
|
||||
};
|
||||
|
||||
void Application::printInfo() const noexcept {
|
||||
std::cout << std::format("Server starting at http://{}:{}", settings_.host, settings_.port)
|
||||
<< std::endl;
|
||||
std::cout << std::format("SQLite initialize at:{}", settings_.db_path) << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "config/settings/settings.hpp"
|
||||
class Application final {
|
||||
public:
|
||||
explicit Application(Settings settings);
|
||||
int run() const;
|
||||
void printInfo() const noexcept;
|
||||
|
||||
private:
|
||||
Settings settings_;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
struct Settings {
|
||||
std::string host{ "127.0.0.1" };
|
||||
std::uint16_t port{ 8080 };
|
||||
std::string db_path{ "auth.db" };
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "db/sqllite_db.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
SqlliteDb::SqlliteDb(const std::string &path) {
|
||||
const int rc = sqlite3_open(path.c_str(), &db_);
|
||||
if(rc != SQLITE_OK) {
|
||||
std::string message = "Failed to open SQLite database";
|
||||
if(db_ != nullptr) {
|
||||
message += ": ";
|
||||
message += sqlite3_errmsg(db_);
|
||||
closeDb();
|
||||
}
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
SqlliteDb::~SqlliteDb() {
|
||||
closeDb();
|
||||
}
|
||||
|
||||
SqlliteDb::SqlliteDb(SqlliteDb &&other) noexcept
|
||||
: db_(std::exchange(other.db_, nullptr)) {}
|
||||
|
||||
SqlliteDb &SqlliteDb::operator=(SqlliteDb &&other) noexcept {
|
||||
if(this != &other) {
|
||||
if(db_ != nullptr) {
|
||||
sqlite3_close(db_);
|
||||
}
|
||||
db_ = std::exchange(other.db_, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SqlliteDb::execute(std::string_view sql) {
|
||||
char *error_message = nullptr;
|
||||
const int rc = sqlite3_exec(db_, sql.data(), nullptr, nullptr, &error_message);
|
||||
|
||||
if(rc != SQLITE_OK) {
|
||||
std::string message = "SQLite execute failed";
|
||||
if(error_message != nullptr) {
|
||||
message += ": ";
|
||||
message += error_message;
|
||||
sqlite3_free(error_message);
|
||||
} else {
|
||||
message += "unknown error";
|
||||
}
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
}
|
||||
|
||||
void SqlliteDb::initialize() {
|
||||
execute("PRAGMA foreign_keys = ON;");
|
||||
execute("PRAGMA journal_mode = WAL;");
|
||||
execute("PRAGMA busy_timeout = 5000;");
|
||||
}
|
||||
|
||||
sqlite3 *SqlliteDb::native_handle() const noexcept {
|
||||
return db_;
|
||||
}
|
||||
|
||||
Statement SqlliteDb::prepare(std::string_view sql) const {
|
||||
return Statement{ db_, sql };
|
||||
}
|
||||
|
||||
void SqlliteDb::closeDb() {
|
||||
if(db_ != nullptr) {
|
||||
sqlite3_close(db_);
|
||||
db_ = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/statement.hpp"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class SqlliteDb {
|
||||
public:
|
||||
explicit SqlliteDb(const std::string &path);
|
||||
~SqlliteDb();
|
||||
|
||||
SqlliteDb(const SqlliteDb &) = delete;
|
||||
SqlliteDb &operator=(const SqlliteDb &) = delete;
|
||||
|
||||
SqlliteDb(SqlliteDb &&other) noexcept;
|
||||
SqlliteDb &operator=(SqlliteDb &&other) noexcept;
|
||||
|
||||
void execute(std::string_view sql);
|
||||
void initialize();
|
||||
|
||||
Statement prepare(std::string_view sql) const;
|
||||
|
||||
sqlite3 *native_handle() const noexcept;
|
||||
|
||||
private:
|
||||
void closeDb();
|
||||
|
||||
private:
|
||||
sqlite3 *db_{ nullptr };
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "db/statement.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
Statement::Statement(sqlite3 *db, std::string_view sql) {
|
||||
const int rc = sqlite3_prepare_v2(db, sql.data(), static_cast<int>(sql.size()), &stmt_, nullptr);
|
||||
if(rc != SQLITE_OK) {
|
||||
throw std::runtime_error("Failed to prepare SQLite statement");
|
||||
}
|
||||
}
|
||||
|
||||
Statement::~Statement() {
|
||||
delete_stament();
|
||||
}
|
||||
|
||||
Statement::Statement(Statement &&other) noexcept
|
||||
: stmt_(std::exchange(other.stmt_, nullptr)) {}
|
||||
|
||||
Statement &Statement::operator=(Statement &&other) noexcept {
|
||||
if(this != &other) {
|
||||
delete_stament();
|
||||
stmt_ = std::exchange(other.stmt_, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Statement::delete_stament() {
|
||||
if(stmt_ != nullptr) {
|
||||
sqlite3_finalize(stmt_);
|
||||
stmt_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Statement::bind_int64(int index, std::int64_t value) {
|
||||
const int rc = sqlite3_bind_int64(stmt_, index, value);
|
||||
if(rc != SQLITE_OK) {
|
||||
throw std::runtime_error("Failed bind int64");
|
||||
}
|
||||
}
|
||||
|
||||
void Statement::bind_text(int index, std::string_view value) {
|
||||
const int rc = sqlite3_bind_text(stmt_, index, value.data(), static_cast<int>(value.size()),
|
||||
SQLITE_TRANSIENT);
|
||||
if(rc != SQLITE_OK) {
|
||||
throw std::runtime_error("Failed bind text");
|
||||
}
|
||||
}
|
||||
|
||||
void Statement::bind_null(int index) {
|
||||
const int rc = sqlite3_bind_null(stmt_, index);
|
||||
if(rc != SQLITE_OK) {
|
||||
throw std::runtime_error("Failed bind null");
|
||||
}
|
||||
}
|
||||
|
||||
bool Statement::step() {
|
||||
const int rc = sqlite3_step(stmt_);
|
||||
if(rc == SQLITE_ROW) {
|
||||
return true;
|
||||
}
|
||||
if(rc == SQLITE_DONE) {
|
||||
return false;
|
||||
}
|
||||
throw std::runtime_error("Failed to step SQLite statement");
|
||||
}
|
||||
|
||||
void Statement::execute() {
|
||||
const int rc = sqlite3_step(stmt_);
|
||||
if(rc != SQLITE_DONE) {
|
||||
throw std::runtime_error("Failed to execute statement");
|
||||
}
|
||||
}
|
||||
|
||||
std::int64_t Statement::column_int64(int index) const {
|
||||
return sqlite3_column_int64(stmt_, index);
|
||||
}
|
||||
|
||||
std::string Statement::column_text(int index) const {
|
||||
const auto *txt = reinterpret_cast<const char *>(sqlite3_column_text(stmt_, index));
|
||||
if(txt == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::string{ txt };
|
||||
}
|
||||
|
||||
bool Statement::column_is_null(int index) const {
|
||||
return sqlite3_column_type(stmt_, index) == SQLITE_NULL;
|
||||
}
|
||||
|
||||
void Statement::reset() {
|
||||
const int rc = sqlite3_reset(stmt_);
|
||||
if(rc != SQLITE_OK) {
|
||||
throw std::runtime_error("Failed reset statement");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <sqlite3.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class Statement {
|
||||
public:
|
||||
Statement(sqlite3 *db, std::string_view sql);
|
||||
~Statement();
|
||||
|
||||
Statement(const Statement &) = delete;
|
||||
Statement &operator=(const Statement &) = delete;
|
||||
|
||||
Statement(Statement &&other) noexcept;
|
||||
Statement &operator=(Statement &&other) noexcept;
|
||||
|
||||
void bind_int64(int index, std::int64_t value);
|
||||
void bind_text(int inxex, std::string_view value);
|
||||
void bind_null(int index);
|
||||
|
||||
bool step();
|
||||
void execute();
|
||||
|
||||
std::int64_t column_int64(int index) const;
|
||||
std::string column_text(int index) const;
|
||||
bool column_is_null(int index) const;
|
||||
|
||||
void reset();
|
||||
|
||||
private:
|
||||
void delete_stament();
|
||||
|
||||
private:
|
||||
sqlite3_stmt *stmt_{ nullptr };
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "http/controllers/health_controller.hpp"
|
||||
|
||||
#include <drogon/drogon.h>
|
||||
#include <json/json.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
void HealthController::handle(
|
||||
const drogon::HttpRequestPtr &,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const {
|
||||
Json::Value body;
|
||||
body["status"] = "ok";
|
||||
auto response = drogon::HttpResponse::newHttpJsonResponse(body);
|
||||
response->setStatusCode(drogon::k200OK);
|
||||
callback(response);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <drogon/HttpController.h>
|
||||
|
||||
class HealthController {
|
||||
public:
|
||||
void handle(const drogon::HttpRequestPtr &req,
|
||||
std::function<void(const drogon::HttpResponsePtr &)> &&callback) const;
|
||||
void testFor() {}
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <iostream>
|
||||
#include "application/application.hpp"
|
||||
#include "config/settings/settings.hpp"
|
||||
|
||||
int main()
|
||||
{
|
||||
Settings s;
|
||||
Application app(s);
|
||||
return app.run();
|
||||
}
|
||||
Submodule
+1
Submodule submodules/Catch2 added at 1df10d28ae
Submodule
+1
Submodule submodules/drogon added at acad9c8ee7
Submodule
+1
Submodule submodules/json added at 9a737481ae
@@ -0,0 +1,6 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
TEST_CASE("smoke test")
|
||||
{
|
||||
REQUIRE(1 + 1 == 2);
|
||||
}
|
||||
Reference in New Issue
Block a user