added shadowrun database

This commit is contained in:
2025-06-01 21:19:54 +02:00
parent 71f771b428
commit 69f7f625f8
20 changed files with 738 additions and 103 deletions

35
src/database/database.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "crow.h"
#include "database.hpp"
Database::Database() :
m_db(nullptr)
{}
Database::~Database() {
sqlite3_close(m_db);
}
bool Database::exec(const char* sqlQuery) {
char* errmsg = nullptr;
int rc = sqlite3_exec(m_db, sqlQuery, nullptr, nullptr, &errmsg);
if (rc != SQLITE_OK) {
CROW_LOG_ERROR << "SQL error: " << errmsg;
sqlite3_free(errmsg);
return false;
}
return true;
}
bool Database::exec(const std::string& sqlQuery)
{
exec(sqlQuery.c_str());
}
bool Database::open(){
int rc = sqlite3_open("example.db", &m_db);
if (rc) {
CROW_LOG_ERROR << "Can't open database: " << sqlite3_errmsg(m_db);
return false;
}
return true;
}

21
src/database/database.hpp Normal file
View File

@@ -0,0 +1,21 @@
#ifndef __DATABASE_H__
#define __DATABASE_H__
#include "sqlite3.h"
#include <string>
class Database {
public:
Database();
~Database();
bool open();
bool exec(const char* sqlQuery);
bool exec(const std::string& sqlQuery);
private:
sqlite3* m_db;
};
#endif // __DATABASE_H__

View File

@@ -5,6 +5,7 @@
#include "systemd.h"
#include "htmx_helper.h"
#include "json_settings.h"
#include "utils.hpp"
using namespace std;

View File

@@ -1,6 +1,4 @@
#include <crow.h>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <string>
#include <print>
@@ -8,16 +6,11 @@
#include "json_settings.h"
#include "htmx_helper.h"
#include "systemd.h"
#include "utils.hpp"
#include "ShadowrunApi.hpp"
using namespace std;
string load_file(const string& path) {
ifstream f(path);
stringstream buffer;
buffer << f.rdbuf();
return buffer.str();
}
optional<string> get_body_name(const string& body) {
const auto pos = body.find('=');
if (pos == std::string::npos) return {};
@@ -34,11 +27,15 @@ int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")([] {
return crow::response(load_file("templates/index.html"));
return crow::response(utils::loadFile("templates/index.html"));
});
CROW_ROUTE(app, "/static/<string>")([](const std::string& file) {
return crow::response(load_file("static/" + file));
return crow::response(utils::loadFile("static/" + file));
});
CROW_ROUTE(app, "/templates/<string>")([](const std::string& file) {
return crow::response(utils::loadFile("templates/" + file));
});
CROW_ROUTE(app, "/status")([] {
@@ -80,8 +77,20 @@ int main() {
} else {
CROW_LOG_ERROR << "failed to load settings : " << opt_settings.error();
}
auto opt_isPortOpen = utils::isLocalPortOpen(httpPort);
if (opt_isPortOpen.has_value()){
if (opt_isPortOpen.value()){
CROW_LOG_ERROR << "Local port : " << httpPort << " is already open";
}
}
else {
CROW_LOG_ERROR << "failed to check if local port is open : " << opt_isPortOpen.error();
}
}
shadowrun::initApi(app);
app.loglevel(crow::LogLevel::INFO);
app.port(httpPort).multithreaded().run();
}

View File

@@ -0,0 +1,26 @@
#include <format>
#include "HtmxShAttributeList.hpp"
#include "utils.hpp"
using namespace std;
HtmxShAttributeList::HtmxShAttributeList(const std::string& id, const vector<string>& itemList){
html += format("<h2>{}</h2>", id);
html += "<div class='grid'>";
for (auto& item : itemList){
string item_id = utils::to_id_format(id + "_" + item);
html += format("<label>{}:<input type='text' name='{}'></label>", item, item_id);
}
html += "</div>";
}
HtmxShAttributeList::HtmxShAttributeList(const std::string& id, const std::vector<std::pair<std::string, std::string>>& itemValueList){
html += format("<h2>{}</h2>", id);
html += "<div class='grid'>";
for (auto& item : itemValueList){
string item_id = utils::to_id_format(id + "_" + item.first);
html += format("<label>{}:<input type='text' name='{}' value='{}'></label>", item, item_id, item.second);
}
html += "</div>";
}

View File

@@ -0,0 +1,18 @@
#ifndef HTMXSHATTRIBUTELIST_H
#define HTMXSHATTRIBUTELIST_H
#include "HtmxObject.h"
#include <vector>
#include <string>
class HtmxShAttributeList : public HtmxObject {
public:
// create new item list
HtmxShAttributeList(const std::string& id, const std::vector<std::string>& itemList);
// 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);
};
#endif // HTMXSHATTRIBUTELIST_H

View File

@@ -0,0 +1,28 @@
#include "HtmxShCondition.hpp"
#include "../utils.hpp"
#include <format>
using namespace std;
HtmxShCondition::HtmxShCondition(std::string id, size_t nbrOfBoxes)
{
html += "<div class='section'>";
html += format("<h2>{}</h2>", id);
html += "<div class='monitor-track'>";
int con_value = -1;
for (size_t i = 0; i < nbrOfBoxes; i++)
{
string item_id = utils::to_id_format(format("{}_{}",id, i));
html += format("<label class='monitor-box'><input type='checkbox' name='{}'></label>", item_id);
if ( ((i + 1) % 3 == 0) && (i != 0) )
{
html += format("<div class='monitor-number'>{}</div>", con_value);
con_value--;
}
}
html += "</div></div>";
}

View File

@@ -0,0 +1,13 @@
#ifndef __HTMXSHCONDITION_H__
#define __HTMXSHCONDITION_H__
#include <string>
#include "HtmxObject.h"
class HtmxShCondition : public HtmxObject {
public:
HtmxShCondition(std::string id, size_t nbrOfBoxes);
};
#endif // __HTMXSHCONDITION_H__

View File

@@ -0,0 +1,43 @@
#include <format>
#include "HtmxShItemList.hpp"
#include "../utils.hpp"
using namespace std;
HtmxShItemList::HtmxShItemList(const std::string& id, const std::vector<std::string>& columns, size_t size){
html += "<div class='section'>";
html += format("<h2>{}</h2>", id);
html += format("<div class='grid grid-{}'>", 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("<input type='text' name='{}' placeholder='{}'>", item_id, col);
}
}
html += "</div></div>";
}
/*
HtmxShItemList::HtmxShItemList(const std::string& id, const std::vector<std::pair<std::string, std::string>>& itemValueList){
html += format("<h2>{}</h2>", id);
html += "<div class='grid'>";
for (auto& item : itemValueList){
string item_id = utils::to_id_format(id + "_" + item.first);
html += format("<label>{}:<input type='text' name='{}' value='{}'></label>", item, item_id, item.second);
}
html += "</div>";
html += "<div class='section'>";
html += format("<h2>{}</h2>", id);
html += format("<div class='grid grid-{}'>", 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("<input type='text' name='{}' placeholder='{}' value='{}'>", item_id, col);
}
}
html += "</div></div>";
}
*/

View File

@@ -0,0 +1,18 @@
#ifndef HTMXSHITEMLIST_H
#define HTMXSHITEMLIST_H
#include "HtmxObject.h"
#include <vector>
#include <string>
class HtmxShItemList : public HtmxObject {
public:
// create new item list,
HtmxShItemList(const std::string& id, const std::vector<std::string>& columns, size_t size);
// 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);
};
#endif // HTMXSHITEMLIST_H

View File

@@ -0,0 +1,106 @@
#include "ShadowrunApi.hpp"
#include "ShadowrunCharacterForm.hpp"
#include "database.hpp"
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,"
"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;
}
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);
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
// Optionally save to a DB or do logic here
// Return response HTML
std::ostringstream out;
out << "<div class='alert alert-success'>"
<< "Character " << name << " submitted successfully!"
<< "</div>";
return crow::response{out.str()};
});
CROW_ROUTE(app, "/api/shadowrun/character-form")
([](const crow::request& req) {
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;
return crow::response{ShadowrunCharacterForm().htmx()};
});
CROW_ROUTE(app, "/api/shadowrun/character-list")
([] {
std::ostringstream html;
// Simulated character database
std::vector<std::string> characters = { "Trogdor", "Alice", "Zigzag" };
html << "<form hx-get='/api/shadowrun/character-form' hx-target='#form-container' hx-params='*'>"
<< "<label>Character Name: "
<< "<select name='name'>";
for (const auto& name : characters) {
html << "<option value='" << name << "'>" << name << "</option>";
}
html << "</select></label>"
<< "<button type='submit'>Load Character</button>"
<< "</form>";
return crow::response{html.str()};
});
if(initDb()){
CROW_LOG_ERROR << "Failed to Init shadowrun database";
}
}
}

View File

@@ -0,0 +1,11 @@
#ifndef __SHADOWRUNAPI_H__
#define __SHADOWRUNAPI_H__
#include <crow.h>
namespace shadowrun {
void initApi(crow::SimpleApp& app);
}
#endif // __SHADOWRUNAPI_H__

View File

@@ -0,0 +1,122 @@
#include <string>
#include <vector>
#include "HtmxShItemList.hpp"
#include "HtmxShAttributeList.hpp"
#include "HtmxShCondition.hpp"
#include "ShadowrunCharacterForm.hpp"
using namespace std;
static const vector<string> cCharacterInfo = {
"Name",
"Metatype",
"Age",
"Sex",
"Nuyen",
"Lifestyle",
"Total Karma",
"Current Karma",
"Street Cred",
"Notoriety",
"Public Awareness"
};
static const vector<string> cAttributes = {
"Body",
"Agility",
"Reaction",
"Strength",
"Charisma",
"Intuition",
"Logic",
"Willpower",
"Edge",
"Essence",
"Initiative"
};
static const vector<string> cSkillParameters = {
"Name",
"RTG.",
"ATT.",
};
static const vector<string> cContactsParameters = {
"Name",
"Loyalty",
};
static const vector<string> cRangedWeaponsParameters = {
"Weapon",
"Damage",
"AP",
"Mode",
"RC",
"Ammo"
};
static const vector<string> cImplantParameters = {
"Implant",
"Rating",
"Essence",
"Notes",
};
static const vector<string> cMeleeWeaponParameters = {
"Weapon",
"Reach",
"Damage",
"AP",
};
static const vector<string> cArmorParamters = {
"Armor",
"Ballistic",
"Impact",
"Notes",
};
ShadowrunCharacterForm::ShadowrunCharacterForm() {
html.reserve(30000);
html += "<form hx-post='/api/shadowrun/submit-character' hx-target='#form-response' hx-swap='innerHTML'>";
html += HtmxShAttributeList("Character Info", cCharacterInfo).htmx();
html += HtmxShAttributeList("Attributes", cAttributes).htmx();
html += "<div style='display: grid; grid-template-columns: 1fr 1fr; gap: 2em;'>";
html += HtmxShItemList("Active Skills", cSkillParameters, 6).htmx();
html += HtmxShItemList("Knowledge Skills", cSkillParameters, 6).htmx();
// add Qualities
html += "<div class='section'>"
"<h2>Qualities</h2>"
"<label>Positive Qualities:"
"<textarea name='positive_qualities' rows='4'></textarea>"
"</label>"
"<label>Negative Qualities:"
"<textarea name='negative_qualities' rows='4'></textarea>"
"</label>"
"</div>";
// add datapack notes
html += "<div class='section'>"
"<h2>Datajack / Commlink / Cyberdeck / Notes</h2>"
"<label>Notes:"
"<textarea name='datapack_notes' rows='6'></textarea>"
"</label>"
"</div>";
html += HtmxShCondition("Physical Condition", 18).htmx();
html += HtmxShCondition("Stun Condition", 12).htmx();
html += HtmxShItemList("Contacts", cContactsParameters, 6).htmx();
html += HtmxShItemList("Ranged Weapons", cRangedWeaponsParameters, 7).htmx();
html += HtmxShItemList("Cyberware and Bioware", cImplantParameters, 7).htmx();
html += HtmxShItemList("Melee Weapons", cMeleeWeaponParameters, 7).htmx();
html += HtmxShItemList("Armor", cArmorParamters , 3).htmx();
html += "</div>";
html += "<div style='text-align:center'>"
"<button type='submit'>Submit</button>"
"</div>"
"</form>";
}

View File

@@ -0,0 +1,14 @@
#ifndef SHADOWRUN_CHARACTER_FORM_HPP
#define SHADOWRUN_CHARACTER_FORM_HPP
#include "htmx/HtmxObject.h"
class ShadowrunCharacterForm : public HtmxObject {
public:
ShadowrunCharacterForm();
private:
};
#endif // SHADOWRUN_CHARACTER_FORM_HPP

79
src/utils.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "utils.hpp"
#include <algorithm>
#include <fstream>
#include <sstream>
using namespace std;
namespace utils {
expected<bool, string> isLocalPortOpen(uint16_t portno) {
const char *hostname = "localhost";
int sockfd;
bool ret;
struct sockaddr_in serv_addr;
struct hostent *server;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
close(sockfd);
return unexpected("ERROR opening socket");
}
server = gethostbyname(hostname);
if (server == NULL) {
close(sockfd);
return unexpected("ERROR, no such host");
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(portno);
if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) {
ret = false;
} else {
ret = true;
}
close(sockfd);
return ret;
}
string to_id_format(const string& s){
string new_s = s;
// transform(new_s.begin(), new_s.end(), new_s.begin(),
// [](unsigned char c){ return std::tolower(c); });
replace( new_s.begin(), new_s.end(), ' ', '-');
return new_s;
}
string loadFile(const string& path) {
ifstream f(path);
stringstream buffer;
buffer << f.rdbuf();
return buffer.str();
}
std::filesystem::path getDataDir(){
return std::getenv("XDG_DATA_HOME")
? std::filesystem::path(std::getenv("XDG_DATA_HOME")) / APPLICATION_NAME
: std::filesystem::path(std::getenv("HOME")) / ".local" / "share" / APPLICATION_NAME;
}
}

19
src/utils.hpp Normal file
View File

@@ -0,0 +1,19 @@
#ifndef UTILS_HPP
#define UTILS_HPP
#include <expected>
#include <string>
#include <cstdint>
#include <filesystem>
namespace utils {
std::expected<bool, std::string> isLocalPortOpen(uint16_t portno);
std::string to_id_format(const std::string& s);
std::string loadFile(const std::string& path);
std::filesystem::path getDataDir();
}
#endif