Compare commits

...

7 Commits

177 changed files with 8240 additions and 4888 deletions

View File

@@ -1,7 +1,7 @@
-include .env
api/run:
go run ./cmd/api -foundry_pass=${FOUNDRY_PASS} -foundry_host=${FOUNDRY_HOST} -port=${LISTEN_PORT} -env=development -log_level=${LOG_LEVEL} -db-dsn=${DB_DSN}
go run ./cmd/api -foundry_pass=${FOUNDRY_PASS} -foundry_host=${FOUNDRY_HOST} -port=${LISTEN_PORT} -env=development -log_level=${LOG_LEVEL} -db-dsn=${DB_DSN} -foundry-worlds=${FOUNDRY_WORLDS} -world-user=${WORLD_USER} -world-pass=${WORLD_PASS}
db/migration/new:
@echo 'Creating migration files for ${name}...'

View File

@@ -1,37 +1,22 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
func (app *application) ShowText(w http.ResponseWriter, r *http.Request) {
data, err := app.foundryApp.Test()
dbConn := app.foundryApp.Transport.DB
world, err := db.GetWorld(dbConn, "kingmaker")
if err != nil {
app.slogger.Error("", "error", err)
w.Write([]byte(err.Error()))
w.Write([]byte("Got error"))
return
}
var dbGame db.Game
start := time.Now()
data.ToDB(&dbGame)
elapsed := time.Since(start)
app.slogger.Info("Game data successfully parsed", "elapsedtime", elapsed)
byteData, err := json.Marshal(data)
if err != nil {
app.slogger.Error("", "error", err)
w.Write([]byte(err.Error()))
return
}
w.Write(byteData)
fmt.Fprintf(w, "Got world with name %s, core_version - %s, next_session - %v", world.ID, world.CoreVersion, world.NextSession)
}
func (app *application) WhenNextSession(w http.ResponseWriter, r *http.Request) {

View File

@@ -13,7 +13,6 @@ func (app *application) healthcheckHandler(w http.ResponseWriter, r *http.Reques
"status": app.foundryApp.Status,
"is_available": app.foundryApp.IsAvailable,
},
"config": app.foundryApp.GetHTTP(),
}
err := app.writeJSON(w, http.StatusOK, env, nil)
if err != nil {

View File

@@ -2,7 +2,6 @@ package main
import (
"context"
"database/sql"
"flag"
"log"
"log/slog"
@@ -13,7 +12,9 @@ import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
@@ -47,8 +48,8 @@ type application struct {
var version = "0.1.0"
func openDB(cfg config) (*sql.DB, error) {
db, err := sql.Open("sqlite3", cfg.db.dsn)
func openDB(cfg config) (*sqlx.DB, error) {
db, err := sqlx.Open("sqlite3", cfg.db.dsn)
if err != nil {
return nil, err
}
@@ -73,7 +74,7 @@ func openDB(cfg config) (*sql.DB, error) {
}
// TODO: change default values
func (app *application) parseFlags() *requests.FoundryHttpRequest {
func (app *application) parseFlags() *transport.FoundryTransportData {
flag.StringVar(&app.cfg.port, "port", ":9090", "Service port")
flag.StringVar(&app.cfg.env, "env", "development", "Enivornment (development|staging|production)")
@@ -85,15 +86,29 @@ func (app *application) parseFlags() *requests.FoundryHttpRequest {
var mode string
flag.StringVar(&mode, "service_mode", "api", "Type of service mode(api|discord|tg)")
foundryHttpData := requests.FoundryHttpRequest{SessionID: nil}
flag.StringVar(&foundryHttpData.Host, "foundry_host", "127.0.0.1", "Address to connect to Foundry")
flag.StringVar(&foundryHttpData.Password, "foundry_pass", "", "Password to connect to Foundry")
var foundryTransportData transport.FoundryTransportData
foundryTransportData.HttpConfig = &requests.FoundryHttpRequest{SessionID: nil}
flag.StringVar(&foundryTransportData.HttpConfig.Host, "foundry_host", "127.0.0.1", "Address to connect to Foundry")
flag.StringVar(&foundryTransportData.HttpConfig.Password, "foundry_pass", "", "Password to connect to Foundry")
var logLevelStr string
flag.StringVar(&logLevelStr, "log_level", "info", "Password to connect to Foundry(debug|info|warn|error)")
var worlds string
flag.StringVar(&worlds, "foundry-worlds", "", "Worlds that initialize in db on startup (format: \"test\", \"test1,test2,test3\", \"test1,test2,test3\")")
var users string
flag.StringVar(&users, "world-user", "", "Usernames to authenticate the world (format: \"test\", \"test1,test2,test3\", \"testForAll\")")
var passwords string
flag.StringVar(&passwords, "world-pass", "", "Passwords to authenticate the world (format: \"test\", \"test1,test2,test3\", \"testForAll\")")
flag.Parse()
worldsData, err := types.CreateWorldDataSlice(worlds, users, passwords)
if err != nil {
app.slogger.Warn("Got err on parsing authentication world data", "err", err)
return nil
}
foundryTransportData.Worlds = worldsData
app.cfg.mode = service_mode(mode)
logLevel, ok := StringToLogLevel[logLevelStr]
@@ -105,20 +120,25 @@ func (app *application) parseFlags() *requests.FoundryHttpRequest {
if !ok {
app.slogger.Warn("Wrong log level. Started with log_level=info", "received", logLevelStr)
}
return &foundryHttpData
foundryTransportData.Logger = app.slogger
return &foundryTransportData
}
// TODO: Handle DB connection error
func main() {
app := application{foundryApp: &foundry.FoundryApi{}}
foundryHttpData := app.parseFlags()
foundryTransportData := app.parseFlags()
if foundryTransportData == nil {
return
}
jar, err := cookiejar.New(nil)
if err != nil {
app.slogger.Error("Error", "text", err.Error())
return
}
foundryHttpData.Jar = jar
foundryTransportData.HttpConfig.Jar = jar
app.slogger.Info("", "dsn", app.cfg.db.dsn)
@@ -129,8 +149,15 @@ func main() {
}
defer db.Close()
app.slogger.Info("Database connection pool established")
foundryTransportData.DbConn = db
app.foundryApp.SetTransport(transport.NewFoundryTransport(foundryTransportData))
err = app.foundryApp.PrepareDB()
if err != nil {
app.slogger.Error("Error on preparing database", "err", err)
return
}
app.foundryApp.SetTransport(transport.NewFoundryTransport(db, app.slogger, foundryHttpData))
go app.foundryApp.StartListenFoundry()
app.serve()

View File

@@ -1,2 +0,0 @@
DROP TABLE IF EXISTS options;
DROP TABLE IF EXISTS foundry_state;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
DROP TRIGGER IF EXISTS tr_delete_package_warnings_to_data_package_warnings_data;
DROP TRIGGER IF EXISTS tr_delete_pack_to_index_index;
DROP TABLE IF EXISTS folder;
DROP TABLE IF EXISTS pack;
DROP TABLE IF EXISTS language;
DROP TABLE IF EXISTS style;
DROP TABLE IF EXISTS media;
DROP TABLE IF EXISTS author;
DROP TABLE IF EXISTS es_modules;
DROP TABLE IF EXISTS scripts;
DROP TABLE IF EXISTS tags;
DROP TABLE IF EXISTS relationships_systems;
DROP TABLE IF EXISTS relationships_requires;
DROP TABLE IF EXISTS relationships_recommends;
DROP TABLE IF EXISTS relationships_conflicts;
DROP TABLE IF EXISTS relationships;
DROP TABLE IF EXISTS compatibility;
DROP TABLE IF EXISTS world;
DROP TABLE IF EXISTS grid;
DROP TABLE IF EXISTS system;
DROP TABLE IF EXISTS package_warnings_data_error;
DROP TABLE IF EXISTS package_warnings_data_warning;
DROP TABLE IF EXISTS package_warnings_to_data;
DROP TABLE IF EXISTS package_warnings_data;
DROP TABLE IF EXISTS package_warnings;
DROP TABLE IF EXISTS news;
DROP TABLE IF EXISTS folder_packs;
DROP TABLE IF EXISTS folder;
DROP TABLE IF EXISTS pack_folder;
DROP TABLE IF EXISTS pack_to_index;
DROP TABLE IF EXISTS index_;
DROP TABLE IF EXISTS ownership;
DROP TABLE IF EXISTS relationships_data;
DROP TABLE IF EXISTS document_types_data_html;
DROP TABLE IF EXISTS document_types_data;
DROP TABLE IF EXISTS document_types;
DROP TABLE IF EXISTS module;
DROP TABLE IF EXISTS setup_language_module;
DROP TABLE IF EXISTS setup_language;
DROP TABLE IF EXISTS release_;
DROP TABLE IF EXISTS setup_options;
DROP TABLE IF EXISTS files_storage;
DROP TABLE IF EXISTS files;
DROP TABLE IF EXISTS featured_content;
DROP TABLE IF EXISTS core_update;
DROP TABLE IF EXISTS setup;

View File

@@ -0,0 +1,566 @@
CREATE TABLE IF NOT EXISTS setup (
id INTEGER PRIMARY KEY AUTOINCREMENT,
is_admin BOOLEAN NOT NULL DEFAULT FALSE,
is_setup BOOLEAN NOT NULL DEFAULT TRUE,
created_at DATETIME NOT NULL DEFAULT current_timestamp
);
CREATE TABLE IF NOT EXISTS core_update (
id INTEGER PRIMARY KEY AUTOINCREMENT,
has_update BOOLEAN NOT NULL DEFAULT FALSE,
can_update BOOLEAN NOT NULL DEFAULT FALSE,
could_reach_website BOOLEAN NOT NULL DEFAULT FALSE,
slow_response BOOLEAN NOT NULL DEFAULT FALSE,
will_disable_modules BOOLEAN NOT NULL DEFAULT FALSE,
version VARCHAR(64) NOT NULL,
channel VARCHAR(64) NOT NULL,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS featured_content (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(128) NOT NULL,
caption VARCHAR(128) NOT NULL,
url VARCHAR(128) NOT NULL,
image VARCHAR(128) NOT NULL,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS files_storage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
storage TEXT NOT NULL,
files_id INTEGER,
FOREIGN KEY (files_id) REFERENCES files(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS setup_options (
id INTEGER PRIMARY KEY AUTOINCREMENT,
css_theme VARCHAR(128) NOT NULL,
data_path VARCHAR(128) NOT NULL,
hostname VARCHAR(128) NOT NULL,
language VARCHAR(128) NOT NULL,
local_hostname VARCHAR(128) NOT NULL,
update_channel VARCHAR(128) NOT NULL,
port INTEGER NOT NULL,
compress_socket BOOLEAN NOT NULL DEFAULT FALSE,
compress_static BOOLEAN NOT NULL DEFAULT FALSE,
fullscreen BOOLEAN NOT NULL DEFAULT FALSE,
hot_reload BOOLEAN NOT NULL DEFAULT FALSE,
proxy_ssl BOOLEAN NOT NULL,
telemetry BOOLEAN NOT NULL,
upnp BOOLEAN NOT NULL,
delete_nedb BOOLEAN NOT NULL,
no_backups BOOLEAN NOT NULL,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS release_ (
id INTEGER PRIMARY KEY AUTOINCREMENT,
generation INTEGER NOT NULL,
build INTEGER NOT NULL,
node_version INTEGER NOT NULL,
max_generation INTEGER NOT NULL,
max_stable_generation INTEGER NOT NULL,
time DATETIME NOT NULL,
channel VARCHAR(128) NOT NULL,
suffix VARCHAR(128) NOT NULL,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS setup_language (
id INTEGER PRIMARY KEY,
label VARCHAR(128) NOT NULL,
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS setup_language_module (
id TEXT PRIMARY KEY,
label VARCHAR(128) NOT NULL,
path VARCHAR(128) NOT NULL,
setup_language_id INTEGER,
FOREIGN KEY (setup_language_id) REFERENCES setup_language(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS module (
id TEXT PRIMARY KEY,
title VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
url VARCHAR(128) NOT NULL,
license VARCHAR(128) NOT NULL,
readme VARCHAR(128) NOT NULL,
bugs VARCHAR(128) NOT NULL,
changelog VARCHAR(128) NOT NULL,
version VARCHAR(128) NOT NULL,
manifest VARCHAR(128) NOT NULL,
download VARCHAR(128) NOT NULL,
socket BOOLEAN NOT NULL DEFAULT FALSE,
protected BOOLEAN NOT NULL DEFAULT FALSE,
exclusive_ BOOLEAN NOT NULL DEFAULT FALSE,
persistent_storage BOOLEAN NOT NULL DEFAULT FALSE,
core_translation BOOLEAN NOT NULL DEFAULT FALSE,
library BOOLEAN NOT NULL DEFAULT FALSE,
locked BOOLEAN NOT NULL DEFAULT FALSE,
owned BOOLEAN NOT NULL DEFAULT FALSE,
has_storage BOOLEAN NOT NULL DEFAULT FALSE,
active BOOLEAN NOT NULL DEFAULT FALSE,
availability INTEGER NOT NULL DEFAULT FALSE,
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS document_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
module_id TEXT UNIQUE,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS document_types_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type VARCHAR(64) NOT NULL,
document_types_id INTEGER,
FOREIGN KEY (document_types_id) REFERENCES document_types(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS document_types_data_html (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value TEXT NOT NULL,
document_types_data_id INTEGER,
FOREIGN KEY (document_types_data_id) REFERENCES document_types_data(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS relationships (
id INTEGER PRIMARY KEY AUTOINCREMENT,
module_id TEXT UNIQUE,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS relationships_systems (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ TEXT NOT NULL,
type TEXT NOT NULL,
manifest TEXT NOT NULL,
relationships_id INTEGER,
FOREIGN KEY (relationships_id) REFERENCES relationships(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS relationships_requires (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ INTEGER NOT NULL,
type TEXT NOT NULL,
manifest TEXT NOT NULL,
relationships_id INTEGER,
FOREIGN KEY (relationships_id) REFERENCES relationships(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS relationships_recommends (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ INTEGER NOT NULL,
type TEXT NOT NULL,
manifest TEXT NOT NULL,
relationships_id INTEGER,
FOREIGN KEY (relationships_id) REFERENCES relationships(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS relationships_conflicts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ INTEGER NOT NULL,
type TEXT NOT NULL,
manifest TEXT NOT NULL,
relationships_id INTEGER,
FOREIGN KEY (relationships_id) REFERENCES relationships(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS compatibility (
id INTEGER PRIMARY KEY AUTOINCREMENT,
minimum VARCHAR(64) NOT NULL,
verified VARCHAR(64) NOT NULL,
maximum VARCHAR(64) NOT NULL,
module_id TEXT UNIQUE,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
ALTER TABLE compatibility ADD COLUMN relationships_systems_id TEXT REFERENCES relationships_systems(id) ON DELETE CASCADE;
ALTER TABLE compatibility ADD COLUMN relationships_requires_id TEXT REFERENCES relationships_requires(id) ON DELETE CASCADE;
ALTER TABLE compatibility ADD COLUMN relationships_recommends_id TEXT REFERENCES relationships_recommends(id) ON DELETE CASCADE;
ALTER TABLE compatibility ADD COLUMN relationships_conflicts_id TEXT REFERENCES relationships_conflicts(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS scripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value TEXT NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS es_modules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value TEXT NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value VARCHAR(128) NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS author (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128) NOT NULL,
url VARCHAR(128) NOT NULL,
email VARCHAR(128) NOT NULL,
discord VARCHAR(128) NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS media (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type VARCHAR(128) NOT NULL,
url VARCHAR(128) NOT NULL,
caption VARCHAR(128) NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS style (
id INTEGER PRIMARY KEY AUTOINCREMENT,
src VARCHAR(128) NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS language (
id INTEGER PRIMARY KEY AUTOINCREMENT,
lang VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL,
path VARCHAR(128) NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS pack (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
label VARCHAR(128) NOT NULL,
banner VARCHAR(128) NOT NULL,
path VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
system VARCHAR(128) NOT NULL,
package_type VARCHAR(128) NOT NULL,
package_name VARCHAR(128) NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ownership (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player VARCHAR(128) NOT NULL,
trusted VARCHAR(128) NOT NULL,
assistant VARCHAR(128) NOT NULL,
pack_id TEXT,
FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS index_ (
id TEXT PRIMARY KEY,
folder VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL
);
CREATE TABLE IF NOT EXISTS pack_to_index (
pack_id TEXT,
index_id TEXT,
PRIMARY KEY (pack_id, index_id),
FOREIGN KEY (index_id) REFERENCES index_(id) ON DELETE CASCADE,
FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE
);
CREATE INDEX idx_pack_to_index_index_id ON pack_to_index(index_id);
CREATE TRIGGER tr_delete_pack_to_index_index
AFTER DELETE ON pack_to_index
FOR EACH ROW
BEGIN
DELETE FROM index_
WHERE id = OLD.index_id
AND NOT EXISTS (SELECT 1 FROM pack_to_index WHERE index_id = OLD.index_id);
END;
CREATE TABLE IF NOT EXISTS pack_folder (
id TEXT PRIMARY KEY,
description VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL,
sorting VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
pack_id TEXT,
FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS folder (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128) NOT NULL,
sorting VARCHAR(128) NOT NULL,
color VARCHAR(128) NOT NULL,
folder_id INTEGER,
FOREIGN KEY (folder_id) REFERENCES folder(id) ON DELETE CASCADE
);
ALTER TABLE folder ADD COLUMN module_id TEXT REFERENCES module(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_folder_module_id ON folder(module_id);
CREATE TABLE IF NOT EXISTS folder_packs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value VARCHAR(128) NOT NULL,
folder_id INTEGER,
FOREIGN KEY (folder_id) REFERENCES folder(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS news (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(64) NOT NULL,
caption VARCHAR(64) NOT NULL,
url VARCHAR(64) NOT NULL,
image VARCHAR(64) NOT NULL,
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS package_warnings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ TEXT NOT NULL,
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS package_warnings_to_data (
package_warnings_id INTEGER,
package_warnings_data_id TEXT,
PRIMARY KEY (package_warnings_id, package_warnings_data_id),
FOREIGN KEY (package_warnings_id) REFERENCES package_warnings(id) ON DELETE CASCADE,
FOREIGN KEY (package_warnings_data_id) REFERENCES package_warnings_data(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS package_warnings_data (
id TEXT PRIMARY KEY,
type VARCHAR(128) NOT NULL,
manifest VARCHAR(128) NOT NULL,
reinstallable BOOLEAN NOT NULL
);
CREATE INDEX idx_package_warnings_to_data_package_warnings_data_id ON package_warnings_to_data(package_warnings_data_id);
CREATE TRIGGER tr_delete_package_warnings_to_data_package_warnings_data
AFTER DELETE ON package_warnings_to_data
FOR EACH ROW
BEGIN
DELETE FROM package_warnings_data
WHERE id = OLD.package_warnings_data_id
AND NOT EXISTS (SELECT 1 FROM package_warnings_to_data WHERE package_warnings_data_id = OLD.package_warnings_data_id);
END;
CREATE TABLE IF NOT EXISTS package_warnings_data_warning (
id INTEGER PRIMARY KEY,
value VARCHAR(128) NOT NULL,
package_warnings_data_id TEXT,
FOREIGN KEY (package_warnings_data_id) REFERENCES package_warnings_data(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS package_warnings_data_error (
id INTEGER PRIMARY KEY,
value VARCHAR(128) NOT NULL,
package_warnings_data_id TEXT,
FOREIGN KEY (package_warnings_data_id) REFERENCES package_warnings_data(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS system (
id TEXT PRIMARY KEY,
title VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
url VARCHAR(128) NOT NULL,
license VARCHAR(128) NOT NULL,
bugs VARCHAR(128) NOT NULL,
changelog VARCHAR(128) NOT NULL,
version VARCHAR(128) NOT NULL,
manifest VARCHAR(128) NOT NULL,
download VARCHAR(128) NOT NULL,
background VARCHAR(128) NOT NULL DEFAULT FALSE,
primary_token_attribute VARCHAR(128) NOT NULL DEFAULT FALSE,
availability INTEGER NOT NULL DEFAULT FALSE,
socket BOOLEAN NOT NULL DEFAULT FALSE,
protected BOOLEAN NOT NULL DEFAULT FALSE,
exclusive_ BOOLEAN NOT NULL DEFAULT FALSE,
persistent_storage BOOLEAN NOT NULL DEFAULT FALSE,
locked BOOLEAN NOT NULL DEFAULT FALSE,
owned BOOLEAN NOT NULL DEFAULT FALSE,
has_storage BOOLEAN NOT NULL DEFAULT FALSE,
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
ALTER TABLE compatibility ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_compatibility_system_id ON compatibility(system_id);
ALTER TABLE relationships ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_relationships_system_id ON relationships(system_id);
ALTER TABLE document_types ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_document_types_system_id ON document_types(system_id);
CREATE TABLE IF NOT EXISTS grid (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER NOT NULL,
size INTEGER NOT NULL,
distance INTEGER NOT NULL,
diagonals INTEGER NOT NULL,
thickness INTEGER NOT NULL,
alpha REAL NOT NULL,
color VARCHAR(128) NOT NULL,
units VARCHAR(128) NOT NULL,
style VARCHAR(128) NOT NULL,
system_id TEXT UNIQUE,
FOREIGN KEY (system_id) REFERENCES system(id) ON DELETE CASCADE
);
ALTER TABLE es_modules ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE scripts ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE tags ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE author ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE media ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE pack ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE style ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE language ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE folder ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS world (
id TEXT PRIMARY KEY,
title VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
version VARCHAR(128) NOT NULL,
system VARCHAR(128) NOT NULL,
background VARCHAR(128) NOT NULL,
join_theme VARCHAR(128) NOT NULL,
core_version VARCHAR(128) NOT NULL,
system_version VARCHAR(128) NOT NULL,
last_played VARCHAR(128) NOT NULL,
playtime INTEGER NOT NULL,
availability INTEGER NOT NULL,
next_session DATETIME NOT NULL,
socket BOOLEAN NOT NULL DEFAULT FALSE,
protected BOOLEAN NOT NULL DEFAULT FALSE,
exclusive_ BOOLEAN NOT NULL DEFAULT FALSE,
persistent_storage BOOLEAN NOT NULL DEFAULT FALSE,
locked BOOLEAN NOT NULL DEFAULT FALSE,
owned BOOLEAN NOT NULL DEFAULT FALSE,
has_storage BOOLEAN NOT NULL DEFAULT FALSE,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
ALTER TABLE compatibility ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_compatibility_world_id ON compatibility(world_id);
ALTER TABLE relationships ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_relationships_world_id ON relationships(world_id);
ALTER TABLE tags ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE scripts ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE es_modules ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE author ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE media ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE style ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE language ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE pack ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
ALTER TABLE folder ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;

View File

@@ -0,0 +1,203 @@
DROP TRIGGER IF EXISTS tr_delete_package_warnings_to_data_package_warnings_data;
DROP TRIGGER IF EXISTS tr_delete_game_to_systems_system;
DROP TRIGGER IF EXISTS tr_delete_game_to_pack_pack;
DROP TRIGGER IF EXISTS tr_delete_game_to_module_module;
DROP TRIGGER IF EXISTS tr_delete_pack_to_index_index;
DROP TABLE IF EXISTS token_turn_maker;
DROP TABLE IF EXISTS token_occludable;
DROP TABLE IF EXISTS token_light_darkness;
DROP TABLE IF EXISTS token_light_animation;
DROP TABLE IF EXISTS token_light;
DROP TABLE IF EXISTS token_bar_2;
DROP TABLE IF EXISTS token_bar_1;
DROP TABLE IF EXISTS token_texture;
DROP TABLE IF EXISTS token_sight;
DROP TABLE IF EXISTS ring_subject;
DROP TABLE IF EXISTS ring_colors;
DROP TABLE IF EXISTS ring;
DROP TABLE IF EXISTS token;
DROP TABLE IF EXISTS actor;
DROP TABLE IF EXISTS sound;
DROP TABLE IF EXISTS playlist;
DROP TABLE IF EXISTS table_result_range;
DROP TABLE IF EXISTS table_result;
DROP TABLE IF EXISTS table_;
DROP TABLE IF EXISTS journal_page_video;
DROP TABLE IF EXISTS journal_page_title;
DROP TABLE IF EXISTS journal_page_text;
DROP TABLE IF EXISTS journal_page;
DROP TABLE IF EXISTS journal;
DROP TABLE IF EXISTS setting;
DROP TABLE IF EXISTS item;
DROP TABLE IF EXISTS world_folder;
DROP TABLE IF EXISTS macro;
DROP TABLE IF EXISTS hotbar;
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS face;
DROP TABLE IF EXISTS back;
DROP TABLE IF EXISTS card;
DROP TABLE IF EXISTS ownership_string;
DROP TABLE IF EXISTS card_deck;
DROP TABLE IF EXISTS combatant;
DROP TABLE IF EXISTS combat_groups;
DROP TABLE IF EXISTS combat;
DROP TABLE IF EXISTS message_rolls;
DROP TABLE IF EXISTS message_whisper;
DROP TABLE IF EXISTS speaker;
DROP TABLE IF EXISTS stats;
DROP TABLE IF EXISTS message;
CREATE TABLE IF NOT EXISTS pack_new (
id VARCHAR(128) PRIMARY KEY,
name VARCHAR(128) NOT NULL,
label VARCHAR(128) NOT NULL,
banner VARCHAR(128) NOT NULL,
path VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
system VARCHAR(128) NOT NULL,
package_type VARCHAR(128) NOT NULL,
package_name VARCHAR(128) NOT NULL,
module_id TEXT,
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE
);
ALTER TABLE pack_new ADD COLUMN system_id TEXT REFERENCES system(id) ON DELETE CASCADE;
ALTER TABLE pack_new ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
INSERT INTO pack_new (id, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id)
SELECT id, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id FROM pack;
DROP TABLE IF EXISTS pack;
ALTER TABLE pack_new RENAME TO pack;
CREATE TABLE IF NOT EXISTS package_warnings_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ TEXT NOT NULL,
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
INSERT INTO package_warnings_new (id, key_, setup_id)
SELECT id, key_, setup_id FROM package_warnings;
DROP TABLE IF EXISTS package_warnings;
ALTER TABLE package_warnings_new RENAME TO package_warnings;
DROP TABLE IF EXISTS game_to_pack;
DROP TABLE IF EXISTS game_to_module;
DROP TABLE IF EXISTS active_users;
DROP TABLE IF EXISTS system_update;
DROP INDEX IF EXISTS idx_core_update_game_id;
CREATE TABLE IF NOT EXISTS core_update_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
has_update BOOLEAN NOT NULL DEFAULT FALSE,
can_update BOOLEAN NOT NULL DEFAULT FALSE,
could_reach_website BOOLEAN NOT NULL DEFAULT FALSE,
slow_response BOOLEAN NOT NULL DEFAULT FALSE,
will_disable_modules BOOLEAN NOT NULL DEFAULT FALSE,
version VARCHAR(64) NOT NULL,
channel VARCHAR(64) NOT NULL,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
INSERT INTO core_update_new (id, has_update, can_update, could_reach_website, slow_response, will_disable_modules, version, channel, setup_id)
SELECT id, has_update, can_update, could_reach_website, slow_response, will_disable_modules, version, channel, setup_id FROM core_update;
DROP TABLE IF EXISTS core_update;
ALTER TABLE core_update_new RENAME TO core_update;
DROP TABLE IF EXISTS game_to_systems;
DROP INDEX IF EXISTS idx_world_game_id;
CREATE TABLE IF NOT EXISTS world_new (
id TEXT PRIMARY KEY,
title VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
version VARCHAR(128) NOT NULL,
system VARCHAR(128) NOT NULL,
background VARCHAR(128) NOT NULL,
join_theme VARCHAR(128) NOT NULL,
core_version VARCHAR(128) NOT NULL,
system_version VARCHAR(128) NOT NULL,
last_played VARCHAR(128) NOT NULL,
playtime INTEGER NOT NULL,
availability INTEGER NOT NULL,
next_session DATETIME NOT NULL,
socket BOOLEAN NOT NULL DEFAULT FALSE,
protected BOOLEAN NOT NULL DEFAULT FALSE,
exclusive_ BOOLEAN NOT NULL DEFAULT FALSE,
persistent_storage BOOLEAN NOT NULL DEFAULT FALSE,
locked BOOLEAN NOT NULL DEFAULT FALSE,
owned BOOLEAN NOT NULL DEFAULT FALSE,
has_storage BOOLEAN NOT NULL DEFAULT FALSE,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
setup_id INTEGER,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
INSERT INTO world_new (id, title, description, version, system, background, join_theme, core_version, system_version, last_played, playtime, availability, next_session, socket, protected, exclusive_, persistent_storage, locked, owned, has_storage, created_at, updated_at, setup_id)
SELECT id, title, description, version, system, background, join_theme, core_version, system_version, last_played, playtime, availability, next_session, socket, protected, exclusive_, persistent_storage, locked, owned, has_storage, created_at, updated_at, setup_id FROM world;
DROP TABLE IF EXISTS world;
ALTER TABLE world_new RENAME TO world;
DROP INDEX IF EXISTS idx_release_game_id;
CREATE TABLE IF NOT EXISTS release_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
generation INTEGER NOT NULL,
build INTEGER NOT NULL,
node_version INTEGER NOT NULL,
max_generation INTEGER NOT NULL,
max_stable_generation INTEGER NOT NULL,
time DATETIME NOT NULL,
channel VARCHAR(128) NOT NULL,
suffix VARCHAR(128) NOT NULL,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
INSERT INTO release_new (id, generation, build, node_version, max_generation, max_stable_generation, time, channel, suffix, setup_id)
SELECT id, generation, build, node_version, max_generation, max_stable_generation, time, channel, suffix, setup_id FROM release_;
DROP TABLE IF EXISTS release_;
ALTER TABLE release_new RENAME TO release_;
DROP TABLE IF EXISTS game_options;
DROP INDEX IF EXISTS idx_files_game_id;
DROP TABLE IF EXISTS files_new;
CREATE TABLE IF NOT EXISTS files_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
setup_id INTEGER UNIQUE,
FOREIGN KEY (setup_id) REFERENCES setup(id) ON DELETE CASCADE
);
INSERT INTO files_new (id, setup_id)
SELECT id, setup_id FROM files;
DROP TABLE IF EXISTS files;
ALTER TABLE files_new RENAME TO files;
DROP TABLE IF EXISTS addresses;
DROP TABLE IF EXISTS game;
CREATE TRIGGER tr_delete_package_warnings_to_data_package_warnings_data
AFTER DELETE ON package_warnings_to_data
FOR EACH ROW
BEGIN
DELETE FROM package_warnings_data
WHERE id = OLD.package_warnings_data_id
AND NOT EXISTS (SELECT 1 FROM package_warnings_to_data WHERE package_warnings_data_id = OLD.package_warnings_data_id);
END;
CREATE TRIGGER tr_delete_pack_to_index_index
AFTER DELETE ON pack_to_index
FOR EACH ROW
BEGIN
DELETE FROM index_
WHERE id = OLD.index_id
AND NOT EXISTS (SELECT 1 FROM pack_to_index WHERE index_id = OLD.index_id);
END;

View File

@@ -0,0 +1,779 @@
CREATE TABLE IF NOT EXISTS game (
id INTEGER PRIMARY KEY AUTOINCREMENT,
demo_mode BOOLEAN NOT NULL DEFAULT FALSE,
idle_logout BOOLEAN NOT NULL DEFAULT FALSE,
paused BOOLEAN NOT NULL DEFAULT FALSE,
user_id VARCHAR(128) NOT NULL,
created_at DATETIME NOT NULL DEFAULT current_timestamp
);
CREATE TABLE IF NOT EXISTS addresses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
local VARCHAR(128) NOT NULL,
remote VARCHAR(128) NOT NULL,
remote_is_accessible BOOLEAN NOT NULL DEFAULT FALSE,
game_id INTEGER UNIQUE,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE files ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_files_game_id ON files(game_id);
CREATE TABLE IF NOT EXISTS game_options (
id INTEGER PRIMARY KEY,
language VARCHAR(128) NOT NULL,
update_channel VARCHAR(128) NOT NULL,
port INTEGER NOT NULL,
game_id INTEGER UNIQUE,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE release_ ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_release_game_id ON release_(game_id);
ALTER TABLE world ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_world_game_id ON world(game_id);
CREATE TABLE IF NOT EXISTS game_to_systems (
game_id INTEGER,
system_id TEXT,
PRIMARY KEY (game_id, system_id),
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE,
FOREIGN KEY (system_id) REFERENCES system(id) ON DELETE CASCADE
);
CREATE INDEX idx_game_to_systems_system_id ON game_to_systems(system_id);
CREATE TRIGGER tr_delete_game_to_systems_system
AFTER DELETE ON game_to_systems
FOR EACH ROW
BEGIN
DELETE FROM system
WHERE id = OLD.system_id
AND NOT EXISTS (SELECT 1 FROM game_to_systems WHERE system_id = OLD.system_id);
END;
ALTER TABLE core_update ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_core_update_game_id ON core_update(game_id);
CREATE TABLE IF NOT EXISTS system_update (
id INTEGER PRIMARY KEY AUTOINCREMENT,
has_update BOOLEAN NOT NULL DEFAULT FALSE,
version VARCHAR(128) NOT NULL,
game_id INTEGER UNIQUE,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS active_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value VARCHAR(128) NOT NULL,
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS game_to_module (
game_id INTEGER,
module_id TEXT,
PRIMARY KEY (game_id, module_id),
FOREIGN KEY (module_id) REFERENCES module(id) ON DELETE CASCADE,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
CREATE INDEX idx_game_to_module_module_id ON game_to_module(module_id);
CREATE TRIGGER tr_delete_game_to_module_module
AFTER DELETE ON game_to_module
FOR EACH ROW
BEGIN
DELETE FROM module
WHERE id = OLD.module_id
AND NOT EXISTS (SELECT 1 FROM game_to_module WHERE module_id = OLD.module_id);
END;
ALTER TABLE package_warnings ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS game_to_pack (
game_id INTEGER,
pack_id TEXT,
PRIMARY KEY (game_id, pack_id),
FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
CREATE INDEX idx_game_to_pack_pack_id ON game_to_pack(pack_id);
CREATE TRIGGER tr_delete_game_to_pack_pack
AFTER DELETE ON game_to_pack
FOR EACH ROW
BEGIN
DELETE FROM pack
WHERE id = OLD.pack_id
AND NOT EXISTS (SELECT 1 FROM game_to_pack WHERE pack_id = OLD.pack_id);
END;
CREATE TABLE IF NOT EXISTS message (
id TEXT PRIMARY KEY,
blind BOOLEAN NOT NULL,
emote BOOLEAN NOT NULL,
style INTEGER NOT NULL,
timestamp DATETIME NOT NULL,
content VARCHAR(128) NOT NULL,
author VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
flavor VARCHAR(128) NOT NULL,
sound VARCHAR(128) NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
core_version VARCHAR(128) NOT NULL,
system_id VARCHAR(128) NOT NULL,
system_version VARCHAR(128) NOT NULL,
last_modified_by VARCHAR(128) NOT NULL,
modified_time DATETIME NOT NULL,
message_id TEXT UNIQUE,
FOREIGN KEY (message_id) REFERENCES message(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS speaker (
id INTEGER PRIMARY KEY AUTOINCREMENT,
scene VARCHAR(128) NOT NULL,
actor VARCHAR(128) NOT NULL,
token VARCHAR(128) NOT NULL,
alias VARCHAR(128) NOT NULL,
message_id TEXT UNIQUE,
FOREIGN KEY (message_id) REFERENCES message(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS message_whisper (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value VARCHAR(128) NOT NULL,
message_id TEXT,
FOREIGN KEY (message_id) REFERENCES message(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS message_rolls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value VARCHAR(128) NOT NULL,
message_id TEXT,
FOREIGN KEY (message_id) REFERENCES message(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS combat (
id TEXT PRIMARY KEY,
type VARCHAR(128) NOT NULL,
scene VARCHAR(128) NOT NULL,
round INTEGER NOT NULL,
turn INTEGER NOT NULL,
sort INTEGER NOT NULL,
active INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN combat_id TEXT REFERENCES combat(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_combat_id ON stats(combat_id);
CREATE TABLE IF NOT EXISTS combat_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value VARCHAR(128) NOT NULL,
combat_id TEXT,
FOREIGN KEY (combat_id) REFERENCES combat(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS combatant (
id TEXT PRIMARY KEY,
token_id VARCHAR(128) NOT NULL,
scene_id VARCHAR(128) NOT NULL,
actor_id VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL,
group_ VARCHAR(128) NOT NULL,
initiative INTEGER NOT NULL,
hidden BOOLEAN NOT NULL,
defeated BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
combat_id TEXT,
FOREIGN KEY (combat_id) REFERENCES combat(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN combatant_id TEXT REFERENCES combatant(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_combatant_id ON stats(combatant_id);
CREATE TABLE IF NOT EXISTS card_deck (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL,
folder VARCHAR(128) NOT NULL,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
rotation INTEGER NOT NULL,
sort INTEGER NOT NULL,
display_count BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN card_deck_id TEXT REFERENCES card_deck(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_card_deck_id ON stats(card_deck_id);
CREATE TABLE IF NOT EXISTS ownership_string (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ VARCHAR(128) NOT NULL,
value INTEGER NOT NULL,
card_deck_id TEXT,
FOREIGN KEY (card_deck_id) REFERENCES card_deck(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS card (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
suit VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
origin VARCHAR(128) NOT NULL,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
rotation INTEGER NOT NULL,
value INTEGER NOT NULL,
face INTEGER NOT NULL,
sort INTEGER NOT NULL,
drawn BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
card_deck_id TEXT,
FOREIGN KEY (card_deck_id) REFERENCES card_deck(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS back (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128) NOT NULL,
text VARCHAR(128) NOT NULL,
card_id TEXT UNIQUE,
FOREIGN KEY (card_id) REFERENCES card(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN card_id TEXT REFERENCES card(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_card_id ON stats(card_id);
CREATE TABLE IF NOT EXISTS face (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL,
text VARCHAR(128) NOT NULL,
card_id TEXT,
FOREIGN KEY (card_id) REFERENCES card(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS user (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
avatar VARCHAR(128) NOT NULL,
character VARCHAR(128) NOT NULL,
color VARCHAR(128) NOT NULL,
pronouns VARCHAR(128) NOT NULL,
role INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN user_id TEXT REFERENCES user(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_user_id ON stats(user_id);
CREATE TABLE IF NOT EXISTS hotbar (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key_ INTEGER NOT NULL,
value VARCHAR(128) NOT NULL,
user_id TEXT,
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS macro (
id TEXT PRIMARY KEY,
command VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL,
author VARCHAR(128) NOT NULL,
scope VARCHAR(128) NOT NULL,
folder VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN macro_id TEXT REFERENCES macro(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_macro_id ON stats(macro_id);
ALTER TABLE ownership_string ADD COLUMN macro_id TEXT REFERENCES macro(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS world_folder (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
folder VARCHAR(128) NOT NULL,
sorting VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
color VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN world_folder_id TEXT REFERENCES world_folder(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_world_folder_id ON stats(world_folder_id);
CREATE TABLE IF NOT EXISTS item (
id TEXT PRIMARY KEY,
img VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
folder VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN item_id TEXT REFERENCES item(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_item_id ON stats(item_id);
ALTER TABLE ownership_string ADD COLUMN item_id TEXT REFERENCES item(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS setting (
id TEXT PRIMARY KEY,
key_ VARCHAR(128) NOT NULL,
value VARCHAR(128) NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN setting_id TEXT REFERENCES setting(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_setting_id ON stats(setting_id);
CREATE TABLE IF NOT EXISTS journal (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS journal_page (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
src VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
journal_id TEXT,
FOREIGN KEY (journal_id) REFERENCES journal(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS journal_page_text (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content VARCHAR(128) NOT NULL,
markdown VARCHAR(128) NOT NULL,
format INTEGER NOT NULL,
journal_page_id TEXT,
FOREIGN KEY (journal_page_id) REFERENCES journal_page(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS journal_page_title (
id INTEGER PRIMARY KEY AUTOINCREMENT,
show BOOLEAN NOT NULL,
level INTEGER NOT NULL,
journal_page_id TEXT,
FOREIGN KEY (journal_page_id) REFERENCES journal_page(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS journal_page_video (
id INTEGER PRIMARY KEY AUTOINCREMENT,
controls BOOLEAN NOT NULL,
volume REAL NOT NULL,
journal_page_id TEXT,
FOREIGN KEY (journal_page_id) REFERENCES journal_page(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN journal_page_id TEXT REFERENCES journal_page(id) ON DELETE CASCADE;
ALTER TABLE ownership_string ADD COLUMN journal_page_id TEXT REFERENCES journal_page(id) ON DELETE CASCADE;
ALTER TABLE ownership_string ADD COLUMN journal_id TEXT REFERENCES journal(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS table_ (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
formula VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL,
folder VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
replacement BOOLEAN NOT NULL,
display_roll BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN table_id TEXT REFERENCES table_(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_table_id ON stats(table_id);
ALTER TABLE ownership_string ADD COLUMN table_id TEXT REFERENCES table_(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS table_result (
id TEXT PRIMARY KEY,
type VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL,
weight INTEGER NOT NULL,
drawn BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
table_id TEXT,
FOREIGN KEY (table_id) REFERENCES table_(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN table_result_id TEXT REFERENCES table_result(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_table_result_id ON stats(table_result_id);
CREATE TABLE IF NOT EXISTS table_result_range (
id INTEGER PRIMARY KEY AUTOINCREMENT,
value INTEGER NOT NULL,
table_result_id TEXT,
FOREIGN KEY (table_result_id) REFERENCES table_result(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS playlist (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
folder VARCHAR(128) NOT NULL,
sorting VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
channel VARCHAR(128) NOT NULL,
mode INTEGER NOT NULL,
fade INTEGER NOT NULL,
seed INTEGER NOT NULL,
sort INTEGER NOT NULL,
playing BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN playlist_id TEXT REFERENCES playlist(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_playlist_id ON stats(playlist_id);
ALTER TABLE ownership_string ADD COLUMN playlist_id TEXT REFERENCES playlist(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS sound (
id TEXT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
path VARCHAR(128) NOT NULL,
channel VARCHAR(128) NOT NULL,
description VARCHAR(128) NOT NULL,
fade INTEGER NOT NULL,
sort INTEGER NOT NULL,
repeat BOOLEAN NOT NULL,
playing BOOLEAN NOT NULL,
volume REAL NOT NULL,
paused_time REAL NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
playlist_id TEXT,
FOREIGN KEY (playlist_id) REFERENCES playlist(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS actor (
id TEXT PRIMARY KEY,
img VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL,
folder VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
);
ALTER TABLE stats ADD COLUMN actor_id TEXT REFERENCES actor(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_actor_id ON stats(actor_id);
ALTER TABLE ownership_string ADD COLUMN actor_id TEXT REFERENCES actor(id) ON DELETE CASCADE;
ALTER TABLE item ADD COLUMN actor_id TEXT REFERENCES actor(id) ON DELETE CASCADE;
CREATE TABLE IF NOT EXISTS token (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(128) NOT NULL,
actor_link BOOLEAN NOT NULL,
append_number BOOLEAN NOT NULL,
prepend_adjective BOOLEAN NOT NULL,
lock_rotation BOOLEAN NOT NULL,
random_img BOOLEAN NOT NULL,
display_name INTEGER NOT NULL,
display_bars INTEGER NOT NULL,
disposition INTEGER NOT NULL,
rotation INTEGER NOT NULL,
alpha INTEGER NOT NULL,
width REAL NOT NULL,
height REAL NOT NULL,
actor_id INTEGER UNIQUE,
FOREIGN KEY (actor_id) REFERENCES actor(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ring (
id INTEGER PRIMARY KEY AUTOINCREMENT,
enabled BOOLEAN NOT NULL,
effects INTEGER NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ring_colors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ring VARCHAR(128) NOT NULL,
background VARCHAR(128) NOT NULL,
ring_id INTEGER UNIQUE,
FOREIGN KEY (ring_id) REFERENCES ring(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ring_subject (
id INTEGER PRIMARY KEY AUTOINCREMENT,
scale INTEGER NOT NULL,
texture VARCHAR(128) NOT NULL,
ring_id INTEGER UNIQUE,
FOREIGN KEY (ring_id) REFERENCES ring(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_sight (
id INTEGER PRIMARY KEY AUTOINCREMENT,
color VARCHAR(128) NOT NULL,
vision_mode VARCHAR(128) NOT NULL,
range_ INTEGER NOT NULL,
angle INTEGER NOT NULL,
attenuation REAL NOT NULL,
brightness REAL NOT NULL,
enabled BOOLEAN NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_texture (
id INTEGER PRIMARY KEY AUTOINCREMENT,
src VARCHAR(128) NOT NULL,
fit VARCHAR(128) NOT NULL,
tint VARCHAR(128) NOT NULL,
scale_x REAL NOT NULL,
scale_y REAL NOT NULL,
offset_x REAL NOT NULL,
offset_y REAL NOT NULL,
rotation REAL NOT NULL,
anchor_x REAL NOT NULL,
anchor_y REAL NOT NULL,
alpha_threshold REAL NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_bar_1 (
id INTEGER PRIMARY KEY AUTOINCREMENT,
attribute VARCHAR(128) NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_bar_2 (
id INTEGER PRIMARY KEY AUTOINCREMENT,
attribute VARCHAR(128) NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_light (
id INTEGER PRIMARY KEY AUTOINCREMENT,
color VARCHAR(128) NOT NULL,
priority INTEGER NOT NULL,
angle INTEGER NOT NULL,
negative BOOLEAN NOT NULL,
alpha REAL NOT NULL,
bright REAL NOT NULL,
coloration REAL NOT NULL,
dim REAL NOT NULL,
attenuation REAL NOT NULL,
luminosity REAL NOT NULL,
saturation REAL NOT NULL,
contrast REAL NOT NULL,
shadows REAL NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_light_animation (
id INTEGER PRIMARY KEY AUTOINCREMENT,
speed INTEGER NOT NULL,
intensity INTEGER NOT NULL,
reverse BOOLEAN NOT NULL,
token_light_id INTEGER UNIQUE,
FOREIGN KEY (token_light_id) REFERENCES token_light(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_light_darkness (
id INTEGER PRIMARY KEY AUTOINCREMENT,
min REAL NOT NULL,
max REAL NOT NULL,
token_light_id INTEGER UNIQUE,
FOREIGN KEY (token_light_id) REFERENCES token_light(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_occludable (
id INTEGER PRIMARY KEY AUTOINCREMENT,
radius INTEGER NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS token_turn_maker (
id INTEGER PRIMARY KEY AUTOINCREMENT,
mode INTEGER NOT NULL,
animation VARCHAR(128) NOT NULL,
src VARCHAR(128) NOT NULL,
disposition BOOLEAN NOT NULL,
token_id INTEGER UNIQUE,
FOREIGN KEY (token_id) REFERENCES token(id) ON DELETE CASCADE
);

View File

@@ -1,3 +0,0 @@
DROP TABLE IF EXISTS modules_languages;
DROP TABLE IF EXISTS modules_compatibility;
DROP TABLE IF EXISTS modules;

View File

@@ -1,32 +0,0 @@
CREATE TABLE IF NOT EXISTS modules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
state_id INTEGER NOT NULL,
text_id VARCHAR(256) NOT NULL,
title VARCHAR(256) NOT NULL,
description TEXT NOT NULL,
url VARCHAR(256) NOT NULL,
version VARCHAR(64) NOT NULL,
availability INTEGER NOT NULL,
created_at DATETIME NOT NULL DEFAULT current_timestamp,
FOREIGN KEY (state_id) REFERENCES foundry_state(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS modules_compatibility (
id INTEGER PRIMARY KEY AUTOINCREMENT,
module_id INTEGER UNIQUE,
minimum VARCHAR(64) NOT NULL,
verified VARCHAR(64) NOT NULL,
maximum VARCHAR(64) NOT NULL,
FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS modules_languages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
module_id INTEGER,
language VARCHAR(256) NOT NULL,
name VARCHAR(256) NOT NULL,
path VARCHAR(256) NOT NULL,
FOREIGN KEY (module_id) REFERENCES modules(id) ON DELETE CASCADE
);

View File

@@ -1,2 +0,0 @@
DROP TABLE IF EXISTS systems_compatibility;
DROP TABLE IF EXISTS systems;

View File

@@ -1,21 +0,0 @@
CREATE TABLE IF NOT EXISTS systems (
id INTEGER PRIMARY KEY AUTOINCREMENT,
state_id INTEGER NOT NULL,
text_id VARCHAR(256) NOT NULL,
title VARCHAR(256) NOT NULL,
description TEXT NOT NULL,
url VARCHAR(256) NOT NULL,
download VARCHAR(256) NOT NULL,
created_at DATETIME NOT NULL DEFAULT current_timestamp,
FOREIGN KEY (state_id) REFERENCES foundry_state(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS systems_compatibility (
id INTEGER PRIMARY KEY AUTOINCREMENT,
system_id INTEGER UNIQUE,
minimum VARCHAR(64) NOT NULL,
verified VARCHAR(64) NOT NULL,
maximum VARCHAR(64) NOT NULL,
FOREIGN KEY (system_id) REFERENCES systems(id) ON DELETE CASCADE
);

View File

@@ -1,2 +0,0 @@
DROP TABLE IF EXISTS worlds_compatibility;
DROP TABLE IF EXISTS worlds;

View File

@@ -1,24 +0,0 @@
CREATE TABLE IF NOT EXISTS worlds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
state_id INTEGER NOT NULL,
text_id VARCHAR(256) NOT NULL,
title VARCHAR(256) NOT NULL,
description TEXT NOT NULL,
system VARCHAR(256) NOT NULL,
core_version VARCHAR(256) NOT NULL,
system_version VARCHAR(256) NOT NULL,
playtime INTEGER NOT NULL,
next_session DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT current_timestamp,
FOREIGN KEY (state_id) REFERENCES foundry_state(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS worlds_compatibility (
id INTEGER PRIMARY KEY AUTOINCREMENT,
world_id INTEGER UNIQUE,
minimum VARCHAR(64) NOT NULL,
verified VARCHAR(64) NOT NULL,
maximum VARCHAR(64) NOT NULL,
FOREIGN KEY (world_id) REFERENCES worlds(id) ON DELETE CASCADE
);

View File

@@ -1,3 +0,0 @@
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS users_hotbar;
DROP TABLE IF EXISTS users_stats;

View File

@@ -1,33 +0,0 @@
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
state_id INTEGER NOT NULL,
name VARCHAR(256) NOT NULL,
role INTEGER NOT NULL,
character VARCHAR(128) NOT NULL,
color VARCHAR(128) NOT NULL,
pronouns VARCHAR(128) NOT NULL,
created_at DATETIME NOT NULL DEFAULT current_timestamp,
FOREIGN KEY (state_id) REFERENCES foundry_state(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS users_hotbar (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
key TEXT NOT NULL,
value TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS users_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER UNIQUE,
core_version TEXT NOT NULL,
system_id VARCHAR(64) NOT NULL,
system_version VARCHAR(64) NOT NULL,
created_time INTEGER NOT NULL,
modified_time INTEGER NOT NULL,
last_modified_by VARCHAR(256) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

1
go.mod
View File

@@ -54,6 +54,7 @@ require (
github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/klauspost/compress v1.18.3 // indirect

6
go.sum
View File

@@ -1,5 +1,6 @@
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
@@ -152,6 +153,7 @@ github.com/go-openapi/swag/yamlutils v0.25.3 h1:LKTJjCn/W1ZfMec0XDL4Vxh8kyAnv1or
github.com/go-openapi/swag/yamlutils v0.25.3/go.mod h1:Y7QN6Wc5DOBXK14/xeo1cQlq0EA0wvLoSv13gDQoCao=
github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw=
github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
@@ -195,6 +197,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s=
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
@@ -207,10 +211,12 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.39 h1:sIwSjlJGOaRJjw44/HXaeTblZMjseqr6OOio1tz/+JI=
github.com/mattn/go-sqlite3 v1.14.39/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=

View File

@@ -6,4 +6,5 @@ var (
ErrActionNotFound = errors.New("Action doesn't found")
ErrObjTypeNotMatch = errors.New("Provided value type didn't match obj field type")
ErrObjNotPointer = errors.New("Passed obj is not pointer")
ErrUserChannelIsClosed = errors.New("User channel is closed")
)

View File

@@ -1,6 +1,8 @@
package actions
import (
"time"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
)
@@ -12,19 +14,7 @@ type WsSessionMsg struct {
func (msg WsSessionMsg) Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error {
if status.IsActive {
tr.Logger.Info("Session msg", "userid", msg.UserId)
if msg.UserId == "" {
err := tr.LogInToWorld()
if err != nil {
return err
}
tr.Logger.Info("World is started. Succesfully logged into world")
close(tr.ReadChan.Reconnect())
return nil
}
// tr.Logger.Warn("World is started. Please, return to setup page to fully initialize database")
// go msg.OnActiveWorld(tr)
return nil
return msg.OnActiveWorld(tr)
}
if tr.IsDbInit {
@@ -32,18 +22,44 @@ func (msg WsSessionMsg) Action(tr *transport.FoundryTransport, status *types.Fou
}
tr.IsDbInit = true
go tr.FillDBWithFoundryData()
return nil
return tr.FillDBWithFoundryData()
}
func (msg WsSessionMsg) OnActiveWorld(tr *transport.FoundryTransport) error {
wsMsg := types.NewWsMessage("world", tr.CurrWsId)
tr.CurrWsId++
tr.Logger.Info("Session msg", "userid", msg.UserId)
if msg.UserId != "" {
return msg.HandleLoggedInUser(tr)
}
answer, err := tr.HandleWebsocketRequest(wsMsg)
userId, userPass, err := tr.GetUserIdAndPass()
if err != nil {
return err
}
tr.Logger.Info("World data", "answer", string(answer))
err = tr.LogInToWorld(userId, userPass)
if err != nil {
return err
}
tr.Logger.Info("World is started. Succesfully logged into world")
close(tr.ReadChan.Reconnect())
return nil
}
func (msg WsSessionMsg) HandleLoggedInUser(tr *transport.FoundryTransport) error {
if tr.LoggedInChan == nil {
tr.Logger.Info("World had been started before application was started. Run insertion of world data")
return tr.InsertGameToDB()
}
select {
case <-tr.LoggedInChan:
return ErrUserChannelIsClosed
default:
tr.LoggedInChan <- true
}
time.Sleep(3 * time.Second)
types.CloseChannel(tr.LoggedInChan)
return nil
}

View File

@@ -25,6 +25,5 @@ func (msg WsShutdownMsg) Action(tr *transport.FoundryTransport, status *types.Fo
}
tr.IsDbInit = true
go tr.FillDBWithFoundryData()
return nil
return tr.FillDBWithFoundryData()
}

View File

@@ -8,10 +8,11 @@ import (
"time"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/actions"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests"
json_models "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/json"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
json_models "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/json"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
"golang.org/x/sync/errgroup"
)
var (
@@ -24,7 +25,7 @@ var (
type FoundryApi struct {
//TODO: make check of admin's authentication
transport *transport.FoundryTransport
Transport *transport.FoundryTransport
IsAvailable bool
// Logger *slog.Logger
@@ -37,16 +38,16 @@ func NewFoundry() *FoundryApi {
}
func (foundry *FoundryApi) SetTransport(tr *transport.FoundryTransport) {
foundry.transport = tr
foundry.Transport = tr
}
func (foundry *FoundryApi) ListenAndServeWS() error {
var err error
foundry.transport.ReadChan = *types.InitWsChannels()
defer foundry.transport.ReadChan.Close()
foundry.Transport.ReadChan = *types.InitWsChannels()
defer foundry.Transport.ReadChan.Close()
foundry.background(foundry.transport.ListenWebSocket)
foundry.background(foundry.Transport.ListenWebSocket)
err = foundry.ServeWebSocket()
if err != nil {
@@ -57,21 +58,21 @@ func (foundry *FoundryApi) ListenAndServeWS() error {
func (foundry *FoundryApi) ServeWebSocket() error {
var err error
wsChannels := &foundry.transport.ReadChan
wsChannels := &foundry.Transport.ReadChan
for {
select {
case message, ok := <-wsChannels.Msg():
if !ok {
foundry.transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode)
foundry.transport.Http.SessionID = nil
foundry.Transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode)
foundry.Transport.Http.SessionID = nil
return ChannelIsClosed
}
foundry.transport.Logger.Debug("Data has been received\n", "msg", message.Code, "type", types.WebSocketCode) //, "msg", message.MsgJson)
foundry.Transport.Logger.Debug("Data has been received\n", "msg", message.Code, "type", types.WebSocketCode) //, "msg", message.MsgJson)
switch message.Code {
case types.RespPingCode, types.RespSessionDataCode:
err := foundry.transport.SendOnlyCodeRequest(message.Code)
err := foundry.Transport.SendOnlyCodeRequest(message.Code)
if err != nil {
wsChannels.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.WebSocketCode}
continue
@@ -84,69 +85,78 @@ func (foundry *FoundryApi) ServeWebSocket() error {
continue
}
foundry.transport.Logger.Debug("RespServerChangeCode", "data", data)
foundry.Transport.Logger.Debug("RespServerChangeCode", "data", data)
if data == nil {
continue
}
err = data.Action(foundry.transport, &foundry.Status)
if err != nil {
wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode}
continue
go func() {
errAction := data.Action(foundry.Transport, &foundry.Status)
if errAction != nil {
wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: errAction, Type: types.WebSocketCode}
}
}()
case types.RespDataCode:
foundry.transport.ExchangeChan.Msgs[message.Id] = make(chan []byte, 1)
foundry.transport.ExchangeChan.Msgs[message.Id] <- []byte(message.MsgJson)
go foundry.transport.CloseMsgChannel(message.Id, 5*time.Second)
tr := foundry.Transport
func() {
tr.ChanMutex.Lock()
defer tr.ChanMutex.Unlock()
tr.ExchangeChan.Msgs[message.Id] = make(chan []byte, 1)
tr.ExchangeChan.Msgs[message.Id] <- []byte(message.MsgJson)
go tr.CloseMsgChannel(tr.ExchangeChan.Msgs[message.Id], message.Id, 5*time.Second)
}()
default:
}
case err = <-wsChannels.Err():
var foundryErr *types.FoundryError
if errors.As(err, &foundryErr) {
if foundryErr.IsFatal {
foundry.transport.CloseWebSocketConn()
foundry.transport.Http.SessionID = nil
foundry.Transport.CloseWebSocketConn()
foundry.Transport.Http.SessionID = nil
return foundryErr
}
foundry.transport.Logger.Warn("Got error when listening or served", "err", err.Error(), "type", foundryErr.Type, "direction", foundryErr.Direction)
foundry.Transport.Logger.Warn("Got error when listening or served", "err", err.Error(), "type", foundryErr.Type, "direction", foundryErr.Direction)
}
case <-wsChannels.Done():
foundry.transport.CloseWebSocketConn()
foundry.transport.Http.SessionID = nil
foundry.Transport.CloseWebSocketConn()
foundry.Transport.Http.SessionID = nil
return ListenIsDone
case <-wsChannels.Reconnect():
foundry.transport.CloseWebSocketConn()
foundry.Transport.CloseWebSocketConn()
return ReconnectToWebSocket
}
}
}
func (foundry *FoundryApi) HandleWSRequest(msgType string) ([]byte, error) {
msg := types.NewWsMessageByPage(msgType, foundry.transport.CurrWsId)
msg := types.NewWsMessageByPage(msgType, foundry.Transport.CurrWsId)
return foundry.transport.HandleWebsocketRequest(msg)
return foundry.Transport.HandleWebsocketRequest(msg)
}
func (foundry *FoundryApi) Shutdown() error {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
foundry.transport.Logger.Info("Completing foundry background tasks")
foundry.Transport.Logger.Info("Completing foundry background tasks")
foundryClosed := make(chan struct{})
go func() {
types.CloseChannel(foundry.transport.ReadChan.Done())
types.CloseChannel(foundry.Transport.ReadChan.Done())
foundry.wg.Wait()
foundry.transport.ExchangeChan.Close()
foundry.transport.ReadChan.Close()
foundry.Transport.ExchangeChan.Close()
foundry.Transport.ReadChan.Close()
types.CloseChannel(foundryClosed)
}()
select {
case <-foundryClosed:
foundry.transport.Logger.Info("Stopped foundry server")
foundry.Transport.Logger.Info("Stopped foundry server")
case <-ctx.Done():
return CloseTimeoutExceed
}
@@ -156,24 +166,24 @@ func (foundry *FoundryApi) Shutdown() error {
func (foundry *FoundryApi) ConnectToWebSocket() (bool, error) {
var err error
if !foundry.transport.HasSessionId() {
err = foundry.transport.InitSessionId()
if !foundry.Transport.HasSessionId() {
err = foundry.Transport.InitSessionId()
if err != nil {
return false, err
}
err = foundry.transport.ConnectToFoundry()
err = foundry.Transport.ConnectToFoundry()
if err != nil {
return false, err
}
}
err = foundry.transport.InitWebSocketConnection()
err = foundry.Transport.InitWebSocketConnection()
if err != nil {
return false, err
}
status, err := foundry.transport.Http.GetStatus()
status, err := foundry.Transport.Http.GetStatus()
if err != nil {
return false, err
}
@@ -195,21 +205,21 @@ func (foundry *FoundryApi) StartListenFoundry() {
ok, err := foundry.ConnectToWebSocket()
if err != nil {
if errors.Is(err, ReconnectToWebSocket) {
if foundry.transport.ReconnectNum >= foundry.transport.ReconnectNumMax {
if foundry.Transport.ReconnectNum >= foundry.Transport.ReconnectNumMax {
return
}
time.Sleep(foundry.transport.ReconnectTimeout)
foundry.transport.ReconnectNum++
foundry.transport.Logger.Info("Reconnecting to WebSocket", "times", foundry.transport.ReconnectNum)
time.Sleep(foundry.Transport.ReconnectTimeout)
foundry.Transport.ReconnectNum++
foundry.Transport.Logger.Info("Reconnecting to WebSocket", "times", foundry.Transport.ReconnectNum)
continue
}
foundry.transport.Logger.Error("Error raised", "err", err.Error())
foundry.Transport.Logger.Error("Error raised", "err", err.Error())
if ok {
timeInterval = 1 * time.Second
}
foundry.transport.Logger.Info("Trying to reconnect", "timer", timeInterval.String())
foundry.Transport.Logger.Info("Trying to reconnect", "timer", timeInterval.String())
time.Sleep(timeInterval)
timeInterval = min(timeInterval*2, 15*time.Second)
@@ -220,11 +230,28 @@ func (foundry *FoundryApi) StartListenFoundry() {
})
}
func (foundry *FoundryApi) Test() (*json_models.Game, error) {
wsMsg := types.NewWsMessage("world", foundry.transport.CurrWsId)
foundry.transport.CurrWsId++
func (foundry *FoundryApi) PrepareDB() error {
dbConn := foundry.Transport.DB
answer, err := foundry.transport.HandleWebsocketRequest(wsMsg)
group, _ := errgroup.WithContext(context.Background())
group.Go(func() error { return db.DeleteSetupAll(dbConn) })
group.Go(func() error { return db.DeleteGameAll(dbConn) })
group.Go(func() error { return db.DeleteSeqAll(dbConn) })
err := group.Wait()
if err != nil && !errors.Is(err, db.ErrorRecordNotFound) {
return err
}
return nil
}
func (foundry *FoundryApi) Test() (*json_models.Game, error) {
wsMsg := types.NewWsMessage("world", foundry.Transport.CurrWsId)
foundry.Transport.CurrWsId++
answer, err := foundry.Transport.HandleWebsocketRequest(wsMsg)
if err != nil {
return nil, err
}
@@ -238,11 +265,6 @@ func (foundry *FoundryApi) Test() (*json_models.Game, error) {
}
elapsed := time.Since(start)
foundry.transport.Logger.Info("World data successfully received and parsed", "parseTime", elapsed)
foundry.Transport.Logger.Info("World data successfully received and parsed", "parseTime", elapsed)
return &game[0], nil
}
// TODO: remove
func (foundry *FoundryApi) GetHTTP() *requests.FoundryHttpRequest {
return foundry.transport.Http
}

View File

@@ -9,7 +9,7 @@ func (f *FoundryApi) background(fn func()) {
defer func() {
if err := recover(); err != nil {
f.transport.Logger.Error(fmt.Sprintf("%s", err))
f.Transport.Logger.Error(fmt.Sprintf("%s", err))
}
}()

View File

@@ -0,0 +1,92 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Actor struct {
ID string
Img string
Name string
Type string
Folder string
Sort int
PrototypeToken *Token
Stats Stats
Items []*Item
Ownership []OwnershipString
}
func (a *Actor) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO actor (game_id, id, img, name, type, folder, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (a *Actor) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: a.ID, fieldName: "actor_id"}
InsertWithCtxParallel(group, tx, a.PrototypeToken, relId)
InsertWithCtxParallel(group, tx, a.Stats, relId)
InsertSliceParallel(group, tx, a.Ownership, relId)
InsertSliceParallel(group, tx, a.Items, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (a *Actor) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, a.ID, a.Img, a.Name, a.Type, a.Folder, a.Sort}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return a.InsertObjects(tx)
}
return nil
}
func (a *Actor) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, a.ID, a.Img, a.Name, a.Type, a.Folder, a.Sort}
mutex := GetMutex("actor_insert")
mutex.Lock()
defer mutex.Unlock()
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return a.InsertObjects(tx)
}
return nil
}

View File

@@ -0,0 +1,52 @@
package db
import (
"context"
"github.com/jmoiron/sqlx"
)
type Addresses struct {
ID uint
Local string
Remote string
RemoteIsAccessible bool
}
func (a *Addresses) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO addresses (game_id, local, remote, remote_is_accessible)
VALUES ($1, $2, $3, $4)
RETURNING id`
}
func (a *Addresses) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, a.Local, a.Remote, a.RemoteIsAccessible}
err := tx.QueryRowx(data.query, args...).Scan(&a.ID)
if err != nil {
return err
}
return nil
}
func (a *Addresses) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, a.Local, a.Remote, a.RemoteIsAccessible}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&a.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,59 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type Author struct {
ID uint
Name string
URL string
Email string
Discord string
// SystemAuthorsFlags SystemAuthorsFlags `json:"flags"`
}
func (a *Author) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO author (%s, name, url, email, discord)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`, data.fieldName)
}
func (a *Author) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, a.Name, a.URL, a.Email, a.Discord}
err := tx.QueryRowx(data.query, args...).Scan(&a.ID)
if err != nil {
return err
}
return nil
}
func (a *Author) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, a.Name, a.URL, a.Email, a.Discord}
mutex := GetMutex("author_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&a.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,263 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type CardDeck struct {
ID string
Name string
Type string
Description string
Img string
Folder string
Width int
Height int
Rotation int
Sort int
DisplayCount bool
Stats Stats
Ownership []OwnershipString
Cards []*Card
}
func (c *CardDeck) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO card_deck (game_id, id, name, type, description, img, folder, width, height, rotation, sort, display_count)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (c *CardDeck) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: c.ID, fieldName: "card_deck_id"}
InsertWithCtxParallel(group, tx, c.Stats, relId)
InsertSliceParallel(group, tx, c.Ownership, relId)
InsertSliceParallel(group, tx, c.Cards, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (c *CardDeck) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.Name, c.Type, c.Description, c.Img, c.Folder, c.Width, c.Height, c.Rotation, c.Sort, c.DisplayCount}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}
func (c *CardDeck) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.Name, c.Type, c.Description, c.Img, c.Folder, c.Width, c.Height, c.Rotation, c.Sort, c.DisplayCount}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}
type Card struct {
ID string
Name string
Type string
Suit string
Description string
Origin string
Width int
Height int
Rotation int
Value int
Face int
Sort int
Drawn bool
Back Back
Stats Stats
Faces []Face
}
func (c *Card) Query(data *InsertId[string]) {
data.query = `
INSERT INTO card (card_deck_id, id, name, type, suit, description, origin, width, height, rotation, value, face, sort, drawn)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
ON CONFLICT(id) DO UPDATE SET
card_deck_id = EXCLUDED.card_deck_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (c *Card) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: c.ID, fieldName: "card_id"}
InsertWithCtxParallel(group, tx, c.Back, relId)
InsertWithCtxParallel(group, tx, c.Stats, relId)
InsertSliceParallel(group, tx, c.Faces, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (c *Card) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.Name, c.Type, c.Suit, c.Description, c.Origin, c.Width, c.Height, c.Rotation, c.Value, c.Face, c.Sort, c.Drawn}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}
func (c *Card) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.Name, c.Type, c.Suit, c.Description, c.Origin, c.Width, c.Height, c.Rotation, c.Value, c.Face, c.Sort, c.Drawn}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}
type Face struct {
ID uint
Name string
Img string
Text string
}
func (f Face) Query(data *InsertId[string]) {
data.query = `
INSERT INTO face (card_id, name, img, text)
VALUES ($1, $2, $3, $4)
RETURNING id`
}
func (f Face) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Name, f.Img, f.Text}
err := tx.QueryRowx(data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
func (f Face) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Name, f.Img, f.Text}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
type Back struct {
ID uint
Name string
Text string
}
func (b Back) Query(data *InsertId[string]) {
data.query = `
INSERT INTO back (card_id, name, text)
VALUES ($1, $2, $3)
RETURNING id`
}
func (b Back) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, b.Name, b.Text}
err := tx.QueryRowx(data.query, args...).Scan(&b.ID)
if err != nil {
return err
}
return nil
}
func (b Back) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, b.Name, b.Text}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&b.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,163 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Combat struct {
ID string
Type string
Scene string
Round int
Turn int
Sort int
Active bool
Stats Stats
Groups []string
Combatants []*Combatant
}
func (c *Combat) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO combat (game_id, id, type, scene, round, turn, sort, active)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (c *Combat) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: c.ID, fieldName: "combat_id", tableName: "combat_groups"}
InsertWithCtxParallel(group, tx, c.Stats, relId)
InsertSimpleSliceParallel(group, tx, c.Groups, &relId)
InsertSliceParallel(group, tx, c.Combatants, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (c *Combat) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.Type, c.Scene, c.Round, c.Turn, c.Sort, c.Active}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}
func (c *Combat) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.Type, c.Scene, c.Round, c.Turn, c.Sort, c.Active}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}
type Combatant struct {
ID string
TokenId string
SceneId string
ActorId string
Type string
Img string
Group string
Initiative int
Hidden bool
Defeated bool
Stats Stats
}
func (c *Combatant) Query(data *InsertId[string]) {
data.query = `
INSERT INTO combatant (combat_id, id, token_id, scene_id, actor_id, type, img, group_, initiative, hidden, defeated)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
ON CONFLICT(id) DO UPDATE SET
combat_id = EXCLUDED.combat_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (c *Combatant) InsertObjects(tx *sqlx.Tx) error {
relId := InsertId[string]{id: c.ID, fieldName: "combatant_id"}
group, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, tx, c.Stats, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (c *Combatant) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.TokenId, c.SceneId, c.ActorId, c.Type, c.Img, c.Group, c.Initiative, c.Hidden, c.Defeated}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}
func (c *Combatant) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.ID, c.TokenId, c.SceneId, c.ActorId, c.Type, c.Img, c.Group, c.Initiative, c.Hidden, c.Defeated}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return c.InsertObjects(tx)
}
return nil
}

View File

@@ -0,0 +1,57 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type Compatibility struct {
ID uint
Minimum string
Verified string
Maximum string
}
func (c Compatibility) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO compatibility (%s, minimum, verified, maximum)
VALUES ($1, $2, $3, $4)
RETURNING id`, data.fieldName)
}
func (c Compatibility) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.Minimum, c.Verified, c.Maximum}
err := tx.QueryRowx(data.query, args...).Scan(&c.ID)
if err != nil {
return err
}
return nil
}
func (c Compatibility) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.Minimum, c.Verified, c.Maximum}
mutex := GetMutex("compatibility_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&c.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,124 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type DocumentTypes struct {
ID uint
Data []*DocumentTypeData
}
func (d DocumentTypes) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO document_types (%s)
VALUES ($1)
RETURNING id`, data.fieldName)
}
func (d DocumentTypes) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id}
err := tx.QueryRowx(data.query, args...).Scan(&d.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, d.Data, InsertId[uint]{id: d.ID})
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (d DocumentTypes) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id}
mutex := GetMutex("document_types_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&d.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, d.Data, InsertId[uint]{id: d.ID})
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
type DocumentTypeData struct {
ID uint
Type string
HtmlFields []string
}
func (d *DocumentTypeData) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO document_types_data (document_types_id, type)
VALUES ($1, $2)
RETURNING id`
}
func (d *DocumentTypeData) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, d.Type}
err := tx.QueryRowx(data.query, args...).Scan(&d.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
relData := &InsertId[uint]{id: d.ID, fieldName: "document_types_data_id", tableName: "document_types_data_html"}
InsertSimpleSliceParallel(group, tx, d.HtmlFields, relData)
return group.Wait()
}
func (d *DocumentTypeData) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, d.Type}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&d.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
relData := &InsertId[uint]{id: d.ID, fieldName: "document_types_data_id", tableName: "document_types_data_html"}
InsertSimpleSliceParallel(group, tx, d.HtmlFields, relData)
return group.Wait()
}

View File

@@ -1,9 +0,0 @@
package db
import "errors"
var (
ErrorSetupMoreThanOne = errors.New("Passed more than one setupData")
ErrorSetupNotFound = errors.New("Not found setup data from your request")
ErrorRecordNotFound = errors.New("Record not found")
)

View File

@@ -0,0 +1,113 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Files struct {
ID uint
Storages []FilesStorage
}
func (f *Files) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO files (%s)
VALUES ($1)
RETURNING id`, data.fieldName)
}
func (f *Files) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id}
err := tx.QueryRowx(data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, f.Storages, InsertId[uint]{id: f.ID})
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (f *Files) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, f.Storages, InsertId[uint]{id: f.ID})
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
type FilesStorage struct {
ID uint
Storage string
}
func (f FilesStorage) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO files_storage (files_id, storage)
VALUES ($1, $2)
RETURNING id`
}
func (f FilesStorage) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Storage}
err := tx.QueryRowx(data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
func (f FilesStorage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Storage}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,151 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"strconv"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Folder struct {
ID uint
Name string
Sorting string
Color string
Packs []string
Folders []*Folder
}
func (f *Folder) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO folder (%s, name, sorting, color)
VALUES ($1, $2, $3, $4)
RETURNING id`, data.fieldName)
}
func (f *Folder) InsertObjects(tx *sqlx.Tx) error {
relId := InsertId[string]{
id: strconv.FormatUint(uint64(f.ID), 10),
fieldName: "folder_id",
tableName: "folder_packs",
}
group, _ := errgroup.WithContext(context.Background())
InsertSimpleSliceParallel(group, tx, f.Packs, &relId)
InsertSliceParallel(group, tx, f.Folders, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (f *Folder) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Name, f.Sorting, f.Color}
err := tx.QueryRowx(data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return f.InsertObjects(tx)
}
func (f *Folder) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Name, f.Sorting, f.Color}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return f.InsertObjects(tx)
}
type WorldFolder struct {
ID string
Name string
Type string
Folder string
Sorting string
Description string
Color string
Sort int
Stats Stats
}
func (w *WorldFolder) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO world_folder (game_id, id, name, type, folder, sorting, description, color, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (w *WorldFolder) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: w.ID, fieldName: "world_folder_id"}
InsertWithCtxParallel(group, tx, w.Stats, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (w *WorldFolder) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, w.ID, w.Name, w.Type, w.Folder, w.Sorting, w.Description, w.Color, w.Sort}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return w.InsertObjects(tx)
}
return nil
}
func (w *WorldFolder) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, w.ID, w.Name, w.Type, w.Folder, w.Sorting, w.Description, w.Color, w.Sort}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return w.InsertObjects(tx)
}
return nil
}

View File

@@ -1,6 +1,9 @@
package db
import "time"
import (
"database/sql"
"time"
)
type FoundryDataType string
@@ -17,3 +20,7 @@ type FoundryData struct {
Game *Game
CreatedAt time.Time
}
type FoundryDataModel struct {
DB *sql.DB
}

View File

@@ -1,306 +0,0 @@
package db
import (
"context"
"database/sql"
"errors"
"time"
)
type StateType int
const (
AuthState = StateType(0)
SetupState = StateType(1)
JoinState = StateType(2)
PlayersState = StateType(3)
UpdateState = StateType(4)
LicenseState = StateType(5)
)
type FoundryState struct {
Id int64
IsAdmin bool
IsSetup bool
Type StateType
CreatedAt time.Time
Options Options
Modules Modules
Systems Systems
Worlds Worlds
Users Users
}
type Compatibility struct {
Id int64
Minimum string
Verified string
Maximum string
}
type Options struct {
Id int64
Language string
}
type FoundryStateModel struct {
DB *sql.DB
}
func (m FoundryStateModel) Insert(state *FoundryState) error {
query := `
INSERT INTO foundry_state (is_admin, is_setup, state_type)
VALUES ($1, $2, $3)
RETURNING id, created_at`
args := []any{state.IsAdmin, state.IsSetup, state.Type}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&state.Id, &state.CreatedAt)
if err != nil {
return err
}
err = m.InsertOptions(&state.Options, state.Id)
if err != nil && err != sql.ErrNoRows {
return err
}
for i := range state.Modules {
err = m.InsertModule(&state.Modules[i], state.Id)
if err != nil && err != sql.ErrNoRows {
return err
}
}
for i := range state.Systems {
err = m.InsertSystem(&state.Systems[i], state.Id)
if err != nil && err != sql.ErrNoRows {
return err
}
}
for i := range state.Worlds {
err = m.InsertWorld(&state.Worlds[i], state.Id)
if err != nil && err != sql.ErrNoRows {
return err
}
}
for i := range state.Users {
err = m.InsertUser(&state.Users[i], state.Id)
if err != nil && err != sql.ErrNoRows {
return err
}
}
return nil
}
func (m FoundryStateModel) InsertOptions(options *Options, stateId int64) error {
query := `
INSERT INTO options (state_id, lang)
VALUES ($1, $2)
RETURNING id`
args := []any{stateId, options.Language}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return m.DB.QueryRowContext(ctx, query, args...).Scan(&options.Id)
}
func (m FoundryStateModel) Get(id int64) (*FoundryState, error) {
if id < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT id, created_at, is_admin, is_setup, state_type
FROM foundry_state
WHERE id = $1`
var state FoundryState
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, id).Scan(
&state.Id,
&state.CreatedAt,
&state.IsAdmin,
&state.IsSetup,
&state.Type,
)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
state.Modules, err = m.GetModules(state.Id)
if err != nil {
return nil, err
}
state.Systems, err = m.GetSystems(state.Id)
if err != nil {
return nil, err
}
state.Worlds, err = m.GetWorlds(state.Id)
if err != nil {
return nil, err
}
state.Users, err = m.GetUsers(state.Id)
if err != nil {
return nil, err
}
options, err := m.GetOptions(state.Id)
if err != nil {
return nil, err
}
state.Options = *options
return &state, nil
}
func (m FoundryStateModel) GetOptions(idState int64) (*Options, error) {
if idState < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT id, lang
FROM options
WHERE state_id = $1`
var options Options
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, idState).Scan(
&options.Id,
&options.Language,
)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
return &options, nil
}
func (m FoundryStateModel) GetIdByType(stateType StateType) (int64, error) {
if stateType < 0 {
return -1, ErrorRecordNotFound
}
query := `
SELECT id
FROM foundry_state
WHERE state_type = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var id int64
err := m.DB.QueryRowContext(ctx, query, stateType).Scan(&id)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return -1, ErrorRecordNotFound
default:
return -1, err
}
}
return id, nil
}
func (m FoundryStateModel) Delete(id int64) error {
if id < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM foundry_state
WHERE id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, id)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func (m FoundryStateModel) DeleteAll() error {
query := `
DELETE FROM foundry_state`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func (m FoundryStateModel) DeleteAllSeq() error {
query := `
DELETE FROM sqlite_sequence`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}

View File

@@ -0,0 +1,157 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"strconv"
"time"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Game struct {
ID uint
CreatedAt time.Time
DemoMode bool
IdleLogout bool
Paused bool
UserID string
Addresses Addresses
Files Files
Options GameOptions
Release Release
World *World
System *System
CoreUpdate CoreUpdate
SystemUpdate SystemUpdate
ActiveUsers []string
Modules []*Module
PackageWarnings []*PackageWarning
Packs []*Pack
Messages []*Message
Combats []*Combat
CardDeck []*CardDeck
Users []*User
Macros []*Macro
Folders []*WorldFolder
Items []*Item
Settings []*Setting
Journals []*Journal
Tables []*Table
Playlists []*Playlist
Actors []*Actor
// Scenes []Scene
}
func (g *Game) InsertObjects(db *sqlx.DB) error {
relData := InsertId[uint]{id: g.ID, fieldName: "game_id"}
err := func() error {
tx := db.MustBegin()
defer tx.Rollback()
groupFunc, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(groupFunc, tx, &g.Addresses, relData)
InsertWithCtxParallel(groupFunc, tx, &g.Files, relData)
InsertWithCtxParallel(groupFunc, tx, &g.Options, relData)
InsertWithCtxParallel(groupFunc, tx, &g.Release, relData)
InsertWithCtxParallel(groupFunc, tx, &g.CoreUpdate, relData)
InsertWithCtxParallel(groupFunc, tx, &g.SystemUpdate, relData)
relDataString := InsertId[string]{id: strconv.FormatUint(uint64(g.ID), 10), fieldName: "game_id"}
InsertWithCtxParallel(groupFunc, tx, g.World, relDataString)
InsertWithCtxParallel(groupFunc, tx, g.System, relDataString)
InsertSimpleSliceParallel(groupFunc, tx, g.ActiveUsers,
&InsertId[uint]{id: g.ID, fieldName: "game_id", tableName: "active_users"})
InsertSliceParallel(groupFunc, tx, g.Modules, relData)
InsertSliceParallel(groupFunc, tx, g.PackageWarnings, relData)
InsertSliceParallel(groupFunc, tx, g.Packs, relDataString)
InsertSliceParallel(groupFunc, tx, g.Messages, relData)
InsertSliceParallel(groupFunc, tx, g.Combats, relData)
InsertSliceParallel(groupFunc, tx, g.CardDeck, relData)
InsertSliceParallel(groupFunc, tx, g.Users, relData)
InsertSliceParallel(groupFunc, tx, g.Macros, relData)
InsertSliceParallel(groupFunc, tx, g.Folders, relData)
errFunc := groupFunc.Wait()
if errFunc != nil && !errors.Is(errFunc, sql.ErrNoRows) {
return errFunc
}
return tx.Commit()
}()
err = func() error {
tx := db.MustBegin()
defer tx.Rollback()
groupFunc, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(groupFunc, tx, g.Settings, relData)
InsertSliceParallel(groupFunc, tx, g.Journals, relData)
InsertSliceParallel(groupFunc, tx, g.Tables, relData)
InsertSliceParallel(groupFunc, tx, g.Playlists, relData)
errFunc := groupFunc.Wait()
if errFunc != nil && !errors.Is(errFunc, sql.ErrNoRows) {
return errFunc
}
return tx.Commit()
}()
err = func() error {
tx := db.MustBegin()
defer tx.Rollback()
groupFunc, _ := errgroup.WithContext(context.Background())
InsertSliceParallelTimeout(groupFunc, tx, g.Items,
InsertId[string]{id: strconv.FormatUint(uint64(g.ID), 10), fieldName: "game_id"},
15*time.Second)
InsertSliceParallelTimeout(groupFunc, tx, g.Actors, relData, 15*time.Second)
errFunc := groupFunc.Wait()
if errFunc != nil && !errors.Is(errFunc, sql.ErrNoRows) {
return errFunc
}
return tx.Commit()
}()
return err
}
func (g *Game) Insert(db *sqlx.DB) error {
tx := db.MustBegin()
defer tx.Rollback()
const query = `
INSERT INTO game (demo_mode, idle_logout, paused, user_id)
VALUES ($1, $2, $3, $4)
RETURNING id, created_at`
args := []any{g.DemoMode, g.IdleLogout, g.Paused, g.UserID}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := tx.QueryRowxContext(ctx, query, args...).Scan(&g.ID, &g.CreatedAt)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
err = g.InsertObjects(db)
if err != nil {
fmt.Printf("%v\n", err)
}
return err
}

View File

@@ -0,0 +1,61 @@
package db
import (
"context"
"github.com/jmoiron/sqlx"
)
type Grid struct {
ID uint
Type int
Size int
Distance int
Diagonals int
Thickness int
Alpha float64
Color string
Units string
Style string
}
func (g *Grid) Query(data *InsertId[string]) {
data.query = `
INSERT INTO grid (system_id, type, size, distance, diagonals, thickness,
alpha, color, units, style)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id`
}
func (g *Grid) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, g.Type, g.Size, g.Distance, g.Diagonals, g.Thickness,
g.Alpha, g.Color, g.Units, g.Style}
err := tx.QueryRowx(data.query, args...).Scan(&g.ID)
if err != nil {
return err
}
return nil
}
func (g *Grid) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, g.Type, g.Size, g.Distance, g.Diagonals, g.Thickness,
g.Alpha, g.Color, g.Units, g.Style}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&g.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,92 @@
package db
import (
"context"
"github.com/jmoiron/sqlx"
)
type Index struct {
ID string
Folder string
Img string
Name string
Type string
}
func (i *Index) Query(data *InsertId[string]) {
data.query = `
INSERT INTO index_ (id, folder, img, name, type)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT(id) DO NOTHING`
}
func (i *Index) ConnectGameQuery(data *InsertId[string]) {
data.query = `
INSERT INTO pack_to_index (pack_id, index_id)
VALUES ($1, $2)
ON CONFLICT(pack_id, index_id) DO NOTHING`
}
func (i *Index) ConnectGame(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, i.ID}
_, err := tx.Exec(data.query, args...)
return err
}
func (i *Index) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{i.ID, i.Folder, i.Img, i.Name, i.Type}
_, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
dataCopy := *data
i.ConnectGameQuery(&dataCopy)
return i.ConnectGame(tx, &dataCopy)
}
func (i *Index) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, i.ID}
_, err := tx.ExecContext(ctx, data.query, args...)
return err
}
func (i *Index) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{i.ID, i.Folder, i.Img, i.Name, i.Type}
mutex := GetMutex("index_insert")
mutex.Lock()
defer mutex.Unlock()
_, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
dataCopy := *data
i.ConnectGameQuery(&dataCopy)
return i.ConnectGameCtx(ctx, tx, &dataCopy)
}

View File

@@ -0,0 +1,89 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Item struct {
ID string
Img string
Name string
Type string
Folder string
Sort int
Stats Stats
Ownership []OwnershipString
}
func (i *Item) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO item (%[1]s, id, img, name, type, folder, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT(id) DO UPDATE SET
%[1]s = EXCLUDED.%[1]s,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`, data.fieldName)
}
func (i *Item) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: i.ID, fieldName: "item_id"}
InsertWithCtxParallel(group, tx, i.Stats, relId)
InsertSliceParallel(group, tx, i.Ownership, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (i *Item) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, i.ID, i.Img, i.Name, i.Type, i.Folder, i.Sort}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return i.InsertObjects(tx)
}
return nil
}
func (i *Item) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, i.ID, i.Img, i.Name, i.Type, i.Folder, i.Sort}
mutex := GetMutex("item_insert")
mutex.Lock()
defer mutex.Unlock()
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return i.InsertObjects(tx)
}
return nil
}

View File

@@ -0,0 +1,81 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Journal struct {
ID string
Name string
Sort int
Pages []*JournalPage
Ownership []OwnershipString
}
func (j *Journal) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO journal (game_id, id, name, sort)
VALUES ($1, $2, $3, $4)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (j *Journal) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: j.ID, fieldName: "journal_id"}
InsertSliceParallel(group, tx, j.Pages, relId)
InsertSliceParallel(group, tx, j.Ownership, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (j *Journal) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, j.ID, j.Name, j.Sort}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return j.InsertObjects(tx)
}
return nil
}
func (j *Journal) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, j.ID, j.Name, j.Sort}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return j.InsertObjects(tx)
}
return nil
}

View File

@@ -0,0 +1,221 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type JournalPage struct {
ID string
Name string
Type string
Src string
Sort int
Text PageText
Title PageTitle
Video PageVideo
Stats Stats
Ownership []OwnershipString
}
func (j *JournalPage) Query(data *InsertId[string]) {
data.query = `
INSERT INTO journal_page (journal_id, id, name, type, src, sort)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT(id) DO UPDATE SET
journal_id = EXCLUDED.journal_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (j *JournalPage) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: j.ID, fieldName: "journal_page_id"}
InsertWithCtxParallel(group, tx, j.Text, relId)
InsertWithCtxParallel(group, tx, j.Title, relId)
InsertWithCtxParallel(group, tx, j.Video, relId)
InsertWithCtxParallel(group, tx, j.Stats, relId)
InsertSliceParallel(group, tx, j.Ownership, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (j *JournalPage) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, j.ID, j.Name, j.Type, j.Src, j.Sort}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return j.InsertObjects(tx)
}
return nil
}
func (j *JournalPage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, j.ID, j.Name, j.Type, j.Src, j.Sort}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return j.InsertObjects(tx)
}
return nil
}
type PageText struct {
ID uint
Content string
Markdown string
Format int
}
func (p PageText) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO journal_page_text (%s, content, markdown, format)
VALUES ($1, $2, $3, $4)`, data.fieldName)
}
func (p PageText) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Content, p.Markdown, p.Format}
_, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
return nil
}
func (p PageText) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Content, p.Markdown, p.Format}
_, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
return nil
}
type PageTitle struct {
ID uint
Show bool
Level int
}
func (p PageTitle) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO journal_page_title (%s, show, level)
VALUES ($1, $2, $3)`, data.fieldName)
}
func (p PageTitle) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Show, p.Level}
_, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
return nil
}
func (p PageTitle) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Show, p.Level}
_, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
return nil
}
type PageVideo struct {
ID uint
Controls bool
Volume float64
}
func (p PageVideo) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO journal_page_video (%s, controls, volume)
VALUES ($1, $2, $3)`, data.fieldName)
}
func (p PageVideo) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Controls, p.Volume}
_, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
return nil
}
func (p PageVideo) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Controls, p.Volume}
_, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,158 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type SetupLanguage struct {
ID string
Label string
Modules []SetupLanguageModule
}
func (l *SetupLanguage) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO setup_language (setup_id, label)
VALUES ($1, $2)
RETURNING id`
}
func (l *SetupLanguage) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Label}
err := tx.QueryRowx(data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, l.Modules, InsertId[string]{id: l.ID})
return group.Wait()
}
func (l *SetupLanguage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Label}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, l.Modules, InsertId[string]{id: l.ID})
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
type SetupLanguageModule struct {
ID string
Label string
Path string
}
func (l SetupLanguageModule) Query(data *InsertId[string]) {
data.query = `
INSERT INTO setup_language_module (setup_language_id, id, label, path)
VALUES ($1, $2, $3, $4)
RETURNING id`
}
func (l SetupLanguageModule) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.ID, l.Label, l.Path}
_, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
return nil
}
func (l SetupLanguageModule) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.ID, l.Label, l.Path}
_, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
return nil
}
type Language struct {
ID uint
Lang string
Name string
Path string
}
func (l *Language) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO language (%s, lang, name, path)
VALUES ($1, $2, $3, $4)
RETURNING id`, data.fieldName)
}
func (l *Language) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Lang, l.Name, l.Path}
err := tx.QueryRowx(data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}
func (l *Language) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Lang, l.Name, l.Path}
mutex := GetMutex("language_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,174 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Light struct {
ID uint
Color string
Priority int
Angle int
Negative bool
Alpha float64
Bright float64
Coloration float64
Dim float64
Attenuation float64
Luminosity float64
Saturation float64
Contrast float64
Shadows float64
LightAnimation LightAnimation
LightDarkness LightDarkness
}
func (l *Light) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO token_light (%s, color, priority, angle, negative, alpha, bright, coloration, dim,
attenuation, luminosity, saturation, contrast, shadows)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
RETURNING id`, data.fieldName)
}
func (l *Light) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[uint]{id: l.ID, fieldName: "token_light_id"}
InsertWithCtxParallel(group, tx, l.LightAnimation, relId)
InsertWithCtxParallel(group, tx, l.LightDarkness, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (l *Light) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Color, l.Priority, l.Angle, l.Negative, l.Alpha, l.Bright, l.Coloration, l.Dim,
l.Attenuation, l.Luminosity, l.Saturation, l.Contrast, l.Shadows}
err := tx.QueryRowx(data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}
func (l *Light) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Color, l.Priority, l.Angle, l.Negative, l.Alpha, l.Bright, l.Coloration, l.Dim,
l.Attenuation, l.Luminosity, l.Saturation, l.Contrast, l.Shadows}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}
type LightAnimation struct {
ID uint
Speed int
Intensity int
Reverse bool
}
func (l LightAnimation) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO token_light_animation (%s, speed, intensity, reverse)
VALUES ($1, $2, $3, $4)
RETURNING id`, data.fieldName)
}
func (l LightAnimation) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Speed, l.Intensity, l.Reverse}
err := tx.QueryRowx(data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}
func (l LightAnimation) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Speed, l.Intensity, l.Reverse}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}
type LightDarkness struct {
ID uint
Min float64
Max float64
}
func (l LightDarkness) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO token_light_darkness (%s, min, max)
VALUES ($1, $2, $3, $4)
RETURNING id`, data.fieldName)
}
func (l LightDarkness) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Min, l.Max}
err := tx.QueryRowx(data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}
func (l LightDarkness) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, l.Min, l.Max}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,87 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Macro struct {
ID string
Command string
Name string
Type string
Img string
Author string
Scope string
Folder string
Sort int
Stats Stats
Ownership []OwnershipString
}
func (m *Macro) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO macro (game_id, id, command, name, type, img, author, scope, folder, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (m *Macro) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: m.ID, fieldName: "macro_id"}
InsertWithCtxParallel(group, tx, m.Stats, relId)
InsertSliceParallel(group, tx, m.Ownership, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (m *Macro) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, m.ID, m.Command, m.Name, m.Type, m.Img, m.Author, m.Scope, m.Folder, m.Sort}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return m.InsertObjects(tx)
}
return nil
}
func (m *Macro) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, m.ID, m.Command, m.Name, m.Type, m.Img, m.Author, m.Scope, m.Folder, m.Sort}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return m.InsertObjects(tx)
}
return nil
}

View File

@@ -0,0 +1,57 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type Media struct {
ID uint
Type string
URL string
Caption string
}
func (m *Media) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO media (%s, type, url, caption)
VALUES ($1, $2, $3, $4)
RETURNING id`, data.fieldName)
}
func (m *Media) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, m.Type, m.URL, m.Caption}
err := tx.QueryRowx(data.query, args...).Scan(&m.ID)
if err != nil {
return err
}
return nil
}
func (m *Media) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, m.Type, m.URL, m.Caption}
mutex := GetMutex("media_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&m.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,139 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Message struct {
ID string
Blind bool
Emote bool
Style int
Timestamp int64
Content string
Author string
Type string
Flavor string
Sound string
Stats Stats
Speaker Speaker
Whisper []string
Rolls []string
}
func (m *Message) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO message (game_id, id, blind, emote, style, timestamp, content, author, type, flavor, sound)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (m *Message) InsertObjects(tx *sqlx.Tx) error {
relId := InsertId[string]{id: m.ID, fieldName: "message_id"}
group, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, tx, m.Stats, relId)
InsertWithCtxParallel(group, tx, m.Speaker, relId)
InsertSimpleSliceParallel(group, tx, m.Whisper, &InsertId[string]{id: m.ID, fieldName: "message_id", tableName: "message_whisper"})
InsertSimpleSliceParallel(group, tx, m.Rolls, &InsertId[string]{id: m.ID, fieldName: "message_id", tableName: "message_rolls"})
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (m *Message) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, m.ID, m.Blind, m.Emote, m.Style, m.Timestamp, m.Content, m.Author, m.Type, m.Flavor, m.Sound}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return m.InsertObjects(tx)
}
return nil
}
func (m *Message) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, m.ID, m.Blind, m.Emote, m.Style, m.Timestamp, m.Content, m.Author, m.Type, m.Flavor, m.Sound}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return m.InsertObjects(tx)
}
return nil
}
type Speaker struct {
ID uint
Scene string
Actor string
Token string
Alias string
}
func (s Speaker) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO speaker (%s, scene, actor, token, alias)
VALUES ($1, $2, $3, $4, $5)`, data.fieldName)
}
func (s Speaker) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.Scene, s.Actor, s.Token, s.Alias}
err := tx.QueryRowx(data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}
func (s Speaker) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.Scene, s.Actor, s.Token, s.Alias}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -1,35 +0,0 @@
package db
import "database/sql"
// var (
// ErrRecordNotFound = errors.New("record not found")
// ErrEditConflict = errors.New("edit conflict")
// )
type Models struct {
FoundryState FoundryStateModel
}
// type Models struct {
// Movies interface {
// Insert(movie *Movie) error
// Get(id int64) (*Movie, error)
// Update(movie *Movie) error
// Delete(id int64) error
// GetAll(title string, genres []string, filter Filters) ([]*Movie, Metadata, error)
// }
// }
func NewModels(db *sql.DB) *Models {
return &Models{
FoundryState: FoundryStateModel{DB: db},
}
}
// func NewMockModels() Models {
// return Models{
// Movies: MockMovieModel{},
// Users: MockMovieModel{},
// }
// }

View File

@@ -4,287 +4,185 @@ import (
"context"
"database/sql"
"errors"
"time"
"strings"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Module struct {
Id int64
TextId string
ID string
Title string
Description string
Url string
URL string
License string
Readme string
Bugs string
Changelog string
Version string
Download string
Manifest string
Socket bool
Protected bool
Exclusive bool
PersistentStorage bool
CoreTranslation bool
Library bool
Locked bool
Owned bool
HasStorage bool
Active bool
Availability int
CreatedAt time.Time
Languages []Language
DocumentTypes DocumentTypes
Relationships Relationships
Compatibility Compatibility
Scripts []string
Esmodules []string
Tags []string
Authors []*Author
Media []*Media
Styles []*Style
Languages []*Language
Packs []*Pack
PackFolders []*Folder
}
type Language struct {
Id int64
Lang string
Name string
Path string
func (m *Module) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO module (setup_id, id, title, description, url, license, readme, bugs,
changelog, version, manifest, download, socket, protected, exclusive_, persistent_storage,
core_translation, library, locked, owned, has_storage, active, availability)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17,
$18, $19, $20, $21, $22, $23)
ON CONFLICT(id) DO NOTHING`
}
type Modules []Module
func (modules Modules) GetById(id int) *Module {
return &modules[id]
func (m *Module) ConnectGameQuery(data *InsertId[uint]) {
data.query = `
INSERT INTO game_to_module (game_id, module_id)
VALUES ($1, $2)`
}
func (m FoundryStateModel) InsertModule(module *Module, stateId int64) error {
query := `
INSERT INTO modules (state_id, text_id, title, description, url, version, availability)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, created_at`
func (m *Module) InsertObjects(tx *sqlx.Tx) error {
relId := InsertId[string]{id: m.ID, fieldName: "module_id"}
group, _ := errgroup.WithContext(context.Background())
args := []any{stateId, module.TextId, module.Title, module.Description, module.Url, module.Version, module.Availability}
InsertWithCtxParallel(group, tx, m.DocumentTypes, relId)
InsertWithCtxParallel(group, tx, m.Relationships, relId)
InsertWithCtxParallel(group, tx, m.Compatibility, relId)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
scriptRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "scripts"}
InsertSimpleSliceParallel(group, tx, m.Scripts, scriptRelId)
esModulesRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "es_modules"}
InsertSimpleSliceParallel(group, tx, m.Esmodules, esModulesRelId)
tagsRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "tags"}
InsertSimpleSliceParallel(group, tx, m.Tags, tagsRelId)
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&module.Id, &module.CreatedAt)
if err != nil {
return err
}
for i := range module.Languages {
err = m.InsertModuleLanguages(&module.Languages[i], module.Id)
if err != nil && err != sql.ErrNoRows {
return err
}
}
return m.InsertModuleCompatibility(&module.Compatibility, module.Id)
}
InsertSliceParallel(group, tx, m.Authors, relId)
InsertSliceParallel(group, tx, m.Media, relId)
InsertSliceParallel(group, tx, m.Styles, relId)
InsertSliceParallel(group, tx, m.Languages, relId)
InsertSliceParallel(group, tx, m.Packs, relId)
InsertSliceParallel(group, tx, m.PackFolders, relId)
func (m FoundryStateModel) InsertModuleCompatibility(compatibility *Compatibility, moduleId int64) error {
query := `
INSERT INTO modules_compatibility (module_id, minimum, verified, maximum)
VALUES ($1, $2, $3, $4)
RETURNING id`
args := []any{moduleId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id)
if err != nil && err != sql.ErrNoRows {
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (m FoundryStateModel) InsertModuleLanguages(lang *Language, moduleId int64) error {
query := `
INSERT INTO modules_languages (module_id, language, name, path)
VALUES ($1, $2, $3, $4)
RETURNING id`
func (m *Module) ConnectGame(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{moduleId, lang.Lang, lang.Name, lang.Path}
args := []any{data.id, m.ID}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&lang.Id)
if err != nil && err != sql.ErrNoRows {
_, err := tx.Exec(data.query, args...)
return err
}
return nil
func (m *Module) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
func (m FoundryStateModel) GetModules(idState int64) (Modules, error) {
if idState < 1 {
return nil, ErrorRecordNotFound
var dataId *uint
if strings.EqualFold(data.fieldName, "setup_id") {
dataId = &data.id
}
query := `
SELECT id, created_at, text_id, title, description, url, version, availability
FROM modules
WHERE state_id = $1`
args := []any{dataId, m.ID, m.Title, m.Description, m.URL, m.License, m.Readme, m.Bugs,
m.Changelog, m.Version, m.Manifest, m.Download, m.Socket, m.Protected, m.Exclusive, m.PersistentStorage,
m.CoreTranslation, m.Library, m.Locked, m.Owned, m.HasStorage, m.Active, m.Availability}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := m.DB.QueryContext(ctx, query, idState)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
modules := make(Modules, 0, 1)
for rows.Next() {
var module Module
err := rows.Scan(
&module.Id,
&module.CreatedAt,
&module.TextId,
&module.Title,
&module.Description,
&module.Url,
&module.Version,
&module.Availability,
)
if err != nil {
return nil, err
}
module.Languages, err = m.GetModuleLanguages(module.Id)
if err != nil {
return nil, err
}
compatibility, err := m.GetModuleCompatibility(module.Id)
if err != nil {
return nil, err
}
module.Compatibility = *compatibility
modules = append(modules, module)
}
if err := rows.Err(); err != nil {
return nil, err
}
return modules, nil
}
func (m FoundryStateModel) GetModuleCompatibility(idModule int64) (*Compatibility, error) {
if idModule < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT id, minimum, verified, maximum
FROM modules_compatibility
WHERE module_id = $1`
var compatibility Compatibility
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, idModule).Scan(
&compatibility.Id,
&compatibility.Minimum,
&compatibility.Verified,
&compatibility.Maximum,
)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
return &compatibility, nil
}
func (m FoundryStateModel) GetModuleLanguages(idModule int64) ([]Language, error) {
if idModule < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT id, language, name, path
FROM modules_languages
WHERE module_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := m.DB.QueryContext(ctx, query, idModule)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
languages := make([]Language, 0, 1)
for rows.Next() {
var lang Language
err := rows.Scan(
&lang.Id,
&lang.Lang,
&lang.Name,
&lang.Path,
)
if err != nil {
return nil, err
}
languages = append(languages, lang)
}
if err := rows.Err(); err != nil {
return nil, err
}
return languages, nil
}
func (m FoundryStateModel) DeleteModules(idState int64) error {
if idState < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM modules
WHERE state_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idState)
res, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = m.InsertObjects(tx)
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
if dataId == nil {
dataCopy := *data
m.ConnectGameQuery(&dataCopy)
err = m.ConnectGame(tx, &dataCopy)
}
func (m FoundryStateModel) DeleteModule(idModule int64) error {
if idModule < 1 {
return ErrorRecordNotFound
return err
}
query := `
DELETE FROM modules
WHERE id = $1`
func (m *Module) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
args := []any{data.id, m.ID}
result, err := m.DB.ExecContext(ctx, query, idModule)
_, err := tx.ExecContext(ctx, data.query, args...)
return err
}
func (m *Module) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
var dataId *uint
if strings.EqualFold(data.fieldName, "setup_id") {
dataId = &data.id
}
args := []any{dataId, m.ID, m.Title, m.Description, m.URL, m.License, m.Readme, m.Bugs,
m.Changelog, m.Version, m.Manifest, m.Download, m.Socket, m.Protected, m.Exclusive, m.PersistentStorage,
m.CoreTranslation, m.Library, m.Locked, m.Owned, m.HasStorage, m.Active, m.Availability}
res, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = m.InsertObjects(tx)
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
if dataId == nil {
dataCopy := *data
m.ConnectGameQuery(&dataCopy)
err = m.ConnectGameCtx(ctx, tx, &dataCopy)
}
return err
}

View File

@@ -0,0 +1,116 @@
package db
import (
"context"
"github.com/jmoiron/sqlx"
)
type GameOptions struct {
ID uint
Language string
UpdateChannel string
Port int
}
func (g *GameOptions) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO game_options (game_id, language, update_channel, port)
VALUES ($1, $2, $3, $4)
RETURNING id`
}
func (g *GameOptions) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, g.Language, g.UpdateChannel, g.Port}
err := tx.QueryRowx(data.query, args...).Scan(&g.ID)
if err != nil {
return err
}
return nil
}
func (g *GameOptions) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, g.Language, g.UpdateChannel, g.Port}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&g.ID)
if err != nil {
return err
}
return nil
}
type SetupOptions struct {
ID uint
CSSTheme string
DataPath string
Hostname string
Language string
LocalHostname string
UpdateChannel string
Port int
CompressSocket bool
CompressStatic bool
Fullscreen bool
HotReload bool
ProxySSL bool
Telemetry bool
Upnp bool
DeleteNEDB bool
NoBackups bool
}
func (s *SetupOptions) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO setup_options (setup_id, css_theme, data_path, hostname, language, local_hostname,
update_channel, port, compress_socket, compress_static, fullscreen, hot_reload, proxy_ssl,
telemetry, upnp, delete_nedb, no_backups)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
RETURNING id`
}
func (s *SetupOptions) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.CSSTheme, s.DataPath, s.Hostname, s.Language, s.LocalHostname,
s.UpdateChannel, s.Port, s.CompressSocket, s.CompressStatic, s.Fullscreen, s.HotReload,
s.ProxySSL, s.Telemetry, s.Upnp, s.DeleteNEDB, s.NoBackups}
err := tx.QueryRowx(data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}
func (s *SetupOptions) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.CSSTheme, s.DataPath, s.Hostname, s.Language, s.LocalHostname,
s.UpdateChannel, s.Port, s.CompressSocket, s.CompressStatic, s.Fullscreen, s.HotReload,
s.ProxySSL, s.Telemetry, s.Upnp, s.DeleteNEDB, s.NoBackups}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,101 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type Ownership struct {
ID uint
Player string
Trusted string
Assistant string
}
func (o Ownership) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO ownership (%s, player, trusted, assistant)
VALUES ($1, $2, $3, $4)
RETURNING id`, data.fieldName)
}
func (o Ownership) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, o.Player, o.Trusted, o.Assistant}
err := tx.QueryRowx(data.query, args...).Scan(&o.ID)
if err != nil {
return err
}
return nil
}
func (o Ownership) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, o.Player, o.Trusted, o.Assistant}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&o.ID)
if err != nil {
return err
}
return nil
}
type OwnershipString struct {
ID uint
Key string
Value int
}
func (o OwnershipString) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO ownership_string (%s, key_, value)
VALUES ($1, $2, $3)
RETURNING id`, data.fieldName)
}
func (o OwnershipString) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, o.Key, o.Value}
err := tx.QueryRowx(data.query, args...).Scan(&o.ID)
if err != nil {
return err
}
return nil
}
func (o OwnershipString) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, o.Key, o.Value}
mutex := GetMutex("ownership_string_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&o.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,211 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Pack struct {
ID string
Name string
Label string
Banner string
Path string
Type string
System string
PackageType string
PackageName string
Ownership Ownership
Index []*Index
Folders []*PackFolder
}
func (p *Pack) Query(data *InsertId[string]) {
if !strings.EqualFold(data.fieldName, "game_id") {
data.query = fmt.Sprintf(`
INSERT INTO pack (%s, id, name, label, banner, path, type, system, package_type, package_name)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT(id) DO NOTHING`, data.fieldName)
} else {
data.query = `
INSERT INTO pack (module_id, id, name, label, banner, path, type, system, package_type, package_name)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT(id) DO NOTHING`
}
}
func (p *Pack) ConnectGameQuery(data *InsertId[string]) {
data.query = `
INSERT INTO game_to_pack (game_id, pack_id)
VALUES ($1, $2)`
}
func (p *Pack) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, tx, p.Ownership,
InsertId[string]{id: p.ID, fieldName: "pack_id"})
relId := InsertId[string]{id: p.ID, fieldName: "pack_id"}
InsertSliceParallel(group, tx, p.Index, relId)
InsertSliceParallel(group, tx, p.Folders, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (p *Pack) ConnectGame(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID}
_, err := tx.Exec(data.query, args...)
return err
}
func (p *Pack) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
var dataId *string
if !strings.EqualFold(data.fieldName, "game_id") {
dataId = &data.id
}
args := []any{dataId, p.ID, p.Name, p.Label, p.Banner, p.Path, p.Type,
p.System, p.PackageType, p.PackageName}
mutex := GetMutex("pack_insert")
mutex.Lock()
defer mutex.Unlock()
res, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = p.InsertObjects(tx)
if err != nil {
return err
}
}
if dataId == nil {
dataCopy := *data
p.ConnectGameQuery(&dataCopy)
err = p.ConnectGame(tx, &dataCopy)
}
return err
}
func (p *Pack) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID}
_, err := tx.ExecContext(ctx, data.query, args...)
return err
}
func (p *Pack) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
var dataId *string
if !strings.EqualFold(data.fieldName, "game_id") {
dataId = &data.id
}
args := []any{dataId, p.ID, p.Name, p.Label, p.Banner, p.Path, p.Type,
p.System, p.PackageType, p.PackageName}
mutex := GetMutex("pack_insert")
mutex.Lock()
defer mutex.Unlock()
res, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = p.InsertObjects(tx)
if err != nil {
return err
}
}
if dataId == nil {
dataCopy := *data
p.ConnectGameQuery(&dataCopy)
err = p.ConnectGameCtx(ctx, tx, &dataCopy)
}
return err
}
type PackFolder struct {
ID string
Description string
Name string
Sorting string
Type string
Sort int
}
func (p *PackFolder) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO pack_folder (%s, id, description, name, sorting, type, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7)`, data.fieldName)
}
func (p *PackFolder) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID, p.Description, p.Name, p.Sorting, p.Type, p.Sort}
_, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
return nil
}
func (p *PackFolder) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID, p.Description, p.Name, p.Sorting, p.Type, p.Sort}
_, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,173 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type PackageWarning struct {
ID uint
Key string
Value *PackageWarningsData
}
func (p *PackageWarning) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO package_warnings (%s, key_)
VALUES ($1, $2)
RETURNING id`, data.fieldName)
}
func (p *PackageWarning) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Key}
err := tx.QueryRowx(data.query, args...).Scan(&p.ID)
if err != nil {
return err
}
InsertWithCtx(tx, p.Value, InsertId[uint]{id: p.ID})
return nil
}
func (p *PackageWarning) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.Key}
mutex := GetMutex("package_warning_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&p.ID)
if err != nil {
return err
}
return InsertWithCtx(tx, p.Value, InsertId[uint]{id: p.ID})
}
type PackageWarningsData struct {
ID string
Type string
Manifest string
Reinstallable bool
Warning []string
Error []string
}
func (p *PackageWarningsData) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
warningData := &InsertId[string]{id: p.ID, fieldName: "package_warnings_data_id", tableName: "package_warnings_data_warning"}
InsertSimpleSliceParallel(group, tx, p.Warning, warningData)
errorData := &InsertId[string]{id: p.ID, fieldName: "package_warnings_data_id", tableName: "package_warnings_data_error"}
InsertSimpleSliceParallel(group, tx, p.Warning, errorData)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (p *PackageWarningsData) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO package_warnings_data (id, type, reinstallable, manifest)
VALUES ($1, $2, $3, $4)
ON CONFLICT(id) DO NOTHING`
}
func (p *PackageWarningsData) ConnectGameQuery(data *InsertId[uint]) {
data.query = `
INSERT INTO package_warnings_to_data (package_warnings_id, package_warnings_data_id)
VALUES ($1, $2)`
}
func (p *PackageWarningsData) ConnectGame(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID}
_, err := tx.Exec(data.query, args...)
return err
}
func (p *PackageWarningsData) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{p.ID, p.Type, p.Reinstallable, p.Manifest}
res, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = p.InsertObjects(tx)
}
if err != nil {
return err
}
dataCopy := *data
p.ConnectGameQuery(&dataCopy)
return p.ConnectGame(tx, &dataCopy)
}
func (p *PackageWarningsData) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID}
_, err := tx.ExecContext(ctx, data.query, args...)
return err
}
func (p *PackageWarningsData) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{p.ID, p.Type, p.Reinstallable, p.Manifest}
res, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = p.InsertObjects(tx)
}
if err != nil {
return err
}
dataCopy := *data
p.ConnectGameQuery(&dataCopy)
return p.ConnectGameCtx(ctx, tx, &dataCopy)
}

View File

@@ -0,0 +1,145 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Playlist struct {
ID string
Name string
Folder string
Sorting string
Description string
Channel string
Mode int
Fade int
Seed int
Sort int
Playing bool
Stats Stats
Ownership []OwnershipString
Sounds []*Sound
}
func (p *Playlist) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO playlist (game_id, id, name, folder, sorting, description, channel, mode, fade, seed, sort, playing)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (p *Playlist) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: p.ID, fieldName: "playlist_id"}
InsertWithCtxParallel(group, tx, p.Stats, relId)
InsertSliceParallel(group, tx, p.Ownership, relId)
InsertSliceParallel(group, tx, p.Sounds, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (p *Playlist) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID, p.Name, p.Folder, p.Sorting, p.Description, p.Channel, p.Mode, p.Fade, p.Seed, p.Sort, p.Playing}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return p.InsertObjects(tx)
}
return nil
}
func (p *Playlist) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, p.ID, p.Name, p.Folder, p.Sorting, p.Description, p.Channel, p.Mode, p.Fade, p.Seed, p.Sort, p.Playing}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return p.InsertObjects(tx)
}
return nil
}
type Sound struct {
ID string
Name string
Path string
Channel string
Description string
Fade int
Sort int
Repeat bool
Playing bool
Volume float64
PausedTime float64
}
func (s *Sound) Query(data *InsertId[string]) {
data.query = `
INSERT INTO sound (playlist_id, id, name, path, channel, description, fade, sort, repeat, playing, volume, paused_time)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
ON CONFLICT(id) DO UPDATE SET
playlist_id = EXCLUDED.playlist_id,
updated_at = datetime('now')`
}
func (s *Sound) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.ID, s.Name, s.Path, s.Channel, s.Description, s.Fade, s.Sort, s.Repeat, s.Playing, s.Volume, s.PausedTime}
_, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
return nil
}
func (s *Sound) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.ID, s.Name, s.Path, s.Channel, s.Description, s.Fade, s.Sort, s.Repeat, s.Playing, s.Volume, s.PausedTime}
_, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,135 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"strconv"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Relationships struct {
ID uint
Systems []RelationshipsData
Requires []RelationshipsData
Recommends []RelationshipsData
Conflicts []RelationshipsData
}
func (r Relationships) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO relationships (%s)
VALUES ($1)
RETURNING id`, data.fieldName)
}
func (r Relationships) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id}
err := tx.QueryRowx(data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, r.Systems, InsertId[uint]{id: r.ID, tableName: "relationships_systems"})
InsertSliceParallel(group, tx, r.Requires, InsertId[uint]{id: r.ID, tableName: "relationships_requires"})
InsertSliceParallel(group, tx, r.Recommends, InsertId[uint]{id: r.ID, tableName: "relationships_recommends"})
InsertSliceParallel(group, tx, r.Conflicts, InsertId[uint]{id: r.ID, tableName: "relationships_conflicts"})
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (r Relationships) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id}
mutex := GetMutex("relationships_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertSliceParallel(group, tx, r.Systems, InsertId[uint]{id: r.ID, tableName: "relationships_systems"})
InsertSliceParallel(group, tx, r.Requires, InsertId[uint]{id: r.ID, tableName: "relationships_requires"})
InsertSliceParallel(group, tx, r.Recommends, InsertId[uint]{id: r.ID, tableName: "relationships_recommends"})
InsertSliceParallel(group, tx, r.Conflicts, InsertId[uint]{id: r.ID, tableName: "relationships_conflicts"})
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
type RelationshipsData struct {
ID uint
Key string
Type string
Manifest string
Compatibility Compatibility
}
func (r RelationshipsData) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO %s (relationships_id, key_, type, manifest)
VALUES ($1, $2, $3, $4)`, data.tableName)
}
func (r RelationshipsData) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Key, r.Type, r.Manifest}
err := tx.QueryRowx(data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, tx, r.Compatibility, InsertId[string]{id: strconv.FormatUint(uint64(r.ID), 10), fieldName: fmt.Sprintf("%s_id", data.tableName)})
return group.Wait()
}
func (r RelationshipsData) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Key, r.Type, r.Manifest}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
group, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, tx, r.Compatibility, InsertId[string]{id: strconv.FormatUint(uint64(r.ID), 10), fieldName: fmt.Sprintf("%s_id", data.tableName)})
return group.Wait()
}

View File

@@ -0,0 +1,61 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type Release struct {
ID uint
Generation int
Build int
NodeVersion int
MaxGeneration int
MaxStableGeneration int
Time int64
Channel string
Suffix string
}
func (r *Release) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO release_ (%s, generation, build, node_version, max_generation, max_stable_generation,
time, channel, suffix)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id`, data.fieldName)
}
func (r *Release) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Generation, r.Build, r.NodeVersion, r.MaxGeneration, r.MaxStableGeneration,
r.Time, r.Channel, r.Suffix}
err := tx.QueryRowx(data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
return nil
}
func (r *Release) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Generation, r.Build, r.NodeVersion, r.MaxGeneration, r.MaxStableGeneration,
r.Time, r.Channel, r.Suffix}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,159 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Ring struct {
ID uint
Enabled bool
Effects int
RingColors RingColors
Subject Subject
}
func (r *Ring) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO ring (%s, enabled, effects)
VALUES ($1, $2, $3)
RETURNING id`, data.fieldName)
}
func (r *Ring) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[uint]{id: r.ID, fieldName: "ring_id"}
InsertWithCtxParallel(group, tx, r.RingColors, relId)
InsertWithCtxParallel(group, tx, r.Subject, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (r *Ring) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Enabled, r.Effects}
err := tx.QueryRowx(data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
return nil
}
func (r *Ring) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Enabled, r.Effects}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
return nil
}
type RingColors struct {
ID uint
Ring string
Background string
}
func (r RingColors) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO ring_colors (%s, ring, background)
VALUES ($1, $2, $3)
RETURNING id`, data.fieldName)
}
func (r RingColors) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Ring, r.Background}
err := tx.QueryRowx(data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
return nil
}
func (r RingColors) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, r.Ring, r.Background}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&r.ID)
if err != nil {
return err
}
return nil
}
type Subject struct {
ID uint
Scale int
Texture string
}
func (s Subject) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO ring_colors (%s, scale, texture)
VALUES ($1, $2, $3)
RETURNING id`, data.fieldName)
}
func (s Subject) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.Scale, s.Texture}
err := tx.QueryRowx(data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}
func (s Subject) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.Scale, s.Texture}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -21,7 +21,7 @@ type Scene struct {
Grid SceneGrid
TokenVision bool
Drawings []SceneDrawing
Tokens []Token
Tokens []*Token
Lights []SceneLight
Notes []Note
Sounds []ScenesSound
@@ -119,7 +119,7 @@ type SceneLight struct {
Rotation int
Walls bool
Vision bool
Config Light
Config *Light
Hidden bool
Elevation int
}

View File

@@ -0,0 +1,79 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Setting struct {
ID string
Key string
Value string
Stats Stats
}
func (s *Setting) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO setting (game_id, id, key_, value)
VALUES ($1, $2, $3, $4)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (s *Setting) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: s.ID, fieldName: "setting_id"}
InsertWithCtxParallel(group, tx, s.Stats, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (s *Setting) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.ID, s.Key, s.Value}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return s.InsertObjects(tx)
}
return nil
}
func (s *Setting) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.ID, s.Key, s.Value}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return s.InsertObjects(tx)
}
return nil
}

View File

@@ -0,0 +1,194 @@
package db
import (
"context"
"database/sql"
"errors"
"strconv"
"time"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Setup struct {
ID uint
CreatedAt time.Time
IsAdmin bool
IsSetup bool
CoreUpdate CoreUpdate
FeaturedContent FeaturedContent
Files Files
Options *SetupOptions
Release Release
Languages []*SetupLanguage
Modules []*Module
News []*News
PackageWarnings []*PackageWarning
Systems []*System
Worlds []*World
}
func (s *Setup) InsertObjects(tx *sqlx.Tx) error {
relData := InsertId[uint]{id: s.ID, fieldName: "setup_id"}
group, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, tx, &s.CoreUpdate, relData)
InsertWithCtxParallel(group, tx, &s.FeaturedContent, relData)
InsertWithCtxParallel(group, tx, &s.Files, relData)
InsertWithCtxParallel(group, tx, s.Options, relData)
InsertWithCtxParallel(group, tx, &s.Release, relData)
InsertSliceParallel(group, tx, s.Languages, relData)
InsertSliceParallel(group, tx, s.Modules, relData)
InsertSliceParallel(group, tx, s.News, relData)
InsertSliceParallel(group, tx, s.PackageWarnings, relData)
relDataString := InsertId[string]{
id: strconv.FormatUint(uint64(s.ID), 10),
fieldName: "setup_id",
}
InsertSliceParallel(group, tx, s.Systems, relDataString)
InsertSliceParallel(group, tx, s.Worlds, relDataString)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (s *Setup) Insert(db *sqlx.DB) error {
tx := db.MustBegin()
defer tx.Rollback()
const query = `
INSERT INTO setup (is_admin, is_setup)
VALUES ($1, $2)
RETURNING id, created_at`
args := []any{s.IsAdmin, s.IsSetup}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := tx.QueryRowxContext(ctx, query, args...).Scan(&s.ID, &s.CreatedAt)
if err != nil {
return err
}
err = s.InsertObjects(tx)
if err != nil {
return err
}
return tx.Commit()
}
type FeaturedContent struct {
ID uint
Title string
Caption string
URL string
Image string
}
func (f *FeaturedContent) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO featured_content (setup_id, title, caption, url, image)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`
}
func (f *FeaturedContent) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Title, f.Caption, f.URL, f.Image}
err := tx.QueryRowx(data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
func (f *FeaturedContent) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, f.Title, f.Caption, f.URL, f.Image}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
type News struct {
ID uint
Title string
Caption string
URL string
Image string
}
func (n *News) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO news (setup_id, title, caption, url, image)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`
}
func (n *News) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, n.Title, n.Caption, n.URL, n.Image}
err := tx.QueryRowx(data.query, args...).Scan(&n.ID)
if err != nil {
return err
}
return nil
}
func (n *News) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, n.Title, n.Caption, n.URL, n.Image}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&n.ID)
if err != nil {
return err
}
return nil
}
func HasSetup(db *sqlx.DB) (bool, error) {
const query = `
SELECT EXISTS(SELECT 1 FROM setup)`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var exists bool
err := db.GetContext(ctx, &exists, query)
if err != nil {
return false, err
}
return exists, nil
}

View File

@@ -0,0 +1,58 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type Stats struct {
ID uint
CoreVersion string
SystemID string
SystemVersion string
LastModifiedBy string
ModifiedTime int64
}
func (s Stats) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO stats (%s, core_version, system_id, system_version, last_modified_by, modified_time)
VALUES ($1, $2, $3, $4, $5, $6)`, data.fieldName)
}
func (s Stats) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.CoreVersion, s.SystemID, s.SystemVersion, s.LastModifiedBy, s.ModifiedTime}
err := tx.QueryRowx(data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}
func (s Stats) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.CoreVersion, s.SystemID, s.SystemVersion, s.LastModifiedBy, s.ModifiedTime}
mutex := GetMutex("stats_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,55 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type Style struct {
ID uint
Src string
}
func (s *Style) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO style (%s, src)
VALUES ($1, $2)
RETURNING id`, data.fieldName)
}
func (s *Style) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.Src}
err := tx.QueryRowx(data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}
func (s *Style) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.Src}
mutex := GetMutex("style_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -4,201 +4,186 @@ import (
"context"
"database/sql"
"errors"
"time"
"strings"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type System struct {
Id int64
TextId string
ID string
Title string
Description string
Url string
URL string
License string
Bugs string
Changelog string
Version string
Manifest string
Download string
CreatedAt time.Time
Background string
PrimaryTokenAttribute string
Availability int
Socket bool
Protected bool
Exclusive bool
PersistentStorage bool
Locked bool
Owned bool
HasStorage bool
Compatibility Compatibility
Relationships Relationships
DocumentTypes DocumentTypes
Grid *Grid
Esmodules []string
Scripts []string
Tags []string
Authors []*Author
Media []*Media
Packs []*Pack
Styles []*Style
Languages []*Language
PackFolders []*Folder
}
type Systems []System
func (systems Systems) GetById(id int) *System {
return &systems[id]
func (s *System) Query(data *InsertId[string]) {
data.query = `
INSERT INTO system (setup_id, id, title, description, url, license, bugs, changelog, version, manifest,
download, background, primary_token_attribute, availability, socket, protected, exclusive_,
persistent_storage, locked, owned, has_storage)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
ON CONFLICT(id) DO NOTHING`
}
func (m FoundryStateModel) InsertSystem(system *System, stateId int64) error {
query := `
INSERT INTO systems (state_id, text_id, title, description, url, download)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, created_at`
args := []any{stateId, system.TextId, system.Title, system.Description, system.Url, system.Download}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&system.Id, &system.CreatedAt)
if err != nil && err != sql.ErrNoRows {
return err
}
return m.InsertSystemCompatibility(&system.Compatibility, system.Id)
func (s *System) ConnectGameQuery(data *InsertId[string]) {
data.query = `
INSERT INTO game_to_systems (game_id, system_id)
VALUES ($1, $2)`
}
func (m FoundryStateModel) InsertSystemCompatibility(compatibility *Compatibility, systemId int64) error {
query := `
INSERT INTO systems_compatibility (system_id, minimum, verified, maximum)
VALUES ($1, $2, $3, $4)`
func (s *System) InsertObjects(tx *sqlx.Tx) error {
relId := InsertId[string]{id: s.ID, fieldName: "system_id"}
group, _ := errgroup.WithContext(context.Background())
args := []any{systemId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum}
InsertWithCtxParallel(group, tx, s.Compatibility, relId)
InsertWithCtxParallel(group, tx, s.Relationships, relId)
InsertWithCtxParallel(group, tx, s.DocumentTypes, relId)
InsertWithCtxParallel(group, tx, s.Grid, relId)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
esModulesRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "es_modules"}
InsertSimpleSliceParallel(group, tx, s.Esmodules, esModulesRelId)
scriptRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "scripts"}
InsertSimpleSliceParallel(group, tx, s.Scripts, scriptRelId)
tagsRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "tags"}
InsertSimpleSliceParallel(group, tx, s.Tags, tagsRelId)
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id)
if err != nil && err != sql.ErrNoRows {
InsertSliceParallel(group, tx, s.Authors, relId)
InsertSliceParallel(group, tx, s.Media, relId)
InsertSliceParallel(group, tx, s.Styles, relId)
InsertSliceParallel(group, tx, s.Languages, relId)
InsertSliceParallel(group, tx, s.Packs, relId)
InsertSliceParallel(group, tx, s.PackFolders, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (m FoundryStateModel) GetSystems(idState int64) (Systems, error) {
if idState < 1 {
return nil, ErrorRecordNotFound
func (s *System) ConnectGame(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
query := `
SELECT id, created_at, text_id, title, description, url, download
FROM systems
WHERE state_id = $1`
args := []any{data.id, s.ID}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := m.DB.QueryContext(ctx, query, idState)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
_, err := tx.Exec(data.query, args...)
return err
}
systems := make(Systems, 0, 1)
for rows.Next() {
var system System
err := rows.Scan(
&system.Id,
&system.CreatedAt,
&system.TextId,
&system.Title,
&system.Description,
&system.Url,
&system.Download,
)
if err != nil {
return nil, err
}
compatibility, err := m.GetModuleCompatibility(system.Id)
if err != nil {
return nil, err
}
system.Compatibility = *compatibility
systems = append(systems, system)
func (s *System) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
if err := rows.Err(); err != nil {
return nil, err
var dataId *string
if strings.EqualFold(data.fieldName, "setup_id") {
dataId = &data.id
}
return systems, nil
}
args := []any{dataId, s.ID, s.Title, s.Description, s.URL, s.License, s.Bugs,
s.Changelog, s.Version, s.Manifest, s.Download, s.Background,
s.PrimaryTokenAttribute, s.Availability, s.Socket, s.Protected, s.Exclusive,
s.PersistentStorage, s.Locked, s.Owned, s.HasStorage}
func (m FoundryStateModel) GetSystemCompatibility(idSystem int64) (*Compatibility, error) {
if idSystem < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT id, minimum, verified, maximum
FROM systems_compatibility
WHERE system_id = $1`
var compatibility Compatibility
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, idSystem).Scan(
&compatibility.Id,
&compatibility.Minimum,
&compatibility.Verified,
&compatibility.Maximum,
)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
return &compatibility, nil
}
func (m FoundryStateModel) DeleteSystems(idState int64) error {
if idState < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM systems
WHERE state_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idState)
res, err := tx.Exec(data.query, args...)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = s.InsertObjects(tx)
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
if dataId == nil {
dataCopy := *data
s.ConnectGameQuery(&dataCopy)
err = s.ConnectGame(tx, &dataCopy)
}
func (m FoundryStateModel) DeleteSystem(idSystem int64) error {
if idSystem < 1 {
return ErrorRecordNotFound
return err
}
query := `
DELETE FROM systems
WHERE id = $1`
func (s *System) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
args := []any{data.id, s.ID}
result, err := m.DB.ExecContext(ctx, query, idSystem)
_, err := tx.ExecContext(ctx, data.query, args...)
return err
}
func (s *System) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
var dataId *string
if strings.EqualFold(data.fieldName, "setup_id") {
dataId = &data.id
}
args := []any{dataId, s.ID, s.Title, s.Description, s.URL, s.License, s.Bugs,
s.Changelog, s.Version, s.Manifest, s.Download, s.Background,
s.PrimaryTokenAttribute, s.Availability, s.Socket, s.Protected, s.Exclusive,
s.PersistentStorage, s.Locked, s.Owned, s.HasStorage}
res, err := tx.ExecContext(ctx, data.query, args...)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
err = s.InsertObjects(tx)
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
if dataId == nil {
dataCopy := *data
s.ConnectGameQuery(&dataCopy)
err = s.ConnectGameCtx(ctx, tx, &dataCopy)
}
return err
}

View File

@@ -0,0 +1,163 @@
package db
import (
"context"
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Table struct {
ID string
Name string
Description string
Formula string
Img string
Folder string
Sort int
Replacement bool
DisplayRoll bool
Stats Stats
Ownership []OwnershipString
Results []*TableResult
}
func (t *Table) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO table_ (game_id, id, name, description, formula, img, folder, sort, replacement, display_roll)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (t *Table) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[string]{id: t.ID, fieldName: "table_id"}
InsertWithCtxParallel(group, tx, t.Stats, relId)
InsertSliceParallel(group, tx, t.Ownership, relId)
InsertSliceParallel(group, tx, t.Results, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (t *Table) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.ID, t.Name, t.Description, t.Formula, t.Img, t.Folder, t.Sort, t.Replacement, t.DisplayRoll}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return t.InsertObjects(tx)
}
return nil
}
func (t *Table) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.ID, t.Name, t.Description, t.Formula, t.Img, t.Folder, t.Sort, t.Replacement, t.DisplayRoll}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return t.InsertObjects(tx)
}
return nil
}
type TableResult struct {
ID string
Type string
Img string
Description string
Name string
Weight int
Drawn bool
Stats Stats
Range []int
}
func (t *TableResult) Query(data *InsertId[string]) {
data.query = `
INSERT INTO table_result (table_id, id, type, img, description, name, weight, drawn)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT(id) DO UPDATE SET
table_id = EXCLUDED.table_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
func (t *TableResult) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, tx, t.Stats, InsertId[string]{id: t.ID, fieldName: "table_result_id"})
InsertSimpleSlice(tx, t.Range, &InsertId[string]{id: t.ID, fieldName: "table_result_id", tableName: "table_result_range"})
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (t *TableResult) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.ID, t.Type, t.Img, t.Description, t.Name, t.Weight, t.Drawn}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return t.InsertObjects(tx)
}
return nil
}
func (t *TableResult) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.ID, t.Type, t.Img, t.Description, t.Name, t.Weight, t.Drawn}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return t.InsertObjects(tx)
}
return nil
}

View File

@@ -0,0 +1,335 @@
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type Token struct {
ID uint
Name string
ActorLink bool
AppendNumber bool
PrependAdjective bool
LockRotation bool
RandomImg bool
DisplayName int
DisplayBars int
Disposition int
Rotation int
Alpha int
Width float64
Height float64
Ring *Ring
Sight *TokenSight
Texture *TokenTexture
Bar1 TokenBar
Bar2 TokenBar
Light *Light
Occludable TokenOccludable
TurnMarker TokenTurnMarker
}
func (t *Token) Query(data *InsertId[string]) {
data.query = `
INSERT INTO token (actor_id, name, actor_link, append_number, prepend_adjective, lock_rotation, random_img, display_name,
display_bars, disposition, rotation, alpha, width, height)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
RETURNING id`
}
func (t *Token) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
relId := InsertId[uint]{id: t.ID, fieldName: "token_id"}
InsertWithCtxParallel(group, tx, t.Ring, relId)
InsertWithCtxParallel(group, tx, t.Sight, relId)
InsertWithCtxParallel(group, tx, t.Texture, relId)
InsertWithCtxParallel(group, tx, t.Bar1, InsertId[uint]{id: t.ID, fieldName: "token_id", tableName: "token_bar_1"})
InsertWithCtxParallel(group, tx, t.Bar1, InsertId[uint]{id: t.ID, fieldName: "token_id", tableName: "token_bar_2"})
InsertWithCtxParallel(group, tx, t.Light, relId)
InsertWithCtxParallel(group, tx, t.Occludable, relId)
InsertWithCtxParallel(group, tx, t.TurnMarker, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (t *Token) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Name, t.ActorLink, t.AppendNumber, t.PrependAdjective, t.LockRotation, t.RandomImg,
t.DisplayName, t.DisplayBars, t.Disposition, t.Rotation, t.Alpha, t.Width, t.Height}
err := tx.QueryRowx(data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return t.InsertObjects(tx)
}
func (t *Token) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Name, t.ActorLink, t.AppendNumber, t.PrependAdjective, t.LockRotation, t.RandomImg,
t.DisplayName, t.DisplayBars, t.Disposition, t.Rotation, t.Alpha, t.Width, t.Height}
mutex := GetMutex("token_insert")
mutex.Lock()
defer mutex.Unlock()
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return t.InsertObjects(tx)
}
type TokenTexture struct {
ID uint
Src string
Fit string
Tint string
ScaleX float64
ScaleY float64
OffsetX float64
OffsetY float64
Rotation float64
AnchorX float64
AnchorY float64
AlphaThreshold float64
}
func (t *TokenTexture) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO token_texture (%s, src, fit, tint, scale_x, scale_y, offset_x, offset_y, rotation, anchor_x, anchor_y, alpha_threshold)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
RETURNING id`, data.fieldName)
}
func (t *TokenTexture) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Src, t.Fit, t.Tint, t.ScaleX, t.ScaleY, t.OffsetX, t.OffsetY, t.Rotation, t.AnchorX, t.AnchorY, t.AlphaThreshold}
err := tx.QueryRowx(data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
func (t *TokenTexture) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Src, t.Fit, t.Tint, t.ScaleX, t.ScaleY, t.OffsetX, t.OffsetY, t.Rotation, t.AnchorX, t.AnchorY, t.AlphaThreshold}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
type TokenSight struct {
ID uint
Color string
VisionMode string
Range int
Angle int
Attenuation float64
Brightness float64
Enabled bool
}
func (t *TokenSight) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO token_sight (%s, color, vision_mode, range_, angle, attenuation, brightness, enabled)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id`, data.fieldName)
}
func (t *TokenSight) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Color, t.VisionMode, t.Range, t.Angle, t.Attenuation, t.Brightness, t.Enabled}
err := tx.QueryRowx(data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
func (t *TokenSight) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Color, t.VisionMode, t.Range, t.Angle, t.Attenuation, t.Brightness, t.Enabled}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
type TokenBar struct {
ID uint
Attribute string
}
func (t TokenBar) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO %s (%s, attribute)
VALUES ($1, $2)
RETURNING id`, data.tableName, data.fieldName)
}
func (t TokenBar) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Attribute}
err := tx.QueryRowx(data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
func (t TokenBar) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Attribute}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
type TokenOccludable struct {
ID uint
Radius int
}
func (t TokenOccludable) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO token_occludable (%s, radius)
VALUES ($1, $2)
RETURNING id`, data.fieldName)
}
func (t TokenOccludable) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Radius}
err := tx.QueryRowx(data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
func (t TokenOccludable) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Radius}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
type TokenTurnMarker struct {
ID uint
Mode int
Animation string
Src string
Disposition bool
}
func (t TokenTurnMarker) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO token_turn_maker (%s, mode, animation, src, disposition)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`, data.fieldName)
}
func (t TokenTurnMarker) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Mode, t.Animation, t.Src, t.Disposition}
err := tx.QueryRowx(data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}
func (t TokenTurnMarker) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, t.Mode, t.Animation, t.Src, t.Disposition}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&t.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,101 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type CoreUpdate struct {
ID uint
HasUpdate bool
CanUpdate bool
CouldReachWebsite bool
SlowResponse bool
WillDisableModules bool
Version string
Channel string
}
func (c *CoreUpdate) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO core_update (%s, has_update, can_update, could_reach_website, slow_response, will_disable_modules, version, channel)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id`, data.fieldName)
}
func (c *CoreUpdate) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.HasUpdate, c.CanUpdate, c.CouldReachWebsite, c.SlowResponse, c.WillDisableModules, c.Version, c.Channel}
err := tx.QueryRowx(data.query, args...).Scan(&c.ID)
if err != nil {
return err
}
return nil
}
func (c *CoreUpdate) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, c.HasUpdate, c.CanUpdate, c.CouldReachWebsite, c.SlowResponse, c.WillDisableModules, c.Version, c.Channel}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&c.ID)
if err != nil {
return err
}
return nil
}
type SystemUpdate struct {
ID uint
HasUpdate bool
Version string
}
func (s *SystemUpdate) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(`
INSERT INTO system_update (%s, has_update, version)
VALUES ($1, $2, $3)
RETURNING id`, data.fieldName)
}
func (s *SystemUpdate) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.HasUpdate, s.Version}
err := tx.QueryRowx(data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}
func (s *SystemUpdate) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.HasUpdate, s.Version}
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&s.ID)
if err != nil {
return err
}
return nil
}

View File

@@ -4,293 +4,125 @@ import (
"context"
"database/sql"
"errors"
"time"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type User struct {
Id int64
ID string
Name string
Role int
Avatar string
Character string
Color string
Pronouns string
CreatedAt time.Time
Hotbar map[string]string
Stats UserStats
Role int
Stats Stats
Hotbar []UserHotbar
}
type UserStats struct {
Id int64
CoreVersion string
SystemId string
SystemVersion string
CreatedTime int64
ModifiedTime int64
LastModifiedBy string
func (u *User) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO user (game_id, id, name, avatar, character, color, pronouns, role)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
type Users []User
func (u *User) InsertObjects(tx *sqlx.Tx) error {
group, _ := errgroup.WithContext(context.Background())
func (users Users) GetById(id int) *User {
return &users[id]
relId := InsertId[string]{id: u.ID, fieldName: "user_id"}
InsertWithCtxParallel(group, tx, u.Stats, relId)
InsertSliceParallel(group, tx, u.Hotbar, relId)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (m FoundryStateModel) InsertUser(user *User, stateId int64) error {
query := `
INSERT INTO users (state_id, name, role, character, color, pronouns)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, created_at`
func (u *User) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{stateId, user.Name, user.Role, user.Character, user.Color, user.Pronouns}
args := []any{data.id, u.ID, u.Name, u.Avatar, u.Character, u.Color, u.Pronouns, u.Role}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&user.Id, &user.CreatedAt)
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
for k, v := range user.Hotbar {
err = m.InsertUserHotbar(k, v, user.Id)
if isInserted {
return u.InsertObjects(tx)
}
return nil
}
func (u *User) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, u.ID, u.Name, u.Avatar, u.Character, u.Color, u.Pronouns, u.Role}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return u.InsertObjects(tx)
}
return nil
}
return m.InsertUserStats(&user.Stats, user.Id)
type UserHotbar struct {
ID uint
Key int
Value string
}
func (m FoundryStateModel) InsertUserHotbar(key string, value string, userId int64) error {
query := `
INSERT INTO users_hotbar (user_id, key, value)
func (u UserHotbar) Query(data *InsertId[string]) {
data.query = `
INSERT INTO hotbar (user_id, key_, value)
VALUES ($1, $2, $3)`
args := []any{userId, key, value}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_, err := m.DB.ExecContext(ctx, query, args...)
if err != nil && err != sql.ErrNoRows {
return err
}
return nil
}
func (m FoundryStateModel) InsertUserStats(stats *UserStats, userId int64) error {
query := `
INSERT INTO users_stats (user_id, core_version, system_id, system_version, created_time, modified_time, last_modified_by)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id`
args := []any{userId, stats.CoreVersion, stats.SystemId, stats.SystemVersion, stats.CreatedTime, stats.ModifiedTime, stats.LastModifiedBy}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return m.DB.QueryRowContext(ctx, query, args...).Scan(&stats.Id)
func (u UserHotbar) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
func (m FoundryStateModel) GetUsers(idState int64) (Users, error) {
if idState < 1 {
return nil, ErrorRecordNotFound
}
args := []any{data.id, u.Key, u.Value}
query := `
SELECT id, created_at, name, role, character, color, pronouns
FROM users
WHERE state_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := m.DB.QueryContext(ctx, query, idState)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
users := make(Users, 0, 1)
for rows.Next() {
var user User
err := rows.Scan(
&user.Id,
&user.CreatedAt,
&user.Name,
&user.Role,
&user.Character,
&user.Color,
&user.Pronouns,
)
if err != nil {
return nil, err
}
user.Hotbar, err = m.GetUserHotbar(user.Id)
if err != nil {
return nil, err
}
userState, err := m.GetUserStats(user.Id)
if err != nil {
return nil, err
}
user.Stats = *userState
users = append(users, user)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}
func (m FoundryStateModel) GetUserHotbar(idUser int64) (map[string]string, error) {
if idUser < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT key, value
FROM users_hotbar
WHERE user_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := m.DB.QueryContext(ctx, query, idUser)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
hotbar := make(map[string]string)
for rows.Next() {
var key string
var val string
err := rows.Scan(
&key,
&val,
)
if err != nil {
return nil, err
}
hotbar[key] = val
}
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
return hotbar, nil
}
func (m FoundryStateModel) GetUserStats(idSystem int64) (*UserStats, error) {
if idSystem < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT id, core_version, system_id, system_version, created_time, modified_time, last_modified_by
FROM users_stats
WHERE user_id = $1`
var userStats UserStats
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, idSystem).Scan(
&userStats.Id,
&userStats.CoreVersion,
&userStats.SystemId,
&userStats.SystemVersion,
&userStats.CreatedTime,
&userStats.ModifiedTime,
&userStats.LastModifiedBy,
)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
return &userStats, nil
}
func (m FoundryStateModel) DeleteUsers(idState int64) error {
if idState < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM users
WHERE state_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idState)
err := tx.QueryRowx(data.query, args...).Scan(&u.ID)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func (m FoundryStateModel) DeleteUser(idUser int64) error {
if idUser < 1 {
return ErrorRecordNotFound
func (u UserHotbar) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
query := `
DELETE FROM users
WHERE id = $1`
args := []any{data.id, u.Key, u.Value}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idUser)
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&u.ID)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}

View File

@@ -0,0 +1,248 @@
package db
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/jmoiron/sqlx"
"github.com/mattn/go-sqlite3"
"golang.org/x/sync/errgroup"
)
var (
ErrNoQuery = errors.New("Query has not been set")
ErrorRecordNotFound = errors.New("Record not found")
)
type AllowedIds interface {
~uint | ~string
}
type InsertId[T AllowedIds] struct {
id T
fieldName string
tableName string
query string
}
type Insertable[T AllowedIds] interface {
Query(data *InsertId[T])
Insert(tx *sqlx.Tx, relId *InsertId[T]) error
InsertCtx(ctx context.Context, tx *sqlx.Tx, relId *InsertId[T]) error
}
func InsertWithCtx[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data I, relId InsertId[T]) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
data.Query(&relId)
return data.InsertCtx(ctx, tx, &relId)
}
func InsertWithCtxParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *sqlx.Tx, data I, relId InsertId[T]) {
g.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
data.Query(&relId)
err := data.InsertCtx(ctx, tx, &relId)
return err
})
}
func InsertSlice[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data []I, relId InsertId[T]) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if len(data) > 0 {
data[0].Query(&relId)
}
for i := range data {
err := data[i].InsertCtx(ctx, tx, &relId)
if err != nil {
return err
}
}
return nil
}
func InsertSliceParallelTimeout[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *sqlx.Tx, data []I, relId InsertId[T], timeout time.Duration) {
g.Go(func() error {
wg, _ := errgroup.WithContext(context.Background())
var err error
if len(data) > 0 {
data[0].Query(&relId)
}
for i := range data {
wg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err = data[i].InsertCtx(ctx, tx, &relId)
return err
})
}
return wg.Wait()
})
}
func InsertSliceParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *sqlx.Tx, data []I, relId InsertId[T]) {
g.Go(func() error {
wg, _ := errgroup.WithContext(context.Background())
var err error
if len(data) > 0 {
data[0].Query(&relId)
}
for i := range data {
wg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = data[i].InsertCtx(ctx, tx, &relId)
return err
})
}
return wg.Wait()
})
}
func InsertSimpleSlice[T AllowedIds, I any](tx *sqlx.Tx, data []I, relId *InsertId[T]) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
query := fmt.Sprintf(`
INSERT INTO %s (%s, value)
VALUES ($1, $2)`, relId.tableName, relId.fieldName)
for i := range data {
_, err := tx.ExecContext(ctx, query, relId.id, data[i])
if err != nil {
return err
}
}
return nil
}
func InsertSimpleSliceParallel[T AllowedIds, I any](g *errgroup.Group, tx *sqlx.Tx, data []I, relId *InsertId[T]) {
g.Go(func() error {
wg, _ := errgroup.WithContext(context.Background())
query := fmt.Sprintf(`
INSERT INTO %s (%s, value)
VALUES ($1, $2)`, relId.tableName, relId.fieldName)
for i := range data {
wg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := tx.ExecContext(ctx, query, relId.id, data[i])
return err
})
}
return wg.Wait()
})
}
func DeleteSetupAll(db *sqlx.DB) error {
const query = `DELETE FROM setup`
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
tx := db.MustBegin()
defer tx.Rollback()
result, err := tx.ExecContext(ctx, query)
if err != nil {
return err
}
tx.Commit()
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func DeleteGameAll(db *sqlx.DB) error {
const query = `DELETE FROM game`
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
tx := db.MustBegin()
defer tx.Rollback()
result, err := tx.ExecContext(ctx, query)
if err != nil {
return err
}
tx.Commit()
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func DeleteSeqAll(db *sqlx.DB) error {
const query = `DELETE FROM sqlite_sequence`
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
tx := db.MustBegin()
defer tx.Rollback()
result, err := tx.ExecContext(ctx, query)
if err != nil {
return err
}
tx.Commit()
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func IsErrUniqueConstraint(err error) {
if sqliteErr, ok := err.(sqlite3.Error); ok {
// SQLITE_CONSTRAINT_UNIQUE (extended code 2067)
// or SQLITE_CONSTRAINT (basic code 19)
if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique ||
sqliteErr.Code == sqlite3.ErrConstraint {
}
}
}
var modelInsertLock sync.Map
func GetMutex(id string) *sync.Mutex {
val, _ := modelInsertLock.LoadOrStore(id, &sync.Mutex{})
return val.(*sync.Mutex)
}

View File

@@ -4,231 +4,179 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type World struct {
Id int64
TextId string
ID string
Title string
Description string
Version string
System string
Background string
JoinTheme string
CoreVersion string
SystemVersion string
LastPlayed string
PlayTime int64
Playtime int
Availability int
NextSession time.Time
CreatedAt time.Time
Socket bool
Protected bool
Exclusive bool
PersistentStorage bool
Locked bool
Owned bool
HasStorage bool
Compatibility Compatibility
Relationships Relationships
Tags []string
Scripts []string
Esmodules []string
Authors []*Author
Media []*Media
Styles []*Style
Languages []*Language
Packs []*Pack
PackFolders []*Folder
}
type Worlds []World
func (worlds Worlds) GetById(id int) *World {
return &worlds[id]
func (w *World) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
INSERT INTO world (%[1]s, id, title, description, version, system, background, join_theme,
core_version, system_version, last_played, playtime, availability, next_session, socket,
protected, exclusive_, persistent_storage, locked, owned, has_storage)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
ON CONFLICT(id) DO UPDATE SET
%[1]s = EXCLUDED.%[1]s,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`,
data.fieldName)
}
func (m FoundryStateModel) InsertWorld(world *World, stateId int64) error {
query := `
INSERT INTO worlds (state_id, text_id, title, description, system, core_version, system_version, playtime, next_session)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, created_at`
func (w *World) InsertObjects(tx *sqlx.Tx) error {
relId := InsertId[string]{id: w.ID, fieldName: "world_id"}
group, _ := errgroup.WithContext(context.Background())
args := []any{stateId, world.TextId, world.Title, world.Description, world.System, world.CoreVersion, world.SystemVersion, world.PlayTime, world.NextSession}
InsertWithCtxParallel(group, tx, w.Compatibility, relId)
InsertWithCtxParallel(group, tx, w.Relationships, relId)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
esModulesRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "es_modules"}
InsertSimpleSliceParallel(group, tx, w.Esmodules, esModulesRelId)
scriptRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "scripts"}
InsertSimpleSliceParallel(group, tx, w.Scripts, scriptRelId)
tagsRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "tags"}
InsertSimpleSliceParallel(group, tx, w.Tags, tagsRelId)
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&world.Id, &world.CreatedAt)
if err != nil && err != sql.ErrNoRows {
return err
}
return m.InsertWorldCompatibility(&world.Compatibility, world.Id)
}
InsertSliceParallel(group, tx, w.Authors, relId)
InsertSliceParallel(group, tx, w.Media, relId)
InsertSliceParallel(group, tx, w.Styles, relId)
InsertSliceParallel(group, tx, w.Languages, relId)
InsertSliceParallel(group, tx, w.Packs, relId)
InsertSliceParallel(group, tx, w.PackFolders, relId)
func (m FoundryStateModel) InsertWorldCompatibility(compatibility *Compatibility, worldId int64) error {
query := `
INSERT INTO worlds_compatibility (world_id, minimum, verified, maximum)
VALUES ($1, $2, $3, $4)
RETURNING id`
args := []any{worldId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id)
if err != nil && err != sql.ErrNoRows {
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (m FoundryStateModel) GetWorlds(idState int64) (Worlds, error) {
if idState < 1 {
return nil, ErrorRecordNotFound
func (w *World) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
query := `
SELECT id, created_at, text_id, title, description, system, core_version, system_version, playtime, next_session
FROM worlds
WHERE state_id = $1`
args := []any{data.id, w.ID, w.Title, w.Description, w.Version, w.System, w.Background, w.JoinTheme,
w.CoreVersion, w.SystemVersion, w.LastPlayed, w.Playtime, w.Availability, w.NextSession, w.Socket,
w.Protected, w.Exclusive, w.PersistentStorage, w.Locked, w.Owned, w.HasStorage}
var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return w.InsertObjects(tx)
}
return nil
}
func (w *World) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, w.ID, w.Title, w.Description, w.Version, w.System, w.Background, w.JoinTheme,
w.CoreVersion, w.SystemVersion, w.LastPlayed, w.Playtime, w.Availability, w.NextSession, w.Socket,
w.Protected, w.Exclusive, w.PersistentStorage, w.Locked, w.Owned, w.HasStorage}
var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil {
return err
}
if isInserted {
return w.InsertObjects(tx)
}
return nil
}
func GetNotStartedWorlds(db *sqlx.DB) ([]string, error) {
const query = `
SELECT id FROM world WHERE game_id IS NULL`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := m.DB.QueryContext(ctx, query, idState)
var worldNames []string
err := db.SelectContext(ctx, &worldNames, query)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
return worldNames, nil
}
worlds := make(Worlds, 0, 1)
func IsWorldInserted(db *sqlx.DB, worldName string) (bool, error) {
const query = `
SELECT EXISTS(SELECT 1 FROM world WHERE id = $1 AND game_id IS NOT NULL)`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var exist bool
err := db.GetContext(ctx, &exist, query, worldName)
if err != nil {
return false, err
}
return exist, nil
}
func GetWorld(db *sqlx.DB, worldName string) (*World, error) {
const query = `
SELECT id, title, description, version, system, background, join_theme, core_version, system_version,
last_played, playtime, availability, next_session, socket, protected, exclusive_, persistent_storage,
locked, owned, has_storage
FROM world WHERE id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
for rows.Next() {
var world World
err := rows.Scan(
&world.Id,
&world.CreatedAt,
&world.TextId,
&world.Title,
&world.Description,
&world.System,
&world.CoreVersion,
&world.SystemVersion,
&world.PlayTime,
&world.NextSession,
err := db.QueryRowxContext(ctx, query, worldName).Scan(
&world.ID, &world.Title, &world.Description, &world.Version, &world.System, &world.Background, &world.JoinTheme,
&world.CoreVersion, &world.SystemVersion, &world.LastPlayed, &world.Playtime, &world.Availability, &world.NextSession,
&world.Socket, &world.Protected, &world.Exclusive, &world.PersistentStorage, &world.Locked, &world.Owned, &world.HasStorage,
)
if err != nil {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
}
compatibility, err := m.GetWorldsCompatibility(world.Id)
if err != nil {
return nil, err
return &world, nil
}
world.Compatibility = *compatibility
worlds = append(worlds, world)
}
if err := rows.Err(); err != nil {
return nil, err
}
return worlds, nil
}
func (m FoundryStateModel) GetWorldsCompatibility(idWorld int64) (*Compatibility, error) {
if idWorld < 1 {
return nil, ErrorRecordNotFound
}
query := `
SELECT id, minimum, verified, maximum
FROM worlds_compatibility
WHERE world_id = $1`
var compatibility Compatibility
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, idWorld).Scan(
&compatibility.Id,
&compatibility.Minimum,
&compatibility.Verified,
&compatibility.Maximum,
)
if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
return &compatibility, nil
}
func (m FoundryStateModel) DeleteWorlds(idState int64) error {
if idState < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM worlds
WHERE state_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idState)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func (m FoundryStateModel) DeleteWorld(idWorld int64) error {
if idWorld < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM worlds
WHERE id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idWorld)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
// func (worlds Worlds) GetSessionTime(worldName string) (*time.Time, error) {
// if worldName == "" && len(worlds) != 1 {
// return nil, ErrorSetupNotFound
// }
// if worldName == "" {
// return &worlds.GetById(0).NextSession, nil
// } else {
// for i := range worlds {
// if worlds[i].Id == worldName {
// return &worlds.GetById(0).NextSession, nil
// }
// }
// }
// return nil, ErrorSetupNotFound
// }
// func (world World) GetSessionTime() *time.Time {
// return &world.NextSession
// }

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Actor struct {
PrototypeToken Token `json:"prototypeToken"`
@@ -18,24 +18,28 @@ type Actor struct {
// ActorsEffects []any `json:"effects"`
}
func (a *Actor) ToDB(dest *db.Actor) bool {
func (a *Actor) ToDB(dest **db.Actor) bool {
if dest == nil {
return false
}
dest.Img = a.Img
dest.Name = a.Name
dest.Type = a.Type
dest.Folder = a.Folder
dest.Sort = a.Sort
dest.ID = a.ID
actor := &db.Actor{
Img: a.Img,
Name: a.Name,
Type: a.Type,
Folder: a.Folder,
Sort: a.Sort,
ID: a.ID,
}
a.PrototypeToken.ToDB(&dest.PrototypeToken)
a.Stats.ToDB(&dest.Stats)
a.PrototypeToken.ToDB(&actor.PrototypeToken)
a.Stats.ToDB(&actor.Stats)
OwnershipToDB(&dest.Ownership, a.Ownership)
OwnershipToDB(&actor.Ownership, a.Ownership)
CopySliceToDB(&dest.Items, a.Items)
CopySliceToDB(&actor.Items, a.Items)
*dest = actor
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Addresses struct {
Local string `json:"local"`

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Author struct {
Name string `json:"name"`
@@ -10,15 +10,19 @@ type Author struct {
// SystemAuthorsFlags SystemAuthorsFlags `json:"flags"`
}
func (a *Author) ToDB(dest *db.Author) bool {
func (a *Author) ToDB(dest **db.Author) bool {
if dest == nil {
return false
}
dest.Name = a.Name
dest.URL = a.URL
dest.Email = a.Email
dest.Discord = a.Discord
author := &db.Author{
Name: a.Name,
URL: a.URL,
Email: a.Email,
Discord: a.Discord,
}
*dest = author
return true
}

View File

@@ -0,0 +1,138 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type CardDeck struct {
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Img string `json:"img"`
Cards []*Card `json:"cards"`
Width int `json:"width"`
Height int `json:"height"`
Rotation int `json:"rotation"`
DisplayCount bool `json:"displayCount"`
Stats Stats `json:"_stats"`
Ownership map[string]int `json:"ownership,omitempty"`
Folder string `json:"folder"`
Sort int `json:"sort"`
ID string `json:"_id"`
// Flags any `json:"flags"`
// Cards0System any `json:"system"`
}
func (c *CardDeck) ToDB(dest **db.CardDeck) bool {
if dest == nil {
return false
}
cardDeck := &db.CardDeck{
Name: c.Name,
Type: c.Type,
Description: c.Description,
Img: c.Img,
Width: c.Width,
Height: c.Height,
Rotation: c.Rotation,
DisplayCount: c.DisplayCount,
Folder: c.Folder,
Sort: c.Sort,
ID: c.ID,
}
c.Stats.ToDB(&cardDeck.Stats)
OwnershipToDB(&cardDeck.Ownership, c.Ownership)
CopySliceToDB(&cardDeck.Cards, c.Cards)
*dest = cardDeck
return true
}
type Card struct {
Name string `json:"name"`
Faces []*Face `json:"faces"`
Width int `json:"width"`
Height int `json:"height"`
Rotation int `json:"rotation"`
Type string `json:"type"`
Value int `json:"value"`
Suit string `json:"suit"`
Description string `json:"description"`
Face int `json:"face"`
Drawn bool `json:"drawn"`
Sort int `json:"sort"`
Back Back `json:"back"`
Origin string `json:"origin"`
ID string `json:"_id"`
Stats Stats `json:"_stats"`
// Flags any `json:"flags"`
// System any `json:"system"`
}
func (c *Card) ToDB(dest **db.Card) bool {
if dest == nil {
return false
}
card := &db.Card{
Name: c.Name,
Width: c.Width,
Height: c.Height,
Rotation: c.Rotation,
Type: c.Type,
Value: c.Value,
Suit: c.Suit,
Description: c.Description,
Face: c.Face,
Drawn: c.Drawn,
Origin: c.Origin,
ID: c.ID,
Sort: c.Sort,
}
c.Back.ToDB(&card.Back)
c.Stats.ToDB(&card.Stats)
CopySliceToDB(&card.Faces, c.Faces)
*dest = card
return true
}
type Face struct {
Name string `json:"name"`
Img string `json:"img"`
Text string `json:"text"`
}
func (f *Face) ToDB(dest *db.Face) bool {
if dest == nil {
return false
}
dest.Name = f.Name
dest.Img = f.Img
dest.Text = f.Text
return true
}
type Back struct {
Img any `json:"img"`
Name string `json:"name"`
Text string `json:"text"`
}
func (b *Back) ToDB(dest *db.Back) bool {
if dest == nil {
return false
}
dest.Name = b.Name
dest.Text = b.Text
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Combat struct {
Id string `json:"_id"`
@@ -17,23 +17,28 @@ type Combat struct {
// Flags any `json:"flags"`
}
func (c *Combat) ToDB(dest *db.Combat) bool {
func (c *Combat) ToDB(dest **db.Combat) bool {
if dest == nil {
return false
}
dest.ID = c.Id
dest.Type = c.Type
dest.Scene = c.Scene
dest.Active = c.Active
dest.Round = c.Round
dest.Turn = c.Turn
dest.Sort = c.Sort
combat := &db.Combat{
ID: c.Id,
Type: c.Type,
Scene: c.Scene,
Active: c.Active,
Round: c.Round,
Turn: c.Turn,
Sort: c.Sort,
}
c.Stats.ToDB(&dest.Stats)
c.Stats.ToDB(&combat.Stats)
copy(dest.Groups, c.Groups)
CopySliceToDB(&dest.Combatants, c.Combatants)
combat.Groups = make([]string, len(c.Groups))
copy(combat.Groups, c.Groups)
CopySliceToDB(&combat.Combatants, c.Combatants)
*dest = combat
return true
}
@@ -54,22 +59,27 @@ type Combatant struct {
// Flags any `json:"flags"`
}
func (c *Combatant) ToDB(dest *db.Combatant) bool {
func (c *Combatant) ToDB(dest **db.Combatant) bool {
if dest == nil {
return false
}
dest.TokenId = c.TokenId
dest.SceneId = c.SceneId
dest.ActorId = c.ActorId
dest.Hidden = c.Hidden
dest.ID = c.Id
dest.Type = c.Type
dest.Img = c.Img
dest.Initiative = c.Initiative
dest.Defeated = c.Defeated
dest.Group = c.Group
c.Stats.ToDB(&dest.Stats)
combatant := &db.Combatant{
TokenId: c.TokenId,
SceneId: c.SceneId,
ActorId: c.ActorId,
Hidden: c.Hidden,
ID: c.Id,
Type: c.Type,
Img: c.Img,
Initiative: c.Initiative,
Defeated: c.Defeated,
Group: c.Group,
}
c.Stats.ToDB(&combatant.Stats)
*dest = combatant
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Compatibility struct {
Minimum string `json:"minimum,omitempty"`

View File

@@ -1,185 +0,0 @@
package json
import (
"encoding/json"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type FoundryState struct {
IsAdmin bool `json:"isAdmin,omitempty"`
IsSetup bool `json:"isSetup,omitempty"`
Languages []Language `json:"languages,omitempty"`
Modules []DataTemplate `json:"modules"`
Release Release `json:"release"`
Systems []DataTemplate `json:"systems,omitempty"`
Worlds []DataTemplate `json:"worlds,omitempty"`
World *DataTemplate `json:"world,omitempty"`
Users Users `json:"users,omitempty"`
Options Options `json:"options,omitempty"`
//coreUpdate struct{}
//featuredContent struct{}
//files struct{}
//news struct{}
//packageWarnings struct{} think about it
}
type Options struct {
Language string `json:"language,omitempty"`
}
func (state FoundryState) GetRelease() Release {
return state.Release
}
func (state FoundryState) GetFoundryStateDB(stateType db.StateType) *db.FoundryState {
dbFoundryState := db.FoundryState{
IsAdmin: state.IsAdmin,
IsSetup: state.IsSetup,
Type: stateType,
Options: db.Options{Language: state.Options.Language},
}
dbFoundryState.Modules = state.GetModules()
dbFoundryState.Systems = state.GetSystems()
dbFoundryState.Worlds = state.GetWorlds()
dbFoundryState.Users = state.GetUsers()
return &dbFoundryState
}
func (state *FoundryState) GetModules() db.Modules {
modulesCopy := make([]db.Module, 0, 8)
for i := range state.Modules {
module := &(state.Modules[i])
moduleCopy := db.Module{
TextId: module.Id,
Title: module.Title,
Description: module.Description,
Compatibility: db.Compatibility{
Minimum: module.Compatibility.Minimum,
Maximum: module.Compatibility.Maximum,
},
Url: module.Url,
Version: module.CoreVersion,
Availability: module.Availability,
}
for j := range module.Languages {
lang := db.Language{
Lang: module.Languages[j].Lang,
Name: module.Languages[j].Name,
Path: module.Languages[j].Path,
}
moduleCopy.Languages = append(moduleCopy.Languages, lang)
}
modulesCopy = append(modulesCopy, moduleCopy)
}
return modulesCopy
}
func (state *FoundryState) GetSystems() db.Systems {
systemsCopy := make([]db.System, 0, 8)
for i := range state.Systems {
system := &(state.Systems[i])
systemCopy := db.System{
TextId: system.Id,
Title: system.Title,
Description: system.Description,
Url: system.Url,
Compatibility: db.Compatibility{
Minimum: system.Compatibility.Minimum,
Maximum: system.Compatibility.Maximum,
},
Download: system.Download,
}
systemsCopy = append(systemsCopy, systemCopy)
}
return systemsCopy
}
func (state *FoundryState) GetWorld() *db.World {
return &db.World{
TextId: state.World.Id,
Title: state.World.Title,
Description: state.World.Description,
Compatibility: db.Compatibility{
Minimum: state.World.Compatibility.Minimum,
Maximum: state.World.Compatibility.Maximum,
},
System: state.World.System,
CoreVersion: state.World.CoreVersion,
SystemVersion: state.World.SystemVersion,
LastPlayed: state.World.LastPlayed,
PlayTime: state.World.PlayTime,
NextSession: state.World.NextSession,
}
}
func (state *FoundryState) GetWorlds() db.Worlds {
worldsCopy := make([]db.World, 0, 8)
for i := range state.Worlds {
world := &(state.Worlds[i])
worldCopy := db.World{
TextId: world.Id,
Title: world.Title,
Description: world.Description,
Compatibility: db.Compatibility{
Minimum: world.Compatibility.Minimum,
Maximum: world.Compatibility.Maximum,
},
System: world.System,
CoreVersion: world.CoreVersion,
SystemVersion: world.SystemVersion,
LastPlayed: world.LastPlayed,
PlayTime: world.PlayTime,
NextSession: world.NextSession,
}
worldsCopy = append(worldsCopy, worldCopy)
}
if state.World != nil {
worldsCopy = append(worldsCopy, *state.GetWorld())
}
return worldsCopy
}
func (state *FoundryState) GetUsers() db.Users {
usersCopy := make(db.Users, 0, 8)
for i := range state.Users {
user := &(state.Users[i])
userCopy := db.User{
Name: user.Name,
Role: user.Role,
Character: user.Character,
Color: user.Color,
Pronouns: user.Pronouns,
Hotbar: user.Hotbar,
Stats: db.UserStats{
CoreVersion: user.Stats.CoreVersion,
SystemId: user.Stats.SystemId,
SystemVersion: user.Stats.SystemVersion,
CreatedTime: user.Stats.CreatedTime,
ModifiedTime: user.Stats.ModifiedTime,
LastModifiedBy: user.Stats.LastModifiedBy,
},
}
usersCopy = append(usersCopy, userCopy)
}
return usersCopy
}
func ParseSetupModel(data []byte) (*FoundryState, error) {
var modelSetup []FoundryState
err := json.Unmarshal(data, &modelSetup)
if err != nil {
return nil, err
}
if len(modelSetup) > 1 {
return nil, ErrorSetupMoreThanOne
}
return &modelSetup[0], nil
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type DetailsLanguages struct {
Details string `json:"details"`

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type DocumentTypes struct {
Actor DocumentTypeData `json:"Actor"`
@@ -13,13 +13,13 @@ func (d *DocumentTypes) ToDB(dest *db.DocumentTypes) bool {
return false
}
dest.Data = make([]db.DocumentTypeData, 2)
dest.Data = make([]*db.DocumentTypeData, 2)
d.Actor.ToDB(&dest.Data[0])
dest.Data[0].Type = "Actor"
dest.Data[0] = &db.DocumentTypeData{Type: "Actor"}
d.Actor.ToDB(dest.Data[0])
d.Item.ToDB(&dest.Data[1])
dest.Data[1].Type = "Item"
dest.Data[1] = &db.DocumentTypeData{Type: "Item"}
d.Item.ToDB(dest.Data[1])
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Environment struct {
GlobalLight EnvironmentGlobalLight `json:"globalLight"`

View File

@@ -1,8 +0,0 @@
package json
import "errors"
var (
ErrorSetupMoreThanOne = errors.New("Passed more than one setupData")
ErrorSetupNotFound = errors.New("Not found setup data from your request")
)

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Files struct {
Storages []string `json:"storages"`
@@ -12,7 +12,10 @@ func (f *Files) ToDB(dest *db.Files) bool {
return false
}
copy(dest.Storages, f.Storages)
dest.Storages = make([]db.FilesStorage, len(f.Storages))
for i := range f.Storages {
dest.Storages[i].Storage = f.Storages[i]
}
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Folder struct {
Name string `json:"name"`
@@ -10,17 +10,22 @@ type Folder struct {
Folders []*Folder `json:"folders,omitempty"`
}
func (f *Folder) ToDB(dest *db.Folder) bool {
func (f *Folder) ToDB(dest **db.Folder) bool {
if dest == nil {
return false
}
dest.Name = f.Name
dest.Sorting = f.Sorting
dest.Color = f.Color
copy(dest.Packs, f.Packs)
folder := &db.Folder{
Name: f.Name,
Sorting: f.Sorting,
Color: f.Color,
}
CopySliceToDB(&dest.Folders, f.Folders)
folder.Packs = make([]string, len(f.Packs))
copy(folder.Packs, f.Packs)
CopySliceToDB(&folder.Folders, f.Folders)
*dest = folder
return true
}
@@ -38,20 +43,25 @@ type WorldFolder struct {
// Flags any `json:"flags,omitempty"`
}
func (w *WorldFolder) ToDB(dest *db.WorldFolder) bool {
func (w *WorldFolder) ToDB(dest **db.WorldFolder) bool {
if dest == nil {
return false
}
dest.Name = w.Name
dest.Type = w.Type
dest.ID = w.ID
dest.Folder = w.Folder
dest.Sorting = w.Sorting
dest.Sort = w.Sort
w.Stats.ToDB(&dest.Stats)
dest.Description = w.Description
dest.Color = w.Color
worldFolder := &db.WorldFolder{
Name: w.Name,
Type: w.Type,
ID: w.ID,
Folder: w.Folder,
Sorting: w.Sorting,
Sort: w.Sort,
Description: w.Description,
Color: w.Color,
}
w.Stats.ToDB(&worldFolder.Stats)
*dest = worldFolder
return true
}

View File

@@ -1,9 +1,10 @@
package json
import (
"encoding/json"
"sync"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type Game struct {
@@ -40,6 +41,19 @@ type Game struct {
// Template Template `json:"template"`
}
func ParseGame(data []byte) (*Game, error) {
var modelGame []Game
err := json.Unmarshal(data, &modelGame)
if err != nil {
return nil, err
}
if len(modelGame) > 1 {
return nil, ErrorSetupMoreThanOne
}
return &modelGame[0], nil
}
func (g *Game) ToDB(dest *db.Game) bool {
dest.UserID = g.UserID
dest.DemoMode = g.DemoMode
@@ -55,23 +69,25 @@ func (g *Game) ToDB(dest *db.Game) bool {
g.Options.ToDB(&dest.Options)
g.CoreUpdate.ToDB(&dest.CoreUpdate)
g.SystemUpdate.ToDB(&dest.SystemUpdate)
dest.ActiveUsers = make([]string, len(g.ActiveUsers))
copy(dest.ActiveUsers, g.ActiveUsers)
wg := sync.WaitGroup{}
go CopySliceToDBParallel(&wg, &dest.Modules, g.Modules)
go CopySliceToDBParallel(&wg, &dest.Packs, g.Packs)
go CopySliceToDBParallel(&wg, &dest.Messages, g.Messages)
go CopySliceToDBParallel(&wg, &dest.Combats, g.Combats)
go CopySliceToDBParallel(&wg, &dest.CardDeck, g.CardDeck)
go CopySliceToDBParallel(&wg, &dest.Users, g.Users)
go CopySliceToDBParallel(&wg, &dest.Macros, g.Macros)
go CopySliceToDBParallel(&wg, &dest.Folders, g.Folders)
go CopySliceToDBParallel(&wg, &dest.Items, g.Items)
go CopySliceToDBParallel(&wg, &dest.Settings, g.Settings)
go CopySliceToDBParallel(&wg, &dest.Journals, g.Journals)
go CopySliceToDBParallel(&wg, &dest.Tables, g.Tables)
go CopySliceToDBParallel(&wg, &dest.Playlists, g.Playlists)
go CopySliceToDBParallel(&wg, &dest.Actors, g.Actors)
CopySliceToDBParallel(&wg, &dest.Modules, g.Modules)
CopySliceToDBParallel(&wg, &dest.Packs, g.Packs)
CopySliceToDBParallel(&wg, &dest.Messages, g.Messages)
CopySliceToDBParallel(&wg, &dest.Combats, g.Combats)
CopySliceToDBParallel(&wg, &dest.CardDeck, g.CardDeck)
CopySliceToDBParallel(&wg, &dest.Users, g.Users)
CopySliceToDBParallel(&wg, &dest.Macros, g.Macros)
CopySliceToDBParallel(&wg, &dest.Folders, g.Folders)
CopySliceToDBParallel(&wg, &dest.Items, g.Items)
CopySliceToDBParallel(&wg, &dest.Settings, g.Settings)
CopySliceToDBParallel(&wg, &dest.Journals, g.Journals)
CopySliceToDBParallel(&wg, &dest.Tables, g.Tables)
CopySliceToDBParallel(&wg, &dest.Playlists, g.Playlists)
CopySliceToDBParallel(&wg, &dest.Actors, g.Actors)
// go CopySliceToDBParallel(&wg, &dest.Scenes, g.Scenes)
wg.Wait()

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Grid struct {
Type int `json:"type"`
@@ -14,20 +14,24 @@ type Grid struct {
Thickness int `json:"thickness,omitempty"`
}
func (g *Grid) ToDB(dest *db.Grid) bool {
func (g *Grid) ToDB(dest **db.Grid) bool {
if dest == nil {
return false
}
dest.Type = g.Type
dest.Size = g.Size
dest.Color = g.Color
dest.Alpha = g.Alpha
dest.Distance = g.Distance
dest.Units = g.Units
dest.Diagonals = g.Diagonals
dest.Style = g.Style
dest.Thickness = g.Thickness
grid := &db.Grid{
Type: g.Type,
Size: g.Size,
Color: g.Color,
Alpha: g.Alpha,
Distance: g.Distance,
Units: g.Units,
Diagonals: g.Diagonals,
Style: g.Style,
Thickness: g.Thickness,
}
*dest = grid
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Index struct {
Id string `json:"_id"`
@@ -10,16 +10,20 @@ type Index struct {
Type string `json:"type"`
}
func (i *Index) ToDB(dest *db.Index) bool {
func (i *Index) ToDB(dest **db.Index) bool {
if dest == nil {
return false
}
dest.ID = i.Id
dest.Folder = i.Folder
dest.Img = i.Img
dest.Name = i.Name
dest.Type = i.Type
index := &db.Index{
ID: i.Id,
Folder: i.Folder,
Img: i.Img,
Name: i.Name,
Type: i.Type,
}
*dest = index
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Item struct {
Img string `json:"img"`
@@ -16,19 +16,23 @@ type Item struct {
// Effects []any `json:"effects"`
}
func (i *Item) ToDB(dest *db.Item) bool {
func (i *Item) ToDB(dest **db.Item) bool {
if dest == nil {
return false
}
dest.Img = i.Img
dest.Name = i.Name
dest.Type = i.Type
dest.Folder = i.Folder
dest.ID = i.ID
dest.Sort = i.Sort
item := &db.Item{
Img: i.Img,
Name: i.Name,
Type: i.Type,
Folder: i.Folder,
ID: i.ID,
Sort: i.Sort,
}
i.Stats.ToDB(&dest.Stats)
i.Stats.ToDB(&item.Stats)
*dest = item
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Journal struct {
Folder any `json:"folder"`
@@ -14,17 +14,21 @@ type Journal struct {
// Categories []any `json:"categories"`
}
func (j *Journal) ToDB(dest *db.Journal) bool {
func (j *Journal) ToDB(dest **db.Journal) bool {
if dest == nil {
return false
}
dest.Name = j.Name
dest.Sort = j.Sort
dest.ID = j.ID
journal := &db.Journal{
Name: j.Name,
Sort: j.Sort,
ID: j.ID,
}
OwnershipToDB(&dest.Ownership, j.Ownership)
CopySliceToDB(&dest.Pages, j.Pages)
OwnershipToDB(&journal.Ownership, j.Ownership)
CopySliceToDB(&journal.Pages, j.Pages)
*dest = journal
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type JournalPage struct {
Name string `json:"name"`
@@ -19,23 +19,27 @@ type JournalPage struct {
// Category any `json:"category"`
}
func (j *JournalPage) ToDB(dest *db.JournalPage) bool {
func (j *JournalPage) ToDB(dest **db.JournalPage) bool {
if dest == nil {
return false
}
dest.Name = j.Name
dest.Type = j.Type
dest.ID = j.ID
dest.Src = j.Src
dest.Sort = j.Sort
journalPage := &db.JournalPage{
Name: j.Name,
Type: j.Type,
ID: j.ID,
Src: j.Src,
Sort: j.Sort,
}
j.Text.ToDB(&dest.Text)
j.Title.ToDB(&dest.Title)
j.Video.ToDB(&dest.Video)
j.Stats.ToDB(&dest.Stats)
j.Text.ToDB(&journalPage.Text)
j.Title.ToDB(&journalPage.Title)
j.Video.ToDB(&journalPage.Video)
j.Stats.ToDB(&journalPage.Stats)
OwnershipToDB(&dest.Ownership, j.Ownership)
OwnershipToDB(&journalPage.Ownership, j.Ownership)
*dest = journalPage
return true
}

View File

@@ -1,13 +1,67 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Language struct {
Id string `json:"id"`
Label string `json:"label"`
Modules []LangModule `json:"modules"`
Lang string `json:"lang"`
Name string `json:"name"`
Path string `json:"path"`
// SystemLanguagesFlags SystemLanguagesFlags `json:"flags"`
}
type LangModule struct {
Id string `json:"id"`
func (l *Language) ToDB(dest **db.Language) bool {
if dest == nil {
return false
}
lang := &db.Language{
Lang: l.Lang,
Name: l.Name,
Path: l.Path,
}
*dest = lang
return true
}
type SetupLanguage struct {
ID string `json:"id"`
Label string `json:"label"`
Modules []*SetupLanguageModule `json:"modules"`
}
func (s *SetupLanguage) ToDB(dest **db.SetupLanguage) bool {
if dest == nil {
return false
}
lang := &db.SetupLanguage{
ID: s.ID,
Label: s.Label,
}
CopySliceToDB(&lang.Modules, s.Modules)
*dest = lang
return true
}
type SetupLanguageModule struct {
ID string `json:"id"`
Label string `json:"label"`
Path string `json:"path"`
}
func (s *SetupLanguageModule) ToDB(dest *db.SetupLanguageModule) bool {
if dest == nil {
return false
}
dest.ID = s.ID
dest.Label = s.Label
dest.Path = s.Path
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Light struct {
Alpha float64 `json:"alpha"`
@@ -20,27 +20,31 @@ type Light struct {
Color string `json:"color"`
}
func (l *Light) ToDB(dest *db.Light) bool {
func (l *Light) ToDB(dest **db.Light) bool {
if dest == nil {
return false
}
dest.Alpha = l.Alpha
dest.Angle = l.Angle
dest.Bright = l.Bright
dest.Coloration = l.Coloration
dest.Dim = l.Dim
dest.Attenuation = l.Attenuation
dest.Luminosity = l.Luminosity
dest.Saturation = l.Saturation
dest.Contrast = l.Contrast
dest.Shadows = l.Shadows
dest.Negative = l.Negative
dest.Priority = l.Priority
dest.Color = l.Color
light := &db.Light{
Alpha: l.Alpha,
Angle: l.Angle,
Bright: l.Bright,
Coloration: l.Coloration,
Dim: l.Dim,
Attenuation: l.Attenuation,
Luminosity: l.Luminosity,
Saturation: l.Saturation,
Contrast: l.Contrast,
Shadows: l.Shadows,
Negative: l.Negative,
Priority: l.Priority,
Color: l.Color,
}
l.LightAnimation.ToDB(&dest.LightAnimation)
l.LightDarkness.ToDB(&dest.LightDarkness)
l.LightAnimation.ToDB(&light.LightAnimation)
l.LightDarkness.ToDB(&light.LightDarkness)
*dest = light
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Macro struct {
Command string `json:"command"`
@@ -17,24 +17,28 @@ type Macro struct {
// Flags any `json:"flags,omitempty"`
}
func (m *Macro) ToDB(dest *db.Macro) bool {
func (m *Macro) ToDB(dest **db.Macro) bool {
if dest == nil {
return false
}
dest.Command = m.Command
dest.Name = m.Name
dest.Type = m.Type
dest.Img = m.Img
dest.ID = m.ID
dest.Author = m.Author
dest.Scope = m.Scope
dest.Folder = m.Folder
dest.Sort = m.Sort
macro := &db.Macro{
Command: m.Command,
Name: m.Name,
Type: m.Type,
Img: m.Img,
ID: m.ID,
Author: m.Author,
Scope: m.Scope,
Folder: m.Folder,
Sort: m.Sort,
}
m.Stats.ToDB(&dest.Stats)
m.Stats.ToDB(&macro.Stats)
OwnershipToDB(&dest.Ownership, m.Ownership)
OwnershipToDB(&macro.Ownership, m.Ownership)
*dest = macro
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Media struct {
Type string `json:"type"`
@@ -8,14 +8,18 @@ type Media struct {
Caption string `json:"caption"`
}
func (m *Media) ToDB(dest *db.Media) bool {
func (m *Media) ToDB(dest **db.Media) bool {
if dest == nil {
return false
}
dest.Type = m.Type
dest.URL = m.URL
dest.Caption = m.Caption
media := &db.Media{
Type: m.Type,
URL: m.URL,
Caption: m.Caption,
}
*dest = media
return true
}

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Message struct {
Content string `json:"content"`
@@ -21,27 +21,33 @@ type Message struct {
// Flags any `json:"flags"`
}
func (m *Message) ToDB(dest *db.Message) bool {
func (m *Message) ToDB(dest **db.Message) bool {
if dest == nil {
return false
}
dest.Content = m.Content
dest.Style = m.Style
dest.Author = m.Author
dest.ID = m.Id
dest.Type = m.Type
dest.Timestamp = m.Timestamp
dest.Flavor = m.Flavor
dest.Blind = m.Blind
dest.Sound = m.Sound
dest.Emote = m.Emote
message := &db.Message{
Content: m.Content,
Style: m.Style,
Author: m.Author,
ID: m.Id,
Type: m.Type,
Timestamp: m.Timestamp,
Flavor: m.Flavor,
Blind: m.Blind,
Sound: m.Sound,
Emote: m.Emote,
}
m.Speaker.ToDB(&dest.Speaker)
m.Stats.ToDB(&dest.Stats)
m.Speaker.ToDB(&message.Speaker)
m.Stats.ToDB(&message.Stats)
copy(dest.Whisper, m.Whisper)
copy(dest.Rolls, m.Rolls)
message.Whisper = make([]string, len(m.Whisper))
copy(message.Whisper, m.Whisper)
message.Rolls = make([]string, len(m.Rolls))
copy(message.Rolls, m.Rolls)
*dest = message
return true
}

View File

@@ -3,7 +3,7 @@ package json
import (
"sync"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type Module struct {
@@ -44,50 +44,57 @@ type Module struct {
// ModulesFlags ModulesFlags `json:"flags,omitempty"`
}
func (m *Module) ToDB(dest *db.Module) bool {
func (m *Module) ToDB(dest **db.Module) bool {
if dest == nil {
return false
}
dest.ID = m.ID
dest.Title = m.Title
dest.Description = m.Description
dest.URL = m.URL
dest.License = m.License
dest.Readme = m.Readme
dest.Bugs = m.Bugs
dest.Changelog = m.Changelog
dest.Version = m.Version
dest.Socket = m.Socket
dest.Manifest = m.Manifest
dest.Download = m.Download
dest.Protected = m.Protected
dest.Exclusive = m.Exclusive
dest.PersistentStorage = m.PersistentStorage
dest.CoreTranslation = m.CoreTranslation
dest.Library = m.Library
dest.Availability = m.Availability
dest.Locked = m.Locked
dest.Owned = m.Owned
dest.HasStorage = m.HasStorage
dest.Active = m.Active
module := &db.Module{
ID: m.ID,
Title: m.Title,
Description: m.Description,
URL: m.URL,
License: m.License,
Readme: m.Readme,
Bugs: m.Bugs,
Changelog: m.Changelog,
Version: m.Version,
Socket: m.Socket,
Manifest: m.Manifest,
Download: m.Download,
Protected: m.Protected,
Exclusive: m.Exclusive,
PersistentStorage: m.PersistentStorage,
CoreTranslation: m.CoreTranslation,
Library: m.Library,
Availability: m.Availability,
Locked: m.Locked,
Owned: m.Owned,
HasStorage: m.HasStorage,
Active: m.Active,
}
copy(dest.Scripts, m.Scripts)
copy(dest.Esmodules, m.Esmodules)
copy(dest.Tags, m.Tags)
module.Scripts = make([]string, len(m.Scripts))
copy(module.Scripts, m.Scripts)
module.Esmodules = make([]string, len(m.Esmodules))
copy(module.Esmodules, m.Esmodules)
module.Tags = make([]string, len(m.Tags))
copy(module.Tags, m.Tags)
m.Compatibility.ToDB(&dest.Compatibility)
m.Relationships.ToDB(&dest.Relationships)
m.DocumentTypes.ToDB(&dest.DocumentTypes)
m.Compatibility.ToDB(&module.Compatibility)
m.Relationships.ToDB(&module.Relationships)
m.DocumentTypes.ToDB(&module.DocumentTypes)
wg := sync.WaitGroup{}
go CopySliceToDBParallel(&wg, &dest.Authors, m.Authors)
go CopySliceToDBParallel(&wg, &dest.Media, m.Media)
go CopySliceToDBParallel(&wg, &dest.Styles, m.Styles)
go CopySliceToDBParallel(&wg, &dest.Languages, m.Languages)
go CopySliceToDBParallel(&wg, &dest.Packs, m.Packs)
go CopySliceToDBParallel(&wg, &dest.PackFolders, m.PackFolders)
CopySliceToDBParallel(&wg, &module.Authors, m.Authors)
CopySliceToDBParallel(&wg, &module.Media, m.Media)
CopySliceToDBParallel(&wg, &module.Styles, m.Styles)
CopySliceToDBParallel(&wg, &module.Languages, m.Languages)
CopySliceToDBParallel(&wg, &module.Packs, m.Packs)
CopySliceToDBParallel(&wg, &module.PackFolders, m.PackFolders)
wg.Wait()
*dest = module
return true
}

View File

@@ -1,59 +0,0 @@
package modules
type DiceStats struct {
PlayerRollData DiceStatsRollData `json:"player_roll_data"`
}
type DiceStatsRollData struct {
PlayerDice []DicePlayerDice `json:"PLAYER_DICE"`
Username string `json:"USERNAME"`
Userid string `json:"USERID"`
Gm bool `json:"GM"`
PlayerRollInfo DiceRollInfo `json:"PLAYER_ROLL_INFO"`
}
type DicePlayerDice struct {
Type string `json:"TYPE"`
Max int `json:"MAX"`
TotalRolls int `json:"TOTAL_ROLLS"`
Rolls []int `json:"ROLLS"`
BlindRolls []int `json:"BLIND_ROLLS"`
StreakSize int `json:"STREAK_SIZE"`
StreakInit int `json:"STREAK_INIT"`
StreakIsBlind bool `json:"STREAK_ISBLIND"`
LongestStreak int `json:"LONGEST_STREAK"`
LongestStreakInit int `json:"LONGEST_STREAK_INIT"`
Mean int `json:"MEAN"`
Median int `json:"MEDIAN"`
Mode int `json:"MODE"`
Means []int `json:"MEANS"`
Medians []int `json:"MEDIANS"`
Modes []int `json:"MODES"`
RollCounters []int `json:"ROLL_COUNTERS"`
AtkRolls []int `json:"ATK_ROLLS"`
DmgRolls []int `json:"DMG_ROLLS"`
SavesRolls []int `json:"SAVES_ROLLS"`
SkillsRolls []int `json:"SKILLS_ROLLS"`
AbilityRolls []int `json:"ABILITY_ROLLS"`
UnknownRolls []int `json:"UNKNOWN_ROLLS"`
PerceptionRolls []int `json:"PERCEPTION_ROLLS"`
InitiativeRolls []int `json:"INITIATIVE_ROLLS"`
AtkRollsBlind []int `json:"ATK_ROLLS_BLIND"`
DmgRollsBlind []int `json:"DMG_ROLLS_BLIND"`
SavesRollsBlind []int `json:"SAVES_ROLLS_BLIND"`
SkillsRollsBlind []int `json:"SKILLS_ROLLS_BLIND"`
AbilityRollsBlind []int `json:"ABILITY_ROLLS_BLIND"`
UnknownRollsBlind []int `json:"UNKNOWN_ROLLS_BLIND"`
PerceptionRollsBlind []int `json:"PERCEPTION_ROLLS_BLIND"`
InitiativeRollsBlind []int `json:"INITIATIVE_ROLLS_BLIND"`
}
type DiceRollInfo struct {
IsRollInfoTracked bool `json:"IS_ROLL_INFO_TRACKED"`
AtkOutcomeTracker []int `json:"ATK_OUTCOME_TRACKER"`
NumUntargetedAtks int `json:"NUM_UNTARGETED_ATKS"`
TotalAttacks int `json:"TOTAL_ATTACKS"`
SaveOutcomeTracker []int `json:"SAVE_OUTCOME_TRACKER"`
NumUntargetedSaves int `json:"NUM_UNTARGETED_SAVES"`
TotalSaves int `json:"TOTAL_SAVES"`
}

Some files were not shown because too many files have changed in this diff Show More