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

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

View File

@@ -1,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
}

View File

@@ -8,10 +8,11 @@ import (
"time"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/actions"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests"
json_models "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/json"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
json_models "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/json"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/transport"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types"
"golang.org/x/sync/errgroup"
)
var (
@@ -24,7 +25,7 @@ var (
type FoundryApi struct {
//TODO: make check of admin's authentication
transport *transport.FoundryTransport
Transport *transport.FoundryTransport
IsAvailable bool
// Logger *slog.Logger
@@ -37,16 +38,16 @@ func NewFoundry() *FoundryApi {
}
func (foundry *FoundryApi) SetTransport(tr *transport.FoundryTransport) {
foundry.transport = tr
foundry.Transport = tr
}
func (foundry *FoundryApi) ListenAndServeWS() error {
var err error
foundry.transport.ReadChan = *types.InitWsChannels()
defer foundry.transport.ReadChan.Close()
foundry.Transport.ReadChan = *types.InitWsChannels()
defer foundry.Transport.ReadChan.Close()
foundry.background(foundry.transport.ListenWebSocket)
foundry.background(foundry.Transport.ListenWebSocket)
err = foundry.ServeWebSocket()
if err != nil {
@@ -57,21 +58,21 @@ func (foundry *FoundryApi) ListenAndServeWS() error {
func (foundry *FoundryApi) ServeWebSocket() error {
var err error
wsChannels := &foundry.transport.ReadChan
wsChannels := &foundry.Transport.ReadChan
for {
select {
case message, ok := <-wsChannels.Msg():
if !ok {
foundry.transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode)
foundry.transport.Http.SessionID = nil
foundry.Transport.Logger.Debug("Read channel is closed", "type", types.WebSocketCode)
foundry.Transport.Http.SessionID = nil
return ChannelIsClosed
}
foundry.transport.Logger.Debug("Data has been received\n", "msg", message.Code, "type", types.WebSocketCode) //, "msg", message.MsgJson)
foundry.Transport.Logger.Debug("Data has been received\n", "msg", message.Code, "type", types.WebSocketCode) //, "msg", message.MsgJson)
switch message.Code {
case types.RespPingCode, types.RespSessionDataCode:
err := foundry.transport.SendOnlyCodeRequest(message.Code)
err := foundry.Transport.SendOnlyCodeRequest(message.Code)
if err != nil {
wsChannels.Err() <- &types.FoundryError{Direction: types.WriterCode, Err: err, Type: types.WebSocketCode}
continue
@@ -84,69 +85,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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,10 +90,13 @@ type WorldFolder struct {
}
func (w *WorldFolder) Query(data *InsertId[uint]) {
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
}

View File

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

View File

@@ -68,24 +68,31 @@ func (g *Game) InsertObjects(tx *sqlx.Tx) error {
InsertSliceParallel(group, tx, g.Modules, relData)
InsertSliceParallel(group, tx, g.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
}

View File

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

View File

@@ -24,9 +24,12 @@ type Item struct {
func (i *Item) Query(data *InsertId[string]) {
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
}

View File

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

View File

@@ -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 {

View File

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

View File

@@ -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 {

View File

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

View File

@@ -4,287 +4,185 @@ import (
"context"
"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
}

View File

@@ -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 {

View File

@@ -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 {

View File

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

View File

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

View File

@@ -4,201 +4,186 @@ import (
"context"
"database/sql"
"errors"
"time"
"strings"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type System struct {
Id int64
TextId string
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
}

View File

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

View File

@@ -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 {

View File

@@ -4,293 +4,125 @@ import (
"context"
"database/sql"
"errors"
"time"
"github.com/jmoiron/sqlx"
"golang.org/x/sync/errgroup"
)
type User struct {
Id int64
ID string
Name string
Role int
Avatar string
Character string
Color string
Pronouns string
CreatedAt time.Time
Hotbar map[string]string
Stats UserStats
Role int
Stats Stats
Hotbar []UserHotbar
}
type UserStats struct {
Id int64
CoreVersion string
SystemId string
SystemVersion string
CreatedTime int64
ModifiedTime int64
LastModifiedBy string
func (u *User) Query(data *InsertId[uint]) {
data.query = `
INSERT INTO user (game_id, id, name, avatar, character, color, pronouns, role)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT(id) DO UPDATE SET
game_id = EXCLUDED.game_id,
updated_at = datetime('now')
RETURNING (created_at == updated_at) AS is_inserted`
}
type Users []User
func (u *User) InsertObjects(tx *sqlx.Tx) error {
group, 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
}

View File

@@ -34,7 +34,7 @@ type Insertable[T AllowedIds] interface {
}
func InsertWithCtx[T AllowedIds, I Insertable[T]](tx *sqlx.Tx, data I, relId InsertId[T]) error {
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")
}
}
}

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

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Actor struct {
PrototypeToken Token `json:"prototypeToken"`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Journal struct {
Folder any `json:"folder"`

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Light struct {
Alpha float64 `json:"alpha"`

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package json
import (
"sync"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type Module struct {

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package json
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"`

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package json
import (
"sync"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type Pack struct {

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package json
import (
"sync"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type Relationships struct {

View File

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

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Ring struct {
Enabled bool `json:"enabled"`

View File

@@ -3,7 +3,7 @@ package json
import (
"sync"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type Scene struct {

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package json
import (
"sync"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
"gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
)
type System struct {

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type Token struct {
DisplayName int `json:"displayName"`

View File

@@ -1,6 +1,6 @@
package json
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/temp_models/db"
import "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db"
type CoreUpdate struct {
HasUpdate bool `json:"hasUpdate"`

View File

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

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