Possible to load basic data from the database

This commit is contained in:
Lukas Forsberg 2025-06-02 22:44:53 +02:00
parent 69f7f625f8
commit 397189c259
15 changed files with 426 additions and 58 deletions

99
.vscode/settings.json vendored
View File

@ -1,6 +1,103 @@
{
"files.associations": {
"text_encoding": "cpp",
"thread": "cpp"
"thread": "cpp",
"deque": "cpp",
"string": "cpp",
"vector": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"hash_map": "cpp",
"strstream": "cpp",
"barrier": "cpp",
"bit": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"cfenv": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"cinttypes": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"coroutine": "cpp",
"csetjmp": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cuchar": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"exception": "cpp",
"expected": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"flat_map": "cpp",
"flat_set": "cpp",
"format": "cpp",
"fstream": "cpp",
"future": "cpp",
"generator": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"latch": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"print": "cpp",
"queue": "cpp",
"ranges": "cpp",
"scoped_allocator": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"spanstream": "cpp",
"sstream": "cpp",
"stack": "cpp",
"stacktrace": "cpp",
"stdexcept": "cpp",
"stdfloat": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"syncstream": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp"
}
}

View File

@ -67,7 +67,8 @@ add_executable(${TARGET_NAME}
src/shadowrun/ShadowrunCharacterForm.cpp
src/shadowrun/ShadowrunApi.cpp
src/shadowrun/ShadowrunApi.hpp
src/shadowrun/ShadowrunDb.cpp
src/shadowrun/ShadowrunDb.hpp
)

View File

@ -1,6 +1,8 @@
#include "crow.h"
#include "database.hpp"
using namespace std;
Database::Database() :
m_db(nullptr)
{}
@ -9,6 +11,15 @@ Database::~Database() {
sqlite3_close(m_db);
}
sqlite3_stmt* Database::prepareStmt(const string& sql){
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
CROW_LOG_ERROR << "Failed to prepare statement: " << sqlite3_errmsg(m_db);
return nullptr;
}
return stmt;
}
bool Database::exec(const char* sqlQuery) {
char* errmsg = nullptr;
int rc = sqlite3_exec(m_db, sqlQuery, nullptr, nullptr, &errmsg);
@ -20,13 +31,72 @@ bool Database::exec(const char* sqlQuery) {
return true;
}
bool Database::exec(const std::string& sqlQuery)
{
exec(sqlQuery.c_str());
bool Database::exec(const std::string& sqlQuery){
return exec(sqlQuery.c_str());
}
map<string, string> Database::getStrMap(const string& sql){
sqlite3_stmt* stmt = prepareStmt(sql);
map<string, string> map;
if (stmt == nullptr)
return map;
while (sqlite3_step(stmt) == SQLITE_ROW) {
string key = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
string value = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
map[key] = value;
}
sqlite3_finalize(stmt);
return map;
}
set<string> Database::getStrSet(const string& sql){
sqlite3_stmt* stmt = prepareStmt(sql);
set<string> vec;
if (stmt == nullptr)
return vec;
while (sqlite3_step(stmt) == SQLITE_ROW) {
string s = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
vec.insert(s);
}
sqlite3_finalize(stmt);
return vec;
}
std::optional<int64_t> Database::getInt(const char* sql) {
sqlite3_stmt* stmt = prepareStmt(sql);
if (stmt == nullptr)
return {};
std::optional<int64_t> id;
if (sqlite3_step(stmt) == SQLITE_ROW) {
id = sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
return id;
}
std::optional<int64_t> Database::insert(const char* sql) {
sqlite3_stmt* stmt = prepareStmt(sql);
if (stmt == nullptr)
return {};
if (sqlite3_step(stmt) != SQLITE_DONE) {
CROW_LOG_ERROR << "Insert failed: " << sqlite3_errmsg(m_db);
sqlite3_finalize(stmt);
return {};
}
sqlite3_finalize(stmt);
return sqlite3_last_insert_rowid(m_db);
}
bool Database::open(){
int rc = sqlite3_open("example.db", &m_db);
int rc = sqlite3_open("app.db", &m_db);
if (rc) {
CROW_LOG_ERROR << "Can't open database: " << sqlite3_errmsg(m_db);
return false;

View File

@ -3,7 +3,8 @@
#include "sqlite3.h"
#include <string>
#include <optional>
#include <set>
class Database {
public:
@ -14,7 +15,18 @@ public:
bool exec(const char* sqlQuery);
bool exec(const std::string& sqlQuery);
/// returns true if the sql statment returns at least one row
std::optional<int64_t> getInt(const char* sql);
std::optional<int64_t> insert(const char* sql);
std::set<std::string> getStrSet(const std::string& sql);
std::map<std::string, std::string> Database::getStrMap(const std::string& sql);
private:
sqlite3_stmt* prepareStmt(const std::string& sql);
sqlite3* m_db;
};

View File

@ -8,7 +8,7 @@ HtmxShAttributeList::HtmxShAttributeList(const std::string& id, const vector<str
html += format("<h2>{}</h2>", id);
html += "<div class='grid'>";
for (auto& item : itemList){
string item_id = utils::to_id_format(id + "_" + item);
string item_id = utils::to_id_format(format("{}_{}", id, item));
html += format("<label>{}:<input type='text' name='{}'></label>", item, item_id);
}
@ -19,8 +19,15 @@ HtmxShAttributeList::HtmxShAttributeList(const std::string& id, const std::vecto
html += format("<h2>{}</h2>", id);
html += "<div class='grid'>";
for (auto& item : itemValueList){
string item_id = utils::to_id_format(id + "_" + item.first);
string item_id = utils::to_id_format(format("{}_{}", id, item));
html += format("<label>{}:<input type='text' name='{}' value='{}'></label>", item, item_id, item.second);
}
html += "</div>";
}
void HtmxShAttributeList::genIds(std::vector<std::string>& vec, const std::string& id, const std::vector<std::string>& itemList)
{
for (auto& item : itemList){
vec.push_back(utils::to_id_format(format("{}_{}", id, item)));
}
}

View File

@ -13,6 +13,8 @@ public:
// create new item list where each item as a value
HtmxShAttributeList(const std::string& id, const std::vector<std::pair<std::string, std::string>>& itemValueList);
static void genIds(std::vector<std::string>& vec, const std::string& id, const std::vector<std::string>& itemList);
};
#endif // HTMXSHATTRIBUTELIST_H

View File

@ -26,3 +26,10 @@ HtmxShCondition::HtmxShCondition(std::string id, size_t nbrOfBoxes)
}
html += "</div></div>";
}
void HtmxShCondition::genIds(std::vector<std::string>& vec, std::string id, size_t nbrOfBoxes)
{
for (int i = 0; i < nbrOfBoxes; i++){
vec.push_back(utils::to_id_format(format("{}_{}",id, i)));
}
}

View File

@ -2,12 +2,15 @@
#define __HTMXSHCONDITION_H__
#include <string>
#include <vector>
#include "HtmxObject.h"
class HtmxShCondition : public HtmxObject {
public:
HtmxShCondition(std::string id, size_t nbrOfBoxes);
static void genIds(std::vector<std::string>& vec, std::string id, size_t nbrOfBoxes);
};
#endif // __HTMXSHCONDITION_H__

View File

@ -40,4 +40,13 @@ HtmxShItemList::HtmxShItemList(const std::string& id, const std::vector<std::pai
html += "</div></div>";
}
*/
*/
void HtmxShItemList::genIds(std::vector<std::string>& vec, const std::string& id, const std::vector<std::string>& 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)));
}
}
}

View File

@ -13,6 +13,8 @@ public:
// create new item list where each item as a value
HtmxShItemList(const std::string& id, const std::vector<std::pair<std::string, std::string>>& itemValueList);
static void genIds(std::vector<std::string>& vec, const std::string& id, const std::vector<std::string>& columns, size_t size);
};
#endif // HTMXSHITEMLIST_H

View File

@ -2,66 +2,71 @@
#include "ShadowrunApi.hpp"
#include "ShadowrunCharacterForm.hpp"
#include "database.hpp"
#include "ShadowrunDb.hpp"
#include <format>
#include <vector>
using namespace std;
namespace shadowrun
{
bool initDb() {
auto db = Database();
static std::unordered_map<std::string, std::string> parse_query_string(const std::string& query) {
std::unordered_map<std::string, std::string> params;
std::istringstream stream(query);
std::string pair;
if (!db.open()){
return false;
while (std::getline(stream, pair, '&')) {
auto pos = pair.find('=');
if (pos != std::string::npos) {
std::string key = pair.substr(0, pos);
std::string value = pair.substr(pos + 1);
params[key] = value; // You may want to URL-decode here
}
}
// Create a tables
const char* create_sql_chars = "CREATE TABLE IF NOT EXISTS shadowrun_characters ("
"id INTEGER PRIMARY KEY,"
"name TEXT,"
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP);";
if (!db.exec(create_sql_chars)){
CROW_LOG_ERROR << "Failed to create shadowrun_characters table";
return false;
}
const char* create_sql_data = "CREATE TABLE IF NOT EXISTS shadowrun_data ("
"id INTEGER PRIMARY KEY,"
"character_id INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"value TEXT,"
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP,"
"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,"
"FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE);";
if (!db.exec(create_sql_data)){
CROW_LOG_ERROR << "Failed to create shadowrun_data table";
return false;
}
return true;
return params;
}
static crow::response rsp(const std::string& msg){
auto str = format("<div class='alert alert-success'>"
"{} </div>", msg);
return crow::response{str};
}
void initApi(crow::SimpleApp& app)
{
CROW_ROUTE(app, "/api/shadowrun/submit-character").methods("POST"_method)(
[](const crow::request& req) {
auto params = crow::query_string(req.body);
auto params = parse_query_string(req.body);
std::string name = params.get("name") ? params.get("name") : "";
std::string metatype = params.get("metatype") ? params.get("metatype") : "";
std::string age = params.get("age") ? params.get("age") : "";
// ... extract more fields as needed
auto name_data = params["Character-Info_Name"];
if (name_data.empty()){
CROW_LOG_WARNING << "Character without name submited, will not be saved";
return rsp("Failed : Character without name submited, will not be saved");
}
// Optionally save to a DB or do logic here
auto key = getKeyOfCharacter(name_data);
if (key < 0){
CROW_LOG_ERROR << "Failed to create id of character : " << name_data;
return rsp("Failed to create id of character");
}
// Return response HTML
std::ostringstream out;
out << "<div class='alert alert-success'>"
<< "Character " << name << " submitted successfully!"
<< "</div>";
vector<pair<const string&, const string&>> idValues;
idValues.reserve(ShadowrunCharacterForm::m_formIds.size());
for (auto& id : ShadowrunCharacterForm::m_formIds) {
auto data = params[id];
if(!
data.empty()){
idValues.push_back(make_pair(id, data));
}
}
return crow::response{out.str()};
if (!storeCharacterData(key, idValues)){
CROW_LOG_ERROR << "Failed to store character data of " << name_data;
return rsp("Failed to store character data");
};
return rsp(format("Character {} submitted successfully", name_data));
});
CROW_ROUTE(app, "/api/shadowrun/character-form")
@ -69,9 +74,7 @@ void initApi(crow::SimpleApp& app)
auto query = crow::query_string(req.url_params);
std::string name = query.get("name") ? query.get("name") : "";
// TODO: Load data from file or DB using `name`
std::string metatype = "Troll";
int age = 28;
auto data = getCharacterData(getKeyOfCharacter(name));
return crow::response{ShadowrunCharacterForm().htmx()};
});
@ -81,7 +84,7 @@ void initApi(crow::SimpleApp& app)
std::ostringstream html;
// Simulated character database
std::vector<std::string> characters = { "Trogdor", "Alice", "Zigzag" };
auto characters = getCharacters();
html << "<form hx-get='/api/shadowrun/character-form' hx-target='#form-container' hx-params='*'>"
<< "<label>Character Name: "
@ -98,7 +101,7 @@ void initApi(crow::SimpleApp& app)
return crow::response{html.str()};
});
if(initDb()){
if(!shadowrun::initDb()){
CROW_LOG_ERROR << "Failed to Init shadowrun database";
}
}

View File

@ -76,6 +76,31 @@ static const vector<string> cArmorParamters = {
"Notes",
};
static const vector<string> genFormIds(){
vector<string> 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, 6);
HtmxShItemList::genIds(vec, "Knowledge Skills", cSkillParameters, 6);
vec.push_back("positive_qualities");
vec.push_back("negative_qualities");
vec.push_back("datapack_notes");
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, 7);
HtmxShItemList::genIds(vec, "Melee Weapons", cMeleeWeaponParameters, 7);
HtmxShItemList::genIds(vec, "Armor", cArmorParamters , 3);
return vec;
}
const std::vector<std::string> ShadowrunCharacterForm::m_formIds = genFormIds();
ShadowrunCharacterForm::ShadowrunCharacterForm() {
html.reserve(30000);

View File

@ -8,7 +8,7 @@ class ShadowrunCharacterForm : public HtmxObject {
public:
ShadowrunCharacterForm();
private:
static const std::vector<std::string> m_formIds;
};
#endif // SHADOWRUN_CHARACTER_FORM_HPP

View File

@ -0,0 +1,110 @@
#include <format>
#include "database.hpp"
#include "crow.h"
using namespace std;
namespace shadowrun {
bool initDb() {
auto db = Database();
if (!db.open()){
return false;
}
// Create a tables
const char* create_sql_chars = "CREATE TABLE IF NOT EXISTS shadowrun_characters ("
"id INTEGER PRIMARY KEY,"
"name TEXT NOT NULL,"
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP);";
if (!db.exec(create_sql_chars)){
CROW_LOG_ERROR << "Failed to create shadowrun_characters table";
return false;
}
const char* create_sql_data = "CREATE TABLE IF NOT EXISTS shadowrun_data ("
"id INTEGER PRIMARY KEY,"
"character_id INTEGER NOT NULL,"
"name TEXT NOT NULL,"
"value TEXT,"
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP,"
"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,"
"FOREIGN KEY (character_id) REFERENCES characters(id) ON DELETE CASCADE);";
if (!db.exec(create_sql_data)){
CROW_LOG_ERROR << "Failed to create shadowrun_data table";
return false;
}
return true;
}
int64_t getKeyOfCharacter(const string& name){
auto sql = format("SELECT 1 FROM shadowrun_characters WHERE name = '{}' LIMIT 1;", name);
auto db = Database();
if (!db.open())
return -1;
auto opt_int = db.getInt(sql.c_str());
if (opt_int.has_value()) {
return opt_int.value();
} else {
sql = format("INSERT INTO shadowrun_characters (name) VALUES ('{}');", name);
auto key = db.insert(sql.c_str());
if(key.has_value()){
return key.value();
} else {
CROW_LOG_ERROR << "Failed to insert character " << name;
return -1;
}
}
}
bool storeCharacterData(int64_t characterKey, vector<pair<const string&, const string&>>& idValues){
auto sql = format("SELECT name FROM shadowrun_data WHERE character_id = {};", characterKey);
auto db = Database();
if (!db.open())
return false;
auto set = db.getStrSet(sql);
for (auto& idValue : idValues) {
// update if already exist
if(set.contains(idValue.first)){
auto sql = format("UPDATE shadowrun_data SET value = {}, updated_at = CURRENT_TIMESTAMP WHERE name = {} AND character_id = {}", idValue.second, idValue.first, characterKey);
if (!db.exec(sql)){
CROW_LOG_WARNING << "Failed to update " << idValue.first << " with " << idValue.second;
}
} else {
auto sql = format("INSERT INTO shadowrun_data (character_id, name, value)"
"VALUES ({}, {}, {})", characterKey, idValue.first, idValue.second);
if (!db.exec(sql)){
CROW_LOG_WARNING << "Failed to insert " << idValue.first << " with " << idValue.second;
}
}
}
return true;
}
std::set<std::string> getCharacters(){
string sql = "SELECT name FROM shadowrun_characters;";
auto db = Database();
if (!db.open())
return std::set<std::string>();
return db.getStrSet(sql);
}
std::map<std::string, std::string> getCharacterData(int64_t characterKey) {
auto sql = format("SELECT name, value FROM shadowrun_data WHERE character_id = {};", characterKey);
auto db = Database();
if (!db.open())
return std::map<std::string, std::string>();
return db.getStrMap(sql);
}
}

View File

@ -0,0 +1,20 @@
#ifndef __SHADOWRUNDB_H__
#define __SHADOWRUNDB_H__
#include <cstdint>
#include <string>
#include <vector>
#include <set>
#include <map>
namespace shadowrun{
bool initDb();
int64_t getKeyOfCharacter(const std::string& name);
bool storeCharacterData(int64_t characterKey, std::vector<std::pair<const std::string&, const std::string&>>& idValues);
std::set<std::string> getCharacters();
std::map<std::string, std::string> getCharacterData(int64_t characterKey);
}
#endif // __SHADOWRUNDB_H__