From fd65631be1ee3587003ee42dceb45db4e719a74c Mon Sep 17 00:00:00 2001 From: lbenedar Date: Fri, 24 Apr 2026 18:04:35 +0300 Subject: [PATCH] finish world initialization, set up init on startup and on shutdown, move all models to main directory --- Makefile | 2 +- cmd/api/handlers.go | 29 +- cmd/api/healthcheck.go | 1 - cmd/api/main.go | 30 +- db/migrations/000001_create_setup.down.sql | 5 +- db/migrations/000001_create_setup.up.sql | 48 ++- db/migrations/000002_create_game.down.sql | 24 +- db/migrations/000002_create_game.up.sql | 81 +++- internal/foundry/actions/session.go | 73 ++-- internal/foundry/foundry.go | 115 ++--- internal/foundry/helpers.go | 2 +- .../{temp_models => models}/db/actor.go | 27 +- .../{temp_models => models}/db/addresses.go | 0 .../{temp_models => models}/db/authors.go | 0 .../{temp_models => models}/db/card.go | 40 +- .../{temp_models => models}/db/combat.go | 44 +- .../db/compatibility.go | 0 .../db/details_language.go | 0 .../db/document_types.go | 0 .../{temp_models => models}/db/environment.go | 0 internal/foundry/models/db/errors.go | 9 - .../{temp_models => models}/db/files.go | 0 .../{temp_models => models}/db/folder.go | 25 +- .../db/foundry_data.go | 0 internal/foundry/models/db/foundry_state.go | 308 -------------- .../{temp_models => models}/db/game.go | 33 +- .../{temp_models => models}/db/grid.go | 0 internal/foundry/models/db/index.go | 88 ++++ .../{temp_models => models}/db/item.go | 23 +- .../{temp_models => models}/db/journal.go | 27 +- .../db/journal_page.go | 26 +- .../{temp_models => models}/db/language.go | 0 .../{temp_models => models}/db/light.go | 0 .../{temp_models => models}/db/macro.go | 18 +- .../{temp_models => models}/db/media.go | 0 .../{temp_models => models}/db/message.go | 22 +- internal/foundry/models/db/models.go | 35 -- internal/foundry/models/db/module.go | 400 +++++++----------- .../{temp_models => models}/db/notes.go | 0 .../{temp_models => models}/db/options.go | 0 .../{temp_models => models}/db/ownership.go | 0 .../{temp_models => models}/db/pack.go | 93 +++- .../db/package_warnings.go | 0 .../{temp_models => models}/db/playlist.go | 36 +- .../db/relationships.go | 0 .../{temp_models => models}/db/release.go | 0 .../{temp_models => models}/db/ring.go | 0 .../{temp_models => models}/db/scene.go | 0 .../{temp_models => models}/db/settings.go | 27 +- .../{temp_models => models}/db/setup.go | 15 + .../{temp_models => models}/db/stats.go | 0 .../{temp_models => models}/db/style.go | 0 internal/foundry/models/db/system.go | 319 +++++++------- .../{temp_models => models}/db/table.go | 53 ++- .../{temp_models => models}/db/token.go | 6 +- .../{temp_models => models}/db/update.go | 0 internal/foundry/models/db/user.go | 322 ++++---------- .../{temp_models => models}/db/utils.go | 87 +++- .../{temp_models => models}/db/wall.go | 0 internal/foundry/models/db/world.go | 334 ++++++--------- .../{temp_models => models}/json/actor.go | 2 +- .../{temp_models => models}/json/addresses.go | 2 +- .../{temp_models => models}/json/authors.go | 2 +- .../{temp_models => models}/json/card.go | 2 +- .../{temp_models => models}/json/combat.go | 2 +- .../json/compatibility.go | 2 +- internal/foundry/models/json/data.go | 185 -------- .../json/details_language.go | 2 +- .../json/document_types.go | 2 +- .../json/environment.go | 2 +- internal/foundry/models/json/errors.go | 8 - .../{temp_models => models}/json/files.go | 2 +- .../{temp_models => models}/json/folder.go | 2 +- .../{temp_models => models}/json/game.go | 2 +- .../{temp_models => models}/json/grid.go | 2 +- .../{temp_models => models}/json/index.go | 2 +- .../{temp_models => models}/json/item.go | 2 +- .../{temp_models => models}/json/journal.go | 2 +- .../json/journal_page.go | 2 +- internal/foundry/models/json/language.go | 64 ++- .../{temp_models => models}/json/light.go | 2 +- .../{temp_models => models}/json/macro.go | 2 +- .../{temp_models => models}/json/media.go | 2 +- .../{temp_models => models}/json/message.go | 2 +- .../{temp_models => models}/json/module.go | 2 +- .../foundry/models/json/modules/dice-stats.go | 59 --- internal/foundry/models/json/modules/pf2e.go | 12 - .../{temp_models => models}/json/notes.go | 2 +- .../{temp_models => models}/json/options.go | 2 +- .../{temp_models => models}/json/ownership.go | 2 +- .../{temp_models => models}/json/pack.go | 2 +- .../json/package_warnings.go | 2 +- .../{temp_models => models}/json/playlist.go | 2 +- .../json/region_behavior.go | 0 .../json/relationships.go | 2 +- internal/foundry/models/json/release.go | 35 +- .../{temp_models => models}/json/ring.go | 2 +- .../{temp_models => models}/json/scene.go | 2 +- .../{temp_models => models}/json/settings.go | 2 +- .../{temp_models => models}/json/setup.go | 2 +- .../{temp_models => models}/json/stats.go | 2 +- .../{temp_models => models}/json/style.go | 2 +- .../{temp_models => models}/json/system.go | 2 +- .../{temp_models => models}/json/table.go | 2 +- internal/foundry/models/json/template.go | 132 ------ .../{temp_models => models}/json/token.go | 2 +- .../{temp_models => models}/json/update.go | 2 +- .../{temp_models => models}/json/user.go | 2 +- internal/foundry/models/json/users.go | 41 -- .../{temp_models => models}/json/utils.go | 2 +- .../{temp_models => models}/json/wall.go | 2 +- .../{temp_models => models}/json/world.go | 2 +- internal/foundry/requests/config.go | 14 - internal/foundry/requests/get_requests.go | 2 - internal/foundry/temp_models/db/index.go | 53 --- internal/foundry/temp_models/db/module.go | 176 -------- internal/foundry/temp_models/db/system.go | 177 -------- internal/foundry/temp_models/db/user.go | 116 ----- internal/foundry/temp_models/db/world.go | 130 ------ internal/foundry/temp_models/json/language.go | 67 --- internal/foundry/temp_models/json/release.go | 32 -- internal/foundry/transport/db.go | 88 ++-- internal/foundry/transport/http.go | 2 +- internal/foundry/transport/transport.go | 23 +- internal/foundry/transport/websocket.go | 7 +- 125 files changed, 1534 insertions(+), 2802 deletions(-) rename internal/foundry/{temp_models => models}/db/actor.go (70%) rename internal/foundry/{temp_models => models}/db/addresses.go (100%) rename internal/foundry/{temp_models => models}/db/authors.go (100%) rename internal/foundry/{temp_models => models}/db/card.go (81%) rename internal/foundry/{temp_models => models}/db/combat.go (68%) rename internal/foundry/{temp_models => models}/db/compatibility.go (100%) rename internal/foundry/{temp_models => models}/db/details_language.go (100%) rename internal/foundry/{temp_models => models}/db/document_types.go (100%) rename internal/foundry/{temp_models => models}/db/environment.go (100%) delete mode 100644 internal/foundry/models/db/errors.go rename internal/foundry/{temp_models => models}/db/files.go (100%) rename internal/foundry/{temp_models => models}/db/folder.go (82%) rename internal/foundry/{temp_models => models}/db/foundry_data.go (100%) delete mode 100644 internal/foundry/models/db/foundry_state.go rename internal/foundry/{temp_models => models}/db/game.go (76%) rename internal/foundry/{temp_models => models}/db/grid.go (100%) create mode 100644 internal/foundry/models/db/index.go rename internal/foundry/{temp_models => models}/db/item.go (70%) rename internal/foundry/{temp_models => models}/db/journal.go (67%) rename internal/foundry/{temp_models => models}/db/journal_page.go (87%) rename internal/foundry/{temp_models => models}/db/language.go (100%) rename internal/foundry/{temp_models => models}/db/light.go (100%) rename internal/foundry/{temp_models => models}/db/macro.go (75%) rename internal/foundry/{temp_models => models}/db/media.go (100%) rename internal/foundry/{temp_models => models}/db/message.go (79%) delete mode 100644 internal/foundry/models/db/models.go rename internal/foundry/{temp_models => models}/db/notes.go (100%) rename internal/foundry/{temp_models => models}/db/options.go (100%) rename internal/foundry/{temp_models => models}/db/ownership.go (100%) rename internal/foundry/{temp_models => models}/db/pack.go (54%) rename internal/foundry/{temp_models => models}/db/package_warnings.go (100%) rename internal/foundry/{temp_models => models}/db/playlist.go (72%) rename internal/foundry/{temp_models => models}/db/relationships.go (100%) rename internal/foundry/{temp_models => models}/db/release.go (100%) rename internal/foundry/{temp_models => models}/db/ring.go (100%) rename internal/foundry/{temp_models => models}/db/scene.go (100%) rename internal/foundry/{temp_models => models}/db/settings.go (65%) rename internal/foundry/{temp_models => models}/db/setup.go (92%) rename internal/foundry/{temp_models => models}/db/stats.go (100%) rename internal/foundry/{temp_models => models}/db/style.go (100%) rename internal/foundry/{temp_models => models}/db/table.go (68%) rename internal/foundry/{temp_models => models}/db/token.go (97%) rename internal/foundry/{temp_models => models}/db/update.go (100%) rename internal/foundry/{temp_models => models}/db/utils.go (64%) rename internal/foundry/{temp_models => models}/db/wall.go (100%) rename internal/foundry/{temp_models => models}/json/actor.go (97%) rename internal/foundry/{temp_models => models}/json/addresses.go (94%) rename internal/foundry/{temp_models => models}/json/authors.go (95%) rename internal/foundry/{temp_models => models}/json/card.go (99%) rename internal/foundry/{temp_models => models}/json/combat.go (98%) rename internal/foundry/{temp_models => models}/json/compatibility.go (94%) delete mode 100644 internal/foundry/models/json/data.go rename internal/foundry/{temp_models => models}/json/details_language.go (92%) rename internal/foundry/{temp_models => models}/json/document_types.go (97%) rename internal/foundry/{temp_models => models}/json/environment.go (99%) delete mode 100644 internal/foundry/models/json/errors.go rename internal/foundry/{temp_models => models}/json/files.go (94%) rename internal/foundry/{temp_models => models}/json/folder.go (98%) rename internal/foundry/{temp_models => models}/json/game.go (99%) rename internal/foundry/{temp_models => models}/json/grid.go (97%) rename internal/foundry/{temp_models => models}/json/index.go (94%) rename internal/foundry/{temp_models => models}/json/item.go (97%) rename internal/foundry/{temp_models => models}/json/journal.go (97%) rename internal/foundry/{temp_models => models}/json/journal_page.go (98%) rename internal/foundry/{temp_models => models}/json/light.go (98%) rename internal/foundry/{temp_models => models}/json/macro.go (97%) rename internal/foundry/{temp_models => models}/json/media.go (93%) rename internal/foundry/{temp_models => models}/json/message.go (98%) rename internal/foundry/{temp_models => models}/json/module.go (99%) delete mode 100644 internal/foundry/models/json/modules/dice-stats.go delete mode 100644 internal/foundry/models/json/modules/pf2e.go rename internal/foundry/{temp_models => models}/json/notes.go (98%) rename internal/foundry/{temp_models => models}/json/options.go (98%) rename internal/foundry/{temp_models => models}/json/ownership.go (93%) rename internal/foundry/{temp_models => models}/json/pack.go (99%) rename internal/foundry/{temp_models => models}/json/package_warnings.go (97%) rename internal/foundry/{temp_models => models}/json/playlist.go (98%) rename internal/foundry/{temp_models => models}/json/region_behavior.go (100%) rename internal/foundry/{temp_models => models}/json/relationships.go (98%) rename internal/foundry/{temp_models => models}/json/ring.go (97%) rename internal/foundry/{temp_models => models}/json/scene.go (99%) rename internal/foundry/{temp_models => models}/json/settings.go (94%) rename internal/foundry/{temp_models => models}/json/setup.go (99%) rename internal/foundry/{temp_models => models}/json/stats.go (96%) rename internal/foundry/{temp_models => models}/json/style.go (91%) rename internal/foundry/{temp_models => models}/json/system.go (99%) rename internal/foundry/{temp_models => models}/json/table.go (98%) delete mode 100644 internal/foundry/models/json/template.go rename internal/foundry/{temp_models => models}/json/token.go (99%) rename internal/foundry/{temp_models => models}/json/update.go (97%) rename internal/foundry/{temp_models => models}/json/user.go (97%) delete mode 100644 internal/foundry/models/json/users.go rename internal/foundry/{temp_models => models}/json/utils.go (98%) rename internal/foundry/{temp_models => models}/json/wall.go (98%) rename internal/foundry/{temp_models => models}/json/world.go (99%) delete mode 100644 internal/foundry/temp_models/db/index.go delete mode 100644 internal/foundry/temp_models/db/module.go delete mode 100644 internal/foundry/temp_models/db/system.go delete mode 100644 internal/foundry/temp_models/db/user.go delete mode 100644 internal/foundry/temp_models/db/world.go delete mode 100644 internal/foundry/temp_models/json/language.go delete mode 100644 internal/foundry/temp_models/json/release.go diff --git a/Makefile b/Makefile index 82608e0..6bfe3d2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -include .env api/run: - go run ./cmd/api -foundry_pass=${FOUNDRY_PASS} -foundry_host=${FOUNDRY_HOST} -port=${LISTEN_PORT} -env=development -log_level=${LOG_LEVEL} -db-dsn=${DB_DSN} + go run ./cmd/api -foundry_pass=${FOUNDRY_PASS} -foundry_host=${FOUNDRY_HOST} -port=${LISTEN_PORT} -env=development -log_level=${LOG_LEVEL} -db-dsn=${DB_DSN} -foundry-worlds=${FOUNDRY_WORLDS} db/migration/new: @echo 'Creating migration files for ${name}...' diff --git a/cmd/api/handlers.go b/cmd/api/handlers.go index 384b060..066bca6 100644 --- a/cmd/api/handlers.go +++ b/cmd/api/handlers.go @@ -1,37 +1,22 @@ package main import ( - "encoding/json" + "fmt" "net/http" - "time" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) func (app *application) ShowText(w http.ResponseWriter, r *http.Request) { - data, err := app.foundryApp.Test() + dbConn := app.foundryApp.Transport.DB + + world, err := db.GetWorld(dbConn, "kingmaker") if err != nil { - app.slogger.Error("", "error", err) - w.Write([]byte(err.Error())) + w.Write([]byte("Got error")) return } - var dbGame db.Game - - start := time.Now() - data.ToDB(&dbGame) - elapsed := time.Since(start) - - app.slogger.Info("Game data successfully parsed", "elapsedtime", elapsed) - - byteData, err := json.Marshal(data) - if err != nil { - app.slogger.Error("", "error", err) - w.Write([]byte(err.Error())) - return - } - - w.Write(byteData) + w.Write([]byte(fmt.Sprintf("Got world with name %s, core_version - %s, next_session - %v", world.ID, world.CoreVersion, world.NextSession))) } func (app *application) WhenNextSession(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/api/healthcheck.go b/cmd/api/healthcheck.go index 779ece1..a334750 100644 --- a/cmd/api/healthcheck.go +++ b/cmd/api/healthcheck.go @@ -13,7 +13,6 @@ func (app *application) healthcheckHandler(w http.ResponseWriter, r *http.Reques "status": app.foundryApp.Status, "is_available": app.foundryApp.IsAvailable, }, - "config": app.foundryApp.GetHTTP(), } err := app.writeJSON(w, http.StatusOK, env, nil) if err != nil { diff --git a/cmd/api/main.go b/cmd/api/main.go index f5d6a01..31b2465 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -7,6 +7,7 @@ import ( "log/slog" "net/http/cookiejar" "os" + "strings" "time" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry" @@ -73,7 +74,7 @@ func openDB(cfg config) (*sqlx.DB, error) { } // TODO: change default values -func (app *application) parseFlags() *requests.FoundryHttpRequest { +func (app *application) parseFlags() *transport.FoundryTransportData { flag.StringVar(&app.cfg.port, "port", ":9090", "Service port") flag.StringVar(&app.cfg.env, "env", "development", "Enivornment (development|staging|production)") @@ -85,15 +86,20 @@ func (app *application) parseFlags() *requests.FoundryHttpRequest { var mode string flag.StringVar(&mode, "service_mode", "api", "Type of service mode(api|discord|tg)") - foundryHttpData := requests.FoundryHttpRequest{SessionID: nil} - flag.StringVar(&foundryHttpData.Host, "foundry_host", "127.0.0.1", "Address to connect to Foundry") - flag.StringVar(&foundryHttpData.Password, "foundry_pass", "", "Password to connect to Foundry") + var foundryTransportData transport.FoundryTransportData + foundryTransportData.HttpConfig = &requests.FoundryHttpRequest{SessionID: nil} + flag.StringVar(&foundryTransportData.HttpConfig.Host, "foundry_host", "127.0.0.1", "Address to connect to Foundry") + flag.StringVar(&foundryTransportData.HttpConfig.Password, "foundry_pass", "", "Password to connect to Foundry") var logLevelStr string flag.StringVar(&logLevelStr, "log_level", "info", "Password to connect to Foundry(debug|info|warn|error)") + var worlds string + flag.StringVar(&worlds, "foundry-worlds", "", "Worlds that initialize in db on startup") flag.Parse() + foundryTransportData.Worlds = strings.Split(worlds, ",") + app.cfg.mode = service_mode(mode) logLevel, ok := StringToLogLevel[logLevelStr] @@ -105,20 +111,21 @@ func (app *application) parseFlags() *requests.FoundryHttpRequest { if !ok { app.slogger.Warn("Wrong log level. Started with log_level=info", "received", logLevelStr) } - return &foundryHttpData + foundryTransportData.Logger = app.slogger + return &foundryTransportData } // TODO: Handle DB connection error func main() { app := application{foundryApp: &foundry.FoundryApi{}} - foundryHttpData := app.parseFlags() + foundryTransportData := app.parseFlags() jar, err := cookiejar.New(nil) if err != nil { app.slogger.Error("Error", "text", err.Error()) return } - foundryHttpData.Jar = jar + foundryTransportData.HttpConfig.Jar = jar app.slogger.Info("", "dsn", app.cfg.db.dsn) @@ -129,8 +136,15 @@ func main() { } defer db.Close() app.slogger.Info("Database connection pool established") + foundryTransportData.DbConn = db + + app.foundryApp.SetTransport(transport.NewFoundryTransport(foundryTransportData)) + err = app.foundryApp.PrepareDB() + if err != nil { + app.slogger.Error("Error on preparing database", "err", err) + return + } - app.foundryApp.SetTransport(transport.NewFoundryTransport(db, app.slogger, foundryHttpData)) go app.foundryApp.StartListenFoundry() app.serve() diff --git a/db/migrations/000001_create_setup.down.sql b/db/migrations/000001_create_setup.down.sql index 8fff6c7..3aa87cb 100644 --- a/db/migrations/000001_create_setup.down.sql +++ b/db/migrations/000001_create_setup.down.sql @@ -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 pack; DROP TABLE IF EXISTS language; @@ -18,13 +20,14 @@ DROP TABLE IF EXISTS grid; DROP TABLE IF EXISTS system; DROP TABLE IF EXISTS package_warnings_data_error; DROP TABLE IF EXISTS package_warnings_data_warning; +DROP TABLE IF EXISTS package_warnings_to_data; DROP TABLE IF EXISTS package_warnings_data; -DROP TRIGGER IF EXISTS tr_delete_package_warnings_to_data_package_warnings_data; DROP TABLE IF EXISTS package_warnings; DROP TABLE IF EXISTS news; DROP TABLE IF EXISTS folder_packs; DROP TABLE IF EXISTS folder; DROP TABLE IF EXISTS pack_folder; +DROP TABLE IF EXISTS pack_to_index; DROP TABLE IF EXISTS index_; DROP TABLE IF EXISTS ownership; DROP TABLE IF EXISTS relationships_data; diff --git a/db/migrations/000001_create_setup.up.sql b/db/migrations/000001_create_setup.up.sql index fe2be6d..e753e42 100644 --- a/db/migrations/000001_create_setup.up.sql +++ b/db/migrations/000001_create_setup.up.sql @@ -301,9 +301,8 @@ CREATE TABLE IF NOT EXISTS language ( ); CREATE TABLE IF NOT EXISTS pack ( - id INTEGER PRIMARY KEY, + id TEXT PRIMARY KEY, - key_ VARCHAR(128) NOT NULL, name VARCHAR(128) NOT NULL, label VARCHAR(128) NOT NULL, banner VARCHAR(128) NOT NULL, @@ -324,7 +323,7 @@ CREATE TABLE IF NOT EXISTS ownership ( trusted 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 ); @@ -334,12 +333,29 @@ CREATE TABLE IF NOT EXISTS index_ ( folder VARCHAR(128) NOT NULL, img 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 ); +CREATE INDEX idx_pack_to_index_index_id ON pack_to_index(index_id); + +CREATE TRIGGER tr_delete_pack_to_index_index +AFTER DELETE ON pack_to_index +FOR EACH ROW +BEGIN + DELETE FROM index_ + WHERE id = OLD.index_id + AND NOT EXISTS (SELECT 1 FROM pack_to_index WHERE index_id = OLD.index_id); +END; + CREATE TABLE IF NOT EXISTS pack_folder ( id TEXT PRIMARY KEY, @@ -349,7 +365,7 @@ CREATE TABLE IF NOT EXISTS pack_folder ( type VARCHAR(128) NOT NULL, sort INTEGER NOT NULL, - pack_id INTEGER, + pack_id TEXT, 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 ); -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 ( id TEXT PRIMARY KEY, @@ -423,6 +430,17 @@ CREATE TABLE IF NOT EXISTS package_warnings_data ( reinstallable BOOLEAN NOT NULL ); +CREATE INDEX idx_package_warnings_to_data_package_warnings_data_id ON package_warnings_to_data(package_warnings_data_id); + +CREATE TRIGGER tr_delete_package_warnings_to_data_package_warnings_data +AFTER DELETE ON package_warnings_to_data +FOR EACH ROW +BEGIN + DELETE FROM package_warnings_data + WHERE id = OLD.package_warnings_data_id + AND NOT EXISTS (SELECT 1 FROM package_warnings_to_data WHERE package_warnings_data_id = OLD.package_warnings_data_id); +END; + CREATE TABLE IF NOT EXISTS package_warnings_data_warning ( id INTEGER PRIMARY KEY, diff --git a/db/migrations/000002_create_game.down.sql b/db/migrations/000002_create_game.down.sql index 8fd2b4c..87074d0 100644 --- a/db/migrations/000002_create_game.down.sql +++ b/db/migrations/000002_create_game.down.sql @@ -1,6 +1,8 @@ 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_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_occludable; DROP TABLE IF EXISTS token_light_darkness; @@ -46,9 +48,8 @@ DROP TABLE IF EXISTS stats; DROP TABLE IF EXISTS message; 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, label 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 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) -SELECT id, key_, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id FROM pack; +INSERT INTO pack_new (id, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id) +SELECT id, name, label, banner, path, type, system, package_type, package_name, module_id, system_id, world_id FROM pack; DROP TABLE IF EXISTS pack; ALTER TABLE pack_new RENAME TO pack; @@ -81,6 +82,8 @@ SELECT id, key_, setup_id FROM package_warnings; DROP TABLE IF EXISTS package_warnings; ALTER TABLE package_warnings_new RENAME TO package_warnings; +DROP TABLE IF EXISTS game_to_pack; +DROP TABLE IF EXISTS game_to_module; DROP TABLE IF EXISTS active_users; DROP TABLE IF EXISTS system_update; DROP INDEX IF EXISTS idx_core_update_game_id; @@ -186,6 +189,15 @@ AFTER DELETE ON package_warnings_to_data FOR EACH ROW BEGIN 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); +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; \ No newline at end of file diff --git a/db/migrations/000002_create_game.up.sql b/db/migrations/000002_create_game.up.sql index f92495d..2be9a7e 100644 --- a/db/migrations/000002_create_game.up.sql +++ b/db/migrations/000002_create_game.up.sql @@ -48,12 +48,14 @@ CREATE TABLE IF NOT EXISTS game_to_systems ( FOREIGN KEY (system_id) REFERENCES system(id) ON DELETE CASCADE ); +CREATE INDEX idx_game_to_systems_system_id ON game_to_systems(system_id); + CREATE TRIGGER tr_delete_game_to_systems_system AFTER DELETE ON game_to_systems FOR EACH ROW BEGIN DELETE FROM system - WHERE id = OLD.id + WHERE id = OLD.system_id AND NOT EXISTS (SELECT 1 FROM game_to_systems WHERE system_id = OLD.system_id); END; @@ -88,17 +90,38 @@ CREATE TABLE IF NOT EXISTS game_to_module ( FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); +CREATE INDEX idx_game_to_module_module_id ON game_to_module(module_id); + CREATE TRIGGER tr_delete_game_to_module_module AFTER DELETE ON game_to_module FOR EACH ROW BEGIN DELETE FROM module - WHERE id = OLD.id + WHERE id = OLD.module_id AND NOT EXISTS (SELECT 1 FROM game_to_module WHERE module_id = OLD.module_id); END; ALTER TABLE package_warnings ADD COLUMN game_id INTEGER REFERENCES game(id) ON DELETE CASCADE; -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 ( id TEXT PRIMARY KEY, @@ -113,6 +136,9 @@ CREATE TABLE IF NOT EXISTS message ( flavor VARCHAR(128) NOT NULL, sound VARCHAR(128) NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -170,6 +196,9 @@ CREATE TABLE IF NOT EXISTS combat ( sort INTEGER NOT NULL, active INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -199,6 +228,9 @@ CREATE TABLE IF NOT EXISTS combatant ( hidden BOOLEAN NOT NULL, defeated BOOLEAN NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + combat_id TEXT, FOREIGN KEY (combat_id) REFERENCES combat(id) ON DELETE CASCADE ); @@ -220,6 +252,9 @@ CREATE TABLE IF NOT EXISTS card_deck ( sort INTEGER NOT NULL, display_count BOOLEAN NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -253,6 +288,9 @@ CREATE TABLE IF NOT EXISTS card ( sort INTEGER NOT NULL, drawn BOOLEAN NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + card_deck_id TEXT, FOREIGN KEY (card_deck_id) REFERENCES card_deck(id) ON DELETE CASCADE ); @@ -291,6 +329,9 @@ CREATE TABLE IF NOT EXISTS user ( pronouns VARCHAR(128) NOT NULL, role INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -320,6 +361,9 @@ CREATE TABLE IF NOT EXISTS macro ( folder VARCHAR(128) NOT NULL, sort INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -339,6 +383,9 @@ CREATE TABLE IF NOT EXISTS world_folder ( color VARCHAR(128) NOT NULL, sort INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -355,6 +402,9 @@ CREATE TABLE IF NOT EXISTS item ( folder VARCHAR(128) NOT NULL, sort INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -369,6 +419,9 @@ CREATE TABLE IF NOT EXISTS setting ( key_ VARCHAR(128) NOT NULL, value VARCHAR(128) NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -382,6 +435,9 @@ CREATE TABLE IF NOT EXISTS journal ( name VARCHAR(128) NOT NULL, sort INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -394,6 +450,9 @@ CREATE TABLE IF NOT EXISTS journal_page ( src VARCHAR(128) NOT NULL, sort INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + journal_id TEXT, FOREIGN KEY (journal_id) REFERENCES journal(id) ON DELETE CASCADE ); @@ -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; -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_id TEXT REFERENCES journal(id) ON DELETE CASCADE; @@ -447,6 +505,9 @@ CREATE TABLE IF NOT EXISTS table_ ( replacement BOOLEAN NOT NULL, display_roll BOOLEAN NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -465,6 +526,9 @@ CREATE TABLE IF NOT EXISTS table_result ( weight INTEGER NOT NULL, drawn BOOLEAN NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + table_id TEXT, FOREIGN KEY (table_id) REFERENCES table_(id) ON DELETE CASCADE ); @@ -495,6 +559,9 @@ CREATE TABLE IF NOT EXISTS playlist ( sort INTEGER NOT NULL, playing BOOLEAN NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); @@ -517,6 +584,9 @@ CREATE TABLE IF NOT EXISTS sound ( volume REAL NOT NULL, paused_time REAL NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + playlist_id TEXT, FOREIGN KEY (playlist_id) REFERENCES playlist(id) ON DELETE CASCADE ); @@ -530,6 +600,9 @@ CREATE TABLE IF NOT EXISTS actor ( folder VARCHAR(128) NOT NULL, sort INTEGER NOT NULL, + created_at TEXT DEFAULT (datetime('now')), + updated_at TEXT DEFAULT (datetime('now')), + game_id INTEGER, FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE ); diff --git a/internal/foundry/actions/session.go b/internal/foundry/actions/session.go index 3148bdd..7e727d4 100644 --- a/internal/foundry/actions/session.go +++ b/internal/foundry/actions/session.go @@ -1,8 +1,8 @@ package actions import ( - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/json" + "time" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport" "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 { if status.IsActive { - tr.Logger.Info("Session msg", "userid", msg.UserId) - if msg.UserId == "" { - err := tr.LogInToWorld() - if err != nil { - return err - } - tr.Logger.Info("World is started. Succesfully logged into world") - close(tr.ReadChan.Reconnect()) - return nil - } - - // 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 + return msg.OnActiveWorld(tr) } 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 { - 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 { return err } - tr.Logger.Info("Game data received") - - gameJson, err := json.ParseGame(msgJson) - if err != nil { - return err - } - - var foundryStateDb db.Game - gameJson.ToDB(&foundryStateDb) - - err = foundryStateDb.Insert(tr.DB) - if err != nil { - tr.ReadChan.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.DbCode} - } - - tr.Logger.Info("Game data succesfully inserted to DB") - + tr.Logger.Info("World is started. Succesfully logged into world") + close(tr.ReadChan.Reconnect()) + return nil +} + +func (msg WsSessionMsg) HandleLoggedInUser(tr *transport.FoundryTransport) error { + if tr.LoggedInChan == nil { + tr.Logger.Info("World had been started before application was started. Run insertion of world data") + go tr.InsertGameToDB() + return nil + } + + select { + case <-tr.LoggedInChan: + return ErrUserChannelIsClosed + default: + tr.LoggedInChan <- true + } + + time.Sleep(3 * time.Second) + types.CloseChannel(tr.LoggedInChan) return nil } diff --git a/internal/foundry/foundry.go b/internal/foundry/foundry.go index beed425..fbc6635 100644 --- a/internal/foundry/foundry.go +++ b/internal/foundry/foundry.go @@ -8,10 +8,11 @@ import ( "time" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/actions" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests" - json_models "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/json" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" + json_models "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/json" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" + "golang.org/x/sync/errgroup" ) var ( @@ -24,7 +25,7 @@ var ( type FoundryApi struct { //TODO: make check of admin's authentication - transport *transport.FoundryTransport + Transport *transport.FoundryTransport IsAvailable bool // Logger *slog.Logger @@ -37,16 +38,16 @@ func NewFoundry() *FoundryApi { } func (foundry *FoundryApi) SetTransport(tr *transport.FoundryTransport) { - foundry.transport = tr + foundry.Transport = tr } func (foundry *FoundryApi) ListenAndServeWS() error { var err error - foundry.transport.ReadChan = *types.InitWsChannels() - defer foundry.transport.ReadChan.Close() + foundry.Transport.ReadChan = *types.InitWsChannels() + defer foundry.Transport.ReadChan.Close() - foundry.background(foundry.transport.ListenWebSocket) + foundry.background(foundry.Transport.ListenWebSocket) err = foundry.ServeWebSocket() if err != nil { @@ -57,21 +58,21 @@ func (foundry *FoundryApi) ListenAndServeWS() error { func (foundry *FoundryApi) ServeWebSocket() error { var err error - wsChannels := &foundry.transport.ReadChan + wsChannels := &foundry.Transport.ReadChan for { select { case message, ok := <-wsChannels.Msg(): if !ok { - foundry.transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode) - foundry.transport.Http.SessionID = nil + foundry.Transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode) + foundry.Transport.Http.SessionID = nil return ChannelIsClosed } - foundry.transport.Logger.Debug("Data has been received\n", "msg", message.Code, "type", types.WebSocketCode) //, "msg", message.MsgJson) + foundry.Transport.Logger.Debug("Data has been received\n", "msg", message.Code, "type", types.WebSocketCode) //, "msg", message.MsgJson) switch message.Code { case types.RespPingCode, types.RespSessionDataCode: - err := foundry.transport.SendOnlyCodeRequest(message.Code) + err := foundry.Transport.SendOnlyCodeRequest(message.Code) if err != nil { wsChannels.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.WebSocketCode} continue @@ -84,69 +85,69 @@ func (foundry *FoundryApi) ServeWebSocket() error { continue } - foundry.transport.Logger.Debug("RespServerChangeCode", "data", data) + foundry.Transport.Logger.Debug("RespServerChangeCode", "data", data) if data == nil { continue } - err = data.Action(foundry.transport, &foundry.Status) + err = data.Action(foundry.Transport, &foundry.Status) if err != nil { wsChannels.Err() <- &types.FoundryError{Direction: types.ReaderCode, Err: err, Type: types.WebSocketCode} continue } case types.RespDataCode: - foundry.transport.ExchangeChan.Msgs[message.Id] = make(chan []byte, 1) - foundry.transport.ExchangeChan.Msgs[message.Id] <- []byte(message.MsgJson) - go foundry.transport.CloseMsgChannel(message.Id, 5*time.Second) + foundry.Transport.ExchangeChan.Msgs[message.Id] = make(chan []byte, 1) + foundry.Transport.ExchangeChan.Msgs[message.Id] <- []byte(message.MsgJson) + go foundry.Transport.CloseMsgChannel(message.Id, 5*time.Second) default: } case err = <-wsChannels.Err(): var foundryErr *types.FoundryError if errors.As(err, &foundryErr) { if foundryErr.IsFatal { - foundry.transport.CloseWebSocketConn() - foundry.transport.Http.SessionID = nil + foundry.Transport.CloseWebSocketConn() + foundry.Transport.Http.SessionID = nil return foundryErr } - foundry.transport.Logger.Warn("Got error when listening or served", "err", err.Error(), "type", foundryErr.Type, "direction", foundryErr.Direction) + foundry.Transport.Logger.Warn("Got error when listening or served", "err", err.Error(), "type", foundryErr.Type, "direction", foundryErr.Direction) } case <-wsChannels.Done(): - foundry.transport.CloseWebSocketConn() - foundry.transport.Http.SessionID = nil + foundry.Transport.CloseWebSocketConn() + foundry.Transport.Http.SessionID = nil return ListenIsDone case <-wsChannels.Reconnect(): - foundry.transport.CloseWebSocketConn() + foundry.Transport.CloseWebSocketConn() return ReconnectToWebSocket } } } func (foundry *FoundryApi) HandleWSRequest(msgType string) ([]byte, error) { - msg := types.NewWsMessageByPage(msgType, foundry.transport.CurrWsId) + msg := types.NewWsMessageByPage(msgType, foundry.Transport.CurrWsId) - return foundry.transport.HandleWebsocketRequest(msg) + return foundry.Transport.HandleWebsocketRequest(msg) } func (foundry *FoundryApi) Shutdown() error { ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() - foundry.transport.Logger.Info("Completing foundry background tasks") + foundry.Transport.Logger.Info("Completing foundry background tasks") foundryClosed := make(chan struct{}) go func() { - types.CloseChannel(foundry.transport.ReadChan.Done()) + types.CloseChannel(foundry.Transport.ReadChan.Done()) foundry.wg.Wait() - foundry.transport.ExchangeChan.Close() - foundry.transport.ReadChan.Close() + foundry.Transport.ExchangeChan.Close() + foundry.Transport.ReadChan.Close() types.CloseChannel(foundryClosed) }() select { case <-foundryClosed: - foundry.transport.Logger.Info("Stopped foundry server") + foundry.Transport.Logger.Info("Stopped foundry server") case <-ctx.Done(): return CloseTimeoutExceed } @@ -156,24 +157,24 @@ func (foundry *FoundryApi) Shutdown() error { func (foundry *FoundryApi) ConnectToWebSocket() (bool, error) { var err error - if !foundry.transport.HasSessionId() { - err = foundry.transport.InitSessionId() + if !foundry.Transport.HasSessionId() { + err = foundry.Transport.InitSessionId() if err != nil { return false, err } - err = foundry.transport.ConnectToFoundry() + err = foundry.Transport.ConnectToFoundry() if err != nil { return false, err } } - err = foundry.transport.InitWebSocketConnection() + err = foundry.Transport.InitWebSocketConnection() if err != nil { return false, err } - status, err := foundry.transport.Http.GetStatus() + status, err := foundry.Transport.Http.GetStatus() if err != nil { return false, err } @@ -195,21 +196,21 @@ func (foundry *FoundryApi) StartListenFoundry() { ok, err := foundry.ConnectToWebSocket() if err != nil { if errors.Is(err, ReconnectToWebSocket) { - if foundry.transport.ReconnectNum >= foundry.transport.ReconnectNumMax { + if foundry.Transport.ReconnectNum >= foundry.Transport.ReconnectNumMax { return } - time.Sleep(foundry.transport.ReconnectTimeout) - foundry.transport.ReconnectNum++ - foundry.transport.Logger.Info("Reconnecting to WebSocket", "times", foundry.transport.ReconnectNum) + time.Sleep(foundry.Transport.ReconnectTimeout) + foundry.Transport.ReconnectNum++ + foundry.Transport.Logger.Info("Reconnecting to WebSocket", "times", foundry.Transport.ReconnectNum) continue } - foundry.transport.Logger.Error("Error raised", "err", err.Error()) + foundry.Transport.Logger.Error("Error raised", "err", err.Error()) if ok { timeInterval = 1 * time.Second } - foundry.transport.Logger.Info("Trying to reconnect", "timer", timeInterval.String()) + foundry.Transport.Logger.Info("Trying to reconnect", "timer", timeInterval.String()) time.Sleep(timeInterval) timeInterval = min(timeInterval*2, 15*time.Second) @@ -220,11 +221,28 @@ func (foundry *FoundryApi) StartListenFoundry() { }) } -func (foundry *FoundryApi) Test() (*json_models.Game, error) { - wsMsg := types.NewWsMessage("world", foundry.transport.CurrWsId) - foundry.transport.CurrWsId++ +func (foundry *FoundryApi) PrepareDB() error { + dbConn := foundry.Transport.DB - answer, err := foundry.transport.HandleWebsocketRequest(wsMsg) + group, _ := errgroup.WithContext(context.Background()) + + group.Go(func() error { return db.DeleteSetupAll(dbConn) }) + group.Go(func() error { return db.DeleteGameAll(dbConn) }) + group.Go(func() error { return db.DeleteSeqAll(dbConn) }) + + err := group.Wait() + if err != nil && !errors.Is(err, db.ErrorRecordNotFound) { + return err + } + + return nil +} + +func (foundry *FoundryApi) Test() (*json_models.Game, error) { + wsMsg := types.NewWsMessage("world", foundry.Transport.CurrWsId) + foundry.Transport.CurrWsId++ + + answer, err := foundry.Transport.HandleWebsocketRequest(wsMsg) if err != nil { return nil, err } @@ -238,11 +256,6 @@ func (foundry *FoundryApi) Test() (*json_models.Game, error) { } elapsed := time.Since(start) - foundry.transport.Logger.Info("World data successfully received and parsed", "parseTime", elapsed) + foundry.Transport.Logger.Info("World data successfully received and parsed", "parseTime", elapsed) return &game[0], nil } - -// TODO: remove -func (foundry *FoundryApi) GetHTTP() *requests.FoundryHttpRequest { - return foundry.transport.Http -} diff --git a/internal/foundry/helpers.go b/internal/foundry/helpers.go index 00fdb0c..22ebea7 100644 --- a/internal/foundry/helpers.go +++ b/internal/foundry/helpers.go @@ -9,7 +9,7 @@ func (f *FoundryApi) background(fn func()) { defer func() { if err := recover(); err != nil { - f.transport.Logger.Error(fmt.Sprintf("%s", err)) + f.Transport.Logger.Error(fmt.Sprintf("%s", err)) } }() diff --git a/internal/foundry/temp_models/db/actor.go b/internal/foundry/models/db/actor.go similarity index 70% rename from internal/foundry/temp_models/db/actor.go rename to internal/foundry/models/db/actor.go index 37e6d06..2132d25 100644 --- a/internal/foundry/temp_models/db/actor.go +++ b/internal/foundry/models/db/actor.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "errors" - "fmt" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -25,9 +24,13 @@ type Actor struct { } func (a *Actor) Query(data *InsertId[uint]) { - data.query = fmt.Sprintf(` - INSERT INTO actor (%s, id, img, name, type, folder, sort) - VALUES ($1, $2, $3, $4, $5, $6, $7)`, data.fieldName) + data.query = ` + INSERT INTO actor (game_id, id, img, name, type, folder, sort) + VALUES ($1, $2, $3, $4, $5, $6, $7) + ON CONFLICT(id) DO UPDATE SET + game_id = EXCLUDED.game_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (a *Actor) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return a.InsertObjects(tx) + if isInserted { + return a.InsertObjects(tx) + } + return nil } diff --git a/internal/foundry/temp_models/db/addresses.go b/internal/foundry/models/db/addresses.go similarity index 100% rename from internal/foundry/temp_models/db/addresses.go rename to internal/foundry/models/db/addresses.go diff --git a/internal/foundry/temp_models/db/authors.go b/internal/foundry/models/db/authors.go similarity index 100% rename from internal/foundry/temp_models/db/authors.go rename to internal/foundry/models/db/authors.go diff --git a/internal/foundry/temp_models/db/card.go b/internal/foundry/models/db/card.go similarity index 81% rename from internal/foundry/temp_models/db/card.go rename to internal/foundry/models/db/card.go index 000fafb..a2a0e36 100644 --- a/internal/foundry/temp_models/db/card.go +++ b/internal/foundry/models/db/card.go @@ -30,7 +30,11 @@ type CardDeck struct { func (c *CardDeck) Query(data *InsertId[uint]) { data.query = ` INSERT INTO card_deck (game_id, id, name, type, description, img, folder, width, height, rotation, sort, display_count) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)` + 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 { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return c.InsertObjects(tx) + } 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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return c.InsertObjects(tx) + } return nil } @@ -101,7 +113,11 @@ type Card struct { func (c *Card) Query(data *InsertId[string]) { data.query = ` INSERT INTO card (card_deck_id, id, name, type, suit, description, origin, width, height, rotation, value, face, sort, drawn) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)` + 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 { @@ -124,13 +140,17 @@ func (c *Card) Insert(tx *sqlx.Tx, data *InsertId[string]) error { 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 { return err } + if isInserted { + return c.InsertObjects(tx) + } return nil } @@ -139,13 +159,17 @@ func (c *Card) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string 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 { return err } + if isInserted { + return c.InsertObjects(tx) + } return nil } diff --git a/internal/foundry/temp_models/db/combat.go b/internal/foundry/models/db/combat.go similarity index 68% rename from internal/foundry/temp_models/db/combat.go rename to internal/foundry/models/db/combat.go index 93e3cb4..f083c6d 100644 --- a/internal/foundry/temp_models/db/combat.go +++ b/internal/foundry/models/db/combat.go @@ -26,7 +26,11 @@ type Combat struct { func (c *Combat) Query(data *InsertId[uint]) { data.query = ` INSERT INTO combat (game_id, id, type, scene, round, turn, sort, active) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + 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 { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return c.InsertObjects(tx) + } 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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return c.InsertObjects(tx) + } return nil } @@ -91,12 +103,16 @@ type Combatant struct { func (c *Combatant) Query(data *InsertId[string]) { data.query = ` - INSERT INTO combatant (combat_id, id, token_id, scene_id, actor_id, img, group_, initiative, hidden, defeated) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` + INSERT INTO combatant (combat_id, id, token_id, scene_id, actor_id, type, img, group_, initiative, hidden, defeated) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + ON CONFLICT(id) DO UPDATE SET + combat_id = EXCLUDED.combat_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (c *Combatant) InsertObjects(tx *sqlx.Tx) error { - relId := InsertId[string]{id: c.ID, fieldName: "combat_id"} + relId := InsertId[string]{id: c.ID, fieldName: "combatant_id"} group, ctx := errgroup.WithContext(context.Background()) InsertWithCtxParallel(group, ctx, tx, c.Stats, relId) @@ -113,13 +129,17 @@ func (c *Combatant) Insert(tx *sqlx.Tx, data *InsertId[string]) error { 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 { return err } + if isInserted { + return c.InsertObjects(tx) + } return nil } @@ -128,12 +148,16 @@ func (c *Combatant) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[s 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 { return err } + if isInserted { + return c.InsertObjects(tx) + } return nil } diff --git a/internal/foundry/temp_models/db/compatibility.go b/internal/foundry/models/db/compatibility.go similarity index 100% rename from internal/foundry/temp_models/db/compatibility.go rename to internal/foundry/models/db/compatibility.go diff --git a/internal/foundry/temp_models/db/details_language.go b/internal/foundry/models/db/details_language.go similarity index 100% rename from internal/foundry/temp_models/db/details_language.go rename to internal/foundry/models/db/details_language.go diff --git a/internal/foundry/temp_models/db/document_types.go b/internal/foundry/models/db/document_types.go similarity index 100% rename from internal/foundry/temp_models/db/document_types.go rename to internal/foundry/models/db/document_types.go diff --git a/internal/foundry/temp_models/db/environment.go b/internal/foundry/models/db/environment.go similarity index 100% rename from internal/foundry/temp_models/db/environment.go rename to internal/foundry/models/db/environment.go diff --git a/internal/foundry/models/db/errors.go b/internal/foundry/models/db/errors.go deleted file mode 100644 index b34a2d3..0000000 --- a/internal/foundry/models/db/errors.go +++ /dev/null @@ -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") -) diff --git a/internal/foundry/temp_models/db/files.go b/internal/foundry/models/db/files.go similarity index 100% rename from internal/foundry/temp_models/db/files.go rename to internal/foundry/models/db/files.go diff --git a/internal/foundry/temp_models/db/folder.go b/internal/foundry/models/db/folder.go similarity index 82% rename from internal/foundry/temp_models/db/folder.go rename to internal/foundry/models/db/folder.go index 4f9b7cf..133de5b 100644 --- a/internal/foundry/temp_models/db/folder.go +++ b/internal/foundry/models/db/folder.go @@ -90,10 +90,13 @@ type WorldFolder struct { } func (w *WorldFolder) Query(data *InsertId[uint]) { - data.query = fmt.Sprintf(` - INSERT INTO world_folder (%s, id, name, type, folder, sorting, description, color, sort) + data.query = ` + INSERT INTO world_folder (game_id, id, name, type, folder, sorting, description, color, sort) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - 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 { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return w.InsertObjects(tx) + if isInserted { + return w.InsertObjects(tx) + } + return nil } diff --git a/internal/foundry/temp_models/db/foundry_data.go b/internal/foundry/models/db/foundry_data.go similarity index 100% rename from internal/foundry/temp_models/db/foundry_data.go rename to internal/foundry/models/db/foundry_data.go diff --git a/internal/foundry/models/db/foundry_state.go b/internal/foundry/models/db/foundry_state.go deleted file mode 100644 index 04a074e..0000000 --- a/internal/foundry/models/db/foundry_state.go +++ /dev/null @@ -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 -} diff --git a/internal/foundry/temp_models/db/game.go b/internal/foundry/models/db/game.go similarity index 76% rename from internal/foundry/temp_models/db/game.go rename to internal/foundry/models/db/game.go index f69d556..bac1ade 100644 --- a/internal/foundry/temp_models/db/game.go +++ b/internal/foundry/models/db/game.go @@ -68,24 +68,31 @@ func (g *Game) InsertObjects(tx *sqlx.Tx) error { InsertSliceParallel(group, tx, g.Modules, relData) InsertSliceParallel(group, tx, g.PackageWarnings, relData) InsertSliceParallel(group, tx, g.Packs, relDataString) - // InsertSliceParallel(group, tx, g.Messages, relData) - // InsertSliceParallel(group, tx, g.Combats, relData) - // InsertSliceParallel(group, tx, g.CardDeck, relData) - // InsertSliceParallel(group, tx, g.Users, relData) - // InsertSliceParallel(group, tx, g.Macros, relData) - // InsertSliceParallel(group, tx, g.Folders, relData) - // InsertSliceParallel(group, tx, g.Items, - // InsertId[string]{id: strconv.FormatUint(uint64(g.ID), 10), fieldName: "game_id"}) - // InsertSliceParallel(group, tx, g.Settings, relData) - // InsertSliceParallel(group, tx, g.Journals, relData) - // InsertSliceParallel(group, tx, g.Tables, relData) - // InsertSliceParallel(group, tx, g.Playlists, relData) - // InsertSliceParallel(group, tx, g.Actors, relData) + InsertSliceParallel(group, tx, g.Messages, relData) + InsertSliceParallel(group, tx, g.Combats, relData) + InsertSliceParallel(group, tx, g.CardDeck, relData) + InsertSliceParallel(group, tx, g.Users, relData) + InsertSliceParallel(group, tx, g.Macros, relData) + InsertSliceParallel(group, tx, g.Folders, relData) + InsertSliceParallel(group, tx, g.Settings, relData) + InsertSliceParallel(group, tx, g.Journals, relData) + InsertSliceParallel(group, tx, g.Tables, relData) + InsertSliceParallel(group, tx, g.Playlists, relData) err := group.Wait() if err != nil && !errors.Is(err, sql.ErrNoRows) { 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 } diff --git a/internal/foundry/temp_models/db/grid.go b/internal/foundry/models/db/grid.go similarity index 100% rename from internal/foundry/temp_models/db/grid.go rename to internal/foundry/models/db/grid.go diff --git a/internal/foundry/models/db/index.go b/internal/foundry/models/db/index.go new file mode 100644 index 0000000..1df0d4a --- /dev/null +++ b/internal/foundry/models/db/index.go @@ -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) +} diff --git a/internal/foundry/temp_models/db/item.go b/internal/foundry/models/db/item.go similarity index 70% rename from internal/foundry/temp_models/db/item.go rename to internal/foundry/models/db/item.go index 5e8b46f..17f520d 100644 --- a/internal/foundry/temp_models/db/item.go +++ b/internal/foundry/models/db/item.go @@ -24,9 +24,12 @@ type Item struct { func (i *Item) Query(data *InsertId[string]) { 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) - 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 { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return i.InsertObjects(tx) + if isInserted { + return i.InsertObjects(tx) + } + return nil } diff --git a/internal/foundry/temp_models/db/journal.go b/internal/foundry/models/db/journal.go similarity index 67% rename from internal/foundry/temp_models/db/journal.go rename to internal/foundry/models/db/journal.go index c5a90e6..5d4dc39 100644 --- a/internal/foundry/temp_models/db/journal.go +++ b/internal/foundry/models/db/journal.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "errors" - "fmt" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -20,9 +19,13 @@ type Journal struct { } func (j *Journal) Query(data *InsertId[uint]) { - data.query = fmt.Sprintf(` - INSERT INTO journal (%s, id, name, sort) - VALUES ($1, $2, $3, $4)`, data.fieldName) + data.query = ` + INSERT INTO journal (game_id, id, name, sort) + VALUES ($1, $2, $3, $4) + ON CONFLICT(id) DO UPDATE SET + game_id = EXCLUDED.game_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (j *Journal) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return j.InsertObjects(tx) + if isInserted { + return j.InsertObjects(tx) + } + return nil } diff --git a/internal/foundry/temp_models/db/journal_page.go b/internal/foundry/models/db/journal_page.go similarity index 87% rename from internal/foundry/temp_models/db/journal_page.go rename to internal/foundry/models/db/journal_page.go index 3754eb5..479de5f 100644 --- a/internal/foundry/temp_models/db/journal_page.go +++ b/internal/foundry/models/db/journal_page.go @@ -25,9 +25,13 @@ type JournalPage struct { } func (j *JournalPage) Query(data *InsertId[string]) { - data.query = fmt.Sprintf(` - INSERT INTO journal_page (%s, id, name, type, src, sort) - VALUES ($1, $2, $3, $4, $5, $6)`, data.fieldName) + data.query = ` + INSERT INTO journal_page (journal_id, id, name, type, src, sort) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT(id) DO UPDATE SET + journal_id = EXCLUDED.journal_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (j *JournalPage) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return j.InsertObjects(tx) + if isInserted { + return j.InsertObjects(tx) + } + return nil } type PageText struct { diff --git a/internal/foundry/temp_models/db/language.go b/internal/foundry/models/db/language.go similarity index 100% rename from internal/foundry/temp_models/db/language.go rename to internal/foundry/models/db/language.go diff --git a/internal/foundry/temp_models/db/light.go b/internal/foundry/models/db/light.go similarity index 100% rename from internal/foundry/temp_models/db/light.go rename to internal/foundry/models/db/light.go diff --git a/internal/foundry/temp_models/db/macro.go b/internal/foundry/models/db/macro.go similarity index 75% rename from internal/foundry/temp_models/db/macro.go rename to internal/foundry/models/db/macro.go index a927fd3..501f513 100644 --- a/internal/foundry/temp_models/db/macro.go +++ b/internal/foundry/models/db/macro.go @@ -27,7 +27,11 @@ type Macro struct { func (m *Macro) Query(data *InsertId[uint]) { data.query = ` INSERT INTO macro (game_id, id, command, name, type, img, author, scope, folder, sort) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + 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 { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return m.InsertObjects(tx) + } 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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return m.InsertObjects(tx) + } return nil } diff --git a/internal/foundry/temp_models/db/media.go b/internal/foundry/models/db/media.go similarity index 100% rename from internal/foundry/temp_models/db/media.go rename to internal/foundry/models/db/media.go diff --git a/internal/foundry/temp_models/db/message.go b/internal/foundry/models/db/message.go similarity index 79% rename from internal/foundry/temp_models/db/message.go rename to internal/foundry/models/db/message.go index 995f884..c3df510 100644 --- a/internal/foundry/temp_models/db/message.go +++ b/internal/foundry/models/db/message.go @@ -30,8 +30,12 @@ type Message struct { func (m *Message) Query(data *InsertId[uint]) { data.query = ` - INSERT INTO addresses (game_id, id, blind, emote, style, timestamp, content, author, type, flavor, sound) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)` + INSERT INTO message (game_id, id, blind, emote, style, timestamp, content, author, type, flavor, sound) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + ON CONFLICT(id) DO UPDATE SET + game_id = EXCLUDED.game_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (m *Message) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return m.InsertObjects(tx) + } 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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } + if isInserted { + return m.InsertObjects(tx) + } return nil } @@ -93,7 +105,7 @@ type Speaker struct { func (s Speaker) Query(data *InsertId[string]) { data.query = fmt.Sprintf(` INSERT INTO speaker (%s, scene, actor, token, alias) - VALUES ($1, $2, $3, $4, $5, $6)`, data.fieldName) + VALUES ($1, $2, $3, $4, $5)`, data.fieldName) } func (s Speaker) Insert(tx *sqlx.Tx, data *InsertId[string]) error { diff --git a/internal/foundry/models/db/models.go b/internal/foundry/models/db/models.go deleted file mode 100644 index 20effae..0000000 --- a/internal/foundry/models/db/models.go +++ /dev/null @@ -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{}, -// } -// } diff --git a/internal/foundry/models/db/module.go b/internal/foundry/models/db/module.go index ddcfb39..b59bfef 100644 --- a/internal/foundry/models/db/module.go +++ b/internal/foundry/models/db/module.go @@ -4,287 +4,185 @@ import ( "context" "database/sql" "errors" - "time" + "strings" + + "github.com/jmoiron/sqlx" + "golang.org/x/sync/errgroup" ) type Module struct { - Id int64 - TextId string - Title string - Description string - Url string - Version string - Availability int - CreatedAt time.Time - Languages []Language - Compatibility Compatibility + ID string + + Title string + Description string + URL string + License string + Readme string + Bugs string + Changelog string + Version string + Download string + Manifest string + Socket bool + Protected bool + Exclusive bool + PersistentStorage bool + CoreTranslation bool + Library bool + Locked bool + Owned bool + HasStorage bool + Active bool + Availability int + DocumentTypes DocumentTypes + Relationships Relationships + Compatibility Compatibility + Scripts []string + Esmodules []string + Tags []string + Authors []*Author + Media []*Media + Styles []*Style + Languages []*Language + Packs []*Pack + PackFolders []*Folder } -type Language struct { - Id int64 - Lang string - Name string - Path string +func (m *Module) Query(data *InsertId[uint]) { + data.query = ` + INSERT INTO module (setup_id, id, title, description, url, license, readme, bugs, + changelog, version, manifest, download, socket, protected, exclusive_, persistent_storage, + core_translation, library, locked, owned, has_storage, active, availability) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, + $18, $19, $20, $21, $22, $23) + ON CONFLICT(id) DO NOTHING` } -type Modules []Module - -func (modules Modules) GetById(id int) *Module { - return &modules[id] +func (m *Module) ConnectGameQuery(data *InsertId[uint]) { + data.query = ` + INSERT INTO game_to_module (game_id, module_id) + VALUES ($1, $2)` } -func (m FoundryStateModel) InsertModule(module *Module, stateId int64) error { - query := ` - INSERT INTO modules (state_id, text_id, title, description, url, version, availability) - VALUES ($1, $2, $3, $4, $5, $6, $7) - RETURNING id, created_at` +func (m *Module) InsertObjects(tx *sqlx.Tx) error { + relId := InsertId[string]{id: m.ID, fieldName: "module_id"} + group, ctx := errgroup.WithContext(context.Background()) - 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) - defer cancel() + scriptRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "scripts"} + InsertSimpleSliceParallel(group, tx, m.Scripts, scriptRelId) + esModulesRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "es_modules"} + InsertSimpleSliceParallel(group, tx, m.Esmodules, esModulesRelId) + tagsRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "tags"} + InsertSimpleSliceParallel(group, tx, m.Tags, tagsRelId) - err := m.DB.QueryRowContext(ctx, query, args...).Scan(&module.Id, &module.CreatedAt) + 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 { return err } - for i := range module.Languages { - err = m.InsertModuleLanguages(&module.Languages[i], module.Id) - if err != nil && err != sql.ErrNoRows { + + rowsAffected, err := res.RowsAffected() + if rowsAffected != 0 { + err = m.InsertObjects(tx) + if err != nil { return err } } - return m.InsertModuleCompatibility(&module.Compatibility, module.Id) -} -func (m FoundryStateModel) InsertModuleCompatibility(compatibility *Compatibility, moduleId int64) error { - query := ` - INSERT INTO modules_compatibility (module_id, minimum, verified, maximum) - VALUES ($1, $2, $3, $4) - RETURNING id` - - args := []any{moduleId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum} - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) - if err != nil && err != sql.ErrNoRows { - 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 + if dataId == nil { + dataCopy := *data + m.ConnectGameQuery(&dataCopy) + err = m.ConnectGame(tx, &dataCopy) } - query := ` - SELECT id, created_at, text_id, title, description, url, version, availability - FROM modules - WHERE state_id = $1` + return err +} - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() +func (m *Module) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { + 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 { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } + return err } - modules := make(Modules, 0, 1) - - for rows.Next() { - var module Module - err := rows.Scan( - &module.Id, - &module.CreatedAt, - &module.TextId, - &module.Title, - &module.Description, - &module.Url, - &module.Version, - &module.Availability, - ) + rowsAffected, err := res.RowsAffected() + if rowsAffected != 0 { + err = m.InsertObjects(tx) 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 { - return nil, err + if dataId == nil { + dataCopy := *data + m.ConnectGameQuery(&dataCopy) + err = m.ConnectGameCtx(ctx, tx, &dataCopy) } - return modules, nil -} - -func (m FoundryStateModel) GetModuleCompatibility(idModule int64) (*Compatibility, error) { - if idModule < 1 { - return nil, ErrorRecordNotFound - } - - query := ` - SELECT id, minimum, verified, maximum - FROM modules_compatibility - WHERE module_id = $1` - - var compatibility Compatibility - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - err := m.DB.QueryRowContext(ctx, query, idModule).Scan( - &compatibility.Id, - &compatibility.Minimum, - &compatibility.Verified, - &compatibility.Maximum, - ) - - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } - } - - return &compatibility, nil -} - -func (m FoundryStateModel) GetModuleLanguages(idModule int64) ([]Language, error) { - if idModule < 1 { - return nil, ErrorRecordNotFound - } - - query := ` - SELECT id, language, name, path - FROM modules_languages - WHERE module_id = $1` - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - rows, err := m.DB.QueryContext(ctx, query, idModule) - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } - } - - languages := make([]Language, 0, 1) - - for rows.Next() { - var lang Language - err := rows.Scan( - &lang.Id, - &lang.Lang, - &lang.Name, - &lang.Path, - ) - if err != nil { - return nil, err - } - languages = append(languages, lang) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return languages, nil -} - -func (m FoundryStateModel) DeleteModules(idState int64) error { - if idState < 1 { - return ErrorRecordNotFound - } - - query := ` - DELETE FROM modules - WHERE state_id = $1` - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - result, err := m.DB.ExecContext(ctx, query, idState) - 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 + return err } diff --git a/internal/foundry/temp_models/db/notes.go b/internal/foundry/models/db/notes.go similarity index 100% rename from internal/foundry/temp_models/db/notes.go rename to internal/foundry/models/db/notes.go diff --git a/internal/foundry/temp_models/db/options.go b/internal/foundry/models/db/options.go similarity index 100% rename from internal/foundry/temp_models/db/options.go rename to internal/foundry/models/db/options.go diff --git a/internal/foundry/temp_models/db/ownership.go b/internal/foundry/models/db/ownership.go similarity index 100% rename from internal/foundry/temp_models/db/ownership.go rename to internal/foundry/models/db/ownership.go diff --git a/internal/foundry/temp_models/db/pack.go b/internal/foundry/models/db/pack.go similarity index 54% rename from internal/foundry/temp_models/db/pack.go rename to internal/foundry/models/db/pack.go index 1cc6010..670face 100644 --- a/internal/foundry/temp_models/db/pack.go +++ b/internal/foundry/models/db/pack.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "strings" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -27,9 +28,23 @@ type Pack struct { } func (p *Pack) Query(data *InsertId[string]) { - data.query = fmt.Sprintf(` - INSERT INTO pack (%s, id, name, label, banner, path, type, system, package_type, package_name) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, data.fieldName) + if !strings.EqualFold(data.fieldName, "game_id") { + data.query = fmt.Sprintf(` + INSERT INTO pack (%s, id, name, label, banner, path, type, system, package_type, package_name) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ON CONFLICT(id) DO NOTHING`, data.fieldName) + } else { + data.query = ` + INSERT INTO pack (module_id, id, name, label, banner, path, type, system, package_type, package_name) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ON CONFLICT(id) DO NOTHING` + } +} + +func (p *Pack) ConnectGameQuery(data *InsertId[string]) { + data.query = ` + INSERT INTO game_to_pack (game_id, pack_id) + VALUES ($1, $2)` } func (p *Pack) InsertObjects(tx *sqlx.Tx) error { @@ -49,20 +64,61 @@ func (p *Pack) InsertObjects(tx *sqlx.Tx) error { return nil } +func (p *Pack) ConnectGame(tx *sqlx.Tx, data *InsertId[string]) error { + if data.query == "" { + return ErrNoQuery + } + + args := []any{data.id, p.ID} + + _, err := tx.Exec(data.query, args...) + return err +} + func (p *Pack) Insert(tx *sqlx.Tx, data *InsertId[string]) error { if data.query == "" { return ErrNoQuery } - 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} - _, err := tx.Exec(data.query, args...) + res, err := tx.Exec(data.query, args...) if err != nil { 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 { @@ -70,15 +126,34 @@ func (p *Pack) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string 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} - _, err := tx.ExecContext(ctx, data.query, args...) + res, err := tx.ExecContext(ctx, data.query, args...) if err != nil { 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 { diff --git a/internal/foundry/temp_models/db/package_warnings.go b/internal/foundry/models/db/package_warnings.go similarity index 100% rename from internal/foundry/temp_models/db/package_warnings.go rename to internal/foundry/models/db/package_warnings.go diff --git a/internal/foundry/temp_models/db/playlist.go b/internal/foundry/models/db/playlist.go similarity index 72% rename from internal/foundry/temp_models/db/playlist.go rename to internal/foundry/models/db/playlist.go index 1a661ab..9b69f09 100644 --- a/internal/foundry/temp_models/db/playlist.go +++ b/internal/foundry/models/db/playlist.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "errors" - "fmt" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -29,9 +28,13 @@ type Playlist struct { } func (p *Playlist) Query(data *InsertId[uint]) { - data.query = fmt.Sprintf(` - INSERT INTO playlist (%s, 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) + data.query = ` + INSERT INTO playlist (game_id, id, name, folder, sorting, description, channel, mode, fade, seed, sort, playing) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + ON CONFLICT(id) DO UPDATE SET + game_id = EXCLUDED.game_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (p *Playlist) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return p.InsertObjects(tx) + if isInserted { + return p.InsertObjects(tx) + } + return nil } type Sound struct { @@ -95,9 +106,12 @@ type Sound struct { } func (s *Sound) Query(data *InsertId[string]) { - data.query = fmt.Sprintf(` - INSERT INTO Sound (%s, 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) + data.query = ` + INSERT INTO sound (playlist_id, id, name, path, channel, description, fade, sort, repeat, playing, volume, paused_time) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + ON CONFLICT(id) DO UPDATE SET + playlist_id = EXCLUDED.playlist_id, + updated_at = datetime('now')` } func (s *Sound) Insert(tx *sqlx.Tx, data *InsertId[string]) error { diff --git a/internal/foundry/temp_models/db/relationships.go b/internal/foundry/models/db/relationships.go similarity index 100% rename from internal/foundry/temp_models/db/relationships.go rename to internal/foundry/models/db/relationships.go diff --git a/internal/foundry/temp_models/db/release.go b/internal/foundry/models/db/release.go similarity index 100% rename from internal/foundry/temp_models/db/release.go rename to internal/foundry/models/db/release.go diff --git a/internal/foundry/temp_models/db/ring.go b/internal/foundry/models/db/ring.go similarity index 100% rename from internal/foundry/temp_models/db/ring.go rename to internal/foundry/models/db/ring.go diff --git a/internal/foundry/temp_models/db/scene.go b/internal/foundry/models/db/scene.go similarity index 100% rename from internal/foundry/temp_models/db/scene.go rename to internal/foundry/models/db/scene.go diff --git a/internal/foundry/temp_models/db/settings.go b/internal/foundry/models/db/settings.go similarity index 65% rename from internal/foundry/temp_models/db/settings.go rename to internal/foundry/models/db/settings.go index 45af818..c480373 100644 --- a/internal/foundry/temp_models/db/settings.go +++ b/internal/foundry/models/db/settings.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "errors" - "fmt" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -19,9 +18,13 @@ type Setting struct { } func (s *Setting) Query(data *InsertId[uint]) { - data.query = fmt.Sprintf(` - INSERT INTO setting (%s, id, key_, value) - VALUES ($1, $2, $3, $4)`, data.fieldName) + data.query = ` + INSERT INTO setting (game_id, id, key_, value) + VALUES ($1, $2, $3, $4) + ON CONFLICT(id) DO UPDATE SET + game_id = EXCLUDED.game_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (s *Setting) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return s.InsertObjects(tx) + if isInserted { + return s.InsertObjects(tx) + } + return nil } diff --git a/internal/foundry/temp_models/db/setup.go b/internal/foundry/models/db/setup.go similarity index 92% rename from internal/foundry/temp_models/db/setup.go rename to internal/foundry/models/db/setup.go index fdd58d2..9ab41ce 100644 --- a/internal/foundry/temp_models/db/setup.go +++ b/internal/foundry/models/db/setup.go @@ -177,3 +177,18 @@ func (n *News) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) 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 +} diff --git a/internal/foundry/temp_models/db/stats.go b/internal/foundry/models/db/stats.go similarity index 100% rename from internal/foundry/temp_models/db/stats.go rename to internal/foundry/models/db/stats.go diff --git a/internal/foundry/temp_models/db/style.go b/internal/foundry/models/db/style.go similarity index 100% rename from internal/foundry/temp_models/db/style.go rename to internal/foundry/models/db/style.go diff --git a/internal/foundry/models/db/system.go b/internal/foundry/models/db/system.go index c2d3889..b19fe5c 100644 --- a/internal/foundry/models/db/system.go +++ b/internal/foundry/models/db/system.go @@ -4,201 +4,186 @@ import ( "context" "database/sql" "errors" - "time" + "strings" + + "github.com/jmoiron/sqlx" + "golang.org/x/sync/errgroup" ) type System struct { - Id int64 - TextId string - Title string - Description string - Url string - Download string - CreatedAt time.Time - Compatibility Compatibility + ID string + + Title string + Description string + URL string + License string + Bugs string + 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 (systems Systems) GetById(id int) *System { - return &systems[id] +func (s *System) Query(data *InsertId[string]) { + data.query = ` + INSERT INTO system (setup_id, id, title, description, url, license, bugs, changelog, version, manifest, + download, background, primary_token_attribute, availability, socket, protected, exclusive_, + persistent_storage, locked, owned, has_storage) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) + ON CONFLICT(id) DO NOTHING` } -func (m FoundryStateModel) InsertSystem(system *System, stateId int64) error { - query := ` - INSERT INTO systems (state_id, text_id, title, description, url, download) - VALUES ($1, $2, $3, $4, $5, $6) - RETURNING id, created_at` - - args := []any{stateId, system.TextId, system.Title, system.Description, system.Url, system.Download} - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - err := m.DB.QueryRowContext(ctx, query, args...).Scan(&system.Id, &system.CreatedAt) - if err != nil && err != sql.ErrNoRows { - return err - } - return m.InsertSystemCompatibility(&system.Compatibility, system.Id) +func (s *System) ConnectGameQuery(data *InsertId[string]) { + data.query = ` + INSERT INTO game_to_systems (game_id, system_id) + VALUES ($1, $2)` } -func (m FoundryStateModel) InsertSystemCompatibility(compatibility *Compatibility, systemId int64) error { - query := ` - INSERT INTO systems_compatibility (system_id, minimum, verified, maximum) - VALUES ($1, $2, $3, $4)` +func (s *System) InsertObjects(tx *sqlx.Tx) error { + relId := InsertId[string]{id: s.ID, fieldName: "system_id"} + group, ctx := errgroup.WithContext(context.Background()) - 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) - defer cancel() + esModulesRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "es_modules"} + InsertSimpleSliceParallel(group, tx, s.Esmodules, esModulesRelId) + scriptRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "scripts"} + InsertSimpleSliceParallel(group, tx, s.Scripts, scriptRelId) + tagsRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "tags"} + InsertSimpleSliceParallel(group, tx, s.Tags, tagsRelId) - err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) - if err != nil && err != sql.ErrNoRows { + InsertSliceParallel(group, tx, s.Authors, relId) + InsertSliceParallel(group, tx, s.Media, relId) + InsertSliceParallel(group, tx, s.Styles, relId) + InsertSliceParallel(group, tx, s.Languages, relId) + InsertSliceParallel(group, tx, s.Packs, relId) + InsertSliceParallel(group, tx, s.PackFolders, relId) + + err := group.Wait() + if err != nil && !errors.Is(err, sql.ErrNoRows) { return err } return nil } -func (m FoundryStateModel) GetSystems(idState int64) (Systems, error) { - if idState < 1 { - return nil, ErrorRecordNotFound +func (s *System) ConnectGame(tx *sqlx.Tx, data *InsertId[string]) error { + if data.query == "" { + return ErrNoQuery } - query := ` - SELECT id, created_at, text_id, title, description, url, download - FROM systems - WHERE state_id = $1` + args := []any{data.id, s.ID} - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() + _, err := tx.Exec(data.query, args...) + 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 { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } + return err } - systems := make(Systems, 0, 1) - - for rows.Next() { - var system System - err := rows.Scan( - &system.Id, - &system.CreatedAt, - &system.TextId, - &system.Title, - &system.Description, - &system.Url, - &system.Download, - ) + rowsAffected, err := res.RowsAffected() + if rowsAffected != 0 { + err = s.InsertObjects(tx) 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 { - return nil, 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 err } } - return &compatibility, nil -} - -func (m FoundryStateModel) DeleteSystems(idState int64) error { - if idState < 1 { - return ErrorRecordNotFound - } - - query := ` - DELETE FROM systems - WHERE state_id = $1` - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - result, err := m.DB.ExecContext(ctx, query, idState) - 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 + if dataId == nil { + dataCopy := *data + s.ConnectGameQuery(&dataCopy) + err = s.ConnectGameCtx(ctx, tx, &dataCopy) + } + + return err } diff --git a/internal/foundry/temp_models/db/table.go b/internal/foundry/models/db/table.go similarity index 68% rename from internal/foundry/temp_models/db/table.go rename to internal/foundry/models/db/table.go index 15b0e65..2b12769 100644 --- a/internal/foundry/temp_models/db/table.go +++ b/internal/foundry/models/db/table.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "errors" - "fmt" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" @@ -27,9 +26,13 @@ type Table struct { } func (t *Table) Query(data *InsertId[uint]) { - data.query = fmt.Sprintf(` - INSERT INTO table_ (%s, id, name, description, formula, img, folder, sort, replacement, display_roll) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, data.fieldName) + data.query = ` + INSERT INTO table_ (game_id, id, name, description, formula, img, folder, sort, replacement, display_roll) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + ON CONFLICT(id) DO UPDATE SET + game_id = EXCLUDED.game_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (t *Table) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return t.InsertObjects(tx) + if isInserted { + return t.InsertObjects(tx) + } + return nil } type TableResult struct { @@ -91,9 +102,13 @@ type TableResult struct { } func (t *TableResult) Query(data *InsertId[string]) { - data.query = fmt.Sprintf(` - INSERT INTO table_result (%s, id, type, img, description, name, weight, drawn) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, data.fieldName) + data.query = ` + INSERT INTO table_result (table_id, id, type, img, description, name, weight, drawn) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + ON CONFLICT(id) DO UPDATE SET + table_id = EXCLUDED.table_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } func (t *TableResult) InsertObjects(tx *sqlx.Tx) error { @@ -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} - _, err := tx.Exec(data.query, args...) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { 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 { @@ -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} - _, err := tx.ExecContext(ctx, data.query, args...) + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) if err != nil { return err } - return t.InsertObjects(tx) + if isInserted { + return t.InsertObjects(tx) + } + return nil } diff --git a/internal/foundry/temp_models/db/token.go b/internal/foundry/models/db/token.go similarity index 97% rename from internal/foundry/temp_models/db/token.go rename to internal/foundry/models/db/token.go index a4ab31e..a0954a0 100644 --- a/internal/foundry/temp_models/db/token.go +++ b/internal/foundry/models/db/token.go @@ -37,11 +37,11 @@ type Token struct { } func (t *Token) Query(data *InsertId[string]) { - data.query = fmt.Sprintf(` - INSERT INTO token (%s, name, actor_link_ append_number, prepend_adjective, lock_rotation, random_img, display_name, + data.query = ` + INSERT INTO token (actor_id, name, actor_link, append_number, prepend_adjective, lock_rotation, random_img, display_name, display_bars, disposition, rotation, alpha, width, height) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) - RETURNING id`, data.fieldName) + RETURNING id` } func (t *Token) InsertObjects(tx *sqlx.Tx) error { diff --git a/internal/foundry/temp_models/db/update.go b/internal/foundry/models/db/update.go similarity index 100% rename from internal/foundry/temp_models/db/update.go rename to internal/foundry/models/db/update.go diff --git a/internal/foundry/models/db/user.go b/internal/foundry/models/db/user.go index 9a39cc1..1e553d9 100644 --- a/internal/foundry/models/db/user.go +++ b/internal/foundry/models/db/user.go @@ -4,293 +4,125 @@ import ( "context" "database/sql" "errors" - "time" + + "github.com/jmoiron/sqlx" + "golang.org/x/sync/errgroup" ) type User struct { - Id int64 + ID string + Name string - Role int + Avatar string Character string Color string Pronouns string - CreatedAt time.Time - Hotbar map[string]string - Stats UserStats + Role int + Stats Stats + Hotbar []UserHotbar } -type UserStats struct { - Id int64 - CoreVersion string - SystemId string - SystemVersion string - CreatedTime int64 - ModifiedTime int64 - LastModifiedBy string +func (u *User) Query(data *InsertId[uint]) { + data.query = ` + INSERT INTO user (game_id, id, name, avatar, character, color, pronouns, role) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + ON CONFLICT(id) DO UPDATE SET + game_id = EXCLUDED.game_id, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted` } -type Users []User +func (u *User) InsertObjects(tx *sqlx.Tx) error { + group, ctx := errgroup.WithContext(context.Background()) -func (users Users) GetById(id int) *User { - return &users[id] + relId := InsertId[string]{id: u.ID, fieldName: "user_id"} + InsertWithCtxParallel(group, 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 { - query := ` - INSERT INTO users (state_id, name, role, character, color, pronouns) - VALUES ($1, $2, $3, $4, $5, $6) - RETURNING id, created_at` +func (u *User) Insert(tx *sqlx.Tx, data *InsertId[uint]) error { + if data.query == "" { + return ErrNoQuery + } - args := []any{stateId, user.Name, user.Role, user.Character, user.Color, user.Pronouns} + args := []any{data.id, u.ID, u.Name, u.Avatar, u.Character, u.Color, u.Pronouns, u.Role} - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - err := m.DB.QueryRowContext(ctx, query, args...).Scan(&user.Id, &user.CreatedAt) + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) if err != nil { return err } - for k, v := range user.Hotbar { - err = m.InsertUserHotbar(k, v, user.Id) - if err != nil { - return err - } + if isInserted { + return u.InsertObjects(tx) } - - return m.InsertUserStats(&user.Stats, user.Id) + return nil } -func (m FoundryStateModel) InsertUserHotbar(key string, value string, userId int64) error { - query := ` - INSERT INTO users_hotbar (user_id, key, value) +func (u *User) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { + if data.query == "" { + return ErrNoQuery + } + + args := []any{data.id, u.ID, u.Name, u.Avatar, u.Character, u.Color, u.Pronouns, u.Role} + + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) + if err != nil { + return err + } + + if isInserted { + return u.InsertObjects(tx) + } + return nil +} + +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)` - - args := []any{userId, key, value} - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - _, err := m.DB.ExecContext(ctx, query, args...) - if err != nil && err != sql.ErrNoRows { - return err - } - return nil } -func (m FoundryStateModel) InsertUserStats(stats *UserStats, userId int64) error { - query := ` - INSERT INTO users_stats (user_id, core_version, system_id, system_version, created_time, modified_time, last_modified_by) - VALUES ($1, $2, $3, $4, $5, $6, $7) - RETURNING id` - - args := []any{userId, stats.CoreVersion, stats.SystemId, stats.SystemVersion, stats.CreatedTime, stats.ModifiedTime, stats.LastModifiedBy} - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - return m.DB.QueryRowContext(ctx, query, args...).Scan(&stats.Id) -} - -func (m FoundryStateModel) GetUsers(idState int64) (Users, error) { - if idState < 1 { - return nil, ErrorRecordNotFound +func (u UserHotbar) Insert(tx *sqlx.Tx, data *InsertId[string]) error { + if data.query == "" { + return ErrNoQuery } - query := ` - SELECT id, created_at, name, role, character, color, pronouns - FROM users - WHERE state_id = $1` + args := []any{data.id, u.Key, u.Value} - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - rows, err := m.DB.QueryContext(ctx, query, idState) - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } - } - - users := make(Users, 0, 1) - - for rows.Next() { - var user User - err := rows.Scan( - &user.Id, - &user.CreatedAt, - &user.Name, - &user.Role, - &user.Character, - &user.Color, - &user.Pronouns, - ) - if err != nil { - return nil, err - } - user.Hotbar, err = m.GetUserHotbar(user.Id) - if err != nil { - return nil, err - } - userState, err := m.GetUserStats(user.Id) - if err != nil { - return nil, err - } - - user.Stats = *userState - users = append(users, user) - } - - if err := rows.Err(); err != nil { - return nil, err - } - - return users, nil -} - -func (m FoundryStateModel) GetUserHotbar(idUser int64) (map[string]string, error) { - if idUser < 1 { - return nil, ErrorRecordNotFound - } - - query := ` - SELECT key, value - FROM users_hotbar - WHERE user_id = $1` - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - rows, err := m.DB.QueryContext(ctx, query, idUser) - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } - } - - hotbar := make(map[string]string) - for rows.Next() { - var key string - var val string - - err := rows.Scan( - &key, - &val, - ) - if err != nil { - return nil, err - } - - hotbar[key] = val - } - - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } - } - - return hotbar, nil -} - -func (m FoundryStateModel) GetUserStats(idSystem int64) (*UserStats, error) { - if idSystem < 1 { - return nil, ErrorRecordNotFound - } - - query := ` - SELECT id, core_version, system_id, system_version, created_time, modified_time, last_modified_by - FROM users_stats - WHERE user_id = $1` - - var userStats UserStats - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - err := m.DB.QueryRowContext(ctx, query, idSystem).Scan( - &userStats.Id, - &userStats.CoreVersion, - &userStats.SystemId, - &userStats.SystemVersion, - &userStats.CreatedTime, - &userStats.ModifiedTime, - &userStats.LastModifiedBy, - ) - - if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } - } - - return &userStats, nil -} - -func (m FoundryStateModel) DeleteUsers(idState int64) error { - if idState < 1 { - return ErrorRecordNotFound - } - - query := ` - DELETE FROM users - WHERE state_id = $1` - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - result, err := m.DB.ExecContext(ctx, query, idState) + err := tx.QueryRowx(data.query, args...).Scan(&u.ID) if err != nil { return err } - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - if rowsAffected == 0 { - return ErrorRecordNotFound - } - return nil } -func (m FoundryStateModel) DeleteUser(idUser int64) error { - if idUser < 1 { - return ErrorRecordNotFound +func (u UserHotbar) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error { + if data.query == "" { + return ErrNoQuery } - query := ` - DELETE FROM users - WHERE id = $1` + args := []any{data.id, u.Key, u.Value} - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - result, err := m.DB.ExecContext(ctx, query, idUser) + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&u.ID) if err != nil { return err } - rowsAffected, err := result.RowsAffected() - if err != nil { - return err - } - if rowsAffected == 0 { - return ErrorRecordNotFound - } - return nil } diff --git a/internal/foundry/temp_models/db/utils.go b/internal/foundry/models/db/utils.go similarity index 64% rename from internal/foundry/temp_models/db/utils.go rename to internal/foundry/models/db/utils.go index ae0518e..fdbb73c 100644 --- a/internal/foundry/temp_models/db/utils.go +++ b/internal/foundry/models/db/utils.go @@ -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 { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() 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]) { g.Go(func() error { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() 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 { - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if len(data) > 0 { @@ -68,6 +68,28 @@ func InsertSlice[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data []I, relId Ins return nil } +func InsertSliceParallelTimeout[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *sqlx.Tx, data []I, relId InsertId[T], timeout time.Duration) { + g.Go(func() error { + wg, _ := errgroup.WithContext(context.Background()) + + var err error + if len(data) > 0 { + data[0].Query(&relId) + } + for i := range data { + wg.Go(func() error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + err = data[i].InsertCtx(ctx, tx, &relId) + return err + }) + } + + return wg.Wait() + }) +} + func InsertSliceParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *sqlx.Tx, data []I, relId InsertId[T]) { g.Go(func() error { wg, _ := errgroup.WithContext(context.Background()) @@ -78,7 +100,7 @@ func InsertSliceParallel[T AllowedIds, I Insertable[T]](g *errgroup.Group, tx *s } for i := range data { wg.Go(func() error { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() 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 { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() 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) for i := range data { wg.Go(func() error { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, 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 { - query := ` - DELETE FROM setup` +func DeleteSetupAll(db *sqlx.DB) error { + const query = `DELETE FROM setup` - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - result, err := db.ExecContext(ctx, query) + tx := db.MustBegin() + defer tx.Rollback() + + result, err := tx.ExecContext(ctx, query) if err != nil { return err } + tx.Commit() rowsAffected, err := result.RowsAffected() if err != nil { @@ -151,17 +176,46 @@ func DeleteAll(db *sqlx.DB) error { return nil } -func DeleteAllSeq(db *sqlx.DB) error { - query := ` - DELETE FROM sqlite_sequence` +func DeleteGameAll(db *sqlx.DB) error { + const query = `DELETE FROM game` - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - result, err := db.ExecContext(ctx, query) + tx := db.MustBegin() + defer tx.Rollback() + + result, err := tx.ExecContext(ctx, query) if err != nil { return err } + tx.Commit() + + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + return ErrorRecordNotFound + } + + return nil +} + +func DeleteSeqAll(db *sqlx.DB) error { + const query = `DELETE FROM sqlite_sequence` + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + tx := db.MustBegin() + defer tx.Rollback() + + result, err := tx.ExecContext(ctx, query) + if err != nil { + return err + } + tx.Commit() rowsAffected, err := result.RowsAffected() if err != nil { @@ -180,7 +234,6 @@ func IsErrUniqueConstraint(err error) { // or SQLITE_CONSTRAINT (basic code 19) if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique || sqliteErr.Code == sqlite3.ErrConstraint { - fmt.Println("Record already exists") } } } diff --git a/internal/foundry/temp_models/db/wall.go b/internal/foundry/models/db/wall.go similarity index 100% rename from internal/foundry/temp_models/db/wall.go rename to internal/foundry/models/db/wall.go diff --git a/internal/foundry/models/db/world.go b/internal/foundry/models/db/world.go index a4c12a7..16558be 100644 --- a/internal/foundry/models/db/world.go +++ b/internal/foundry/models/db/world.go @@ -4,231 +4,179 @@ import ( "context" "database/sql" "errors" + "fmt" "time" + + "github.com/jmoiron/sqlx" + "golang.org/x/sync/errgroup" ) type World struct { - Id int64 - TextId string - Title string - Description string - System string - CoreVersion string - SystemVersion string - LastPlayed string - PlayTime int64 - NextSession time.Time - CreatedAt time.Time - Compatibility Compatibility + ID string + + Title string + Description string + Version string + System string + Background string + JoinTheme string + CoreVersion string + SystemVersion string + LastPlayed string + 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 (worlds Worlds) GetById(id int) *World { - return &worlds[id] +func (w *World) Query(data *InsertId[string]) { + data.query = fmt.Sprintf(` + INSERT INTO world (%[1]s, id, title, description, version, system, background, join_theme, + core_version, system_version, last_played, playtime, availability, next_session, socket, + protected, exclusive_, persistent_storage, locked, owned, has_storage) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) + ON CONFLICT(id) DO UPDATE SET + %[1]s = EXCLUDED.%[1]s, + updated_at = datetime('now') + RETURNING (created_at == updated_at) AS is_inserted`, + data.fieldName) } -func (m FoundryStateModel) InsertWorld(world *World, stateId int64) error { - query := ` - INSERT INTO worlds (state_id, text_id, title, description, system, core_version, system_version, playtime, next_session) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - RETURNING id, created_at` +func (w *World) InsertObjects(tx *sqlx.Tx) error { + relId := InsertId[string]{id: w.ID, fieldName: "world_id"} + group, ctx := errgroup.WithContext(context.Background()) - args := []any{stateId, world.TextId, world.Title, world.Description, world.System, world.CoreVersion, world.SystemVersion, world.PlayTime, world.NextSession} + InsertWithCtxParallel(group, ctx, tx, w.Compatibility, relId) + InsertWithCtxParallel(group, ctx, tx, w.Relationships, relId) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() + esModulesRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "es_modules"} + InsertSimpleSliceParallel(group, tx, w.Esmodules, esModulesRelId) + scriptRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "scripts"} + InsertSimpleSliceParallel(group, tx, w.Scripts, scriptRelId) + tagsRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "tags"} + InsertSimpleSliceParallel(group, tx, w.Tags, tagsRelId) - err := m.DB.QueryRowContext(ctx, query, args...).Scan(&world.Id, &world.CreatedAt) - if err != nil && err != sql.ErrNoRows { - return err - } - return m.InsertWorldCompatibility(&world.Compatibility, world.Id) -} + InsertSliceParallel(group, tx, w.Authors, relId) + InsertSliceParallel(group, tx, w.Media, relId) + InsertSliceParallel(group, tx, w.Styles, relId) + InsertSliceParallel(group, tx, w.Languages, relId) + InsertSliceParallel(group, tx, w.Packs, relId) + InsertSliceParallel(group, tx, w.PackFolders, relId) -func (m FoundryStateModel) InsertWorldCompatibility(compatibility *Compatibility, worldId int64) error { - query := ` - INSERT INTO worlds_compatibility (world_id, minimum, verified, maximum) - VALUES ($1, $2, $3, $4) - RETURNING id` - - args := []any{worldId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum} - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) - if err != nil && err != sql.ErrNoRows { + err := group.Wait() + if err != nil && !errors.Is(err, sql.ErrNoRows) { return err } return nil } -func (m FoundryStateModel) GetWorlds(idState int64) (Worlds, error) { - if idState < 1 { - return nil, ErrorRecordNotFound +func (w *World) Insert(tx *sqlx.Tx, data *InsertId[string]) error { + if data.query == "" { + return ErrNoQuery } - query := ` - SELECT id, created_at, text_id, title, description, system, core_version, system_version, playtime, next_session - FROM worlds - WHERE state_id = $1` + args := []any{data.id, w.ID, w.Title, w.Description, w.Version, w.System, w.Background, w.JoinTheme, + w.CoreVersion, w.SystemVersion, w.LastPlayed, w.Playtime, w.Availability, w.NextSession, w.Socket, + w.Protected, w.Exclusive, w.PersistentStorage, w.Locked, w.Owned, w.HasStorage} + + var isInserted bool + err := tx.QueryRowx(data.query, args...).Scan(&isInserted) + if err != nil { + return err + } + + if isInserted { + return w.InsertObjects(tx) + } + return nil +} + +func (w *World) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error { + if data.query == "" { + return ErrNoQuery + } + + args := []any{data.id, w.ID, w.Title, w.Description, w.Version, w.System, w.Background, w.JoinTheme, + w.CoreVersion, w.SystemVersion, w.LastPlayed, w.Playtime, w.Availability, w.NextSession, w.Socket, + w.Protected, w.Exclusive, w.PersistentStorage, w.Locked, w.Owned, w.HasStorage} + + var isInserted bool + err := tx.QueryRowxContext(ctx, data.query, args...).Scan(&isInserted) + if err != nil { + return err + } + + if isInserted { + return w.InsertObjects(tx) + } + return nil +} + +func GetNotStartedWorlds(db *sqlx.DB) ([]string, error) { + const query = ` + SELECT id FROM world WHERE game_id IS NULL` ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - rows, err := m.DB.QueryContext(ctx, query, idState) + var worldNames []string + err := db.SelectContext(ctx, &worldNames, query) if err != nil { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } - } - - 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 worlds, nil + return worldNames, nil } -func (m FoundryStateModel) GetWorldsCompatibility(idWorld int64) (*Compatibility, error) { - if idWorld < 1 { - return nil, ErrorRecordNotFound - } - - query := ` - SELECT id, minimum, verified, maximum - FROM worlds_compatibility - WHERE world_id = $1` - - var compatibility Compatibility +func IsWorldInserted(db *sqlx.DB, worldName string) (bool, error) { + const query = ` + SELECT EXISTS(SELECT 1 FROM world WHERE id = $1 AND game_id IS NOT NULL)` ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - err := m.DB.QueryRowContext(ctx, query, idWorld).Scan( - &compatibility.Id, - &compatibility.Minimum, - &compatibility.Verified, - &compatibility.Maximum, + var exist bool + err := db.GetContext(ctx, &exist, query, worldName) + if err != nil { + return false, err + } + return exist, nil +} + +func GetWorld(db *sqlx.DB, worldName string) (*World, error) { + const query = ` + SELECT id, title, description, version, system, background, join_theme, core_version, system_version, + last_played, playtime, availability, next_session, socket, protected, exclusive_, persistent_storage, + locked, owned, has_storage + FROM world WHERE id = $1` + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + 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 { - switch { - case errors.Is(err, sql.ErrNoRows): - return nil, ErrorRecordNotFound - default: - return nil, err - } + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, err } - - return &compatibility, nil + return &world, 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 -// } diff --git a/internal/foundry/temp_models/json/actor.go b/internal/foundry/models/json/actor.go similarity index 97% rename from internal/foundry/temp_models/json/actor.go rename to internal/foundry/models/json/actor.go index bbdf8fa..033a433 100644 --- a/internal/foundry/temp_models/json/actor.go +++ b/internal/foundry/models/json/actor.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Actor struct { PrototypeToken Token `json:"prototypeToken"` diff --git a/internal/foundry/temp_models/json/addresses.go b/internal/foundry/models/json/addresses.go similarity index 94% rename from internal/foundry/temp_models/json/addresses.go rename to internal/foundry/models/json/addresses.go index ed9bd32..1fdd94a 100644 --- a/internal/foundry/temp_models/json/addresses.go +++ b/internal/foundry/models/json/addresses.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Addresses struct { Local string `json:"local"` diff --git a/internal/foundry/temp_models/json/authors.go b/internal/foundry/models/json/authors.go similarity index 95% rename from internal/foundry/temp_models/json/authors.go rename to internal/foundry/models/json/authors.go index 7baa1fe..cbc2936 100644 --- a/internal/foundry/temp_models/json/authors.go +++ b/internal/foundry/models/json/authors.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Author struct { Name string `json:"name"` diff --git a/internal/foundry/temp_models/json/card.go b/internal/foundry/models/json/card.go similarity index 99% rename from internal/foundry/temp_models/json/card.go rename to internal/foundry/models/json/card.go index b03d661..e3bb64d 100644 --- a/internal/foundry/temp_models/json/card.go +++ b/internal/foundry/models/json/card.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type CardDeck struct { Name string `json:"name"` diff --git a/internal/foundry/temp_models/json/combat.go b/internal/foundry/models/json/combat.go similarity index 98% rename from internal/foundry/temp_models/json/combat.go rename to internal/foundry/models/json/combat.go index 578bf13..2dbf12b 100644 --- a/internal/foundry/temp_models/json/combat.go +++ b/internal/foundry/models/json/combat.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Combat struct { Id string `json:"_id"` diff --git a/internal/foundry/temp_models/json/compatibility.go b/internal/foundry/models/json/compatibility.go similarity index 94% rename from internal/foundry/temp_models/json/compatibility.go rename to internal/foundry/models/json/compatibility.go index a02a2be..59adfd2 100644 --- a/internal/foundry/temp_models/json/compatibility.go +++ b/internal/foundry/models/json/compatibility.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Compatibility struct { Minimum string `json:"minimum,omitempty"` diff --git a/internal/foundry/models/json/data.go b/internal/foundry/models/json/data.go deleted file mode 100644 index 6f0b0c3..0000000 --- a/internal/foundry/models/json/data.go +++ /dev/null @@ -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 -} diff --git a/internal/foundry/temp_models/json/details_language.go b/internal/foundry/models/json/details_language.go similarity index 92% rename from internal/foundry/temp_models/json/details_language.go rename to internal/foundry/models/json/details_language.go index 6ba2f9f..981401b 100644 --- a/internal/foundry/temp_models/json/details_language.go +++ b/internal/foundry/models/json/details_language.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type DetailsLanguages struct { Details string `json:"details"` diff --git a/internal/foundry/temp_models/json/document_types.go b/internal/foundry/models/json/document_types.go similarity index 97% rename from internal/foundry/temp_models/json/document_types.go rename to internal/foundry/models/json/document_types.go index 4f8c981..543f65a 100644 --- a/internal/foundry/temp_models/json/document_types.go +++ b/internal/foundry/models/json/document_types.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type DocumentTypes struct { Actor DocumentTypeData `json:"Actor"` diff --git a/internal/foundry/temp_models/json/environment.go b/internal/foundry/models/json/environment.go similarity index 99% rename from internal/foundry/temp_models/json/environment.go rename to internal/foundry/models/json/environment.go index 1a3f3af..321060e 100644 --- a/internal/foundry/temp_models/json/environment.go +++ b/internal/foundry/models/json/environment.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Environment struct { GlobalLight EnvironmentGlobalLight `json:"globalLight"` diff --git a/internal/foundry/models/json/errors.go b/internal/foundry/models/json/errors.go deleted file mode 100644 index de145a6..0000000 --- a/internal/foundry/models/json/errors.go +++ /dev/null @@ -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") -) diff --git a/internal/foundry/temp_models/json/files.go b/internal/foundry/models/json/files.go similarity index 94% rename from internal/foundry/temp_models/json/files.go rename to internal/foundry/models/json/files.go index 888ed5b..965d222 100644 --- a/internal/foundry/temp_models/json/files.go +++ b/internal/foundry/models/json/files.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Files struct { Storages []string `json:"storages"` diff --git a/internal/foundry/temp_models/json/folder.go b/internal/foundry/models/json/folder.go similarity index 98% rename from internal/foundry/temp_models/json/folder.go rename to internal/foundry/models/json/folder.go index d5f21b1..8e7f751 100644 --- a/internal/foundry/temp_models/json/folder.go +++ b/internal/foundry/models/json/folder.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Folder struct { Name string `json:"name"` diff --git a/internal/foundry/temp_models/json/game.go b/internal/foundry/models/json/game.go similarity index 99% rename from internal/foundry/temp_models/json/game.go rename to internal/foundry/models/json/game.go index fb7bb16..653d2ab 100644 --- a/internal/foundry/temp_models/json/game.go +++ b/internal/foundry/models/json/game.go @@ -4,7 +4,7 @@ import ( "encoding/json" "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type Game struct { diff --git a/internal/foundry/temp_models/json/grid.go b/internal/foundry/models/json/grid.go similarity index 97% rename from internal/foundry/temp_models/json/grid.go rename to internal/foundry/models/json/grid.go index d458f9a..2f4e9dd 100644 --- a/internal/foundry/temp_models/json/grid.go +++ b/internal/foundry/models/json/grid.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Grid struct { Type int `json:"type"` diff --git a/internal/foundry/temp_models/json/index.go b/internal/foundry/models/json/index.go similarity index 94% rename from internal/foundry/temp_models/json/index.go rename to internal/foundry/models/json/index.go index 3042d27..d6a517a 100644 --- a/internal/foundry/temp_models/json/index.go +++ b/internal/foundry/models/json/index.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Index struct { Id string `json:"_id"` diff --git a/internal/foundry/temp_models/json/item.go b/internal/foundry/models/json/item.go similarity index 97% rename from internal/foundry/temp_models/json/item.go rename to internal/foundry/models/json/item.go index e527e27..1629720 100644 --- a/internal/foundry/temp_models/json/item.go +++ b/internal/foundry/models/json/item.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Item struct { Img string `json:"img"` diff --git a/internal/foundry/temp_models/json/journal.go b/internal/foundry/models/json/journal.go similarity index 97% rename from internal/foundry/temp_models/json/journal.go rename to internal/foundry/models/json/journal.go index 8eaad7b..ca40540 100644 --- a/internal/foundry/temp_models/json/journal.go +++ b/internal/foundry/models/json/journal.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Journal struct { Folder any `json:"folder"` diff --git a/internal/foundry/temp_models/json/journal_page.go b/internal/foundry/models/json/journal_page.go similarity index 98% rename from internal/foundry/temp_models/json/journal_page.go rename to internal/foundry/models/json/journal_page.go index 7b0cca7..590faaa 100644 --- a/internal/foundry/temp_models/json/journal_page.go +++ b/internal/foundry/models/json/journal_page.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type JournalPage struct { Name string `json:"name"` diff --git a/internal/foundry/models/json/language.go b/internal/foundry/models/json/language.go index fb5e0ab..70ed4d8 100644 --- a/internal/foundry/models/json/language.go +++ b/internal/foundry/models/json/language.go @@ -1,13 +1,67 @@ package json +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" + type Language struct { - Id string `json:"id"` - Label string `json:"label"` - Modules []LangModule `json:"modules"` + Lang string `json:"lang"` + Name string `json:"name"` + Path string `json:"path"` + // SystemLanguagesFlags SystemLanguagesFlags `json:"flags"` } -type LangModule struct { - Id string `json:"id"` +func (l *Language) ToDB(dest **db.Language) bool { + if dest == nil { + return false + } + + lang := &db.Language{ + Lang: l.Lang, + Name: l.Name, + Path: l.Path, + } + + *dest = lang + + return true +} + +type SetupLanguage struct { + ID string `json:"id"` + Label string `json:"label"` + Modules []*SetupLanguageModule `json:"modules"` +} + +func (s *SetupLanguage) ToDB(dest **db.SetupLanguage) bool { + if dest == nil { + return false + } + + lang := &db.SetupLanguage{ + ID: s.ID, + Label: s.Label, + } + + CopySliceToDB(&lang.Modules, s.Modules) + + *dest = lang + + return true +} + +type SetupLanguageModule struct { + ID string `json:"id"` Label string `json:"label"` Path string `json:"path"` } + +func (s *SetupLanguageModule) ToDB(dest *db.SetupLanguageModule) bool { + if dest == nil { + return false + } + + dest.ID = s.ID + dest.Label = s.Label + dest.Path = s.Path + + return true +} diff --git a/internal/foundry/temp_models/json/light.go b/internal/foundry/models/json/light.go similarity index 98% rename from internal/foundry/temp_models/json/light.go rename to internal/foundry/models/json/light.go index fe3d0b3..c03b34e 100644 --- a/internal/foundry/temp_models/json/light.go +++ b/internal/foundry/models/json/light.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Light struct { Alpha float64 `json:"alpha"` diff --git a/internal/foundry/temp_models/json/macro.go b/internal/foundry/models/json/macro.go similarity index 97% rename from internal/foundry/temp_models/json/macro.go rename to internal/foundry/models/json/macro.go index b513655..95b8f55 100644 --- a/internal/foundry/temp_models/json/macro.go +++ b/internal/foundry/models/json/macro.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Macro struct { Command string `json:"command"` diff --git a/internal/foundry/temp_models/json/media.go b/internal/foundry/models/json/media.go similarity index 93% rename from internal/foundry/temp_models/json/media.go rename to internal/foundry/models/json/media.go index e336f51..55b7917 100644 --- a/internal/foundry/temp_models/json/media.go +++ b/internal/foundry/models/json/media.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Media struct { Type string `json:"type"` diff --git a/internal/foundry/temp_models/json/message.go b/internal/foundry/models/json/message.go similarity index 98% rename from internal/foundry/temp_models/json/message.go rename to internal/foundry/models/json/message.go index bc0a40c..4a16282 100644 --- a/internal/foundry/temp_models/json/message.go +++ b/internal/foundry/models/json/message.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Message struct { Content string `json:"content"` diff --git a/internal/foundry/temp_models/json/module.go b/internal/foundry/models/json/module.go similarity index 99% rename from internal/foundry/temp_models/json/module.go rename to internal/foundry/models/json/module.go index d4ea47a..18ce5bc 100644 --- a/internal/foundry/temp_models/json/module.go +++ b/internal/foundry/models/json/module.go @@ -3,7 +3,7 @@ package json import ( "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type Module struct { diff --git a/internal/foundry/models/json/modules/dice-stats.go b/internal/foundry/models/json/modules/dice-stats.go deleted file mode 100644 index 8350cdd..0000000 --- a/internal/foundry/models/json/modules/dice-stats.go +++ /dev/null @@ -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"` -} diff --git a/internal/foundry/models/json/modules/pf2e.go b/internal/foundry/models/json/modules/pf2e.go deleted file mode 100644 index 76cdd2f..0000000 --- a/internal/foundry/models/json/modules/pf2e.go +++ /dev/null @@ -1,12 +0,0 @@ -package modules - -type Pf2eModule struct { - settings Pf2eSettings -} - -type Pf2eSettings struct { - showEffectPanel bool - showCheckDialogs bool - showDamageDialogs bool - monochromeDarkvision bool -} diff --git a/internal/foundry/temp_models/json/notes.go b/internal/foundry/models/json/notes.go similarity index 98% rename from internal/foundry/temp_models/json/notes.go rename to internal/foundry/models/json/notes.go index 46bcd11..7bcea9e 100644 --- a/internal/foundry/temp_models/json/notes.go +++ b/internal/foundry/models/json/notes.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Note struct { EntryID string `json:"entryId"` diff --git a/internal/foundry/temp_models/json/options.go b/internal/foundry/models/json/options.go similarity index 98% rename from internal/foundry/temp_models/json/options.go rename to internal/foundry/models/json/options.go index a01dd44..d8221cf 100644 --- a/internal/foundry/temp_models/json/options.go +++ b/internal/foundry/models/json/options.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type GameOptions struct { Language string `json:"language"` diff --git a/internal/foundry/temp_models/json/ownership.go b/internal/foundry/models/json/ownership.go similarity index 93% rename from internal/foundry/temp_models/json/ownership.go rename to internal/foundry/models/json/ownership.go index e4f70c0..a427b15 100644 --- a/internal/foundry/temp_models/json/ownership.go +++ b/internal/foundry/models/json/ownership.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Ownership struct { Player string `json:"PLAYER,omitempty"` diff --git a/internal/foundry/temp_models/json/pack.go b/internal/foundry/models/json/pack.go similarity index 99% rename from internal/foundry/temp_models/json/pack.go rename to internal/foundry/models/json/pack.go index c8c3781..c4739f2 100644 --- a/internal/foundry/temp_models/json/pack.go +++ b/internal/foundry/models/json/pack.go @@ -3,7 +3,7 @@ package json import ( "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type Pack struct { diff --git a/internal/foundry/temp_models/json/package_warnings.go b/internal/foundry/models/json/package_warnings.go similarity index 97% rename from internal/foundry/temp_models/json/package_warnings.go rename to internal/foundry/models/json/package_warnings.go index b0548e3..2806eea 100644 --- a/internal/foundry/temp_models/json/package_warnings.go +++ b/internal/foundry/models/json/package_warnings.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type PackageWarningsData struct { Id string `json:"id"` diff --git a/internal/foundry/temp_models/json/playlist.go b/internal/foundry/models/json/playlist.go similarity index 98% rename from internal/foundry/temp_models/json/playlist.go rename to internal/foundry/models/json/playlist.go index 873d633..31c3fd6 100644 --- a/internal/foundry/temp_models/json/playlist.go +++ b/internal/foundry/models/json/playlist.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Playlist struct { Name string `json:"name"` diff --git a/internal/foundry/temp_models/json/region_behavior.go b/internal/foundry/models/json/region_behavior.go similarity index 100% rename from internal/foundry/temp_models/json/region_behavior.go rename to internal/foundry/models/json/region_behavior.go diff --git a/internal/foundry/temp_models/json/relationships.go b/internal/foundry/models/json/relationships.go similarity index 98% rename from internal/foundry/temp_models/json/relationships.go rename to internal/foundry/models/json/relationships.go index abc5786..b61b90b 100644 --- a/internal/foundry/temp_models/json/relationships.go +++ b/internal/foundry/models/json/relationships.go @@ -3,7 +3,7 @@ package json import ( "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type Relationships struct { diff --git a/internal/foundry/models/json/release.go b/internal/foundry/models/json/release.go index b67400c..9f14595 100644 --- a/internal/foundry/models/json/release.go +++ b/internal/foundry/models/json/release.go @@ -1,11 +1,32 @@ package json +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" + type Release struct { - Generation int `json:"generation"` - Channel string `json:"channel"` - Suffix string `json:"suffix"` - Build int `json:"build"` - Node_version int `json:"node_version"` - Time int64 `json:"time"` - flags struct{} `json:"-"` + Generation int `json:"generation"` + Channel string `json:"channel"` + Suffix string `json:"suffix"` + Build int `json:"build"` + NodeVersion int `json:"node_version"` + MaxGeneration int `json:"maxGeneration"` + 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 } diff --git a/internal/foundry/temp_models/json/ring.go b/internal/foundry/models/json/ring.go similarity index 97% rename from internal/foundry/temp_models/json/ring.go rename to internal/foundry/models/json/ring.go index d77656c..76d92b2 100644 --- a/internal/foundry/temp_models/json/ring.go +++ b/internal/foundry/models/json/ring.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Ring struct { Enabled bool `json:"enabled"` diff --git a/internal/foundry/temp_models/json/scene.go b/internal/foundry/models/json/scene.go similarity index 99% rename from internal/foundry/temp_models/json/scene.go rename to internal/foundry/models/json/scene.go index df866fc..08c57f0 100644 --- a/internal/foundry/temp_models/json/scene.go +++ b/internal/foundry/models/json/scene.go @@ -3,7 +3,7 @@ package json import ( "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type Scene struct { diff --git a/internal/foundry/temp_models/json/settings.go b/internal/foundry/models/json/settings.go similarity index 94% rename from internal/foundry/temp_models/json/settings.go rename to internal/foundry/models/json/settings.go index ae972fb..9564117 100644 --- a/internal/foundry/temp_models/json/settings.go +++ b/internal/foundry/models/json/settings.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Setting struct { Key string `json:"key"` diff --git a/internal/foundry/temp_models/json/setup.go b/internal/foundry/models/json/setup.go similarity index 99% rename from internal/foundry/temp_models/json/setup.go rename to internal/foundry/models/json/setup.go index 01dda9f..4eaed6a 100644 --- a/internal/foundry/temp_models/json/setup.go +++ b/internal/foundry/models/json/setup.go @@ -4,7 +4,7 @@ import ( "encoding/json" "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type Setup struct { diff --git a/internal/foundry/temp_models/json/stats.go b/internal/foundry/models/json/stats.go similarity index 96% rename from internal/foundry/temp_models/json/stats.go rename to internal/foundry/models/json/stats.go index 8eef673..2ee60d1 100644 --- a/internal/foundry/temp_models/json/stats.go +++ b/internal/foundry/models/json/stats.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Stats struct { CoreVersion string `json:"coreVersion"` diff --git a/internal/foundry/temp_models/json/style.go b/internal/foundry/models/json/style.go similarity index 91% rename from internal/foundry/temp_models/json/style.go rename to internal/foundry/models/json/style.go index da98bd7..28cd942 100644 --- a/internal/foundry/temp_models/json/style.go +++ b/internal/foundry/models/json/style.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Style struct { Src string `json:"src"` diff --git a/internal/foundry/temp_models/json/system.go b/internal/foundry/models/json/system.go similarity index 99% rename from internal/foundry/temp_models/json/system.go rename to internal/foundry/models/json/system.go index 1f47aa6..642c1a0 100644 --- a/internal/foundry/temp_models/json/system.go +++ b/internal/foundry/models/json/system.go @@ -3,7 +3,7 @@ package json import ( "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type System struct { diff --git a/internal/foundry/temp_models/json/table.go b/internal/foundry/models/json/table.go similarity index 98% rename from internal/foundry/temp_models/json/table.go rename to internal/foundry/models/json/table.go index 53dc03c..166af3c 100644 --- a/internal/foundry/temp_models/json/table.go +++ b/internal/foundry/models/json/table.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Table struct { Name string `json:"name"` diff --git a/internal/foundry/models/json/template.go b/internal/foundry/models/json/template.go deleted file mode 100644 index 8a8fa12..0000000 --- a/internal/foundry/models/json/template.go +++ /dev/null @@ -1,132 +0,0 @@ -package json - -import "time" - -type DataTemplate struct { - Id string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Authors []DataAuthor `json:"authors,omitempty"` - Url string `json:"url,omitempty"` - Flags Flags `json:"flags,omitempty"` - License string `json:"license,omitempty"` - Readme string `json:"readme,omitempty"` - Bugs string `json:"bugs,omitempty"` - Changelog string `json:"changelog,omitempty"` - Media []DataMedia `json:"media,omitempty"` - Version string `json:"version,omitempty"` - Compatibility Compatibility `json:"compatibility,omitempty"` - Scripts []string `json:"scripts"` - Esmodules []string `json:"esmodules"` - Styles []struct { - Src string `json:"src,omitempty"` - } `json:"styles,omitempty"` - Languages []DataLanguage `json:"languages,omitempty"` - Packs []DataPack `json:"packs,omitempty"` - PackFolder []DataPackFolder `json:"packFolder,omitempty"` - Relationships Relationship `json:"relationships,omitempty"` - Socket bool `json:"socket,omitempty"` - Manifest string `json:"manifest,omitempty"` - Download string `json:"download,omitempty"` - Protected bool `json:"protected,omitempty"` - Exclusive bool `json:"exclusive,omitempty"` - PersistentStorage bool `json:"persistentStorage,omitempty"` - Availability int `json:"availability,omitempty"` - Locked bool `json:"locked,omitempty"` - Owned bool `json:"owned,omitempty"` - HasStorage bool `json:"hasStorage,omitempty"` - - //module - CoreTranslation bool `json:"coreTranslation,omitempty"` - Library bool `json:"library,omitempty"` - - //system - Background string `json:"background,omitempty"` - Grid DataGrid `json:"grid,omitempty"` - PrimaryTokenAttribute string `json:"primaryTokenAttribute,omitempty"` - - //world - System string `json:"system,omitempty"` - JoinTheme string `json:"joinTheme,omitempty"` - CoreVersion string `json:"coreVersion,omitempty"` - SystemVersion string `json:"systemVersion,omitempty"` - LastPlayed string `json:"lastPlayed,omitempty"` - PlayTime int64 `json:"playTime,omitempty"` - NextSession time.Time `json:"nextSession,omitempty"` -} - -type DataAuthor struct { - Name string `json:"name,omitempty"` - Url string `json:"url,omitempty"` - Discord string `json:"discord,omitempty"` - flags struct{} `json:"-"` -} - -type DataMedia struct { - Type string `json:"type,omitempty"` - Url string `json:"url,omitempty"` - Loop bool `json:"loop,omitempty"` - flags struct{} `json:"-"` -} - -type DataLanguage struct { - Lang string `json:"lang,omitempty"` - Name string `json:"name,omitempty"` - Path string `json:"path,omitempty"` - Flags struct{} `json:"-"` -} - -type DataPack struct { - Name string `json:"name,omitempty"` - Label string `json:"label,omitempty"` - Banner string `json:"banner,omitempty"` - Path string `json:"path,omitempty"` - Type string `json:"type,omitempty"` - System string `json:"system,omitempty"` - Ownership map[string]string `json:"ownership,omitempty"` - flags struct{} `json:"-"` -} - -type DataPackFolder struct { - Name string `json:"name,omitempty"` - Sorting string `json:"sorting,omitempty"` - Color string `json:"color,omitempty"` - Packs []string `json:"packs,omitempty"` - Folders []DataPackFolder `json:"folders,omitempty"` -} - -type DataGrid struct { - Type int `json:"type,omitempty"` - Distance int `json:"distance,omitempty"` - Units string `json:"units,omitempty"` - Diagonals int `json:"diagonals,omitempty"` -} - -type Compatibility struct { - Minimum string `json:"minimum,omitempty"` - Verified string `json:"verified,omitempty"` - Maximum string `json:"maximum,omitempty"` -} - -type Flags struct { - HotReload FlagsHotReload `json:"hotReload"` - Styles []string `json:"styles"` -} - -type FlagsHotReload struct { - Enabled bool `json:"enabled"` - Extensions []string `json:"extensions"` - Paths []string `json:"paths"` -} - -type Relationship struct { - systems []struct{} `json:"-"` - requires []struct{} `json:"-"` - Recommends []struct { - Id string `json:"id"` - Type string `json:"type"` - compatibility struct{} `json:"-"` - } `json:"recommends"` - conflicts []struct{} `json:"-"` - flags struct{} `json:"-"` -} diff --git a/internal/foundry/temp_models/json/token.go b/internal/foundry/models/json/token.go similarity index 99% rename from internal/foundry/temp_models/json/token.go rename to internal/foundry/models/json/token.go index e7e19de..de554d1 100644 --- a/internal/foundry/temp_models/json/token.go +++ b/internal/foundry/models/json/token.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Token struct { DisplayName int `json:"displayName"` diff --git a/internal/foundry/temp_models/json/update.go b/internal/foundry/models/json/update.go similarity index 97% rename from internal/foundry/temp_models/json/update.go rename to internal/foundry/models/json/update.go index 49851f9..0d72336 100644 --- a/internal/foundry/temp_models/json/update.go +++ b/internal/foundry/models/json/update.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type CoreUpdate struct { HasUpdate bool `json:"hasUpdate"` diff --git a/internal/foundry/temp_models/json/user.go b/internal/foundry/models/json/user.go similarity index 97% rename from internal/foundry/temp_models/json/user.go rename to internal/foundry/models/json/user.go index e4e785c..72962a1 100644 --- a/internal/foundry/temp_models/json/user.go +++ b/internal/foundry/models/json/user.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type User struct { Name string `json:"name"` diff --git a/internal/foundry/models/json/users.go b/internal/foundry/models/json/users.go deleted file mode 100644 index 4d0243b..0000000 --- a/internal/foundry/models/json/users.go +++ /dev/null @@ -1,41 +0,0 @@ -package json - -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/json/modules" - -type User struct { - Name string `json:"name,omitempty"` - Role int `json:"role,omitempty"` - Id string `json:"_id,omitempty"` - avatar struct{} `json:"-"` - Character string `json:"character,omitempty"` - Color string `json:"color,omitempty"` - Pronouns string `json:"pronouns,omitempty"` - Hotbar map[string]string `json:"hotbar,omitempty"` - permissions struct{} `json:"-"` - Flags UserFlags `json:"flags,omitempty"` - Stats UserStats `json:"_stats,omitempty"` -} - -type UserFlags struct { - World map[string]bool `json:"world,omitempty"` - Pf2e modules.Pf2eModule `json:"pf2e,omitempty"` - DiceStats modules.DiceStats `json:"diceStats,omitempty"` -} - -type UserStats struct { - compendiumSource struct{} `json:"-"` - duplicateSource struct{} `json:"-"` - exportSource struct{} `json:"-"` - CoreVersion string `json:"coreVersion,omitempty"` - SystemId string `json:"systemId,omitempty"` - SystemVersion string `json:"systemVersion,omitempty"` - CreatedTime int64 `json:"createdTime,omitempty"` - ModifiedTime int64 `json:"modifiedTime,omitempty"` - LastModifiedBy string `json:"lastModifiedBy,omitempty"` -} - -type Users []User - -func (users Users) GetById(id int) *User { - return &users[id] -} diff --git a/internal/foundry/temp_models/json/utils.go b/internal/foundry/models/json/utils.go similarity index 98% rename from internal/foundry/temp_models/json/utils.go rename to internal/foundry/models/json/utils.go index 4356980..0174e21 100644 --- a/internal/foundry/temp_models/json/utils.go +++ b/internal/foundry/models/json/utils.go @@ -4,7 +4,7 @@ import ( "errors" "sync" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) var ( diff --git a/internal/foundry/temp_models/json/wall.go b/internal/foundry/models/json/wall.go similarity index 98% rename from internal/foundry/temp_models/json/wall.go rename to internal/foundry/models/json/wall.go index 596c1f8..0c3cbc8 100644 --- a/internal/foundry/temp_models/json/wall.go +++ b/internal/foundry/models/json/wall.go @@ -1,6 +1,6 @@ package json -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" +import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" type Wall struct { C []int `json:"c"` diff --git a/internal/foundry/temp_models/json/world.go b/internal/foundry/models/json/world.go similarity index 99% rename from internal/foundry/temp_models/json/world.go rename to internal/foundry/models/json/world.go index cb46e01..52aecba 100644 --- a/internal/foundry/temp_models/json/world.go +++ b/internal/foundry/models/json/world.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) type World struct { diff --git a/internal/foundry/requests/config.go b/internal/foundry/requests/config.go index 4087f83..4c4b096 100644 --- a/internal/foundry/requests/config.go +++ b/internal/foundry/requests/config.go @@ -6,8 +6,6 @@ import ( "net/http" "net/http/cookiejar" "net/url" - - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) var ( @@ -36,18 +34,6 @@ var WsTypeData = map[string]string{ UpdatePath: "getUpdateData", } -var PathToSetupState = map[string]db.StateType{ - SetupPath: db.SetupState, - // AuthPath: db.AuthState, - // UpdatePath: db.UpdateState, - // LicensePath: db.LicenseState, -} - -var PathToWorldState = map[string]db.StateType{ - JoinPath: db.JoinState, - // PlayersPath: db.PlayersState, -} - type FoundryHttpRequest struct { Host string Password string diff --git a/internal/foundry/requests/get_requests.go b/internal/foundry/requests/get_requests.go index f5a1ad9..3d43021 100644 --- a/internal/foundry/requests/get_requests.go +++ b/internal/foundry/requests/get_requests.go @@ -24,8 +24,6 @@ func (conf *FoundryHttpRequest) GetSessionId() error { return err } - fmt.Printf("%v\n", conf.Jar) - _, err = conf.SetSessionTokenFromHeader() return err } diff --git a/internal/foundry/temp_models/db/index.go b/internal/foundry/temp_models/db/index.go deleted file mode 100644 index cd7d9ad..0000000 --- a/internal/foundry/temp_models/db/index.go +++ /dev/null @@ -1,53 +0,0 @@ -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_ (pack_id, id, folder, img, name, type) - VALUES ($1, $2, $3, $4, $5, $6) - RETURNING id` -} - -func (i *Index) Insert(tx *sqlx.Tx, data *InsertId[string]) error { - if data.query == "" { - return ErrNoQuery - } - - args := []any{data.id, i.ID, i.Folder, i.Img, i.Name, i.Type} - - _, err := tx.Exec(data.query, args...) - if err != nil { - return err - } - - return nil -} - -func (i *Index) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error { - if data.query == "" { - return ErrNoQuery - } - - args := []any{data.id, i.ID, i.Folder, i.Img, i.Name, i.Type} - - _, err := tx.ExecContext(ctx, data.query, args...) - if err != nil { - return err - } - - return nil -} diff --git a/internal/foundry/temp_models/db/module.go b/internal/foundry/temp_models/db/module.go deleted file mode 100644 index 5d02142..0000000 --- a/internal/foundry/temp_models/db/module.go +++ /dev/null @@ -1,176 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "errors" - "strings" - - "github.com/jmoiron/sqlx" - "golang.org/x/sync/errgroup" -) - -type Module struct { - ID string - - Title string - Description string - URL string - License string - Readme string - Bugs string - Changelog string - Version string - Download string - Manifest string - Socket bool - Protected bool - Exclusive bool - PersistentStorage bool - CoreTranslation bool - Library bool - Locked bool - Owned bool - HasStorage bool - Active bool - Availability int - DocumentTypes DocumentTypes - Relationships Relationships - Compatibility Compatibility - Scripts []string - Esmodules []string - Tags []string - Authors []*Author - Media []*Media - Styles []*Style - Languages []*Language - Packs []*Pack - PackFolders []*Folder -} - -func (m *Module) Query(data *InsertId[uint]) { - data.query = ` - INSERT INTO module (setup_id, id, title, description, url, license, readme, bugs, - changelog, version, manifest, download, socket, protected, exclusive_, persistent_storage, - core_translation, library, locked, owned, has_storage, active, availability) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, - $18, $19, $20, $21, $22, $23) - ON CONFLICT(id) DO NOTHING` -} - -func (m *Module) ConnectGameQuery(data *InsertId[uint]) { - data.query = ` - INSERT INTO game_to_module (game_id, module_id) - VALUES ($1, $2)` -} - -func (m *Module) InsertObjects(tx *sqlx.Tx) error { - relId := InsertId[string]{id: m.ID, fieldName: "module_id"} - group, ctx := errgroup.WithContext(context.Background()) - - InsertWithCtxParallel(group, ctx, tx, m.DocumentTypes, relId) - InsertWithCtxParallel(group, ctx, tx, m.Relationships, relId) - InsertWithCtxParallel(group, ctx, tx, m.Compatibility, relId) - - scriptRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "scripts"} - InsertSimpleSliceParallel(group, tx, m.Scripts, scriptRelId) - esModulesRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "es_modules"} - InsertSimpleSliceParallel(group, tx, m.Esmodules, esModulesRelId) - tagsRelId := &InsertId[string]{id: m.ID, fieldName: "module_id", tableName: "tags"} - InsertSimpleSliceParallel(group, tx, m.Tags, tagsRelId) - - 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 - } - - setupID := sql.NullInt64{Int64: int64(data.id), Valid: strings.EqualFold(data.fieldName, "setup_id")} - - args := []any{setupID, data.id, 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 { - return err - } - - rowsAffected, err := res.RowsAffected() - if rowsAffected != 0 { - err = m.InsertObjects(tx) - if err != nil { - return err - } - } - - dataCopy := *data - m.ConnectGameQuery(&dataCopy) - - return m.ConnectGame(tx, &dataCopy) -} - -func (m *Module) ConnectGameCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { - if data.query == "" { - return ErrNoQuery - } - - 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 - } - - setupID := sql.NullInt64{Int64: int64(data.id), Valid: strings.EqualFold(data.fieldName, "setup_id")} - - args := []any{setupID, data.id, m.ID, m.Title, m.Description, m.URL, m.License, m.Readme, m.Bugs, - m.Changelog, m.Version, m.Manifest, m.Download, m.Socket, m.Protected, m.Exclusive, m.PersistentStorage, - m.CoreTranslation, m.Library, m.Locked, m.Owned, m.HasStorage, m.Active, m.Availability} - - res, err := tx.ExecContext(ctx, data.query, args...) - if err != nil { - return err - } - - rowsAffected, err := res.RowsAffected() - if rowsAffected != 0 { - err = m.InsertObjects(tx) - if err != nil { - return err - } - } - - dataCopy := *data - m.ConnectGameQuery(&dataCopy) - - return m.ConnectGameCtx(ctx, tx, &dataCopy) -} diff --git a/internal/foundry/temp_models/db/system.go b/internal/foundry/temp_models/db/system.go deleted file mode 100644 index 89f45b3..0000000 --- a/internal/foundry/temp_models/db/system.go +++ /dev/null @@ -1,177 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "errors" - "strings" - - "github.com/jmoiron/sqlx" - "golang.org/x/sync/errgroup" -) - -type System struct { - ID string - - Title string - Description string - URL string - License string - Bugs string - 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 -} - -func (s *System) Query(data *InsertId[string]) { - data.query = ` - INSERT INTO system (setup_id, id, title, description, url, license, bugs, changelog, version, manifest, - download, background, primary_token_attribute, availability, socket, protected, exclusive_, - persistent_storage, locked, owned, has_storage) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) - ON CONFLICT(id) DO NOTHING` -} - -func (s *System) ConnectGameQuery(data *InsertId[string]) { - data.query = ` - INSERT INTO game_to_systems (game_id, system_id) - VALUES ($1, $2)` -} - -func (s *System) InsertObjects(tx *sqlx.Tx) error { - relId := InsertId[string]{id: s.ID, fieldName: "system_id"} - group, ctx := errgroup.WithContext(context.Background()) - - 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) - - esModulesRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "es_modules"} - InsertSimpleSliceParallel(group, tx, s.Esmodules, esModulesRelId) - scriptRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "scripts"} - InsertSimpleSliceParallel(group, tx, s.Scripts, scriptRelId) - tagsRelId := &InsertId[string]{id: s.ID, fieldName: "system_id", tableName: "tags"} - InsertSimpleSliceParallel(group, tx, s.Tags, tagsRelId) - - InsertSliceParallel(group, tx, s.Authors, relId) - InsertSliceParallel(group, tx, s.Media, relId) - InsertSliceParallel(group, tx, s.Styles, relId) - InsertSliceParallel(group, tx, s.Languages, relId) - InsertSliceParallel(group, tx, s.Packs, relId) - InsertSliceParallel(group, tx, s.PackFolders, relId) - - err := group.Wait() - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return err - } - return nil -} - -func (s *System) ConnectGame(tx *sqlx.Tx, data *InsertId[string]) error { - if data.query == "" { - return ErrNoQuery - } - - args := []any{data.id, s.ID} - - _, err := tx.Exec(data.query, args...) - return err -} - -func (s *System) Insert(tx *sqlx.Tx, data *InsertId[string]) error { - if data.query == "" { - return ErrNoQuery - } - - setupID := sql.NullString{String: data.id, Valid: strings.EqualFold(data.fieldName, "setup_id")} - - args := []any{setupID, 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 { - return err - } - - rowsAffected, err := res.RowsAffected() - if rowsAffected != 0 { - err = s.InsertObjects(tx) - if err != nil { - return err - } - } - - dataCopy := *data - s.ConnectGameQuery(&dataCopy) - - return s.ConnectGame(tx, &dataCopy) -} - -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 - } - - setupID := sql.NullString{String: data.id, Valid: strings.EqualFold(data.fieldName, "setup_id")} - - args := []any{setupID, 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 { - return err - } - } - - dataCopy := *data - s.ConnectGameQuery(&dataCopy) - - return s.ConnectGameCtx(ctx, tx, &dataCopy) -} diff --git a/internal/foundry/temp_models/db/user.go b/internal/foundry/temp_models/db/user.go deleted file mode 100644 index ae735ce..0000000 --- a/internal/foundry/temp_models/db/user.go +++ /dev/null @@ -1,116 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "errors" - - "github.com/jmoiron/sqlx" - "golang.org/x/sync/errgroup" -) - -type User struct { - ID string - - Name string - Avatar string - Character string - Color string - Pronouns string - Role int - Stats Stats - Hotbar []UserHotbar -} - -func (u *User) Query(data *InsertId[uint]) { - data.query = ` - INSERT INTO user (game_id, id, name, avatar, character, color, pronouns, role) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` -} - -func (u *User) InsertObjects(tx *sqlx.Tx) error { - group, ctx := errgroup.WithContext(context.Background()) - - relId := InsertId[string]{id: u.ID, fieldName: "user_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 (u *User) Insert(tx *sqlx.Tx, data *InsertId[uint]) error { - if data.query == "" { - return ErrNoQuery - } - - args := []any{data.id, u.ID, u.Name, u.Avatar, u.Character, u.Color, u.Pronouns, u.Role} - - _, err := tx.Exec(data.query, args...) - if err != nil { - return err - } - - return nil -} - -func (u *User) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error { - if data.query == "" { - return ErrNoQuery - } - - args := []any{data.id, u.ID, u.Name, u.Avatar, u.Character, u.Color, u.Pronouns, u.Role} - - _, err := tx.ExecContext(ctx, data.query, args...) - if err != nil { - return err - } - - 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, id, key_, value) - VALUES ($1, $2, $3, $4)` -} - -func (u UserHotbar) Insert(tx *sqlx.Tx, data *InsertId[string]) error { - if data.query == "" { - return ErrNoQuery - } - - args := []any{data.id, u.ID, u.Key, u.Value} - - _, err := tx.Exec(data.query, args...) - if err != nil { - return err - } - - return nil -} - -func (u UserHotbar) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error { - if data.query == "" { - return ErrNoQuery - } - - args := []any{data.id, u.ID, u.Key, u.Value} - - _, err := tx.ExecContext(ctx, data.query, args...) - if err != nil { - return err - } - - return nil -} diff --git a/internal/foundry/temp_models/db/world.go b/internal/foundry/temp_models/db/world.go deleted file mode 100644 index 1d7c9f2..0000000 --- a/internal/foundry/temp_models/db/world.go +++ /dev/null @@ -1,130 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "errors" - "fmt" - "time" - - "github.com/jmoiron/sqlx" - "golang.org/x/sync/errgroup" -) - -type World struct { - ID string - - Title string - Description string - Version string - System string - Background string - JoinTheme string - CoreVersion string - SystemVersion string - LastPlayed string - 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 -} - -func (w *World) Query(data *InsertId[string]) { - data.query = fmt.Sprintf(` - INSERT INTO world (%[1]s, id, title, description, version, system, background, join_theme, - core_version, system_version, last_played, playtime, availability, next_session, socket, - protected, exclusive_, persistent_storage, locked, owned, has_storage) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) - ON CONFLICT(id) DO UPDATE SET - %[1]s = EXCLUDED.%[1]s, - updated_at = datetime('now') - RETURNING (created_at == updated_at) AS was_inserted`, - data.fieldName) -} - -func (w *World) InsertObjects(tx *sqlx.Tx) error { - relId := InsertId[string]{id: w.ID, fieldName: "world_id"} - group, ctx := errgroup.WithContext(context.Background()) - - InsertWithCtxParallel(group, ctx, tx, w.Compatibility, relId) - InsertWithCtxParallel(group, ctx, tx, w.Relationships, relId) - - esModulesRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "es_modules"} - InsertSimpleSliceParallel(group, tx, w.Esmodules, esModulesRelId) - scriptRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "scripts"} - InsertSimpleSliceParallel(group, tx, w.Scripts, scriptRelId) - tagsRelId := &InsertId[string]{id: w.ID, fieldName: "world_id", tableName: "tags"} - InsertSimpleSliceParallel(group, tx, w.Tags, tagsRelId) - - InsertSliceParallel(group, tx, w.Authors, relId) - InsertSliceParallel(group, tx, w.Media, relId) - InsertSliceParallel(group, tx, w.Styles, relId) - InsertSliceParallel(group, tx, w.Languages, relId) - InsertSliceParallel(group, tx, w.Packs, relId) - InsertSliceParallel(group, tx, w.PackFolders, relId) - - err := group.Wait() - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return err - } - return nil -} - -func (w *World) Insert(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.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 -} diff --git a/internal/foundry/temp_models/json/language.go b/internal/foundry/temp_models/json/language.go deleted file mode 100644 index db6498e..0000000 --- a/internal/foundry/temp_models/json/language.go +++ /dev/null @@ -1,67 +0,0 @@ -package json - -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" - -type Language struct { - Lang string `json:"lang"` - Name string `json:"name"` - Path string `json:"path"` - // SystemLanguagesFlags SystemLanguagesFlags `json:"flags"` -} - -func (l *Language) ToDB(dest **db.Language) bool { - if dest == nil { - return false - } - - lang := &db.Language{ - Lang: l.Lang, - Name: l.Name, - Path: l.Path, - } - - *dest = lang - - return true -} - -type SetupLanguage struct { - ID string `json:"id"` - Label string `json:"label"` - Modules []*SetupLanguageModule `json:"modules"` -} - -func (s *SetupLanguage) ToDB(dest **db.SetupLanguage) bool { - if dest == nil { - return false - } - - lang := &db.SetupLanguage{ - ID: s.ID, - Label: s.Label, - } - - CopySliceToDB(&lang.Modules, s.Modules) - - *dest = lang - - return true -} - -type SetupLanguageModule struct { - ID string `json:"id"` - Label string `json:"label"` - Path string `json:"path"` -} - -func (s *SetupLanguageModule) ToDB(dest *db.SetupLanguageModule) bool { - if dest == nil { - return false - } - - dest.ID = s.ID - dest.Label = s.Label - dest.Path = s.Path - - return true -} diff --git a/internal/foundry/temp_models/json/release.go b/internal/foundry/temp_models/json/release.go deleted file mode 100644 index 0edc779..0000000 --- a/internal/foundry/temp_models/json/release.go +++ /dev/null @@ -1,32 +0,0 @@ -package json - -import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" - -type Release struct { - Generation int `json:"generation"` - Channel string `json:"channel"` - Suffix string `json:"suffix"` - Build int `json:"build"` - NodeVersion int `json:"node_version"` - MaxGeneration int `json:"maxGeneration"` - 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 -} diff --git a/internal/foundry/transport/db.go b/internal/foundry/transport/db.go index 80e6d96..7514a37 100644 --- a/internal/foundry/transport/db.go +++ b/internal/foundry/transport/db.go @@ -1,37 +1,39 @@ package transport import ( + "slices" + "time" + + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/json" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db" - "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/json" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" ) func (tr *FoundryTransport) FillDBWithFoundryData() { - err := tr.InitSetupData() + now := time.Now() + + setupExist, err := db.HasSetup(tr.DB) if err != nil { tr.ReadChan.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.DbCode} return } - // idState, err := tr.Models.FoundryState.GetIdByType(db.SetupState) - // if err != nil { - // tr.ReadChan.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.DbCode} - // return - // } + if !setupExist { + err := tr.InsertSetupToDB() + if err != nil { + tr.ReadChan.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.DbCode} + return + } + } err = tr.InitWorldsData() if err != nil { tr.ReadChan.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.DbCode} return } -} -func (tr *FoundryTransport) InitSetupData() error { - db.DeleteAll(tr.DB) - db.DeleteAllSeq(tr.DB) - - return tr.InsertSetupToDB() + tr.Logger.Info("Initialization has been successful", "elapsed_time", time.Since(now).String()) } func (tr *FoundryTransport) InsertSetupToDB() error { @@ -57,11 +59,23 @@ func (tr *FoundryTransport) InsertSetupToDB() error { } func (tr *FoundryTransport) InitWorldsData() error { - worlds := []string{"kingmaker"} //TODO: get world name from database + worldNames, err := db.GetNotStartedWorlds(tr.DB) + if err != nil { + return err + } + + for i := range tr.InitWorlds { + initWorld := tr.InitWorlds[i] + if !slices.Contains(worldNames, initWorld) { + tr.Logger.Warn("World name does not exist in database or have been already inserted. Skip!", "world_name", initWorld) + continue + } + + err = tr.InitWorldData(initWorld) + if err != nil { + return err + } - for i := range worlds { - worldName := worlds[i] - tr.InitWorldData(worldName) _, err := tr.Http.PostReturnToSetup() if err != nil { return err @@ -71,34 +85,48 @@ func (tr *FoundryTransport) InitWorldsData() error { } func (tr *FoundryTransport) InitWorldData(worldName string) error { - tr.Logger.Debug("World name", "name", worldName) + tr.Logger.Debug("Launching world...", "world_name", worldName) err := tr.LaunchWorld(worldName) if err != nil { return err } + + err = tr.ConnectToWorld() + if err != nil { + return err + } return tr.InsertGameToDB() } func (tr *FoundryTransport) InsertGameToDB() error { - err := tr.ConnectToWorld() - if err != nil { - return err - } - msgJson, err := tr.GetJsonData("world") if err != nil { return err } - tr.Logger.Info("Game data received") - - gameJson, err := json.ParseGame(msgJson) + game, err := json.ParseGame(msgJson) if err != nil { return err } - var foundryStateDb db.Game - gameJson.ToDB(&foundryStateDb) + var gameDb db.Game + game.ToDB(&gameDb) - return foundryStateDb.Insert(tr.DB) + isInserted, err := db.IsWorldInserted(tr.DB, game.World.ID) + if err != nil { + return err + } + if isInserted { + return nil + } + + tr.Logger.Info("Game data received", "world_name", game.World.ID) + + err = gameDb.Insert(tr.DB) + if err != nil { + return err + } + + tr.Logger.Info("Game data inserted to DB", "world_name", game.World.ID) + return nil } diff --git a/internal/foundry/transport/http.go b/internal/foundry/transport/http.go index ff28483..5d0e5ea 100644 --- a/internal/foundry/transport/http.go +++ b/internal/foundry/transport/http.go @@ -24,7 +24,7 @@ func (tr *FoundryTransport) LaunchWorld(worldName string) error { select { case <-tr.ExchangeChan.ProgressMsg[worldName]: - tr.Logger.Debug("World has been started(from GetInitialData)", "world", worldName) + tr.Logger.Debug("World has been launched(from GetInitialData)", "world", worldName) case <-ctx.Done(): close(tr.ExchangeChan.ProgressMsg[worldName]) err := ctx.Err() diff --git a/internal/foundry/transport/transport.go b/internal/foundry/transport/transport.go index ac2126a..00068b3 100644 --- a/internal/foundry/transport/transport.go +++ b/internal/foundry/transport/transport.go @@ -27,13 +27,21 @@ type FoundryTransport struct { IsLogin bool // Models *db.Models - DB *sqlx.DB - IsDbInit bool + DB *sqlx.DB + IsDbInit bool + InitWorlds []string Logger *slog.Logger } -func NewFoundryTransport(dbConn *sqlx.DB, logger *slog.Logger, httpConfig *requests.FoundryHttpRequest) *FoundryTransport { +type FoundryTransportData struct { + DbConn *sqlx.DB + Logger *slog.Logger + HttpConfig *requests.FoundryHttpRequest + Worlds []string +} + +func NewFoundryTransport(data *FoundryTransportData) *FoundryTransport { return &FoundryTransport{ CurrWsId: 0, ExchangeChan: types.ExchangeChannels{ @@ -41,17 +49,18 @@ func NewFoundryTransport(dbConn *sqlx.DB, logger *slog.Logger, httpConfig *reque ProgressMsg: map[string]chan struct{}{}, }, - Http: httpConfig, + Http: data.HttpConfig, ReconnectTimeout: 500 * time.Millisecond, IsAuth: false, IsLogin: false, ReconnectNumMax: 1, ReconnectNum: 0, - DB: dbConn, - IsDbInit: false, + DB: data.DbConn, + IsDbInit: false, + InitWorlds: data.Worlds, - Logger: logger, + Logger: data.Logger, } } diff --git a/internal/foundry/transport/websocket.go b/internal/foundry/transport/websocket.go index 5fb5128..c1950aa 100644 --- a/internal/foundry/transport/websocket.go +++ b/internal/foundry/transport/websocket.go @@ -32,16 +32,11 @@ func (tr *FoundryTransport) InitWebSocketConnection() error { dialer.ReadBufferSize = 128 * 1024 * 1024 dialer.WriteBufferSize = 128 * 1024 * 1024 - wsConn, resp, err := dialer.Dial(u.String(), wsHeader) + wsConn, _, err := dialer.Dial(u.String(), wsHeader) if err != nil { return err } - cookies := resp.Cookies() - for i := range cookies { - fmt.Printf("TEST%d: %v\n", i, cookies[i]) - } - tr.WsConn = wsConn tr.CurrWsId = 0 tr.Logger.Info("Successefully connected to Foundry Websocket")