diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bbc72a..e77dc7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,16 +49,8 @@ add_executable(${TARGET_NAME} src/main.cpp src/utils.hpp src/utils.cpp - src/htmx/HtmxTable.cpp - src/htmx/HtmxTable.h src/systemd.cpp src/systemd.h - src/htmx/HtmxTableRow.cpp - src/htmx/HtmxTableRow.h - src/htmx/HtmxObject.cpp - src/htmx/HtmxObject.h - src/htmx_helper.cpp - src/htmx_helper.h src/json_settings.cpp src/json_settings.h @@ -66,14 +58,6 @@ add_executable(${TARGET_NAME} src/database/database.hpp # Shadowrun - src/shadowrun/HtmxShItemList.cpp - src/shadowrun/HtmxShItemList.hpp - src/shadowrun/HtmxShAttributeList.cpp - src/shadowrun/HtmxShAttributeList.hpp - src/shadowrun/HtmxShCondition.cpp - src/shadowrun/HtmxShCondition.hpp - src/shadowrun/ShadowrunCharacterForm.hpp - src/shadowrun/ShadowrunCharacterForm.cpp src/shadowrun/ShadowrunApi.cpp src/shadowrun/ShadowrunApi.hpp src/shadowrun/ShadowrunDb.cpp @@ -82,7 +66,9 @@ add_executable(${TARGET_NAME} # login src/login/login.cpp src/login/login.hpp - + src/login/loginDb.cpp + src/login/Session.cpp + src/login/SessionHandler.cpp ) # warnings to ignore diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 83a2db6..40ef8dc 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,8 +1,69 @@ - \ No newline at end of file + + + +
+

Login

+ + + + + + {#if error} +

{error}

+ {/if} + + +
+ + + + \ No newline at end of file diff --git a/src/database/database.hpp b/src/database/database.hpp index eebcb9c..dd03683 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -11,6 +11,7 @@ #include "crow.h" #include "sqlite_orm.h" #include "ShadowrunDb.hpp" +#include "loginDb.hpp" class Database { @@ -84,6 +85,12 @@ private: inline auto make_database() { auto storage = sqlite_orm::make_storage(Database::dbFile, + sqlite_orm::make_table("users", + sqlite_orm::make_column("id", &login::User::id, sqlite_orm::primary_key()), + sqlite_orm::make_column("username", &login::User::username, sqlite_orm::not_null()), + sqlite_orm::make_column("password_hash", &login::User::password_hash, sqlite_orm::not_null()), + sqlite_orm::make_column("created_at", &login::User::created_at, sqlite_orm::default_value("CURRENT_TIMESTAMP")) + ), sqlite_orm::make_table("shadowrun_characters", sqlite_orm::make_column("id", &shadowrun::ShadowrunCharacter::id, sqlite_orm::primary_key()), sqlite_orm::make_column("name", &shadowrun::ShadowrunCharacter::name, sqlite_orm::not_null()), diff --git a/src/database/databasepool.h b/src/database/databasepool.h index c783763..83e70d6 100644 --- a/src/database/databasepool.h +++ b/src/database/databasepool.h @@ -36,5 +36,4 @@ private: std::condition_variable cv; }; - extern DatabasePool dbpool; \ No newline at end of file diff --git a/src/htmx/HtmxObject.cpp b/src/htmx/HtmxObject.cpp deleted file mode 100644 index 928b84f..0000000 --- a/src/htmx/HtmxObject.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// -// Created by lukas on 5/11/25. -// - -#include "HtmxObject.h" - -using namespace std; - -const string& HtmxObject::htmx() const { - return html; -} \ No newline at end of file diff --git a/src/htmx/HtmxObject.h b/src/htmx/HtmxObject.h deleted file mode 100644 index 73be4e6..0000000 --- a/src/htmx/HtmxObject.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Created by lukas on 5/11/25. -// - -#ifndef HTMXOBJECT_H -#define HTMXOBJECT_H - -#include - -class HtmxObject { -public: - - HtmxObject() = default; - - /** - * Get the HTMX representation of the object as a string - * @return htmx string - */ - const std::string& htmx() const; - -protected: - std::string html; -}; - - - -#endif //HTMXOBJECT_H diff --git a/src/htmx/HtmxTable.cpp b/src/htmx/HtmxTable.cpp deleted file mode 100644 index b5e87bc..0000000 --- a/src/htmx/HtmxTable.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// -// Created by lukas on 5/11/25. -// - -#include -#include "HtmxTable.h" - -using namespace std; - -void HtmxTable::add_row(const HtmxTableRow& row){ - html += row.htmx(); -} - -void HtmxTable::complete() { - html += ""; -} - diff --git a/src/htmx/HtmxTable.h b/src/htmx/HtmxTable.h deleted file mode 100644 index 9a57e8d..0000000 --- a/src/htmx/HtmxTable.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by lukas on 5/11/25. -// - -#ifndef HTMXTABLE_H -#define HTMXTABLE_H - -#include "HtmxObject.h" -#include "HtmxTableRow.h" -#include "format" - -class HtmxTable : public HtmxObject { - -public: - template - requires std::convertible_to, std::string_view> - HtmxTable(const R& strings) { - // define the table header - html = ""; - for (const auto& s : strings) { - html += format("", s); - } - html += ""; - } - - /** - * Add a htmx row to the table - * - * @param row - * @return htmx representation of the row - */ - void add_row(const HtmxTableRow& row); - - /** - * Complete the table - */ - void complete(); - -}; -#endif //HTMXTABLE_H diff --git a/src/htmx/HtmxTableRow.cpp b/src/htmx/HtmxTableRow.cpp deleted file mode 100644 index 5b479f2..0000000 --- a/src/htmx/HtmxTableRow.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// Created by lukas on 5/11/25. -// -#include -#include "HtmxTableRow.h" - -using namespace std; - -void HtmxTableRow::add_button(string_view endpoint, string_view name, string_view text) { - html += format("", endpoint, name, text); -} - -static string_view get_button_class(bool is_active) { - return is_active ? "active-button" : "inactive-button"; -} - -void HtmxTableRow::add_status_box(std::string_view name, bool is_active) { - html += format("", get_button_class(is_active), name); -} - -void HtmxTableRow::add(string_view text) { - html += format("", text); -} - -void HtmxTableRow::complete() { - html += ""; -} \ No newline at end of file diff --git a/src/htmx/HtmxTableRow.h b/src/htmx/HtmxTableRow.h deleted file mode 100644 index a95c2f3..0000000 --- a/src/htmx/HtmxTableRow.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by lukas on 5/11/25. -// - -#ifndef HTMXTABLEROW_H -#define HTMXTABLEROW_H - -#include -#include "HtmxObject.h" - -class HtmxTableRow : public HtmxObject { - -public: - void add_status_box(std::string_view name, bool is_active); - - void add_button(std::string_view endpoint, std::string_view name, std::string_view text); - - void add(std::string_view text); - - /** - * Complete the row - */ - void complete(); -}; - - - -#endif //HTMXTABLEROW_H diff --git a/src/htmx_helper.cpp b/src/htmx_helper.cpp deleted file mode 100644 index 0dc3144..0000000 --- a/src/htmx_helper.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// Created by lukas on 5/11/25. -// -#include -#include "systemd.h" -#include "htmx_helper.h" -#include "json_settings.h" -#include "utils.hpp" - -using namespace std; - -HtmxTableRow create_service_table_row(string_view service_name, string_view service_id) { - HtmxTableRow row; - - const bool isRunning = systemd::is_service_active(service_id); - const bool isEnabled = systemd::is_service_active(service_id); - - const auto running = isRunning ? "Running" : "Stopped"; - const auto enabled = isEnabled ? "Enabled" : "Disabled"; - - row.add(service_name); - row.add_status_box(running, isRunning); - row.add_status_box(enabled, isEnabled); - - // create buttons - row.add_button("/toggle-service", service_name, isRunning ? "Stop" : "Start"); - row.add_button("/restart-service", service_name, "Restart"); - row.complete(); - - return row; -} - -HtmxTableRow create_error_table_row(string_view error) { - HtmxTableRow row; - row.add("Error"); - row.add(error); - return row; -} - -HtmxTable create_service_table() { - constexpr array cols = { - "Service", "Status", "State" - }; - - HtmxTable table(cols); - - auto settings = AppSettings::loadAppSettings(); - - if(settings.has_value()){ - AppSettings& data = settings.value(); - for (auto& service : data.services) { - table.add_row(create_service_table_row(service.name, service.service)); - } - } - else { - table.add_row(create_error_table_row(settings.error())); - } - - return table; -} \ No newline at end of file diff --git a/src/htmx_helper.h b/src/htmx_helper.h deleted file mode 100644 index 7e7807b..0000000 --- a/src/htmx_helper.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by lukas on 5/11/25. -// - -#ifndef HTMX_HELPER_H -#define HTMX_HELPER_H - -#include -#include "htmx/HtmxTable.h" - -HtmxTableRow create_service_table_row(std::string_view service_name, std::string_view service_id); -HtmxTableRow create_error_table_row(std::string_view error); -HtmxTable create_service_table(); - -#endif //HTMX_HELPER_H diff --git a/src/login/Session.cpp b/src/login/Session.cpp new file mode 100644 index 0000000..aeb4829 --- /dev/null +++ b/src/login/Session.cpp @@ -0,0 +1,22 @@ +#include "Session.hpp" +#include + +using namespace login; + +Session::Session(int userId) +: m_userId(userId) +, m_expiresAt(std::chrono::steady_clock::now() + SESSION_LIFETIME) +{ +} + +void Session::extend(){ + m_expiresAt = std::chrono::steady_clock::now() + SESSION_LIFETIME; +} + +void Session::extend(std::chrono::time_point now){ + m_expiresAt = now + SESSION_LIFETIME; +} + +bool Session::isExpired(std::chrono::time_point now){ + return now > m_expiresAt; +} \ No newline at end of file diff --git a/src/login/Session.hpp b/src/login/Session.hpp new file mode 100644 index 0000000..11b563a --- /dev/null +++ b/src/login/Session.hpp @@ -0,0 +1,31 @@ +#ifndef __SESSION_H__ +#define __SESSION_H__ + +#include + +namespace login { + +class Session { + +public: + static constexpr auto SESSION_LIFETIME = std::chrono::minutes(30); + + Session(int userId); + + // extend the session lifetime + void extend(); + void extend(std::chrono::time_point now); + + bool isExpired(std::chrono::time_point now); + + const int userId() { return m_userId; } + const std::chrono::steady_clock::time_point expiresAt() {return m_expiresAt;} + +private: + const int m_userId; + std::chrono::steady_clock::time_point m_expiresAt; +}; + +} + +#endif // __SESSION_H__ \ No newline at end of file diff --git a/src/login/SessionHandler.cpp b/src/login/SessionHandler.cpp new file mode 100644 index 0000000..2659624 --- /dev/null +++ b/src/login/SessionHandler.cpp @@ -0,0 +1,75 @@ +#include "SessionHandler.hpp" +#include +#include +#include "crow/logging.h" + +using namespace login; + +// generate random string +std::string randomString(size_t length) { + static const char charset[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::string result; + result.resize(length); + std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution<> dist(0, sizeof(charset)-2); + for (size_t i = 0; i < length; ++i) result[i] = charset[dist(rng)]; + return result; +} + +SessionHandler::SessionHandler() +: cleanupThread(std::thread(&SessionHandler::cleanupWorker, this)) +, stopCleanupThread(false) +{ +} + +SessionHandler::~SessionHandler() { + stopCleanupThread = true; + if (cleanupThread.joinable()) + cleanupThread.join(); +} + +void SessionHandler::cleanupWorker(){ + while (!stopCleanupThread) { + std::this_thread::sleep_for(std::chrono::minutes(5)); + auto now = std::chrono::steady_clock::now(); + { + std::lock_guard lock(sessionMutex); + for (auto it = sessions.begin(); it != sessions.end();) { + if (it->second.isExpired(now)){ + CROW_LOG_INFO << "Session " << it->first << " expired for userId: " << it->second.userId(); + it = sessions.erase(it); + } + else { + ++it; + } + } + } + } +} + +std::optional SessionHandler::createSession(int userId){ + std::string sessionId = randomString(32); + std::lock_guard lock(sessionMutex); + if (!sessions.emplace(sessionId, Session(userId)).second){ + return {}; + } + return sessionId; +} + +std::optional SessionHandler::isSessionValid(const std::string& sessionId){ + auto now = std::chrono::steady_clock::now(); + std::lock_guard lock(sessionMutex); + auto it = sessions.find(sessionId); + if(it != sessions.end()){ + if (it->second.isExpired(now)){ + CROW_LOG_INFO << "Session " << it->first << " expired for userId: " << it->second.userId(); + it = sessions.erase(it); + return {}; + } + return it->second.userId(); + } + return {}; +} + + diff --git a/src/login/SessionHandler.hpp b/src/login/SessionHandler.hpp new file mode 100644 index 0000000..27de5ba --- /dev/null +++ b/src/login/SessionHandler.hpp @@ -0,0 +1,33 @@ +#ifndef __SESSIONHANDLER_H__ +#define __SESSIONHANDLER_H__ + +#include "Session.hpp" +#include +#include +#include +#include + +namespace login { + +class SessionHandler { + +public: + SessionHandler(); + ~SessionHandler(); + + std::optional createSession(int userId); + + // return the user id if the user is logged in + std::optional isSessionValid(const std::string& sessionId); +private: + void cleanupWorker(); + std::thread cleanupThread; + std::atomic stopCleanupThread; + std::mutex sessionMutex; + + std::unordered_map sessions; +}; + +} + +#endif // __SESSIONHANDLER_H__ \ No newline at end of file diff --git a/src/login/login.cpp b/src/login/login.cpp index bc9f29d..1d67b37 100644 --- a/src/login/login.cpp +++ b/src/login/login.cpp @@ -1,20 +1,13 @@ +#include #include "login.hpp" -#include "sodium.h" +#include "crow/http_response.h" #include "databasepool.h" #include "utils.hpp" -#include +#include "SessionHandler.hpp" namespace login { -std::unordered_map sessions; - -bool is_logged_in(const crow::request& req) { - std::string session_id = get_session_id(req); - if (session_id.empty()) return false; - auto it = sessions.find(session_id); - if (it == sessions.end()) return false; - return !it->second.user_id.empty(); -} +SessionHandler sessionHandler; std::string hashPassword(const std::string& password) { @@ -29,38 +22,18 @@ std::string hashPassword(const std::string& password) crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE ) != 0) { - std::cerr << "Out of memory while hashing password!" << std::endl; + CROW_LOG_ERROR << "Out of memory while hashing password!"; return ""; } return hash; } -std::optional createUser(const std::string& username, const std::string& password){ - auto db = Database(); - if (!db.open()) - return false; - - std::string password_hash = hashPassword(password); - if(password_hash.empty()) - return false; - - std::string insert_sql = - "INSERT INTO users (username, password_hash) VALUES ('" - + username + "', '" + password_hash + "');"; - - return db.insert(insert_sql); -} - bool verifyHashWithPassword(const std::string& hash, std::string const& password) { - if (crypto_pwhash_str_verify(hash.c_str(), password.c_str(), password.size()) == 0) { - return true; - } else { - return false; - } + return crypto_pwhash_str_verify(hash.c_str(), password.c_str(), password.size()) == 0; } -std::string get_session_id(const crow::request& req) { +std::string getSessionId(const crow::request& req) { auto cookie_header = req.get_header_value("Cookie"); std::string prefix = "session_id="; auto pos = cookie_header.find(prefix); @@ -68,124 +41,65 @@ std::string get_session_id(const crow::request& req) { return cookie_header.substr(pos + prefix.size(), 32); } -// Utility: generate random string -std::string random_string(size_t length) { - static const char charset[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - std::string result; - result.resize(length); - std::mt19937 rng(std::random_device{}()); - std::uniform_int_distribution<> dist(0, sizeof(charset)-2); - for (size_t i = 0; i < length; ++i) result[i] = charset[dist(rng)]; - return result; +bool isLoggedIn(const crow::request& req) { + std::string sessionId = getSessionId(req); + if (sessionId.empty()) + return false; + auto userId = sessionHandler.isSessionValid(sessionId); + return userId.has_value(); } -std::optional loginUser(const std::string& username, const std::string& password) +std::optional loginUser(const std::string& username, const std::string& password) { - auto sql = "SELECT id, password_hash FROM users WHERE username = ? LIMIT 1;"; - auto db = Database(); - if (!db.open()){ - return {}; - } - - auto opt_pair = db.get(sql, {username}); - if (opt_pair.has_value()) { - if (verifyHashWithPassword(opt_pair.value().second, password)) + auto user = getUser(username); + if (user.has_value()) { + if (verifyHashWithPassword(user.value().password_hash, password)) { - return opt_pair.value().first; + return sessionHandler.createSession(user.value().id); } } return {}; } -bool initDB() -{ - auto db = Database(); - - if (!db.open()){ - return false; - } - - // Create a tables - const char* create_sql_chars = "CREATE TABLE IF NOT EXISTS users (" - "id INTEGER PRIMARY KEY," - "username TEXT NOT NULL," - "password_hash TEXT NOT NULL," - "created_at DATETIME DEFAULT CURRENT_TIMESTAMP);"; - - if (!db.exec(create_sql_chars)){ - CROW_LOG_ERROR << "Failed to create users table"; - return false; - } - - return true; -} - bool initLogin(crow::SimpleApp& app) { if (sodium_init() < 0) { CROW_LOG_ERROR << "Failed to Init Sodium"; return false; } - if(!initDB()) - { - return false; - } // createUser("lukas", "Trollar4928"); - - CROW_ROUTE(app, "/login").methods("GET"_method) - ([](const crow::request& req){ - std::string csrf = random_string(32); - // store CSRF in a temporary session cookie - std::string session_id = random_string(32); - sessions[session_id] = Session{"", csrf}; - - crow::response res; - res.body = "
" - "" - "" - "" - ""; - res.add_header("Set-Cookie", "session_id=" + session_id + "; HttpOnly; Secure; SameSite=Strict"); - return res; - }); CROW_ROUTE(app, "/login").methods("POST"_method) - ([](const crow::request& req){ - auto cookie_it = req.get_header_value("Cookie").find("session_id="); - if (cookie_it == std::string::npos) - return crow::response(401, "No session"); - - // extract session_id - std::string session_id = req.get_header_value("Cookie").substr(cookie_it + 11, 32); - auto it = sessions.find(session_id); - if (it == sessions.end()) return crow::response(401, "Invalid session"); - - auto session = it->second; - - // parse form + ([](const crow::request& req) { auto body = utils::parseBody(req.body); - if (body.empty()) - return crow::response(400); + if (!body.empty()) + return crow::response(400, "Invalid JSON"); - std::string csrf_token = body.at("csrf_token"); - std::string username = body.at("username"); - std::string password = body.at("password"); + auto usenameIt = body.find("username"); + auto passwordIt = body.find("password"); + if(usenameIt == body.end() || passwordIt == body.end()) + return crow::response(400, "No username or password in body"); - if (csrf_token != session.csrf_token) return crow::response(403, "CSRF failed"); + const std::string& username = usenameIt->second; + const std::string& password = passwordIt->second; - std::optional userId = loginUser(username, password); - - if (!userId.has_value()) { + // Validate credentials + auto sessionId = loginUser(username, password); + if(!sessionId.has_value()) return crow::response(401, "Invalid credentials"); - } - - // set user id - sessions[session_id].user_id = std::to_string(userId.value()); + // Set cookie crow::response res; - res.add_header("HX-Redirect", "/templates/dashboard.html"); // htmx redirect + res.code = 200; + res.set_header( + "Set-Cookie", + "session_id=" + sessionId.value() + + "; HttpOnly; Path=/; SameSite=Strict" + // add "; Secure" when using HTTPS + ); + + res.body = "Logged in"; return res; }); diff --git a/src/login/login.hpp b/src/login/login.hpp index 194fe4a..203248b 100644 --- a/src/login/login.hpp +++ b/src/login/login.hpp @@ -9,29 +9,15 @@ namespace login { -struct Session { - std::string user_id; - std::string csrf_token; -}; - bool initLogin(crow::SimpleApp& app); -bool is_logged_in(const crow::request& req); - -std::string get_session_id(const crow::request& req); +bool isLoggedIn(const crow::request& req); // lambda to be used by endpoint that requiere login inline auto login_required = [](auto handler){ return [handler](const crow::request& req){ - if (!is_logged_in(req)) { - crow::response res; - if (req.get_header_value("HX-Request") == "true") - res = crow::response(401, "Login required"); - else { - res = crow::response(302); - res.add_header("Location", "/login"); - } - return res; + if (!isLoggedIn(req)) { + return crow::response(401, "Login required"); } return handler(req); }; diff --git a/src/login/loginDb.cpp b/src/login/loginDb.cpp new file mode 100644 index 0000000..f910ef8 --- /dev/null +++ b/src/login/loginDb.cpp @@ -0,0 +1,38 @@ +#include "loginDb.hpp" +#include "databasepool.h" +#include + +using namespace sqlite_orm; + +namespace login { + +int createUser(const std::string& username, const std::string& password_hash){ + if (username.empty() || password_hash.empty()) + return -1; + + int64_t id; + auto db = dbpool.acquire(); + + auto user = db->get_optional( + where(c(&User::username) == username) + ); + + if (user.has_value()) { + id = user.value().id; + } else { + auto c = newUser(username, password_hash); + id = db->insert(c); + } + dbpool.release(db); + return id; +} + +std::optional getUser(const std::string& username){ + auto db = dbpool.acquire(); + auto user = db->get_optional( + where(c(&User::username) == username) + ); + dbpool.release(db); + return user; +} +} \ No newline at end of file diff --git a/src/login/loginDb.hpp b/src/login/loginDb.hpp new file mode 100644 index 0000000..804a9bd --- /dev/null +++ b/src/login/loginDb.hpp @@ -0,0 +1,34 @@ +#ifndef __LOGINDB_H__ +#define __LOGINDB_H__ + +#include +#include +#include +#include +#include "json.hpp" +#include "utils.hpp" +#include "sqlite_orm.h" +#include "magic_enum.hpp" + +namespace login +{ + +struct User { + int id; + std::string username; + std::string password_hash; + std::string last_login; + std::string created_at; // SQLite stores DATETIME as TEXT +}; + +inline User newUser(const std::string& username, std::string password_hash){ + return User {-1, username, password_hash, utils::currentTime(), utils::currentTime()}; +} + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, id, username, password_hash, last_login, created_at) + +int createUser(const std::string& username, const std::string& password_hash); +std::optional getUser(const std::string& username); + +} +#endif // __LOGINDB_H__ \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0b7c212..84daf50 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,6 @@ #include #include #include "json_settings.h" -#include "htmx_helper.h" #include "systemd.h" #include "utils.hpp" #include "login.hpp" diff --git a/src/shadowrun/HtmxShAttributeList.cpp b/src/shadowrun/HtmxShAttributeList.cpp deleted file mode 100644 index 07847a9..0000000 --- a/src/shadowrun/HtmxShAttributeList.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include "HtmxShAttributeList.hpp" -#include "utils.hpp" - -using namespace std; - -HtmxShAttributeList::HtmxShAttributeList(const std::string& id, const vector& itemList, std::map& data){ - html += "
"; - html += format("

{}

", id); - html += "
"; - for (auto& item : itemList){ - string item_id = utils::to_id_format(format("{}_{}", id, item)); - auto value = data.contains(item_id) ? data[item_id] : ""; - html += format("", item, item_id, value); - } - html += "
"; -} - -HtmxShAttributeList::HtmxShAttributeList(const std::string& id, const std::vector>& itemValueList){ - html += format("

{}

", id); - html += "
"; - for (auto& item : itemValueList){ - string item_id = utils::to_id_format(format("{}_{}", id, item)); - html += format("", item, item_id, item.second); - } - html += "
"; -} - -void HtmxShAttributeList::genIds(std::vector& vec, const std::string& id, const std::vector& itemList) -{ - for (auto& item : itemList){ - vec.push_back(utils::to_id_format(format("{}_{}", id, item))); - } -} \ No newline at end of file diff --git a/src/shadowrun/HtmxShAttributeList.hpp b/src/shadowrun/HtmxShAttributeList.hpp deleted file mode 100644 index effb473..0000000 --- a/src/shadowrun/HtmxShAttributeList.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef HTMXSHATTRIBUTELIST_H -#define HTMXSHATTRIBUTELIST_H - -#include "HtmxObject.h" -#include -#include -#include - -class HtmxShAttributeList : public HtmxObject { - -public: - // create new item list - HtmxShAttributeList(const std::string& id, const std::vector& itemList, std::map& data); - - // create new item list where each item as a value - HtmxShAttributeList(const std::string& id, const std::vector>& itemValueList); - - static void genIds(std::vector& vec, const std::string& id, const std::vector& itemList); -}; - -#endif // HTMXSHATTRIBUTELIST_H \ No newline at end of file diff --git a/src/shadowrun/HtmxShCondition.cpp b/src/shadowrun/HtmxShCondition.cpp deleted file mode 100644 index e620fdc..0000000 --- a/src/shadowrun/HtmxShCondition.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "HtmxShCondition.hpp" -#include "../utils.hpp" -#include - -using namespace std; - -HtmxShCondition::HtmxShCondition(std::string id, size_t nbrOfBoxes, std::map& data) -{ - html += "
"; - html += format("

{}

", id); - html += "
"; - - int con_value = -1; - - for (size_t i = 0; i < nbrOfBoxes; i++) - { - string item_id = utils::to_id_format(format("Checkbox_{}_{}",id, i)); - auto value = data.contains(item_id) && data[item_id] == "1" ? "checked" : ""; - - html += format("", item_id, value); - - if ( ((i + 1) % 3 == 0) && (i != 0) ) - { - html += format("
{}
", con_value); - con_value--; - } - - } - html += "
"; -} - -void HtmxShCondition::genIds(std::vector& vec, std::string id, size_t nbrOfBoxes) -{ - for (int i = 0; i < nbrOfBoxes; i++){ - vec.push_back(utils::to_id_format(format("Checkbox_{}_{}",id, i))); - } -} diff --git a/src/shadowrun/HtmxShCondition.hpp b/src/shadowrun/HtmxShCondition.hpp deleted file mode 100644 index fe5c8cc..0000000 --- a/src/shadowrun/HtmxShCondition.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef __HTMXSHCONDITION_H__ -#define __HTMXSHCONDITION_H__ - -#include -#include -#include -#include "HtmxObject.h" - -class HtmxShCondition : public HtmxObject { - -public: - HtmxShCondition(std::string id, size_t nbrOfBoxes, std::map& data); - - static void genIds(std::vector& vec, std::string id, size_t nbrOfBoxes); -}; - -#endif // __HTMXSHCONDITION_H__ \ No newline at end of file diff --git a/src/shadowrun/HtmxShItemList.cpp b/src/shadowrun/HtmxShItemList.cpp deleted file mode 100644 index 5ff2f2d..0000000 --- a/src/shadowrun/HtmxShItemList.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include "HtmxShItemList.hpp" -#include "../utils.hpp" - -using namespace std; - -HtmxShItemList::HtmxShItemList(const std::string& id, const std::vector& columns, size_t size, std::map& data){ - html += "
"; - html += format("

{}

", id); - html += format("
", columns.size()); - for (size_t i = 0; i < size; i++){ - for (auto& col : columns){ - string item_id = utils::to_id_format(format("{}_{}_{}", id, i, col)); - auto value = data.contains(item_id) ? data[item_id] : ""; - html += format("", item_id, col, value); - } - } - - html += "
"; -} - -/* -HtmxShItemList::HtmxShItemList(const std::string& id, const std::vector>& itemValueList){ - html += format("

{}

", id); - html += "
"; - for (auto& item : itemValueList){ - string item_id = utils::to_id_format(id + "_" + item.first); - html += format("", item, item_id, item.second); - } - html += "
"; - - html += "
"; - html += format("

{}

", id); - html += format("
", columns.size()); - for (size_t i = 0; i < size; i++){ - for (auto& col : columns){ - string item_id = utils::to_id_format(format("{}_{}_{}", id, i, col)); - html += format("", item_id, col); - } - } - - html += "
"; -} -*/ - -void HtmxShItemList::genIds(std::vector& vec, const std::string& id, const std::vector& columns, size_t size) -{ - for (size_t i = 0; i < size; i++){ - for (auto& col : columns){ - vec.push_back(utils::to_id_format(format("{}_{}_{}", id, i, col))); - } - } -} diff --git a/src/shadowrun/HtmxShItemList.hpp b/src/shadowrun/HtmxShItemList.hpp deleted file mode 100644 index 186f0fd..0000000 --- a/src/shadowrun/HtmxShItemList.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef HTMXSHITEMLIST_H -#define HTMXSHITEMLIST_H - -#include "HtmxObject.h" -#include -#include -#include - -class HtmxShItemList : public HtmxObject { - -public: - // create new item list, - HtmxShItemList(const std::string& id, const std::vector& columns, size_t size, std::map& data); - - // create new item list where each item as a value - HtmxShItemList(const std::string& id, const std::vector>& itemValueList); - - static void genIds(std::vector& vec, const std::string& id, const std::vector& columns, size_t size); -}; - -#endif // HTMXSHITEMLIST_H \ No newline at end of file diff --git a/src/shadowrun/ShadowrunCharacterForm.cpp b/src/shadowrun/ShadowrunCharacterForm.cpp deleted file mode 100644 index 0748928..0000000 --- a/src/shadowrun/ShadowrunCharacterForm.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include -#include -#include -#include "HtmxShItemList.hpp" -#include "HtmxShAttributeList.hpp" -#include "HtmxShCondition.hpp" -#include "ShadowrunCharacterForm.hpp" - -using namespace std; - -static const vector cCharacterInfo = { - "Name", - "Metatype", - "Age", - "Sex", - "Nuyen", - "Lifestyle", - "Total Karma", - "C. Karma", - "Street Cred", - "Notoriety", - "Fame" -}; - -static const vector cAttributes = { - "Body", - "Agility", - "Reaction", - "Strength", - "Charisma", - "Intuition", - "Logic", - "Willpower", - "Edge", - "Essence", - "Initiative" -}; - -static const vector cSkillParameters = { - "Name", - "RTG.", - "ATT.", -}; - -static const vector cContactsParameters = { - "Name", - "Loyalty", - "Conection" -}; - -static const vector cRangedWeaponsParameters = { - "Weapon", - "Damage", - "AP", - "Mode", - "RC", - "Ammo", - "Availability" -}; - -static const vector cImplantParameters = { - "Implant", - "Rating", - "Essence", - "Notes", -}; - -static const vector cMeleeWeaponParameters = { - "Weapon", - "Reach", - "Damage", - "AP", -}; - -static const vector cArmorParamters = { - "Armor", - "Ballistic", - "Impact", - "Notes", -}; - -static const vector genCheckboxIds(){ - vector vec; - HtmxShCondition::genIds(vec, "Physical Condition", 18); - HtmxShCondition::genIds(vec, "Stun Condition", 12); - return vec; -} - -static const vector genFormIds(){ - vector vec; - vec.reserve(200); - - // OBS make sure to update both here and in ShadowrunCharacterForm() - HtmxShAttributeList::genIds(vec, "Character Info", cCharacterInfo); - HtmxShAttributeList::genIds(vec, "Attributes", cAttributes); - HtmxShItemList::genIds(vec, "Active Skills", cSkillParameters, 8); - HtmxShItemList::genIds(vec, "Knowledge Skills", cSkillParameters, 8); - vec.push_back("positive_qualities"); - vec.push_back("negative_qualities"); - vec.push_back("datapack_notes"); - - auto v = genCheckboxIds(); - vec.insert(vec.end(), v.begin(), v.end()); - - HtmxShCondition::genIds(vec, "Physical Condition", 18); - HtmxShCondition::genIds(vec, "Stun Condition", 12); - HtmxShItemList::genIds(vec, "Contacts", cContactsParameters, 6); - HtmxShItemList::genIds(vec, "Ranged Weapons", cRangedWeaponsParameters, 7); - HtmxShItemList::genIds(vec, "Cyberware and Bioware", cImplantParameters, 18); - HtmxShItemList::genIds(vec, "Melee Weapons", cMeleeWeaponParameters, 7); - HtmxShItemList::genIds(vec, "Armor", cArmorParamters , 3); - vec.push_back("notes"); - - return vec; -} - -const std::vector ShadowrunCharacterForm::m_formIds = genFormIds(); -const std::vector ShadowrunCharacterForm::m_checkboxIds = genCheckboxIds(); - -ShadowrunCharacterForm::ShadowrunCharacterForm(std::map& data) { - html.reserve(30000); - html += "
"; - html += "
"; - - html += HtmxShAttributeList("Character Info", cCharacterInfo, data).htmx(); - html += HtmxShAttributeList("Attributes", cAttributes, data).htmx(); - html += HtmxShItemList("Active Skills", cSkillParameters, 8, data).htmx(); - html += HtmxShItemList("Knowledge Skills", cSkillParameters, 8, data).htmx(); - - auto valuePos = data.contains("positive_qualities") ? data["positive_qualities"] : ""; - auto valueNeg = data.contains("negative_qualities") ? data["negative_qualities"] : ""; - - // add Qualities - html += format("
" - "

Qualities

" - "" - "" - "
", valuePos, valueNeg); - - auto valueNotes = data.contains("datapack_notes") ? data["datapack_notes"] : ""; - // add datapack notes - html += format("
" - "

Datajack / Commlink / Cyberdeck / Notes

" - "" - "
", valueNotes); - - html += HtmxShCondition("Physical Condition", 18, data).htmx(); - html += HtmxShCondition("Stun Condition", 12, data).htmx(); - html += HtmxShItemList("Contacts", cContactsParameters, 6, data).htmx(); - html += HtmxShItemList("Ranged Weapons", cRangedWeaponsParameters, 7, data).htmx(); - html += HtmxShItemList("Cyberware and Bioware", cImplantParameters, 18, data).htmx(); - html += HtmxShItemList("Melee Weapons", cMeleeWeaponParameters, 7, data).htmx(); - html += HtmxShItemList("Armor", cArmorParamters , 3, data).htmx(); - html += "
"; - - valueNotes = data.contains("notes") ? data["notes"] : ""; - html += format("
" - "
", valueNotes); - - - html += "
" - "" - "
" - ""; -} \ No newline at end of file diff --git a/src/shadowrun/ShadowrunCharacterForm.hpp b/src/shadowrun/ShadowrunCharacterForm.hpp deleted file mode 100644 index fdc4473..0000000 --- a/src/shadowrun/ShadowrunCharacterForm.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef SHADOWRUN_CHARACTER_FORM_HPP -#define SHADOWRUN_CHARACTER_FORM_HPP - -#include "htmx/HtmxObject.h" -#include -#include -#include -class ShadowrunCharacterForm : public HtmxObject { - -public: - ShadowrunCharacterForm(std::map& data); - - static const std::vector m_formIds; - static const std::vector m_checkboxIds; -}; - -#endif // SHADOWRUN_CHARACTER_FORM_HPP \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp index aa4146c..4b11be9 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -35,7 +35,6 @@ map parseBody(const string& body) } start = nextPos + 1; } - return data; } @@ -134,7 +133,6 @@ std::string urlDecode(const std::string& str) { return decoded.str(); } - string currentTime(){ auto now = std::chrono::system_clock::now(); std::time_t t = std::chrono::system_clock::to_time_t(now);
{}
\ - \ - {} \ - \ - {}{}