finish world initialization, set up init on startup and on shutdown, move all models to main directory

This commit is contained in:
lbenedar
2026-04-24 18:04:35 +03:00
parent 07025afefc
commit fd65631be1
125 changed files with 1534 additions and 2802 deletions

View File

@@ -1,7 +1,7 @@
-include .env -include .env
api/run: 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}
db/migration/new: db/migration/new:
@echo 'Creating migration files for ${name}...' @echo 'Creating migration files for ${name}...'

View File

@@ -1,37 +1,22 @@
package main package main
import ( import (
"encoding/json" "fmt"
"net/http" "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) { 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 { if err != nil {
app.slogger.Error("", "error", err) w.Write([]byte("Got error"))
w.Write([]byte(err.Error()))
return return
} }
var dbGame db.Game w.Write([]byte(fmt.Sprintf("Got world with name %s, core_version - %s, next_session - %v", world.ID, world.CoreVersion, world.NextSession)))
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)
} }
func (app *application) WhenNextSession(w http.ResponseWriter, r *http.Request) { 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, "status": app.foundryApp.Status,
"is_available": app.foundryApp.IsAvailable, "is_available": app.foundryApp.IsAvailable,
}, },
"config": app.foundryApp.GetHTTP(),
} }
err := app.writeJSON(w, http.StatusOK, env, nil) err := app.writeJSON(w, http.StatusOK, env, nil)
if err != nil { if err != nil {

View File

@@ -7,6 +7,7 @@ import (
"log/slog" "log/slog"
"net/http/cookiejar" "net/http/cookiejar"
"os" "os"
"strings"
"time" "time"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry"
@@ -73,7 +74,7 @@ func openDB(cfg config) (*sqlx.DB, error) {
} }
// TODO: change default values // 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.port, "port", ":9090", "Service port")
flag.StringVar(&app.cfg.env, "env", "development", "Enivornment (development|staging|production)") flag.StringVar(&app.cfg.env, "env", "development", "Enivornment (development|staging|production)")
@@ -85,15 +86,20 @@ func (app *application) parseFlags() *requests.FoundryHttpRequest {
var mode string var mode string
flag.StringVar(&mode, "service_mode", "api", "Type of service mode(api|discord|tg)") flag.StringVar(&mode, "service_mode", "api", "Type of service mode(api|discord|tg)")
foundryHttpData := requests.FoundryHttpRequest{SessionID: nil} var foundryTransportData transport.FoundryTransportData
flag.StringVar(&foundryHttpData.Host, "foundry_host", "127.0.0.1", "Address to connect to Foundry") foundryTransportData.HttpConfig = &requests.FoundryHttpRequest{SessionID: nil}
flag.StringVar(&foundryHttpData.Password, "foundry_pass", "", "Password to connect to Foundry") 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 var logLevelStr string
flag.StringVar(&logLevelStr, "log_level", "info", "Password to connect to Foundry(debug|info|warn|error)") 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")
flag.Parse() flag.Parse()
foundryTransportData.Worlds = strings.Split(worlds, ",")
app.cfg.mode = service_mode(mode) app.cfg.mode = service_mode(mode)
logLevel, ok := StringToLogLevel[logLevelStr] logLevel, ok := StringToLogLevel[logLevelStr]
@@ -105,20 +111,21 @@ func (app *application) parseFlags() *requests.FoundryHttpRequest {
if !ok { if !ok {
app.slogger.Warn("Wrong log level. Started with log_level=info", "received", logLevelStr) 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 // TODO: Handle DB connection error
func main() { func main() {
app := application{foundryApp: &foundry.FoundryApi{}} app := application{foundryApp: &foundry.FoundryApi{}}
foundryHttpData := app.parseFlags() foundryTransportData := app.parseFlags()
jar, err := cookiejar.New(nil) jar, err := cookiejar.New(nil)
if err != nil { if err != nil {
app.slogger.Error("Error", "text", err.Error()) app.slogger.Error("Error", "text", err.Error())
return return
} }
foundryHttpData.Jar = jar foundryTransportData.HttpConfig.Jar = jar
app.slogger.Info("", "dsn", app.cfg.db.dsn) app.slogger.Info("", "dsn", app.cfg.db.dsn)
@@ -129,8 +136,15 @@ func main() {
} }
defer db.Close() defer db.Close()
app.slogger.Info("Database connection pool established") 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() go app.foundryApp.StartListenFoundry()
app.serve() app.serve()

View File

@@ -1,3 +1,5 @@
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 folder;
DROP TABLE IF EXISTS pack; DROP TABLE IF EXISTS pack;
DROP TABLE IF EXISTS language; DROP TABLE IF EXISTS language;
@@ -18,13 +20,14 @@ DROP TABLE IF EXISTS grid;
DROP TABLE IF EXISTS system; DROP TABLE IF EXISTS system;
DROP TABLE IF EXISTS package_warnings_data_error; DROP TABLE IF EXISTS package_warnings_data_error;
DROP TABLE IF EXISTS package_warnings_data_warning; 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_data;
DROP TRIGGER IF EXISTS tr_delete_package_warnings_to_data_package_warnings_data;
DROP TABLE IF EXISTS package_warnings; DROP TABLE IF EXISTS package_warnings;
DROP TABLE IF EXISTS news; DROP TABLE IF EXISTS news;
DROP TABLE IF EXISTS folder_packs; DROP TABLE IF EXISTS folder_packs;
DROP TABLE IF EXISTS folder; DROP TABLE IF EXISTS folder;
DROP TABLE IF EXISTS pack_folder; DROP TABLE IF EXISTS pack_folder;
DROP TABLE IF EXISTS pack_to_index;
DROP TABLE IF EXISTS index_; DROP TABLE IF EXISTS index_;
DROP TABLE IF EXISTS ownership; DROP TABLE IF EXISTS ownership;
DROP TABLE IF EXISTS relationships_data; DROP TABLE IF EXISTS relationships_data;

View File

@@ -301,9 +301,8 @@ CREATE TABLE IF NOT EXISTS language (
); );
CREATE TABLE IF NOT EXISTS pack ( CREATE TABLE IF NOT EXISTS pack (
id INTEGER PRIMARY KEY, id TEXT PRIMARY KEY,
key_ VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL,
label VARCHAR(128) NOT NULL, label VARCHAR(128) NOT NULL,
banner VARCHAR(128) NOT NULL, banner VARCHAR(128) NOT NULL,
@@ -324,7 +323,7 @@ CREATE TABLE IF NOT EXISTS ownership (
trusted VARCHAR(128) NOT NULL, trusted VARCHAR(128) NOT NULL,
assistant VARCHAR(128) NOT NULL, assistant VARCHAR(128) NOT NULL,
pack_id INTEGER UNIQUE, pack_id TEXT,
FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE
); );
@@ -334,12 +333,29 @@ CREATE TABLE IF NOT EXISTS index_ (
folder VARCHAR(128) NOT NULL, folder VARCHAR(128) NOT NULL,
img VARCHAR(128) NOT NULL, img VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL,
type VARCHAR(128) NOT NULL, type VARCHAR(128) NOT NULL
);
pack_id INTEGER, 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 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 ( CREATE TABLE IF NOT EXISTS pack_folder (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
@@ -349,7 +365,7 @@ CREATE TABLE IF NOT EXISTS pack_folder (
type VARCHAR(128) NOT NULL, type VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
pack_id INTEGER, pack_id TEXT,
FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE FOREIGN KEY (pack_id) REFERENCES pack(id) ON DELETE CASCADE
); );
@@ -406,15 +422,6 @@ CREATE TABLE IF NOT EXISTS package_warnings_to_data (
FOREIGN KEY (package_warnings_data_id) REFERENCES package_warnings_data(id) ON DELETE CASCADE FOREIGN KEY (package_warnings_data_id) REFERENCES package_warnings_data(id) ON DELETE CASCADE
); );
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.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 ( CREATE TABLE IF NOT EXISTS package_warnings_data (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
@@ -423,6 +430,17 @@ CREATE TABLE IF NOT EXISTS package_warnings_data (
reinstallable BOOLEAN 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 ( CREATE TABLE IF NOT EXISTS package_warnings_data_warning (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,

View File

@@ -1,6 +1,8 @@
DROP TRIGGER IF EXISTS tr_delete_package_warnings_to_data_package_warnings_data; 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_systems_system;
DROP TRIGGER IF EXISTS tr_delete_game_to_module_module; 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_turn_maker;
DROP TABLE IF EXISTS token_occludable; DROP TABLE IF EXISTS token_occludable;
DROP TABLE IF EXISTS token_light_darkness; DROP TABLE IF EXISTS token_light_darkness;
@@ -46,9 +48,8 @@ DROP TABLE IF EXISTS stats;
DROP TABLE IF EXISTS message; DROP TABLE IF EXISTS message;
CREATE TABLE IF NOT EXISTS pack_new ( CREATE TABLE IF NOT EXISTS pack_new (
id INTEGER PRIMARY KEY, id VARCHAR(128) PRIMARY KEY,
key_ VARCHAR(128) NOT NULL,
name VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL,
label VARCHAR(128) NOT NULL, label VARCHAR(128) NOT NULL,
banner VARCHAR(128) NOT NULL, banner VARCHAR(128) NOT NULL,
@@ -63,8 +64,8 @@ CREATE TABLE IF NOT EXISTS pack_new (
); );
ALTER TABLE pack_new ADD COLUMN system_id TEXT REFERENCES system(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; ALTER TABLE pack_new ADD COLUMN world_id TEXT REFERENCES world(id) ON DELETE CASCADE;
INSERT INTO pack_new (id, key_, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id) INSERT INTO pack_new (id, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id)
SELECT id, key_, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id FROM pack; 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; DROP TABLE IF EXISTS pack;
ALTER TABLE pack_new RENAME TO pack; ALTER TABLE pack_new RENAME TO pack;
@@ -81,6 +82,8 @@ SELECT id, key_, setup_id FROM package_warnings;
DROP TABLE IF EXISTS package_warnings; DROP TABLE IF EXISTS package_warnings;
ALTER TABLE package_warnings_new RENAME TO 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 active_users;
DROP TABLE IF EXISTS system_update; DROP TABLE IF EXISTS system_update;
DROP INDEX IF EXISTS idx_core_update_game_id; DROP INDEX IF EXISTS idx_core_update_game_id;
@@ -186,6 +189,15 @@ AFTER DELETE ON package_warnings_to_data
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
DELETE FROM package_warnings_data DELETE FROM package_warnings_data
WHERE id = OLD.id 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); 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; END;

View File

@@ -48,12 +48,14 @@ CREATE TABLE IF NOT EXISTS game_to_systems (
FOREIGN KEY (system_id) REFERENCES system(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 CREATE TRIGGER tr_delete_game_to_systems_system
AFTER DELETE ON game_to_systems AFTER DELETE ON game_to_systems
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
DELETE FROM system DELETE FROM system
WHERE id = OLD.id WHERE id = OLD.system_id
AND NOT EXISTS (SELECT 1 FROM game_to_systems WHERE system_id = OLD.system_id); AND NOT EXISTS (SELECT 1 FROM game_to_systems WHERE system_id = OLD.system_id);
END; END;
@@ -88,17 +90,38 @@ CREATE TABLE IF NOT EXISTS game_to_module (
FOREIGN KEY (game_id) REFERENCES game(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 CREATE TRIGGER tr_delete_game_to_module_module
AFTER DELETE ON game_to_module AFTER DELETE ON game_to_module
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
DELETE FROM module DELETE FROM module
WHERE id = OLD.id WHERE id = OLD.module_id
AND NOT EXISTS (SELECT 1 FROM game_to_module WHERE module_id = OLD.module_id); AND NOT EXISTS (SELECT 1 FROM game_to_module WHERE module_id = OLD.module_id);
END; END;
ALTER TABLE package_warnings ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE; ALTER TABLE package_warnings ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE;
ALTER TABLE pack 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 ( CREATE TABLE IF NOT EXISTS message (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
@@ -113,6 +136,9 @@ CREATE TABLE IF NOT EXISTS message (
flavor VARCHAR(128) NOT NULL, flavor VARCHAR(128) NOT NULL,
sound VARCHAR(128) NOT NULL, sound VARCHAR(128) NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -170,6 +196,9 @@ CREATE TABLE IF NOT EXISTS combat (
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
active INTEGER NOT NULL, active INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -199,6 +228,9 @@ CREATE TABLE IF NOT EXISTS combatant (
hidden BOOLEAN NOT NULL, hidden BOOLEAN NOT NULL,
defeated BOOLEAN NOT NULL, defeated BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
combat_id TEXT, combat_id TEXT,
FOREIGN KEY (combat_id) REFERENCES combat(id) ON DELETE CASCADE FOREIGN KEY (combat_id) REFERENCES combat(id) ON DELETE CASCADE
); );
@@ -220,6 +252,9 @@ CREATE TABLE IF NOT EXISTS card_deck (
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
display_count BOOLEAN NOT NULL, display_count BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -253,6 +288,9 @@ CREATE TABLE IF NOT EXISTS card (
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
drawn BOOLEAN NOT NULL, drawn BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
card_deck_id TEXT, card_deck_id TEXT,
FOREIGN KEY (card_deck_id) REFERENCES card_deck(id) ON DELETE CASCADE FOREIGN KEY (card_deck_id) REFERENCES card_deck(id) ON DELETE CASCADE
); );
@@ -291,6 +329,9 @@ CREATE TABLE IF NOT EXISTS user (
pronouns VARCHAR(128) NOT NULL, pronouns VARCHAR(128) NOT NULL,
role INTEGER NOT NULL, role INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -320,6 +361,9 @@ CREATE TABLE IF NOT EXISTS macro (
folder VARCHAR(128) NOT NULL, folder VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -339,6 +383,9 @@ CREATE TABLE IF NOT EXISTS world_folder (
color VARCHAR(128) NOT NULL, color VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -355,6 +402,9 @@ CREATE TABLE IF NOT EXISTS item (
folder VARCHAR(128) NOT NULL, folder VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -369,6 +419,9 @@ CREATE TABLE IF NOT EXISTS setting (
key_ VARCHAR(128) NOT NULL, key_ VARCHAR(128) NOT NULL,
value VARCHAR(128) NOT NULL, value VARCHAR(128) NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -382,6 +435,9 @@ CREATE TABLE IF NOT EXISTS journal (
name VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -394,6 +450,9 @@ CREATE TABLE IF NOT EXISTS journal_page (
src VARCHAR(128) NOT NULL, src VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
journal_id TEXT, journal_id TEXT,
FOREIGN KEY (journal_id) REFERENCES journal(id) ON DELETE CASCADE FOREIGN KEY (journal_id) REFERENCES journal(id) ON DELETE CASCADE
); );
@@ -430,7 +489,6 @@ CREATE TABLE IF NOT EXISTS journal_page_video (
); );
ALTER TABLE stats ADD COLUMN journal_page_id TEXT REFERENCES journal_page(id) ON DELETE CASCADE; ALTER TABLE stats ADD COLUMN journal_page_id TEXT REFERENCES journal_page(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_stats_journal_page_id ON stats(journal_page_id);
ALTER TABLE ownership_string 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; ALTER TABLE ownership_string ADD COLUMN journal_id TEXT REFERENCES journal(id) ON DELETE CASCADE;
@@ -447,6 +505,9 @@ CREATE TABLE IF NOT EXISTS table_ (
replacement BOOLEAN NOT NULL, replacement BOOLEAN NOT NULL,
display_roll BOOLEAN NOT NULL, display_roll BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -465,6 +526,9 @@ CREATE TABLE IF NOT EXISTS table_result (
weight INTEGER NOT NULL, weight INTEGER NOT NULL,
drawn BOOLEAN NOT NULL, drawn BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
table_id TEXT, table_id TEXT,
FOREIGN KEY (table_id) REFERENCES table_(id) ON DELETE CASCADE FOREIGN KEY (table_id) REFERENCES table_(id) ON DELETE CASCADE
); );
@@ -495,6 +559,9 @@ CREATE TABLE IF NOT EXISTS playlist (
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
playing BOOLEAN NOT NULL, playing BOOLEAN NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );
@@ -517,6 +584,9 @@ CREATE TABLE IF NOT EXISTS sound (
volume REAL NOT NULL, volume REAL NOT NULL,
paused_time REAL NOT NULL, paused_time REAL NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
playlist_id TEXT, playlist_id TEXT,
FOREIGN KEY (playlist_id) REFERENCES playlist(id) ON DELETE CASCADE FOREIGN KEY (playlist_id) REFERENCES playlist(id) ON DELETE CASCADE
); );
@@ -530,6 +600,9 @@ CREATE TABLE IF NOT EXISTS actor (
folder VARCHAR(128) NOT NULL, folder VARCHAR(128) NOT NULL,
sort INTEGER NOT NULL, sort INTEGER NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
game_id INTEGER, game_id INTEGER,
FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE
); );

View File

@@ -1,8 +1,8 @@
package actions package actions
import ( import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" "time"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/json"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
) )
@@ -14,29 +14,7 @@ type WsSessionMsg struct {
func (msg WsSessionMsg) Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error { func (msg WsSessionMsg) Action(tr *transport.FoundryTransport, status *types.FoundryStatus) error {
if status.IsActive { if status.IsActive {
tr.Logger.Info("Session msg", "userid", msg.UserId) return msg.OnActiveWorld(tr)
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
}
// select {
// case <-tr.LoggedInChan:
// return ErrUserChannelIsClosed
// default:
// tr.LoggedInChan <- true
// }
// time.Sleep(3 * time.Second)
// types.CloseChannel(tr.LoggedInChan)
go msg.OnActiveWorld(tr)
return nil
} }
if tr.IsDbInit { if tr.IsDbInit {
@@ -49,27 +27,36 @@ func (msg WsSessionMsg) Action(tr *transport.FoundryTransport, status *types.Fou
} }
func (msg WsSessionMsg) OnActiveWorld(tr *transport.FoundryTransport) error { func (msg WsSessionMsg) OnActiveWorld(tr *transport.FoundryTransport) error {
msgJson, err := tr.GetJsonData("world") tr.Logger.Info("Session msg", "userid", msg.UserId)
if msg.UserId != "" {
return msg.HandleLoggedInUser(tr)
}
err := tr.LogInToWorld()
if err != nil { if err != nil {
return err return err
} }
tr.Logger.Info("Game data received") tr.Logger.Info("World is started. Succesfully logged into world")
close(tr.ReadChan.Reconnect())
gameJson, err := json.ParseGame(msgJson) return nil
if err != nil { }
return err
} func (msg WsSessionMsg) HandleLoggedInUser(tr *transport.FoundryTransport) error {
if tr.LoggedInChan == nil {
var foundryStateDb db.Game tr.Logger.Info("World had been started before application was started. Run insertion of world data")
gameJson.ToDB(&foundryStateDb) go tr.InsertGameToDB()
return nil
err = foundryStateDb.Insert(tr.DB) }
if err != nil {
tr.ReadChan.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.DbCode} select {
} case <-tr.LoggedInChan:
return ErrUserChannelIsClosed
tr.Logger.Info("Game data succesfully inserted to DB") default:
tr.LoggedInChan <- true
}
time.Sleep(3 * time.Second)
types.CloseChannel(tr.LoggedInChan)
return nil return nil
} }

View File

@@ -8,10 +8,11 @@ import (
"time" "time"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/actions" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/actions"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
json_models "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/json" 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/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
"golang.org/x/sync/errgroup"
) )
var ( var (
@@ -24,7 +25,7 @@ var (
type FoundryApi struct { type FoundryApi struct {
//TODO: make check of admin's authentication //TODO: make check of admin's authentication
transport *transport.FoundryTransport Transport *transport.FoundryTransport
IsAvailable bool IsAvailable bool
// Logger *slog.Logger // Logger *slog.Logger
@@ -37,16 +38,16 @@ func NewFoundry() *FoundryApi {
} }
func (foundry *FoundryApi) SetTransport(tr *transport.FoundryTransport) { func (foundry *FoundryApi) SetTransport(tr *transport.FoundryTransport) {
foundry.transport = tr foundry.Transport = tr
} }
func (foundry *FoundryApi) ListenAndServeWS() error { func (foundry *FoundryApi) ListenAndServeWS() error {
var err error var err error
foundry.transport.ReadChan = *types.InitWsChannels() foundry.Transport.ReadChan = *types.InitWsChannels()
defer foundry.transport.ReadChan.Close() defer foundry.Transport.ReadChan.Close()
foundry.background(foundry.transport.ListenWebSocket) foundry.background(foundry.Transport.ListenWebSocket)
err = foundry.ServeWebSocket() err = foundry.ServeWebSocket()
if err != nil { if err != nil {
@@ -57,21 +58,21 @@ func (foundry *FoundryApi) ListenAndServeWS() error {
func (foundry *FoundryApi) ServeWebSocket() error { func (foundry *FoundryApi) ServeWebSocket() error {
var err error var err error
wsChannels := &foundry.transport.ReadChan wsChannels := &foundry.Transport.ReadChan
for { for {
select { select {
case message, ok := <-wsChannels.Msg(): case message, ok := <-wsChannels.Msg():
if !ok { if !ok {
foundry.transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode) foundry.Transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode)
foundry.transport.Http.SessionID = nil foundry.Transport.Http.SessionID = nil
return ChannelIsClosed 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 { switch message.Code {
case types.RespPingCode, types.RespSessionDataCode: case types.RespPingCode, types.RespSessionDataCode:
err := foundry.transport.SendOnlyCodeRequest(message.Code) err := foundry.Transport.SendOnlyCodeRequest(message.Code)
if err != nil { if err != nil {
wsChannels.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.WebSocketCode} wsChannels.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.WebSocketCode}
continue continue
@@ -84,69 +85,69 @@ func (foundry *FoundryApi) ServeWebSocket() error {
continue continue
} }
foundry.transport.Logger.Debug("RespServerChangeCode", "data", data) foundry.Transport.Logger.Debug("RespServerChangeCode", "data", data)
if data == nil { if data == nil {
continue continue
} }
err = data.Action(foundry.transport, &foundry.Status) err = data.Action(foundry.Transport, &foundry.Status)
if err != nil { if err != nil {
wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode} wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode}
continue continue
} }
case types.RespDataCode: case types.RespDataCode:
foundry.transport.ExchangeChan.Msgs[message.Id] = make(chan []byte, 1) foundry.Transport.ExchangeChan.Msgs[message.Id] = make(chan []byte, 1)
foundry.transport.ExchangeChan.Msgs[message.Id] <- []byte(message.MsgJson) foundry.Transport.ExchangeChan.Msgs[message.Id] <- []byte(message.MsgJson)
go foundry.transport.CloseMsgChannel(message.Id, 5*time.Second) go foundry.Transport.CloseMsgChannel(message.Id, 5*time.Second)
default: default:
} }
case err = <-wsChannels.Err(): case err = <-wsChannels.Err():
var foundryErr *types.FoundryError var foundryErr *types.FoundryError
if errors.As(err, &foundryErr) { if errors.As(err, &foundryErr) {
if foundryErr.IsFatal { if foundryErr.IsFatal {
foundry.transport.CloseWebSocketConn() foundry.Transport.CloseWebSocketConn()
foundry.transport.Http.SessionID = nil foundry.Transport.Http.SessionID = nil
return foundryErr 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(): case <-wsChannels.Done():
foundry.transport.CloseWebSocketConn() foundry.Transport.CloseWebSocketConn()
foundry.transport.Http.SessionID = nil foundry.Transport.Http.SessionID = nil
return ListenIsDone return ListenIsDone
case <-wsChannels.Reconnect(): case <-wsChannels.Reconnect():
foundry.transport.CloseWebSocketConn() foundry.Transport.CloseWebSocketConn()
return ReconnectToWebSocket return ReconnectToWebSocket
} }
} }
} }
func (foundry *FoundryApi) HandleWSRequest(msgType string) ([]byte, error) { 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 { func (foundry *FoundryApi) Shutdown() error {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel() defer cancel()
foundry.transport.Logger.Info("Completing foundry background tasks") foundry.Transport.Logger.Info("Completing foundry background tasks")
foundryClosed := make(chan struct{}) foundryClosed := make(chan struct{})
go func() { go func() {
types.CloseChannel(foundry.transport.ReadChan.Done()) types.CloseChannel(foundry.Transport.ReadChan.Done())
foundry.wg.Wait() foundry.wg.Wait()
foundry.transport.ExchangeChan.Close() foundry.Transport.ExchangeChan.Close()
foundry.transport.ReadChan.Close() foundry.Transport.ReadChan.Close()
types.CloseChannel(foundryClosed) types.CloseChannel(foundryClosed)
}() }()
select { select {
case <-foundryClosed: case <-foundryClosed:
foundry.transport.Logger.Info("Stopped foundry server") foundry.Transport.Logger.Info("Stopped foundry server")
case <-ctx.Done(): case <-ctx.Done():
return CloseTimeoutExceed return CloseTimeoutExceed
} }
@@ -156,24 +157,24 @@ func (foundry *FoundryApi) Shutdown() error {
func (foundry *FoundryApi) ConnectToWebSocket() (bool, error) { func (foundry *FoundryApi) ConnectToWebSocket() (bool, error) {
var err error var err error
if !foundry.transport.HasSessionId() { if !foundry.Transport.HasSessionId() {
err = foundry.transport.InitSessionId() err = foundry.Transport.InitSessionId()
if err != nil { if err != nil {
return false, err return false, err
} }
err = foundry.transport.ConnectToFoundry() err = foundry.Transport.ConnectToFoundry()
if err != nil { if err != nil {
return false, err return false, err
} }
} }
err = foundry.transport.InitWebSocketConnection() err = foundry.Transport.InitWebSocketConnection()
if err != nil { if err != nil {
return false, err return false, err
} }
status, err := foundry.transport.Http.GetStatus() status, err := foundry.Transport.Http.GetStatus()
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -195,21 +196,21 @@ func (foundry *FoundryApi) StartListenFoundry() {
ok, err := foundry.ConnectToWebSocket() ok, err := foundry.ConnectToWebSocket()
if err != nil { if err != nil {
if errors.Is(err, ReconnectToWebSocket) { if errors.Is(err, ReconnectToWebSocket) {
if foundry.transport.ReconnectNum >= foundry.transport.ReconnectNumMax { if foundry.Transport.ReconnectNum >= foundry.Transport.ReconnectNumMax {
return return
} }
time.Sleep(foundry.transport.ReconnectTimeout) time.Sleep(foundry.Transport.ReconnectTimeout)
foundry.transport.ReconnectNum++ foundry.Transport.ReconnectNum++
foundry.transport.Logger.Info("Reconnecting to WebSocket", "times", foundry.transport.ReconnectNum) foundry.Transport.Logger.Info("Reconnecting to WebSocket", "times", foundry.Transport.ReconnectNum)
continue continue
} }
foundry.transport.Logger.Error("Error raised", "err", err.Error()) foundry.Transport.Logger.Error("Error raised", "err", err.Error())
if ok { if ok {
timeInterval = 1 * time.Second 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) time.Sleep(timeInterval)
timeInterval = min(timeInterval*2, 15*time.Second) timeInterval = min(timeInterval*2, 15*time.Second)
@@ -220,11 +221,28 @@ func (foundry *FoundryApi) StartListenFoundry() {
}) })
} }
func (foundry *FoundryApi) Test() (*json_models.Game, error) { func (foundry *FoundryApi) PrepareDB() error {
wsMsg := types.NewWsMessage("world", foundry.transport.CurrWsId) dbConn := foundry.Transport.DB
foundry.transport.CurrWsId++
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 { if err != nil {
return nil, err return nil, err
} }
@@ -238,11 +256,6 @@ func (foundry *FoundryApi) Test() (*json_models.Game, error) {
} }
elapsed := time.Since(start) 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 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() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
f.transport.Logger.Error(fmt.Sprintf("%s", err)) f.Transport.Logger.Error(fmt.Sprintf("%s", err))
} }
}() }()

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -25,9 +24,13 @@ type Actor struct {
} }
func (a *Actor) Query(data *InsertId[uint]) { func (a *Actor) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO actor (%s, id, img, name, type, folder, sort) INSERT INTO actor (game_id, id, img, name, type, folder, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7)`, data.fieldName) 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 { func (a *Actor) InsertObjects(tx *sqlx.Tx) error {
@@ -53,12 +56,16 @@ func (a *Actor) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, a.ID, a.Img, a.Name, a.Type, a.Folder, a.Sort} args := []any{data.id, a.ID, a.Img, a.Name, a.Type, a.Folder, a.Sort}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return a.InsertObjects(tx) if isInserted {
return a.InsertObjects(tx)
}
return nil
} }
func (a *Actor) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { func (a *Actor) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
@@ -68,10 +75,14 @@ func (a *Actor) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]
args := []any{data.id, a.ID, a.Img, a.Name, a.Type, a.Folder, a.Sort} args := []any{data.id, a.ID, a.Img, a.Name, a.Type, a.Folder, a.Sort}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return a.InsertObjects(tx) if isInserted {
return a.InsertObjects(tx)
}
return nil
} }

View File

@@ -30,7 +30,11 @@ type CardDeck struct {
func (c *CardDeck) Query(data *InsertId[uint]) { func (c *CardDeck) Query(data *InsertId[uint]) {
data.query = ` data.query = `
INSERT INTO card_deck (game_id, id, name, type, description, img, folder, width, height, rotation, sort, display_count) 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)` 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 { func (c *CardDeck) InsertObjects(tx *sqlx.Tx) error {
@@ -55,11 +59,15 @@ func (c *CardDeck) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
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} 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}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }
@@ -70,11 +78,15 @@ func (c *CardDeck) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[ui
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} 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}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }
@@ -101,7 +113,11 @@ type Card struct {
func (c *Card) Query(data *InsertId[string]) { func (c *Card) Query(data *InsertId[string]) {
data.query = ` data.query = `
INSERT INTO card (card_deck_id, id, name, type, suit, description, origin, width, height, rotation, value, face, sort, drawn) 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)` 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 { func (c *Card) InsertObjects(tx *sqlx.Tx) error {
@@ -124,13 +140,17 @@ func (c *Card) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
return ErrNoQuery 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.Sort, c.Drawn} 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}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }
@@ -139,13 +159,17 @@ func (c *Card) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string
return ErrNoQuery 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.Sort, c.Drawn} 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}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }

View File

@@ -26,7 +26,11 @@ type Combat struct {
func (c *Combat) Query(data *InsertId[uint]) { func (c *Combat) Query(data *InsertId[uint]) {
data.query = ` data.query = `
INSERT INTO combat (game_id, id, type, scene, round, turn, sort, active) INSERT INTO combat (game_id, id, type, scene, round, turn, sort, active)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` 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 { func (c *Combat) InsertObjects(tx *sqlx.Tx) error {
@@ -51,11 +55,15 @@ func (c *Combat) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, c.ID, c.Type, c.Scene, c.Round, c.Turn, c.Sort, c.Active} args := []any{data.id, c.ID, c.Type, c.Scene, c.Round, c.Turn, c.Sort, c.Active}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }
@@ -66,11 +74,15 @@ func (c *Combat) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint
args := []any{data.id, c.ID, c.Type, c.Scene, c.Round, c.Turn, c.Sort, c.Active} args := []any{data.id, c.ID, c.Type, c.Scene, c.Round, c.Turn, c.Sort, c.Active}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }
@@ -91,12 +103,16 @@ type Combatant struct {
func (c *Combatant) Query(data *InsertId[string]) { func (c *Combatant) Query(data *InsertId[string]) {
data.query = ` data.query = `
INSERT INTO combatant (combat_id, id, token_id, scene_id, actor_id, img, group_, initiative, hidden, defeated) 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)` 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 { func (c *Combatant) InsertObjects(tx *sqlx.Tx) error {
relId := InsertId[string]{id: c.ID, fieldName: "combat_id"} relId := InsertId[string]{id: c.ID, fieldName: "combatant_id"}
group, ctx := errgroup.WithContext(context.Background()) group, ctx := errgroup.WithContext(context.Background())
InsertWithCtxParallel(group, ctx, tx, c.Stats, relId) InsertWithCtxParallel(group, ctx, tx, c.Stats, relId)
@@ -113,13 +129,17 @@ func (c *Combatant) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
return ErrNoQuery return ErrNoQuery
} }
args := []any{data.id, c.ID, c.TokenId, c.SceneId, c.ActorId, c.Img, c.Group, c.Initiative, c.Hidden, c.Defeated} args := []any{data.id, c.ID, c.TokenId, c.SceneId, c.ActorId, c.Type, c.Img, c.Group, c.Initiative, c.Hidden, c.Defeated}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }
@@ -128,12 +148,16 @@ func (c *Combatant) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[s
return ErrNoQuery return ErrNoQuery
} }
args := []any{data.id, c.ID, c.TokenId, c.SceneId, c.ActorId, c.Img, c.Group, c.Initiative, c.Hidden, c.Defeated} args := []any{data.id, c.ID, c.TokenId, c.SceneId, c.ActorId, c.Type, c.Img, c.Group, c.Initiative, c.Hidden, c.Defeated}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return c.InsertObjects(tx)
}
return nil return nil
} }

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

@@ -90,10 +90,13 @@ type WorldFolder struct {
} }
func (w *WorldFolder) Query(data *InsertId[uint]) { func (w *WorldFolder) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO world_folder (%s, id, name, type, folder, sorting, description, color, sort) INSERT INTO world_folder (game_id, id, name, type, folder, sorting, description, color, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id`, data.fieldName) 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 { func (w *WorldFolder) InsertObjects(tx *sqlx.Tx) error {
@@ -116,12 +119,16 @@ func (w *WorldFolder) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, w.ID, w.Name, w.Type, w.Folder, w.Sorting, w.Description, w.Color, w.Sort} args := []any{data.id, w.ID, w.Name, w.Type, w.Folder, w.Sorting, w.Description, w.Color, w.Sort}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return w.InsertObjects(tx) if isInserted {
return w.InsertObjects(tx)
}
return nil
} }
func (w *WorldFolder) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { func (w *WorldFolder) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
@@ -131,10 +138,14 @@ func (w *WorldFolder) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId
args := []any{data.id, w.ID, w.Name, w.Type, w.Folder, w.Sorting, w.Description, w.Color, w.Sort} args := []any{data.id, w.ID, w.Name, w.Type, w.Folder, w.Sorting, w.Description, w.Color, w.Sort}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return w.InsertObjects(tx) if isInserted {
return w.InsertObjects(tx)
}
return nil
} }

View File

@@ -1,308 +0,0 @@
package db
import (
"context"
"database/sql"
"errors"
"time"
"github.com/jmoiron/sqlx"
)
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 *sqlx.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

@@ -68,24 +68,31 @@ func (g *Game) InsertObjects(tx *sqlx.Tx) error {
InsertSliceParallel(group, tx, g.Modules, relData) InsertSliceParallel(group, tx, g.Modules, relData)
InsertSliceParallel(group, tx, g.PackageWarnings, relData) InsertSliceParallel(group, tx, g.PackageWarnings, relData)
InsertSliceParallel(group, tx, g.Packs, relDataString) InsertSliceParallel(group, tx, g.Packs, relDataString)
// InsertSliceParallel(group, tx, g.Messages, relData) InsertSliceParallel(group, tx, g.Messages, relData)
// InsertSliceParallel(group, tx, g.Combats, relData) InsertSliceParallel(group, tx, g.Combats, relData)
// InsertSliceParallel(group, tx, g.CardDeck, relData) InsertSliceParallel(group, tx, g.CardDeck, relData)
// InsertSliceParallel(group, tx, g.Users, relData) InsertSliceParallel(group, tx, g.Users, relData)
// InsertSliceParallel(group, tx, g.Macros, relData) InsertSliceParallel(group, tx, g.Macros, relData)
// InsertSliceParallel(group, tx, g.Folders, relData) InsertSliceParallel(group, tx, g.Folders, relData)
// InsertSliceParallel(group, tx, g.Items, InsertSliceParallel(group, tx, g.Settings, relData)
// InsertId[string]{id: strconv.FormatUint(uint64(g.ID), 10), fieldName: "game_id"}) InsertSliceParallel(group, tx, g.Journals, relData)
// InsertSliceParallel(group, tx, g.Settings, relData) InsertSliceParallel(group, tx, g.Tables, relData)
// InsertSliceParallel(group, tx, g.Journals, relData) InsertSliceParallel(group, tx, g.Playlists, relData)
// InsertSliceParallel(group, tx, g.Tables, relData)
// InsertSliceParallel(group, tx, g.Playlists, relData)
// InsertSliceParallel(group, tx, g.Actors, relData)
err := group.Wait() err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) { if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err return err
} }
InsertSliceParallelTimeout(group, tx, g.Items,
InsertId[string]{id: strconv.FormatUint(uint64(g.ID), 10), fieldName: "game_id"},
15*time.Second)
InsertSliceParallelTimeout(group, tx, g.Actors, relData, 15*time.Second)
err = group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil return nil
} }

View File

@@ -0,0 +1,88 @@
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}
_, 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

@@ -24,9 +24,12 @@ type Item struct {
func (i *Item) Query(data *InsertId[string]) { func (i *Item) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(` data.query = fmt.Sprintf(`
INSERT INTO world_folder (%s, id, img, name, type, folder, sort) INSERT INTO item (%[1]s, id, img, name, type, folder, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id`, data.fieldName) 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 { func (i *Item) InsertObjects(tx *sqlx.Tx) error {
@@ -50,12 +53,16 @@ func (i *Item) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
args := []any{data.id, i.ID, i.Img, i.Name, i.Type, i.Folder, i.Sort} args := []any{data.id, i.ID, i.Img, i.Name, i.Type, i.Folder, i.Sort}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return i.InsertObjects(tx) if isInserted {
return i.InsertObjects(tx)
}
return nil
} }
func (i *Item) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error { func (i *Item) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
@@ -65,10 +72,14 @@ func (i *Item) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string
args := []any{data.id, i.ID, i.Img, i.Name, i.Type, i.Folder, i.Sort} args := []any{data.id, i.ID, i.Img, i.Name, i.Type, i.Folder, i.Sort}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return i.InsertObjects(tx) if isInserted {
return i.InsertObjects(tx)
}
return nil
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -20,9 +19,13 @@ type Journal struct {
} }
func (j *Journal) Query(data *InsertId[uint]) { func (j *Journal) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO journal (%s, id, name, sort) INSERT INTO journal (game_id, id, name, sort)
VALUES ($1, $2, $3, $4)`, data.fieldName) 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 { func (j *Journal) InsertObjects(tx *sqlx.Tx) error {
@@ -46,12 +49,16 @@ func (j *Journal) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, j.ID, j.Name, j.Sort} args := []any{data.id, j.ID, j.Name, j.Sort}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return j.InsertObjects(tx) if isInserted {
return j.InsertObjects(tx)
}
return nil
} }
func (j *Journal) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { func (j *Journal) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
@@ -61,10 +68,14 @@ func (j *Journal) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uin
args := []any{data.id, j.ID, j.Name, j.Sort} args := []any{data.id, j.ID, j.Name, j.Sort}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return j.InsertObjects(tx) if isInserted {
return j.InsertObjects(tx)
}
return nil
} }

View File

@@ -25,9 +25,13 @@ type JournalPage struct {
} }
func (j *JournalPage) Query(data *InsertId[string]) { func (j *JournalPage) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO journal_page (%s, id, name, type, src, sort) INSERT INTO journal_page (journal_id, id, name, type, src, sort)
VALUES ($1, $2, $3, $4, $5, $6)`, data.fieldName) 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 { func (j *JournalPage) InsertObjects(tx *sqlx.Tx) error {
@@ -55,12 +59,16 @@ func (j *JournalPage) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
args := []any{data.id, j.ID, j.Name, j.Type, j.Src, j.Sort} args := []any{data.id, j.ID, j.Name, j.Type, j.Src, j.Sort}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return j.InsertObjects(tx) if isInserted {
return j.InsertObjects(tx)
}
return nil
} }
func (j *JournalPage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error { func (j *JournalPage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
@@ -70,12 +78,16 @@ func (j *JournalPage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId
args := []any{data.id, j.ID, j.Name, j.Type, j.Src, j.Sort} args := []any{data.id, j.ID, j.Name, j.Type, j.Src, j.Sort}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return j.InsertObjects(tx) if isInserted {
return j.InsertObjects(tx)
}
return nil
} }
type PageText struct { type PageText struct {

View File

@@ -27,7 +27,11 @@ type Macro struct {
func (m *Macro) Query(data *InsertId[uint]) { func (m *Macro) Query(data *InsertId[uint]) {
data.query = ` data.query = `
INSERT INTO macro (game_id, id, command, name, type, img, author, scope, folder, sort) INSERT INTO macro (game_id, id, command, name, type, img, author, scope, folder, sort)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` 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 { func (m *Macro) InsertObjects(tx *sqlx.Tx) error {
@@ -51,11 +55,15 @@ func (m *Macro) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, m.ID, m.Command, m.Name, m.Type, m.Img, m.Author, m.Scope, m.Folder, m.Sort} args := []any{data.id, m.ID, m.Command, m.Name, m.Type, m.Img, m.Author, m.Scope, m.Folder, m.Sort}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return m.InsertObjects(tx)
}
return nil return nil
} }
@@ -66,10 +74,14 @@ func (m *Macro) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]
args := []any{data.id, m.ID, m.Command, m.Name, m.Type, m.Img, m.Author, m.Scope, m.Folder, m.Sort} args := []any{data.id, m.ID, m.Command, m.Name, m.Type, m.Img, m.Author, m.Scope, m.Folder, m.Sort}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return m.InsertObjects(tx)
}
return nil return nil
} }

View File

@@ -30,8 +30,12 @@ type Message struct {
func (m *Message) Query(data *InsertId[uint]) { func (m *Message) Query(data *InsertId[uint]) {
data.query = ` data.query = `
INSERT INTO addresses (game_id, id, blind, emote, style, timestamp, content, author, type, flavor, sound) 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)` 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 { func (m *Message) InsertObjects(tx *sqlx.Tx) error {
@@ -58,11 +62,15 @@ func (m *Message) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, m.ID, m.Blind, m.Emote, m.Style, m.Timestamp, m.Content, m.Author, m.Type, m.Flavor, m.Sound} args := []any{data.id, m.ID, m.Blind, m.Emote, m.Style, m.Timestamp, m.Content, m.Author, m.Type, m.Flavor, m.Sound}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return m.InsertObjects(tx)
}
return nil return nil
} }
@@ -73,11 +81,15 @@ func (m *Message) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uin
args := []any{data.id, m.ID, m.Blind, m.Emote, m.Style, m.Timestamp, m.Content, m.Author, m.Type, m.Flavor, m.Sound} args := []any{data.id, m.ID, m.Blind, m.Emote, m.Style, m.Timestamp, m.Content, m.Author, m.Type, m.Flavor, m.Sound}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
if isInserted {
return m.InsertObjects(tx)
}
return nil return nil
} }
@@ -93,7 +105,7 @@ type Speaker struct {
func (s Speaker) Query(data *InsertId[string]) { func (s Speaker) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(` data.query = fmt.Sprintf(`
INSERT INTO speaker (%s, scene, actor, token, alias) INSERT INTO speaker (%s, scene, actor, token, alias)
VALUES ($1, $2, $3, $4, $5, $6)`, data.fieldName) VALUES ($1, $2, $3, $4, $5)`, data.fieldName)
} }
func (s Speaker) Insert(tx *sqlx.Tx, data *InsertId[string]) error { func (s Speaker) Insert(tx *sqlx.Tx, data *InsertId[string]) error {

View File

@@ -1,35 +0,0 @@
package db
import "github.com/jmoiron/sqlx"
// 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 *sqlx.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" "context"
"database/sql" "database/sql"
"errors" "errors"
"time" "strings"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
) )
type Module struct { type Module struct {
Id int64 ID string
TextId string
Title string Title string
Description string Description string
Url string URL string
Version string License string
Availability int Readme string
CreatedAt time.Time Bugs string
Languages []Language Changelog string
Compatibility Compatibility 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
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 { func (m *Module) Query(data *InsertId[uint]) {
Id int64 data.query = `
Lang string INSERT INTO module (setup_id, id, title, description, url, license, readme, bugs,
Name string changelog, version, manifest, download, socket, protected, exclusive_, persistent_storage,
Path string 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 (m *Module) ConnectGameQuery(data *InsertId[uint]) {
data.query = `
func (modules Modules) GetById(id int) *Module { INSERT INTO game_to_module (game_id, module_id)
return &modules[id] VALUES ($1, $2)`
} }
func (m FoundryStateModel) InsertModule(module *Module, stateId int64) error { func (m *Module) InsertObjects(tx *sqlx.Tx) error {
query := ` relId := InsertId[string]{id: m.ID, fieldName: "module_id"}
INSERT INTO modules (state_id, text_id, title, description, url, version, availability) group, ctx := errgroup.WithContext(context.Background())
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, created_at`
args := []any{stateId, module.TextId, module.Title, module.Description, module.Url, module.Version, module.Availability} InsertWithCtxParallel(group, ctx, tx, m.DocumentTypes, relId)
InsertWithCtxParallel(group, ctx, tx, m.Relationships, relId)
InsertWithCtxParallel(group, ctx, tx, m.Compatibility, relId)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) scriptRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "scripts"}
defer cancel() 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) 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)
err := group.Wait()
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
}
func (m *Module) ConnectGame(tx *sqlx.Tx, data *InsertId[uint]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, m.ID}
_, err := tx.Exec(data.query, args...)
return err
}
func (m *Module) Insert(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.Exec(data.query, args...)
if err != nil { if err != nil {
return err return err
} }
for i := range module.Languages {
err = m.InsertModuleLanguages(&module.Languages[i], module.Id) rowsAffected, err := res.RowsAffected()
if err != nil && err != sql.ErrNoRows { if rowsAffected != 0 {
err = m.InsertObjects(tx)
if err != nil {
return err return err
} }
} }
return m.InsertModuleCompatibility(&module.Compatibility, module.Id)
}
func (m FoundryStateModel) InsertModuleCompatibility(compatibility *Compatibility, moduleId int64) error { if dataId == nil {
query := ` dataCopy := *data
INSERT INTO modules_compatibility (module_id, minimum, verified, maximum) m.ConnectGameQuery(&dataCopy)
VALUES ($1, $2, $3, $4) err = m.ConnectGame(tx, &dataCopy)
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 {
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`
args := []any{moduleId, lang.Lang, lang.Name, lang.Path}
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 {
return err
}
return nil
}
func (m FoundryStateModel) GetModules(idState int64) (Modules, error) {
if idState < 1 {
return nil, ErrorRecordNotFound
} }
query := ` return err
SELECT id, created_at, text_id, title, description, url, version, availability }
FROM modules
WHERE state_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) func (m *Module) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
defer cancel() if data.query == "" {
return ErrNoQuery
}
rows, err := m.DB.QueryContext(ctx, query, idState) args := []any{data.id, m.ID}
_, 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 { if err != nil {
switch { return err
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
} }
modules := make(Modules, 0, 1) rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
for rows.Next() { err = m.InsertObjects(tx)
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 { if err != nil {
return nil, err return 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 { if dataId == nil {
return nil, err dataCopy := *data
m.ConnectGameQuery(&dataCopy)
err = m.ConnectGameCtx(ctx, tx, &dataCopy)
} }
return modules, nil return err
}
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)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func (m FoundryStateModel) DeleteModule(idModule int64) error {
if idModule < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM modules
WHERE id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idModule)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
} }

View File

@@ -5,6 +5,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -27,9 +28,23 @@ type Pack struct {
} }
func (p *Pack) Query(data *InsertId[string]) { func (p *Pack) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(` if !strings.EqualFold(data.fieldName, "game_id") {
INSERT INTO pack (%s, id, name, label, banner, path, type, system, package_type, package_name) data.query = fmt.Sprintf(`
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, data.fieldName) 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 { func (p *Pack) InsertObjects(tx *sqlx.Tx) error {
@@ -49,20 +64,61 @@ func (p *Pack) InsertObjects(tx *sqlx.Tx) error {
return nil 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 { func (p *Pack) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" { if data.query == "" {
return ErrNoQuery return ErrNoQuery
} }
args := []any{data.id, p.ID, p.Name, p.Label, p.Banner, p.Path, p.Type, 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} p.System, p.PackageType, p.PackageName}
_, err := tx.Exec(data.query, args...) res, err := tx.Exec(data.query, args...)
if err != nil { if err != nil {
return err return err
} }
return p.InsertObjects(tx) 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 { func (p *Pack) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
@@ -70,15 +126,34 @@ func (p *Pack) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string
return ErrNoQuery return ErrNoQuery
} }
args := []any{data.id, p.ID, p.Name, p.Label, p.Banner, p.Path, p.Type, 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} p.System, p.PackageType, p.PackageName}
_, err := tx.ExecContext(ctx, data.query, args...) res, err := tx.ExecContext(ctx, data.query, args...)
if err != nil { if err != nil {
return err return err
} }
return p.InsertObjects(tx) 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 { type PackFolder struct {

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -29,9 +28,13 @@ type Playlist struct {
} }
func (p *Playlist) Query(data *InsertId[uint]) { func (p *Playlist) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO playlist (%s, id, name, folder, sorting, description, channel, mode, fade, seed, sort, playing) 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)`, data.fieldName) 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 { func (p *Playlist) InsertObjects(tx *sqlx.Tx) error {
@@ -56,12 +59,16 @@ func (p *Playlist) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
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} 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}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return p.InsertObjects(tx) if isInserted {
return p.InsertObjects(tx)
}
return nil
} }
func (p *Playlist) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { func (p *Playlist) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
@@ -71,12 +78,16 @@ func (p *Playlist) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[ui
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} 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}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return p.InsertObjects(tx) if isInserted {
return p.InsertObjects(tx)
}
return nil
} }
type Sound struct { type Sound struct {
@@ -95,9 +106,12 @@ type Sound struct {
} }
func (s *Sound) Query(data *InsertId[string]) { func (s *Sound) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO Sound (%s, id, name, path, channel, description, fade, sort, repeat, playing, volume, paused_time) 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)`, data.fieldName) 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 { func (s *Sound) Insert(tx *sqlx.Tx, data *InsertId[string]) error {

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -19,9 +18,13 @@ type Setting struct {
} }
func (s *Setting) Query(data *InsertId[uint]) { func (s *Setting) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO setting (%s, id, key_, value) INSERT INTO setting (game_id, id, key_, value)
VALUES ($1, $2, $3, $4)`, data.fieldName) 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 { func (s *Setting) InsertObjects(tx *sqlx.Tx) error {
@@ -44,12 +47,16 @@ func (s *Setting) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, s.ID, s.Key, s.Value} args := []any{data.id, s.ID, s.Key, s.Value}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return s.InsertObjects(tx) if isInserted {
return s.InsertObjects(tx)
}
return nil
} }
func (s *Setting) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { func (s *Setting) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
@@ -59,10 +66,14 @@ func (s *Setting) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uin
args := []any{data.id, s.ID, s.Key, s.Value} args := []any{data.id, s.ID, s.Key, s.Value}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return s.InsertObjects(tx) if isInserted {
return s.InsertObjects(tx)
}
return nil
} }

View File

@@ -177,3 +177,18 @@ func (n *News) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint])
return nil 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

@@ -4,201 +4,186 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"time" "strings"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
) )
type System struct { type System struct {
Id int64 ID string
TextId string
Title string Title string
Description string Description string
Url string URL string
Download string License string
CreatedAt time.Time Bugs string
Compatibility Compatibility Changelog string
Version string
Manifest string
Download string
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 (s *System) Query(data *InsertId[string]) {
data.query = `
func (systems Systems) GetById(id int) *System { INSERT INTO system (setup_id, id, title, description, url, license, bugs, changelog, version, manifest,
return &systems[id] 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 { func (s *System) ConnectGameQuery(data *InsertId[string]) {
query := ` data.query = `
INSERT INTO systems (state_id, text_id, title, description, url, download) INSERT INTO game_to_systems (game_id, system_id)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2)`
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 (m FoundryStateModel) InsertSystemCompatibility(compatibility *Compatibility, systemId int64) error { func (s *System) InsertObjects(tx *sqlx.Tx) error {
query := ` relId := InsertId[string]{id: s.ID, fieldName: "system_id"}
INSERT INTO systems_compatibility (system_id, minimum, verified, maximum) group, ctx := errgroup.WithContext(context.Background())
VALUES ($1, $2, $3, $4)`
args := []any{systemId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum} InsertWithCtxParallel(group, ctx, tx, s.Compatibility, relId)
InsertWithCtxParallel(group, ctx, tx, s.Relationships, relId)
InsertWithCtxParallel(group, ctx, tx, s.DocumentTypes, relId)
InsertWithCtxParallel(group, ctx, tx, s.Grid, relId)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) esModulesRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "es_modules"}
defer cancel() 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) InsertSliceParallel(group, tx, s.Authors, relId)
if err != nil && err != sql.ErrNoRows { 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 err
} }
return nil return nil
} }
func (m FoundryStateModel) GetSystems(idState int64) (Systems, error) { func (s *System) ConnectGame(tx *sqlx.Tx, data *InsertId[string]) error {
if idState < 1 { if data.query == "" {
return nil, ErrorRecordNotFound return ErrNoQuery
} }
query := ` args := []any{data.id, s.ID}
SELECT id, created_at, text_id, title, description, url, download
FROM systems
WHERE state_id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) _, err := tx.Exec(data.query, args...)
defer cancel() return err
}
rows, err := m.DB.QueryContext(ctx, query, idState) func (s *System) Insert(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.Exec(data.query, args...)
if err != nil { if err != nil {
switch { return err
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
} }
systems := make(Systems, 0, 1) rowsAffected, err := res.RowsAffected()
if rowsAffected != 0 {
for rows.Next() { err = s.InsertObjects(tx)
var system System
err := rows.Scan(
&system.Id,
&system.CreatedAt,
&system.TextId,
&system.Title,
&system.Description,
&system.Url,
&system.Download,
)
if err != nil { if err != nil {
return nil, err return err
} }
compatibility, err := m.GetModuleCompatibility(system.Id) }
if dataId == nil {
dataCopy := *data
s.ConnectGameQuery(&dataCopy)
err = s.ConnectGame(tx, &dataCopy)
}
return err
}
func (s *System) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
if data.query == "" {
return ErrNoQuery
}
args := []any{data.id, s.ID}
_, 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 := res.RowsAffected()
if rowsAffected != 0 {
err = s.InsertObjects(tx)
if err != nil { if err != nil {
return nil, err return err
}
system.Compatibility = *compatibility
systems = append(systems, system)
}
if err := rows.Err(); err != nil {
return nil, err
}
return systems, nil
}
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 if dataId == nil {
} dataCopy := *data
s.ConnectGameQuery(&dataCopy)
func (m FoundryStateModel) DeleteSystems(idState int64) error { err = s.ConnectGameCtx(ctx, tx, &dataCopy)
if idState < 1 { }
return ErrorRecordNotFound
} return err
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)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
}
func (m FoundryStateModel) DeleteSystem(idSystem int64) error {
if idSystem < 1 {
return ErrorRecordNotFound
}
query := `
DELETE FROM systems
WHERE id = $1`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := m.DB.ExecContext(ctx, query, idSystem)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return ErrorRecordNotFound
}
return nil
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -27,9 +26,13 @@ type Table struct {
} }
func (t *Table) Query(data *InsertId[uint]) { func (t *Table) Query(data *InsertId[uint]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO table_ (%s, id, name, description, formula, img, folder, sort, replacement, display_roll) 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)`, data.fieldName) 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 { func (t *Table) InsertObjects(tx *sqlx.Tx) error {
@@ -54,12 +57,16 @@ func (t *Table) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
args := []any{data.id, t.ID, t.Name, t.Description, t.Formula, t.Img, t.Folder, t.Sort, t.Replacement, t.DisplayRoll} args := []any{data.id, t.ID, t.Name, t.Description, t.Formula, t.Img, t.Folder, t.Sort, t.Replacement, t.DisplayRoll}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return t.InsertObjects(tx) if isInserted {
return t.InsertObjects(tx)
}
return nil
} }
func (t *Table) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { func (t *Table) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
@@ -69,12 +76,16 @@ func (t *Table) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]
args := []any{data.id, t.ID, t.Name, t.Description, t.Formula, t.Img, t.Folder, t.Sort, t.Replacement, t.DisplayRoll} args := []any{data.id, t.ID, t.Name, t.Description, t.Formula, t.Img, t.Folder, t.Sort, t.Replacement, t.DisplayRoll}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return t.InsertObjects(tx) if isInserted {
return t.InsertObjects(tx)
}
return nil
} }
type TableResult struct { type TableResult struct {
@@ -91,9 +102,13 @@ type TableResult struct {
} }
func (t *TableResult) Query(data *InsertId[string]) { func (t *TableResult) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO table_result (%s, id, type, img, description, name, weight, drawn) INSERT INTO table_result (table_id, id, type, img, description, name, weight, drawn)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, data.fieldName) 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 { func (t *TableResult) InsertObjects(tx *sqlx.Tx) error {
@@ -116,12 +131,16 @@ func (t *TableResult) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
args := []any{data.id, t.ID, t.Type, t.Img, t.Description, t.Name, t.Weight, t.Drawn} args := []any{data.id, t.ID, t.Type, t.Img, t.Description, t.Name, t.Weight, t.Drawn}
_, err := tx.Exec(data.query, args...) var isInserted bool
err := tx.QueryRowx(data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return t.InsertObjects(tx) if isInserted {
return t.InsertObjects(tx)
}
return nil
} }
func (t *TableResult) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error { func (t *TableResult) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
@@ -131,10 +150,14 @@ func (t *TableResult) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId
args := []any{data.id, t.ID, t.Type, t.Img, t.Description, t.Name, t.Weight, t.Drawn} args := []any{data.id, t.ID, t.Type, t.Img, t.Description, t.Name, t.Weight, t.Drawn}
_, err := tx.ExecContext(ctx, data.query, args...) var isInserted bool
err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted)
if err != nil { if err != nil {
return err return err
} }
return t.InsertObjects(tx) if isInserted {
return t.InsertObjects(tx)
}
return nil
} }

View File

@@ -37,11 +37,11 @@ type Token struct {
} }
func (t *Token) Query(data *InsertId[string]) { func (t *Token) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(` data.query = `
INSERT INTO token (%s, name, actor_link_ append_number, prepend_adjective, lock_rotation, random_img, display_name, 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) display_bars, disposition, rotation, alpha, width, height)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
RETURNING id`, data.fieldName) RETURNING id`
} }
func (t *Token) InsertObjects(tx *sqlx.Tx) error { func (t *Token) InsertObjects(tx *sqlx.Tx) error {

View File

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

View File

@@ -34,7 +34,7 @@ type Insertable[T AllowedIds] interface {
} }
func InsertWithCtx[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data I, 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(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
data.Query(&relId) data.Query(&relId)
@@ -43,7 +43,7 @@ func InsertWithCtx[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data I, relId Ins
func InsertWithCtxParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, ctx context.Context, tx *sqlx.Tx, data I, relId InsertId[T]) { func InsertWithCtxParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, ctx context.Context, tx *sqlx.Tx, data I, relId InsertId[T]) {
g.Go(func() error { g.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
data.Query(&relId) data.Query(&relId)
@@ -53,7 +53,7 @@ func InsertWithCtxParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, ctx
} }
func InsertSlice[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data []I, relId InsertId[T]) error { func InsertSlice[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data []I, relId InsertId[T]) error {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
if len(data) > 0 { if len(data) > 0 {
@@ -68,6 +68,28 @@ func InsertSlice[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data []I, relId Ins
return nil 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]) { func InsertSliceParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *sqlx.Tx, data []I, relId InsertId[T]) {
g.Go(func() error { g.Go(func() error {
wg, _ := errgroup.WithContext(context.Background()) wg, _ := errgroup.WithContext(context.Background())
@@ -78,7 +100,7 @@ func InsertSliceParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *s
} }
for i := range data { for i := range data {
wg.Go(func() error { wg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
err = data[i].InsertCtx(ctx, tx, &relId) err = data[i].InsertCtx(ctx, tx, &relId)
@@ -91,7 +113,7 @@ func InsertSliceParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *s
} }
func InsertSimpleSlice[T AllowedIds, I any](tx *sqlx.Tx, data []I, relId *InsertId[T]) error { func InsertSimpleSlice[T AllowedIds, I any](tx *sqlx.Tx, data []I, relId *InsertId[T]) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
query := fmt.Sprintf(` query := fmt.Sprintf(`
@@ -116,7 +138,7 @@ func InsertSimpleSliceParallel[T AllowedIds, I any](g *errgroup.Group, tx *sqlx.
VALUES ($1, $2)`, relId.tableName, relId.fieldName) VALUES ($1, $2)`, relId.tableName, relId.fieldName)
for i := range data { for i := range data {
wg.Go(func() error { wg.Go(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
_, err := tx.ExecContext(ctx, query, relId.id, data[i]) _, err := tx.ExecContext(ctx, query, relId.id, data[i])
@@ -128,17 +150,20 @@ func InsertSimpleSliceParallel[T AllowedIds, I any](g *errgroup.Group, tx *sqlx.
}) })
} }
func DeleteAll(db *sqlx.DB) error { func DeleteSetupAll(db *sqlx.DB) error {
query := ` const query = `DELETE FROM setup`
DELETE FROM setup`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
result, err := db.ExecContext(ctx, query) tx := db.MustBegin()
defer tx.Rollback()
result, err := tx.ExecContext(ctx, query)
if err != nil { if err != nil {
return err return err
} }
tx.Commit()
rowsAffected, err := result.RowsAffected() rowsAffected, err := result.RowsAffected()
if err != nil { if err != nil {
@@ -151,17 +176,46 @@ func DeleteAll(db *sqlx.DB) error {
return nil return nil
} }
func DeleteAllSeq(db *sqlx.DB) error { func DeleteGameAll(db *sqlx.DB) error {
query := ` const query = `DELETE FROM game`
DELETE FROM sqlite_sequence`
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
result, err := db.ExecContext(ctx, query) tx := db.MustBegin()
defer tx.Rollback()
result, err := tx.ExecContext(ctx, query)
if err != nil { if err != nil {
return err 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() rowsAffected, err := result.RowsAffected()
if err != nil { if err != nil {
@@ -180,7 +234,6 @@ func IsErrUniqueConstraint(err error) {
// or SQLITE_CONSTRAINT (basic code 19) // or SQLITE_CONSTRAINT (basic code 19)
if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique || if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique ||
sqliteErr.Code == sqlite3.ErrConstraint { sqliteErr.Code == sqlite3.ErrConstraint {
fmt.Println("Record already exists")
} }
} }
} }

View File

@@ -4,231 +4,179 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"time" "time"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
) )
type World struct { type World struct {
Id int64 ID string
TextId string
Title string Title string
Description string Description string
System string Version string
CoreVersion string System string
SystemVersion string Background string
LastPlayed string JoinTheme string
PlayTime int64 CoreVersion string
NextSession time.Time SystemVersion string
CreatedAt time.Time LastPlayed string
Compatibility Compatibility Playtime int
Availability int
NextSession 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 (w *World) Query(data *InsertId[string]) {
data.query = fmt.Sprintf(`
func (worlds Worlds) GetById(id int) *World { INSERT INTO world (%[1]s, id, title, description, version, system, background, join_theme,
return &worlds[id] 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 { func (w *World) InsertObjects(tx *sqlx.Tx) error {
query := ` relId := InsertId[string]{id: w.ID, fieldName: "world_id"}
INSERT INTO worlds (state_id, text_id, title, description, system, core_version, system_version, playtime, next_session) group, ctx := errgroup.WithContext(context.Background())
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, created_at`
args := []any{stateId, world.TextId, world.Title, world.Description, world.System, world.CoreVersion, world.SystemVersion, world.PlayTime, world.NextSession} InsertWithCtxParallel(group, ctx, tx, w.Compatibility, relId)
InsertWithCtxParallel(group, ctx, tx, w.Relationships, relId)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) esModulesRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "es_modules"}
defer cancel() 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) InsertSliceParallel(group, tx, w.Authors, relId)
if err != nil && err != sql.ErrNoRows { InsertSliceParallel(group, tx, w.Media, relId)
return err InsertSliceParallel(group, tx, w.Styles, relId)
} InsertSliceParallel(group, tx, w.Languages, relId)
return m.InsertWorldCompatibility(&world.Compatibility, world.Id) InsertSliceParallel(group, tx, w.Packs, relId)
} InsertSliceParallel(group, tx, w.PackFolders, relId)
func (m FoundryStateModel) InsertWorldCompatibility(compatibility *Compatibility, worldId int64) error { err := group.Wait()
query := ` if err != nil && !errors.Is(err, sql.ErrNoRows) {
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 {
return err return err
} }
return nil return nil
} }
func (m FoundryStateModel) GetWorlds(idState int64) (Worlds, error) { func (w *World) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
if idState < 1 { if data.query == "" {
return nil, ErrorRecordNotFound return ErrNoQuery
} }
query := ` args := []any{data.id, w.ID, w.Title, w.Description, w.Version, w.System, w.Background, w.JoinTheme,
SELECT id, created_at, text_id, title, description, system, core_version, system_version, playtime, next_session w.CoreVersion, w.SystemVersion, w.LastPlayed, w.Playtime, w.Availability, w.NextSession, w.Socket,
FROM worlds w.Protected, w.Exclusive, w.PersistentStorage, w.Locked, w.Owned, w.HasStorage}
WHERE state_id = $1`
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) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() defer cancel()
rows, err := m.DB.QueryContext(ctx, query, idState) var worldNames []string
err := db.SelectContext(ctx, &worldNames, query)
if err != nil { if err != nil {
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
}
worlds := make(Worlds, 0, 1)
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,
)
if err != nil {
return nil, err
}
compatibility, err := m.GetWorldsCompatibility(world.Id)
if err != nil {
return nil, err
}
world.Compatibility = *compatibility
worlds = append(worlds, world)
}
if err := rows.Err(); err != nil {
return nil, err return nil, err
} }
return worldNames, nil
return worlds, nil
} }
func (m FoundryStateModel) GetWorldsCompatibility(idWorld int64) (*Compatibility, error) { func IsWorldInserted(db *sqlx.DB, worldName string) (bool, error) {
if idWorld < 1 { const query = `
return nil, ErrorRecordNotFound SELECT EXISTS(SELECT 1 FROM world WHERE id = $1 AND game_id IS NOT NULL)`
}
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) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel() defer cancel()
err := m.DB.QueryRowContext(ctx, query, idWorld).Scan( var exist bool
&compatibility.Id, err := db.GetContext(ctx, &exist, query, worldName)
&compatibility.Minimum, if err != nil {
&compatibility.Verified, return false, err
&compatibility.Maximum, }
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()
var world World
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 && !errors.Is(err, sql.ErrNoRows) {
if err != nil { return nil, err
switch {
case errors.Is(err, sql.ErrNoRows):
return nil, ErrorRecordNotFound
default:
return nil, err
}
} }
return &world, nil
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 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 { type Actor struct {
PrototypeToken Token `json:"prototypeToken"` PrototypeToken Token `json:"prototypeToken"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Addresses struct {
Local string `json:"local"` Local string `json:"local"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Author struct {
Name string `json:"name"` Name string `json:"name"`

View File

@@ -1,6 +1,6 @@
package json 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 CardDeck struct { type CardDeck struct {
Name string `json:"name"` Name string `json:"name"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Combat struct {
Id string `json:"_id"` Id string `json:"_id"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Compatibility struct {
Minimum string `json:"minimum,omitempty"` 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 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 { type DetailsLanguages struct {
Details string `json:"details"` Details string `json:"details"`

View File

@@ -1,6 +1,6 @@
package json 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 { type DocumentTypes struct {
Actor DocumentTypeData `json:"Actor"` Actor DocumentTypeData `json:"Actor"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Environment struct {
GlobalLight EnvironmentGlobalLight `json:"globalLight"` 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 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 { type Files struct {
Storages []string `json:"storages"` Storages []string `json:"storages"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Folder struct {
Name string `json:"name"` Name string `json:"name"`

View File

@@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"sync" "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 { type Game struct {

View File

@@ -1,6 +1,6 @@
package json 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 Grid struct {
Type int `json:"type"` Type int `json:"type"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Index struct {
Id string `json:"_id"` Id string `json:"_id"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Item struct {
Img string `json:"img"` Img string `json:"img"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Journal struct {
Folder any `json:"folder"` Folder any `json:"folder"`

View File

@@ -1,6 +1,6 @@
package json 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 { type JournalPage struct {
Name string `json:"name"` Name string `json:"name"`

View File

@@ -1,13 +1,67 @@
package json package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Language struct { type Language struct {
Id string `json:"id"` Lang string `json:"lang"`
Label string `json:"label"` Name string `json:"name"`
Modules []LangModule `json:"modules"` Path string `json:"path"`
// SystemLanguagesFlags SystemLanguagesFlags `json:"flags"`
} }
type LangModule struct { func (l *Language) ToDB(dest **db.Language) bool {
Id string `json:"id"` 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"` Label string `json:"label"`
Path string `json:"path"` 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 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 { type Light struct {
Alpha float64 `json:"alpha"` Alpha float64 `json:"alpha"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Macro struct {
Command string `json:"command"` Command string `json:"command"`

View File

@@ -1,6 +1,6 @@
package json 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 Media struct {
Type string `json:"type"` Type string `json:"type"`

View File

@@ -1,6 +1,6 @@
package json 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 { type Message struct {
Content string `json:"content"` Content string `json:"content"`

View File

@@ -3,7 +3,7 @@ package json
import ( import (
"sync" "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 { type Module struct {

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"`
}

View File

@@ -1,12 +0,0 @@
package modules
type Pf2eModule struct {
settings Pf2eSettings
}
type Pf2eSettings struct {
showEffectPanel bool
showCheckDialogs bool
showDamageDialogs bool
monochromeDarkvision bool
}

View File

@@ -1,6 +1,6 @@
package json 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 Note struct { type Note struct {
EntryID string `json:"entryId"` EntryID string `json:"entryId"`

View File

@@ -1,6 +1,6 @@
package json 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 GameOptions struct { type GameOptions struct {
Language string `json:"language"` Language string `json:"language"`

View File

@@ -1,6 +1,6 @@
package json 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 Ownership struct { type Ownership struct {
Player string `json:"PLAYER,omitempty"` Player string `json:"PLAYER,omitempty"`

View File

@@ -3,7 +3,7 @@ package json
import ( import (
"sync" "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 Pack struct { type Pack struct {

View File

@@ -1,6 +1,6 @@
package json 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 PackageWarningsData struct { type PackageWarningsData struct {
Id string `json:"id"` Id string `json:"id"`

View File

@@ -1,6 +1,6 @@
package json 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 Playlist struct { type Playlist struct {
Name string `json:"name"` Name string `json:"name"`

View File

@@ -3,7 +3,7 @@ package json
import ( import (
"sync" "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 Relationships struct { type Relationships struct {

View File

@@ -1,11 +1,32 @@
package json package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Release struct { type Release struct {
Generation int `json:"generation"` Generation int `json:"generation"`
Channel string `json:"channel"` Channel string `json:"channel"`
Suffix string `json:"suffix"` Suffix string `json:"suffix"`
Build int `json:"build"` Build int `json:"build"`
Node_version int `json:"node_version"` NodeVersion int `json:"node_version"`
Time int64 `json:"time"` MaxGeneration int `json:"maxGeneration"`
flags struct{} `json:"-"` MaxStableGeneration int `json:"maxStableGeneration"`
Time int64 `json:"time"`
// Flags Flags `json:"flags"`
}
func (r *Release) ToDB(dest *db.Release) bool {
if dest == nil {
return false
}
dest.Generation = r.Generation
dest.Channel = r.Channel
dest.Suffix = r.Suffix
dest.Build = r.Build
dest.NodeVersion = r.NodeVersion
dest.MaxGeneration = r.MaxGeneration
dest.MaxStableGeneration = r.MaxStableGeneration
dest.Time = r.Time
return true
} }

View File

@@ -1,6 +1,6 @@
package json 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 Ring struct { type Ring struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`

View File

@@ -3,7 +3,7 @@ package json
import ( import (
"sync" "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 Scene struct { type Scene struct {

View File

@@ -1,6 +1,6 @@
package json 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 Setting struct { type Setting struct {
Key string `json:"key"` Key string `json:"key"`

View File

@@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"sync" "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 Setup struct { type Setup struct {

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