add setup insert methods

This commit is contained in:
lbenedar
2026-04-16 18:30:00 +03:00
parent 69d3d38ab7
commit 1b7c7e9ae3
23 changed files with 818 additions and 144 deletions

View File

@@ -2,7 +2,6 @@ package main
import (
"context"
"database/sql"
"flag"
"log"
"log/slog"
@@ -14,6 +13,7 @@ import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
)
@@ -47,8 +47,8 @@ type application struct {
var version = "0.1.0"
func openDB(cfg config) (*sql.DB, error) {
db, err := sql.Open("sqlite3", cfg.db.dsn)
func openDB(cfg config) (*sqlx.DB, error) {
db, err := sqlx.Open("sqlite3", cfg.db.dsn)
if err != nil {
return nil, err
}

View File

@@ -690,18 +690,18 @@ CREATE TABLE IF NOT EXISTS package_warnings_data (
);
CREATE TABLE IF NOT EXISTS package_warnings_data_warning (
id TEXT PRIMARY KEY,
id INTEGER PRIMARY KEY,
warning VARCHAR(128) NOT NULL,
value VARCHAR(128) NOT NULL,
package_warnings_data_id TEXT,
FOREIGN KEY (package_warnings_data_id) REFERENCES package_warnings_data(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS package_warnings_data_error (
id TEXT PRIMARY KEY,
id INTEGER PRIMARY KEY,
error VARCHAR(128) NOT NULL,
value VARCHAR(128) NOT NULL,
package_warnings_data_id TEXT,
FOREIGN KEY (package_warnings_data_id) REFERENCES package_warnings_data(id) ON DELETE CASCADE
@@ -1175,7 +1175,7 @@ CREATE TABLE IF NOT EXISTS files_storage (
);
CREATE TABLE IF NOT EXISTS setup_language (
id TEXT PRIMARY KEY,
id INTEGER PRIMARY KEY,
label VARCHAR(128) NOT NULL,
@@ -1189,7 +1189,7 @@ CREATE TABLE IF NOT EXISTS setup_language_module (
label VARCHAR(128) NOT NULL,
path VARCHAR(128) NOT NULL,
setup_language_id TEXT,
setup_language_id INTEGER,
FOREIGN KEY (setup_language_id) REFERENCES setup_language(id) ON DELETE CASCADE
);

1
go.mod
View File

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

6
go.sum
View File

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

View File

@@ -5,6 +5,8 @@ import (
"database/sql"
"errors"
"time"
"github.com/jmoiron/sqlx"
)
type StateType int
@@ -44,7 +46,7 @@ type Options struct {
}
type FoundryStateModel struct {
DB *sql.DB
DB *sqlx.DB
}
func (m FoundryStateModel) Insert(state *FoundryState) error {

View File

@@ -1,6 +1,6 @@
package db
import "database/sql"
import "github.com/jmoiron/sqlx"
// var (
// ErrRecordNotFound = errors.New("record not found")
@@ -21,7 +21,7 @@ type Models struct {
// }
// }
func NewModels(db *sql.DB) *Models {
func NewModels(db *sqlx.DB) *Models {
return &Models{
FoundryState: FoundryStateModel{DB: db},
}

View File

@@ -1,7 +1,99 @@
package db
import (
"context"
"fmt"
"sync"
"github.com/jmoiron/sqlx"
)
type Files struct {
ID uint
Storages []string
Storages []FilesStorage
}
func (f *Files) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
query := fmt.Sprintf(`
INSERT INTO files (%s)
VALUES ($1)
RETURNING id`, data.fieldName)
args := []any{data.id}
err := tx.QueryRowx(query, args...).Scan(&f.ID)
if err != nil {
return err
}
syncDB := SyncDbOperations{
wg: sync.WaitGroup{},
errChan: make(chan error),
}
InsertSliceParallel[uint](&syncDB, tx, f.Storages, &InsertId[uint]{id: f.ID})
return syncDB.Wait()
}
func (f *Files) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
query := fmt.Sprintf(`
INSERT INTO files (%s)
VALUES ($1)
RETURNING id`, data.fieldName)
args := []any{data.id}
err := tx.QueryRowxContext(ctx, query, args...).Scan(&f.ID)
if err != nil {
return err
}
syncDB := SyncDbOperations{
wg: sync.WaitGroup{},
errChan: make(chan error),
}
InsertSliceParallel(&syncDB, tx, f.Storages, &InsertId[uint]{id: f.ID})
return syncDB.Wait()
}
type FilesStorage struct {
ID uint
Storage string
}
func (f FilesStorage) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
query := `
INSERT INTO files (files_id, storage)
VALUES ($1, $2)
RETURNING id`
args := []any{data.id, f.Storage}
err := tx.QueryRowx(query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
func (f FilesStorage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
const query = `
INSERT INTO featured_content (files_id, storage)
VALUES ($1, $2)
RETURNING id`
args := []any{data.id, f.Storage}
err := tx.QueryRowxContext(ctx, query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}

View File

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

View File

@@ -11,13 +11,13 @@ type Game struct {
Files Files
Options GameOptions
Release Release
World World
System System
World *World
System *System
CoreUpdate CoreUpdate
SystemUpdate SystemUpdate
ActiveUsers []string
Modules []Module
PackageWarnings []PackageWarning
Modules []*Module
PackageWarnings []*PackageWarning
Packs []Pack
Messages []Message
Combats []Combat

View File

@@ -1,5 +1,11 @@
package db
import (
"context"
"github.com/jmoiron/sqlx"
)
type Language struct {
ID uint
@@ -15,9 +21,77 @@ type SetupLanguage struct {
Modules []SetupLanguageModule
}
func (l SetupLanguage) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
const query = `
INSERT INTO setup_language (setup_id, label)
VALUES ($1, $2)
RETURNING id`
args := []any{data.id, l.Label}
err := tx.QueryRowx(query, args...).Scan(&l.ID)
if err != nil {
return err
}
InsertSlice(tx, l.Modules, &InsertId[string]{id: l.ID})
return nil
}
func (l SetupLanguage) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
const query = `
INSERT INTO setup_language (setup_id, label)
VALUES ($1, $2)
RETURNING id`
args := []any{data.id, l.Label}
err := tx.QueryRowxContext(ctx, query, args...).Scan(&l.ID)
if err != nil {
return err
}
InsertSlice(tx, l.Modules, &InsertId[string]{id: l.ID})
return nil
}
type SetupLanguageModule struct {
ID string
Label string
Path string
}
func (l SetupLanguageModule) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
const query = `
INSERT INTO setup_language_module (setup_language_id, label, path)
VALUES ($1, $2, $3)
RETURNING id`
args := []any{data.id, l.Label, l.Path}
err := tx.QueryRowx(query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}
func (l SetupLanguageModule) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
const query = `
INSERT INTO setup_language_module (setup_language_id, label, path)
VALUES ($1, $2, $3)
RETURNING id`
args := []any{data.id, l.Label, l.Path}
err := tx.QueryRowxContext(ctx, query, args...).Scan(&l.ID)
if err != nil {
return err
}
return nil
}

View File

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

View File

@@ -1,5 +1,12 @@
package db
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
)
type PackageWarningsData struct {
ID string
@@ -10,9 +17,89 @@ type PackageWarningsData struct {
Manifest string
}
func (p *PackageWarningsData) InsertSliceType(tx *sqlx.Tx, data *InsertId[string], sliceType string) error {
query := fmt.Sprintf(`
INSERT INTO package_warnings_data (%s, id, type, reinstallable, manifest)
VALUES ($1, $2, $3, $4, $5)`, data.fieldName)
args := []any{data.id, p.ID, p.Type, p.Reinstallable, p.Manifest}
_, err := tx.Exec(query, args...)
if err != nil {
return err
}
return nil
}
func (p *PackageWarningsData) Insert(tx *sqlx.Tx, data *InsertId[string]) error {
query := fmt.Sprintf(`
INSERT INTO package_warnings_data (%s, id, type, reinstallable, manifest)
VALUES ($1, $2, $3, $4, $5)`, data.fieldName)
args := []any{data.id, p.ID, p.Type, p.Reinstallable, p.Manifest}
_, err := tx.Exec(query, args...)
if err != nil {
return err
}
return nil
}
func (p *PackageWarningsData) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[string]) error {
query := fmt.Sprintf(`
INSERT INTO package_warnings_data (%s, id, type, reinstallable, manifest)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`, data.fieldName)
args := []any{data.id, p.ID, p.Type, p.Reinstallable, p.Manifest}
_, err := tx.ExecContext(ctx, query, args...)
if err != nil {
return err
}
return nil
}
type PackageWarning struct {
ID string
Key string
Value PackageWarningsData
}
func (p *PackageWarning) Insert(tx *sqlx.Tx, data *InsertId[uint]) error {
query := fmt.Sprintf(`
INSERT INTO package_warnings (%s, id)
VALUES ($1, $2)`, data.fieldName)
args := []any{data.id, p.ID}
_, err := tx.Execx(query, args...)
if err != nil {
return err
}
InsertWithCtx(tx, &p.Value, &InsertId[string]{id: p.ID})
return nil
}
func (p *PackageWarning) InsertCtx(ctx context.Context, tx *sqlx.Tx, data *InsertId[uint]) error {
query := fmt.Sprintf(`
INSERT INTO package_warnings (%s, id)
VALUES ($1, $2)`, data.fieldName)
args := []any{data.id, p.ID}
_, err := tx.Exec(query, args...)
if err != nil {
return err
}
InsertWithCtx(tx, &p.Value, &InsertId[string]{id: p.ID})
return nil
}

View File

@@ -3,7 +3,10 @@ package db
type Relationships struct {
ID uint
Data []RelationshipsData
Systems []RelationshipsData
Requires []RelationshipsData
Recommends []RelationshipsData
Conflicts []RelationshipsData
}
type RelationshipsData struct {

View File

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

View File

@@ -1,21 +1,72 @@
package db
import (
"context"
"sync"
"time"
"github.com/jmoiron/sqlx"
)
type Setup struct {
ID uint
CreatedAt time.Time
IsAdmin bool
IsSetup bool
CoreUpdate CoreUpdate
FeaturedContent FeaturedContent
Files Files
Options SetupOptions
Release Release
Languages []SetupLanguage
Modules []Module
News []News
PackageWarnings []PackageWarning
Systems []System
Worlds []World
CoreUpdate CoreUpdate //+
FeaturedContent FeaturedContent //+
Files Files //+
Options *SetupOptions //+
Release Release //+
Languages []SetupLanguage //+
Modules []*Module
News []News //+
PackageWarnings []*PackageWarning
Systems []*System
Worlds []*World
}
func (s *Setup) Insert(db *sqlx.DB) error {
tx := db.MustBegin()
defer tx.Rollback()
const query = `
INSERT INTO setup (is_admin, is_setup)
VALUES ($1, $2)
RETURNING id, created_at`
args := []any{s.IsAdmin, s.IsSetup}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := tx.QueryRowContext(ctx, query, args...).Scan(&s.ID, &s.CreatedAt)
if err != nil {
return err
}
id := InsertId[uint]{id: s.ID, fieldName: "setup_id"}
dataSync := SyncDbOperations{
wg: sync.WaitGroup{},
errChan: make(chan error),
}
defer close(dataSync.errChan)
go InsertSliceParallel(&dataSync, tx, s.News, &id)
go InsertSliceParallel(&dataSync, tx, s.Languages, &id)
InsertWithCtx(tx, &s.FeaturedContent, &id)
InsertWithCtx(tx, &s.Files, &id)
InsertWithCtx(tx, &s.Release, &id)
InsertWithCtx(tx, &s.CoreUpdate, &id)
err = dataSync.Wait()
if err != nil {
return err
}
return tx.Commit()
}
type FeaturedContent struct {
@@ -27,6 +78,38 @@ type FeaturedContent struct {
Image string
}
func (f *FeaturedContent) Insert(tx *sqlx.Tx, setup *InsertId[uint]) error {
const query = `
INSERT INTO featured_content (setup_id, title, caption, url, image)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`
args := []any{setup.id, f.Title, f.Caption, f.URL, f.Image}
err := tx.QueryRowx(query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
func (f *FeaturedContent) InsertCtx(ctx context.Context, tx *sqlx.Tx, setup *InsertId[uint]) error {
const query = `
INSERT INTO featured_content (setup_id, title, caption, url, image)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`
args := []any{setup.id, f.Title, f.Caption, f.URL, f.Image}
err := tx.QueryRowxContext(ctx, query, args...).Scan(&f.ID)
if err != nil {
return err
}
return nil
}
type News struct {
ID uint
@@ -35,3 +118,35 @@ type News struct {
URL string
Image string
}
func (n News) Insert(tx *sqlx.Tx, setup *InsertId[uint]) error {
const insertNews = `
INSERT INTO news (setup_id, title, caption, url, image)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`
args := []any{setup.id, n.Title, n.Caption, n.URL, n.Image}
err := tx.QueryRow(insertNews, args...).Scan(&n.ID)
if err != nil {
return err
}
return nil
}
func (n News) InsertCtx(ctx context.Context, tx *sqlx.Tx, setup *InsertId[uint]) error {
const insertNews = `
INSERT INTO news (setup_id, title, caption, url, image)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`
args := []any{setup.id, n.Title, n.Caption, n.URL, n.Image}
err := tx.QueryRowContext(ctx, insertNews, args...).Scan(&n.ID)
if err != nil {
return err
}
return nil
}

View File

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

View File

@@ -0,0 +1,116 @@
package db
import (
"context"
"fmt"
"sync"
"time"
"github.com/jmoiron/sqlx"
)
type AllowedIds interface {
~uint | ~string
}
type InsertId[T AllowedIds] struct {
id T
fieldName string
tableName string
}
type SyncDbOperations struct {
errChan chan error
wg sync.WaitGroup
}
func (s *SyncDbOperations) Wait() error {
return WaitSync(&s.wg, s.errChan)
}
func WaitSync(wg *sync.WaitGroup, errChan chan error) error {
wgDone := make(chan struct{})
go func() {
wg.Wait()
close(wgDone)
}()
select {
case <-wgDone:
return nil
case err := <-errChan:
close(wgDone)
return err
}
}
type Insertable[T AllowedIds] interface {
Insert(tx *sqlx.Tx, relId *InsertId[T]) error
InsertCtx(ctx context.Context, tx *sqlx.Tx, relId *InsertId[T]) error
}
func InsertWithCtx[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data I, relId *InsertId[T]) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return data.InsertCtx(ctx, tx, relId)
}
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)
defer cancel()
for i := range data {
err := data[i].InsertCtx(ctx, tx, relId)
if err != nil {
return err
}
}
return nil
}
func InsertSliceParallel[T AllowedIds, I Insertable[T]](syncDb *SyncDbOperations, tx *sqlx.Tx, data []I, relId *InsertId[T]) {
syncDb.wg.Add(1)
wg := sync.WaitGroup{}
errChan := make(chan error)
defer close(errChan)
for i := range data {
go func() {
wg.Add(1)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
errChan <- data[i].InsertCtx(ctx, tx, relId)
wg.Done()
}()
}
err := WaitSync(&wg, errChan)
if err != nil {
syncDb.errChan <- err
}
syncDb.wg.Done()
}
func InsertSimpleSliceToTable[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data []I, relId *InsertId[T]) error {
query := fmt.Sprintf(`
INSERT INTO %s (%s, value)
VALUES (:id, :value)`, relId.tableName, relId.fieldName)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
for i := range data {
_, err := tx.ExecContext(ctx, query, relId.id, data[i])
if err != nil {
return err
}
}
return nil
}

View File

@@ -12,7 +12,10 @@ func (f *Files) ToDB(dest *db.Files) bool {
return false
}
copy(dest.Storages, f.Storages)
dest.Storages = make([]db.FilesStorage, len(f.Storages))
for i := range f.Storages {
dest.Storages[i].Storage = f.Storages[i]
}
return true
}

View File

@@ -44,50 +44,54 @@ type Module struct {
// ModulesFlags ModulesFlags `json:"flags,omitempty"`
}
func (m *Module) ToDB(dest *db.Module) bool {
func (m *Module) ToDB(dest **db.Module) bool {
if dest == nil {
return false
}
dest.ID = m.ID
dest.Title = m.Title
dest.Description = m.Description
dest.URL = m.URL
dest.License = m.License
dest.Readme = m.Readme
dest.Bugs = m.Bugs
dest.Changelog = m.Changelog
dest.Version = m.Version
dest.Socket = m.Socket
dest.Manifest = m.Manifest
dest.Download = m.Download
dest.Protected = m.Protected
dest.Exclusive = m.Exclusive
dest.PersistentStorage = m.PersistentStorage
dest.CoreTranslation = m.CoreTranslation
dest.Library = m.Library
dest.Availability = m.Availability
dest.Locked = m.Locked
dest.Owned = m.Owned
dest.HasStorage = m.HasStorage
dest.Active = m.Active
module := &db.Module{
ID: m.ID,
Title: m.Title,
Description: m.Description,
URL: m.URL,
License: m.License,
Readme: m.Readme,
Bugs: m.Bugs,
Changelog: m.Changelog,
Version: m.Version,
Socket: m.Socket,
Manifest: m.Manifest,
Download: m.Download,
Protected: m.Protected,
Exclusive: m.Exclusive,
PersistentStorage: m.PersistentStorage,
CoreTranslation: m.CoreTranslation,
Library: m.Library,
Availability: m.Availability,
Locked: m.Locked,
Owned: m.Owned,
HasStorage: m.HasStorage,
Active: m.Active,
}
copy(dest.Scripts, m.Scripts)
copy(dest.Esmodules, m.Esmodules)
copy(dest.Tags, m.Tags)
copy(module.Scripts, m.Scripts)
copy(module.Esmodules, m.Esmodules)
copy(module.Tags, m.Tags)
m.Compatibility.ToDB(&dest.Compatibility)
m.Relationships.ToDB(&dest.Relationships)
m.DocumentTypes.ToDB(&dest.DocumentTypes)
m.Compatibility.ToDB(&module.Compatibility)
m.Relationships.ToDB(&module.Relationships)
m.DocumentTypes.ToDB(&module.DocumentTypes)
wg := sync.WaitGroup{}
go CopySliceToDBParallel(&wg, &dest.Authors, m.Authors)
go CopySliceToDBParallel(&wg, &dest.Media, m.Media)
go CopySliceToDBParallel(&wg, &dest.Styles, m.Styles)
go CopySliceToDBParallel(&wg, &dest.Languages, m.Languages)
go CopySliceToDBParallel(&wg, &dest.Packs, m.Packs)
go CopySliceToDBParallel(&wg, &dest.PackFolders, m.PackFolders)
go CopySliceToDBParallel(&wg, &module.Authors, m.Authors)
go CopySliceToDBParallel(&wg, &module.Media, m.Media)
go CopySliceToDBParallel(&wg, &module.Styles, m.Styles)
go CopySliceToDBParallel(&wg, &module.Languages, m.Languages)
go CopySliceToDBParallel(&wg, &module.Packs, m.Packs)
go CopySliceToDBParallel(&wg, &module.PackFolders, m.PackFolders)
wg.Wait()
*dest = module
return true
}

View File

@@ -43,49 +43,53 @@ type System struct {
// SystemFlags SystemFlags `json:"flags"`
}
func (s *System) ToDB(dest *db.System) bool {
func (s *System) ToDB(dest **db.System) bool {
if dest == nil {
return false
}
dest.ID = s.ID
dest.Title = s.Title
dest.Description = s.Description
dest.URL = s.URL
dest.License = s.License
dest.Bugs = s.Bugs
dest.Changelog = s.Changelog
dest.Version = s.Version
dest.Socket = s.Socket
dest.Manifest = s.Manifest
dest.Download = s.Download
dest.Protected = s.Protected
dest.Exclusive = s.Exclusive
dest.PersistentStorage = s.PersistentStorage
dest.Background = s.Background
dest.PrimaryTokenAttribute = s.PrimaryTokenAttribute
dest.Availability = s.Availability
dest.Locked = s.Locked
dest.Owned = s.Owned
dest.HasStorage = s.HasStorage
system := &db.System{
ID: s.ID,
Title: s.Title,
Description: s.Description,
URL: s.URL,
License: s.License,
Bugs: s.Bugs,
Changelog: s.Changelog,
Version: s.Version,
Socket: s.Socket,
Manifest: s.Manifest,
Download: s.Download,
Protected: s.Protected,
Exclusive: s.Exclusive,
PersistentStorage: s.PersistentStorage,
Background: s.Background,
PrimaryTokenAttribute: s.PrimaryTokenAttribute,
Availability: s.Availability,
Locked: s.Locked,
Owned: s.Owned,
HasStorage: s.HasStorage,
}
copy(dest.Scripts, s.Scripts)
copy(dest.Esmodules, s.Esmodules)
copy(dest.Tags, s.Tags)
copy(system.Scripts, s.Scripts)
copy(system.Esmodules, s.Esmodules)
copy(system.Tags, s.Tags)
s.Compatibility.ToDB(&dest.Compatibility)
s.Relationships.ToDB(&dest.Relationships)
s.DocumentTypes.ToDB(&dest.DocumentTypes)
s.Grid.ToDB(&dest.Grid)
s.Compatibility.ToDB(&system.Compatibility)
s.Relationships.ToDB(&system.Relationships)
s.DocumentTypes.ToDB(&system.DocumentTypes)
s.Grid.ToDB(&system.Grid)
wg := sync.WaitGroup{}
go CopySliceToDBParallel(&wg, &dest.Authors, s.Authors)
go CopySliceToDBParallel(&wg, &dest.Media, s.Media)
go CopySliceToDBParallel(&wg, &dest.Styles, s.Styles)
go CopySliceToDBParallel(&wg, &dest.Languages, s.Languages)
go CopySliceToDBParallel(&wg, &dest.Packs, s.Packs)
go CopySliceToDBParallel(&wg, &dest.PackFolders, s.PackFolders)
go CopySliceToDBParallel(&wg, &system.Authors, s.Authors)
go CopySliceToDBParallel(&wg, &system.Media, s.Media)
go CopySliceToDBParallel(&wg, &system.Styles, s.Styles)
go CopySliceToDBParallel(&wg, &system.Languages, s.Languages)
go CopySliceToDBParallel(&wg, &system.Packs, s.Packs)
go CopySliceToDBParallel(&wg, &system.PackFolders, s.PackFolders)
wg.Wait()
*dest = system
return true
}

View File

@@ -28,8 +28,8 @@ func HotbarToDB(dest *[]db.UserHotbar, src map[int]string) {
}
}
func PackageWarningsToDB(dest *[]db.PackageWarning, src map[string]PackageWarningsData) {
*dest = make([]db.PackageWarning, len(src))
func PackageWarningsToDB(dest *[]*db.PackageWarning, src map[string]PackageWarningsData) {
*dest = make([]*db.PackageWarning, len(src))
i := 0
for k, v := range src {

View File

@@ -43,48 +43,52 @@ type World struct {
// Flags any `json:"flags"`
}
func (w *World) ToDB(dest *db.World) bool {
func (w *World) ToDB(dest **db.World) bool {
if dest == nil {
return false
}
dest.ID = w.ID
dest.Title = w.Title
dest.Description = w.Description
dest.Version = w.Version
dest.Socket = w.Socket
dest.Protected = w.Protected
dest.Exclusive = w.Exclusive
dest.PersistentStorage = w.PersistentStorage
dest.System = w.System
dest.Background = w.Background
dest.JoinTheme = w.JoinTheme
dest.CoreVersion = w.CoreVersion
dest.SystemVersion = w.SystemVersion
dest.LastPlayed = w.LastPlayed
dest.Playtime = w.Playtime
dest.NextSession = w.NextSession
dest.Availability = w.Availability
dest.Locked = w.Locked
dest.Owned = w.Owned
dest.HasStorage = w.HasStorage
world := &db.World{
ID: w.ID,
Title: w.Title,
Description: w.Description,
Version: w.Version,
Socket: w.Socket,
Protected: w.Protected,
Exclusive: w.Exclusive,
PersistentStorage: w.PersistentStorage,
System: w.System,
Background: w.Background,
JoinTheme: w.JoinTheme,
CoreVersion: w.CoreVersion,
SystemVersion: w.SystemVersion,
LastPlayed: w.LastPlayed,
Playtime: w.Playtime,
NextSession: w.NextSession,
Availability: w.Availability,
Locked: w.Locked,
Owned: w.Owned,
HasStorage: w.HasStorage,
}
copy(dest.Scripts, w.Scripts)
copy(dest.Esmodules, w.Esmodules)
copy(dest.Tags, w.Tags)
copy(world.Scripts, w.Scripts)
copy(world.Esmodules, w.Esmodules)
copy(world.Tags, w.Tags)
w.Compatibility.ToDB(&dest.Compatibility)
w.Relationships.ToDB(&dest.Relationships)
w.Compatibility.ToDB(&world.Compatibility)
w.Relationships.ToDB(&world.Relationships)
wg := sync.WaitGroup{}
go CopySliceToDBParallel(&wg, &dest.Authors, w.Authors)
go CopySliceToDBParallel(&wg, &dest.Media, w.Media)
go CopySliceToDBParallel(&wg, &dest.Styles, w.Styles)
go CopySliceToDBParallel(&wg, &dest.Languages, w.Languages)
go CopySliceToDBParallel(&wg, &dest.Packs, w.Packs)
go CopySliceToDBParallel(&wg, &dest.PackFolders, w.PackFolders)
go CopySliceToDBParallel(&wg, &world.Authors, w.Authors)
go CopySliceToDBParallel(&wg, &world.Media, w.Media)
go CopySliceToDBParallel(&wg, &world.Styles, w.Styles)
go CopySliceToDBParallel(&wg, &world.Languages, w.Languages)
go CopySliceToDBParallel(&wg, &world.Packs, w.Packs)
go CopySliceToDBParallel(&wg, &world.PackFolders, w.PackFolders)
wg.Wait()
*dest = world
return true
}

View File

@@ -1,7 +1,6 @@
package transport
import (
"database/sql"
"log/slog"
"sync"
"time"
@@ -10,6 +9,7 @@ import (
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
"github.com/gorilla/websocket"
"github.com/jmoiron/sqlx"
)
type FoundryTransport struct {
@@ -32,7 +32,7 @@ type FoundryTransport struct {
Logger *slog.Logger
}
func NewFoundryTransport(dbConn *sql.DB, logger *slog.Logger, httpConfig *requests.FoundryHttpRequest) *FoundryTransport {
func NewFoundryTransport(dbConn *sqlx.DB, logger *slog.Logger, httpConfig *requests.FoundryHttpRequest) *FoundryTransport {
return &FoundryTransport{
CurrWsId: 0,
ExchangeChan: types.ExchangeChannels{