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

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

View File

@@ -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
// }