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:
2026-04-04 20:41:24 +03:00
parent 69daeab04d
commit 062b64bce8
19 changed files with 558 additions and 0 deletions
+56
View File
@@ -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
+9
View File
@@ -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
+17
View File
@@ -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"
}
]
}
+43
View File
@@ -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"
}
]
}
+67
View File
@@ -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)
+66
View File
@@ -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;
}
+11
View File
@@ -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_;
};
+10
View File
@@ -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" };
};
+72
View File
@@ -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;
}
}
+32
View File
@@ -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 };
};
+96
View File
@@ -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");
}
}
+37
View File
@@ -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() {}
};
+10
View File
@@ -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();
}
+1
Submodule submodules/Catch2 added at 1df10d28ae
+1
Submodule submodules/drogon added at acad9c8ee7
+1
Submodule submodules/json added at 9a737481ae
+6
View File
@@ -0,0 +1,6 @@
#include <catch2/catch_test_macros.hpp>
TEST_CASE("smoke test")
{
REQUIRE(1 + 1 == 2);
}