first draft is working

This commit is contained in:
Lukas Forsberg 2025-11-22 19:41:46 +01:00
parent e4289ee1ac
commit b3c4395fac
12 changed files with 1767 additions and 119 deletions

8
.vscode/tasks.json vendored
View File

@ -9,6 +9,14 @@
"problemMatcher": [
"$gcc"
]
},
{
"label": "build Frontend",
"type": "shell",
"command": "npm run build",
"options": {
"cwd": "frontend"
}
}
]
}

View File

@ -33,11 +33,20 @@ foreach(file IN LISTS TEMPLATE_FILES)
configure_file("${file}" "${CMAKE_BINARY_DIR}/${rel_path}" COPYONLY)
endforeach()
# warnings to ignore
add_compile_options(-Wno-deprecated-declarations)
add_compile_options(-Wno-deprecated-literal-operator)
# Use Crow from system include (installed via yay -S crow + asio)
include_directories(/usr/include src src/htmx src/shadowrun src/database src/login)
include_directories(
/usr/include
include/libs
src
src/htmx
src/shadowrun
src/database
src/login
)
add_executable(${TARGET_NAME}
src/main.cpp
@ -47,7 +56,6 @@ add_executable(${TARGET_NAME}
src/htmx/HtmxTable.h
src/systemd.cpp
src/systemd.h
src/json.hpp
src/htmx/HtmxTableRow.cpp
src/htmx/HtmxTableRow.h
src/htmx/HtmxObject.cpp
@ -56,11 +64,9 @@ add_executable(${TARGET_NAME}
src/htmx_helper.h
src/json_settings.cpp
src/json_settings.h
src/json.hpp
src/database/database.cpp
src/database/database.hpp
src/database/sqlite_orm.h
# Shadowrun
src/shadowrun/HtmxShItemList.cpp

View File

@ -1,21 +1,37 @@
<script lang="ts">
import { onMount } from 'svelte';
import { API_BASE } from '$lib/config';
export let currentCharacter: any;
export let currentCharacterData: any;
// Use character data if provided
let characterInfo = currentCharacter?.characterInfo || {
//{ name: "Name", value: "", type: ""},
Metatype : "",
Age : 30,
Sex : "Man",
Nuyen: 0,
Lifestyle: "",
"Total Karma": 0,
"C. Karma": 0,
"Street Cred": 0,
Notoriety : 0,
Fame : 0
let characterData = currentCharacterData ?? {};
characterData.Info ??= {
Metatype : "",
Age : 30,
Sex : "Man",
Nuyen: 0,
Lifestyle: "",
"Total Karma": 0,
"C. Karma": 0,
"Street Cred": 0,
Notoriety : 0,
Fame : 0
};
characterData.Attributes ??= {
Agility: 1,
Body: 1,
Charisma: 1,
Edge: 1,
Essence: 1,
Initiative: 1,
Intuition: 1,
Logic: 1,
Reaction: 1,
Strength: 1,
Willpower: 1,
};
const characterInfoTypes = {
@ -29,22 +45,7 @@
"Street Cred": "number",
Notoriety: "number",
Fame: "number"
};
// Use character data if provided
let attributes = currentCharacter?.attributes || [
{ name: 'Body', value: 1 },
{ name: 'Agility', value: 1 },
{ name: 'Reaction', value: 1 },
{ name: 'Strength', value: 1 },
{ name: 'Charisma', value: 1 },
{ name: 'Intuition', value: 1 },
{ name: 'Logic', value: 1 },
{ name: 'Willpower', value: 1 },
{ name: 'Edge', value: 1 },
{ name: 'Essence', value: 0 },
{ name: 'Initiative', value: 0}
];
};
let skills = currentCharacter?.skills || [
{ name: 'Pistols', rating: 0, linked: 'Agility' },
@ -53,10 +54,10 @@
];
async function saveCharacterData() {
const res = await fetch(`${API_BASE}/api/shadowrun/characters_data`, {
const res = await fetch(`${API_BASE}/api/shadowrun/characters_data/${currentCharacter.id}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ characterInfo })
body: JSON.stringify(characterData)
});
}
@ -64,26 +65,33 @@
let inventory = currentCharacter?.inventory || [];
</script>
<h1>Name: {currentCharacter.name}</h1>
<h2>Character Info</h2>
{#each Object.entries(characterInfo) as [key, value], i}
{#each Object.entries(characterData["Info"]) as [key, value], i}
<div class="input-row">
<label for={"field-" + i}>{key}</label>
<input
id={"field-" + i}
type={characterInfoTypes[key]}
bind:value={value}
bind:value={characterData["Info"][key]}
min={characterInfoTypes[key] === "number" ? 0 : null} />
</div>
{/each}
<h2>Attributes</h2>
{#each attributes as field, i}
<label>
{field.name}:
<input type="number" min="1" bind:value={field.value} />
</label>
{#each Object.entries(characterData["Attributes"]) as [key, value], i}
<div class="input-row">
<label for={"field-" + i}>{key}</label>
<input
id={"field-" + i}
type="number"
bind:value={characterData["Attributes"][key]}
min=0 />
</div>
{/each}
<!--
<h2>Skills</h2>
{#each skills as skill, i}
<div>
@ -97,3 +105,5 @@
<span>Dice Pool: {skill.rating + attributes[skill.linked]}</span>
</div>
{/each}
-->
<button on:click={saveCharacterData}>Save</button>

View File

@ -9,6 +9,8 @@
};
let characters: Character[] = [];
let currentCharacter: any = null;
let currentCharacterData : any = null
let selectedCharId: number | null = null;
let creatingNew = false;
let newCharName = '';
@ -20,6 +22,12 @@
characters = await res.json();
}
async function loadCharacterData(characterId : number) {
const res = await fetch(`${API_BASE}/api/shadowrun/characters_data/${characterId}`);
if (res.ok)
currentCharacterData = await res.json();
}
onMount(loadCharacters);
async function loadCharacter() {
@ -28,6 +36,7 @@
const res = await fetch(`${API_BASE}/api/shadowrun/characters/${selectedCharId}`);
if (res.ok) {
const data = await res.json();
await loadCharacterData(data.id)
currentCharacter = data;
creatingNew = false;
}
@ -49,7 +58,6 @@
}
}
let currentCharacter: any = null;
</script>
{#if !currentCharacter}
@ -76,5 +84,5 @@
<button on:click={createCharacter}>Create</button>
</div>
{:else}
<CharacterSheet {currentCharacter} />
<CharacterSheet {currentCharacter} {currentCharacterData} />
{/if}

1564
include/libs/magic_enum.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,9 @@
#include "ShadowrunApi.hpp"
#include "ShadowrunCharacterForm.hpp"
#include "ShadowrunDb.hpp"
#include "login.hpp"
#include <format>
#include <set>
#include <vector>
using namespace std;
@ -113,7 +111,10 @@ void initApi(crow::SimpleApp& app)
CROW_ROUTE(app, "/api/shadowrun/characters")
([&]() {
auto characters = getCharacters();
return utils::toJsonArray(characters);
auto res =
crow::response(200, utils::toJsonArray(characters));
res.set_header("Content-Type", "application/json");
return res;
});
CROW_ROUTE(app, "/api/shadowrun/characters").methods("POST"_method)
@ -124,7 +125,9 @@ void initApi(crow::SimpleApp& app)
if(id > 0){
auto character = getChracter(id);
if (character.has_value()){
return crow::response(200, nlohmann::json(character.value()).dump());
auto res = crow::response(200, nlohmann::json(character.value()).dump());
res.set_header("Content-Type", "application/json");
return res;
}
}
return crow::response(405, "Failed to create character");
@ -135,15 +138,53 @@ void initApi(crow::SimpleApp& app)
auto optCharacter = getChracter(id);
if (!optCharacter.has_value())
return crow::response(404, "Character not found");
return crow::response(200, nlohmann::json(optCharacter.value()).dump());
auto res = crow::response(200, nlohmann::json(optCharacter.value()).dump());
res.set_header("Content-Type", "application/json");
return res;
});
CROW_ROUTE(app, "/api/shadowrun/characters_data/<int>")
([&](int id) {
auto character_data = getChracterData(id);
if (!character_data.empty())
return crow::response(405, "No character data found");
return crow::response(200, utils::toJsonArray(character_data));
nlohmann::json j;
const auto characterData = getChracterData(id);
if(characterData.empty())
return crow::response(405, "Character not found");
for(const auto& data : characterData ){
const auto& key = magic_enum::enum_cast<Type>(data.type);
if(key.has_value()){
auto res = utils::parseJson(data.json);
if(res){
j[magic_enum::enum_name(key.value())] = res.value();
} else {
CROW_LOG_ERROR << "Failed to parse json: " << res.error();
}
} else {
CROW_LOG_ERROR << "Read invalid type from database: " << data.type;
}
}
auto res = crow::response(200, j.dump());
res.set_header("Content-Type", "application/json");
return res;
});
CROW_ROUTE(app, "/api/shadowrun/characters_data/<int>").methods("POST"_method)
([&](const crow::request& req, int id) {
nlohmann::json j = nlohmann::json::parse(req.body);
for (auto type : magic_enum::enum_values<Type>()) {
const auto& key = magic_enum::enum_name(type);
if (j.contains(key)){
storeCharacterData(id, type, j[key].dump());
}
}
auto res = crow::response(200, "Saved Character data");
return res;
//return crow::response(405, ret.error());
});
}

View File

@ -1,13 +1,7 @@
#include "ShadowrunDb.hpp"
#include <format>
#include <functional>
#include <map>
#include <optional>
#include "database.hpp"
#include "databasepool.h"
#include "crow.h"
#include "utils.hpp"
#include "sqlite_orm.h"
using namespace std;
using namespace sqlite_orm;
@ -28,53 +22,11 @@ int64_t createCharacter(const string& name){
if (!character.empty()) {
return character[0].id;
} else {
auto c = createShadowrunCharacter(name);
auto c = newShadowrunCharacter(name);
return db->insert(c);
}
}
/*
bool storeCharacterData(int64_t characterKey, vector<pair<const string, const string>>& idValues){
auto characterData = getCharacterData(characterKey);
std::map<string, ShadowrunCharacterData*> dataMap;
for(auto& data : characterData) {
dataMap[data.name] = &data;
}
auto db = dbpool.acquire();
for (auto& idValue : idValues) {
const string& name = idValue.first;
const string& value = idValue.second;
// update if already exist
auto it = dataMap.find(name);
if(it != dataMap.end()){
db->update_all(
sqlite_orm::set(
assign(&ShadowrunData::value, idValue.second),
assign(&ShadowrunData::updated_at, utils::currentTime())
),
where(
c(&ShadowrunData::name) == name and
c(&ShadowrunData::character_id) == characterKey
)
);
} else {
ShadowrunData entry {
.id = -1,
.character_id = static_cast<int>(characterKey),
.name = name,
.value = value,
.created_at = utils::currentTime(),
.updated_at = "",
};
db->insert(entry);
}
}
return true;
}
*/
std::vector<ShadowrunCharacter> getCharacters(){
auto db = dbpool.acquire();
return db->get_all<ShadowrunCharacter>();
@ -105,8 +57,31 @@ vector<ShadowrunCharacterData> getChracterData(int character_id)
return characterData;
}
int storeCharacterData(const ShadowrunCharacterData& data)
{
int storeCharacterData(int characterId, const Type type, const string& json){
auto db = dbpool.acquire();
auto characterData = db->get_all<ShadowrunCharacterData>(
where(
(c(&ShadowrunCharacterData::character_id) == characterId) and
(c(&ShadowrunCharacterData::type) == static_cast<int>(type)))
);
if(characterData.empty()){
ShadowrunCharacterData character = newShadowrunCharacterData(characterId, type, json);
return db->insert(character);
}
else {
if (characterData.size() > 1){
CROW_LOG_ERROR << "Character ID: " << characterId << "has mote than 1 type: " << magic_enum::enum_name(type);
}
auto& character = characterData[0];
character.json = json;
character.updated_at = utils::currentTime();
db->update(character);
return character.id;
}
}
int storeCharacterData(const ShadowrunCharacterData& data){
auto db = dbpool.acquire();
auto characterData = db->get_optional<ShadowrunCharacterData>(data.id);
@ -119,13 +94,4 @@ int storeCharacterData(const ShadowrunCharacterData& data)
}
}
/*
vector<ShadowrunData> getCharacterData(int64_t characterKey) {
auto db = dbpool.acquire();
return db->get_all<ShadowrunData>(
where(c(&ShadowrunData::character_id) == characterKey)
);
}
*/
}

View File

@ -7,8 +7,17 @@
#include <optional>
#include "json.hpp"
#include "utils.hpp"
#include "sqlite_orm.h"
#include "magic_enum.hpp"
namespace shadowrun {
enum class Type {
Unknown = 0,
Info = 1,
Attributes = 2,
Skills = 3,
};
struct ShadowrunCharacter {
int id;
std::string name;
@ -17,26 +26,40 @@ namespace shadowrun {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ShadowrunCharacter, id, name, created_at)
inline ShadowrunCharacter createShadowrunCharacter(const std::string& name){
inline ShadowrunCharacter newShadowrunCharacter(const std::string& name){
return ShadowrunCharacter {-1, name, utils::currentTime()};
}
struct ShadowrunCharacterData {
int id;
int character_id;
std::string type;
int type;
std::string json;
std::string created_at;
std::string updated_at;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ShadowrunCharacterData, id, character_id, type, json, created_at, updated_at);
inline ShadowrunCharacterData newShadowrunCharacterData(int character_id, Type type, const std::string json){
std::string time = utils::currentTime();
return ShadowrunCharacterData {
.id = -1,
.character_id = character_id,
.type = static_cast<int>(type),
.json = json,
.created_at = time,
.updated_at = time,
};
}
int64_t createCharacter(const std::string& name);
bool storeCharacterData(int64_t characterKey, std::vector<std::pair<const std::string, const std::string>>& idValues);
std::vector<ShadowrunCharacter> getCharacters();
std::optional<ShadowrunCharacter> getChracter(int id);
std::vector<ShadowrunCharacterData> getChracterData(int character_id);
int storeCharacterData(const ShadowrunCharacterData& data);
int storeCharacterData(int characterId, const Type type, const std::string& json);
}
#endif // __SHADOWRUNDB_H__

View File

@ -143,4 +143,12 @@ string currentTime(){
return ss.str();
}
std::expected<nlohmann::json, std::string> parseJson(const std::string& input) {
try {
return nlohmann::json::parse(input);
}
catch (const nlohmann::json::exception& e) {
return std::unexpected(e.what());
}
}
}

View File

@ -6,8 +6,8 @@
#include <cstdint>
#include <filesystem>
#include <map>
#include <expected>
#include "json.hpp"
namespace utils {
std::map<std::string, std::string> parseBody(const std::string& body);
@ -33,6 +33,20 @@ namespace utils {
}
return arr.dump();
}
std::expected<nlohmann::json, std::string> parseJson(const std::string& input);
template <typename T>
std::expected<T, std::string> fromJson(const std::string& input) {
try {
return nlohmann::json::parse(input).get<T>();
}
catch (const nlohmann::json::exception& e) {
return std::unexpected(e.what());
}
}
}
#endif