add db models, refactor json models, add references in migrations

This commit is contained in:
lbenedar
2026-04-06 16:40:49 +03:00
parent a16edb9b61
commit 095dac5db7
28 changed files with 709 additions and 259 deletions

View File

@@ -1,44 +0,0 @@
package models
import (
"encoding/json"
)
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 struct {
Language string `json:"language,omitempty"`
} `json:"options,omitempty"`
//coreUpdate struct{}
//featuredContent struct{}
//files struct{}
//news struct{}
//packageWarnings struct{} think about it
}
func (state FoundryState) GetRelease() Release {
return state.Release
}
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,4 +1,4 @@
package models
package db
import "errors"

View File

@@ -0,0 +1,113 @@
package db
import (
"context"
"database/sql"
"time"
)
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 int
IsAdmin bool
IsSetup bool
Type StateType
CreatedAt time.Time
Options Options
Modules Modules
Systems Systems
Worlds Worlds
Users Users
}
type Compatibility struct {
Id int
Minimum string
Verified string
Maximum string
}
type Options struct {
Id int
Language string
}
type FoundryStateModel struct {
DB *sql.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 {
return err
}
for i := range state.Modules {
err = m.InsertModule(&state.Modules[i], state.Id)
if err != nil {
return err
}
}
for i := range state.Systems {
err = m.InsertSystem(&state.Systems[i], state.Id)
if err != nil {
return err
}
}
for i := range state.Worlds {
err = m.InsertWorld(&state.Worlds[i], state.Id)
if err != nil {
return err
}
}
for i := range state.Users {
err = m.InsertUser(&state.Users[i], state.Id)
if err != nil {
return err
}
}
return nil
}
func (m FoundryStateModel) InsertOptions(options *Options, stateId int) 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)
}

View File

@@ -0,0 +1,35 @@
package db
import "database/sql"
// 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 *sql.DB) Models {
return Models{
FoundryState: FoundryStateModel{DB: db},
}
}
// func NewMockModels() Models {
// return Models{
// Movies: MockMovieModel{},
// Users: MockMovieModel{},
// }
// }

View File

@@ -0,0 +1,84 @@
package db
import (
"context"
"time"
)
type Module struct {
Id int
TextId string
Title string
Description string
Url string
Version string
Availability int
CreatedAt time.Time
Languages []Language
Compatibility Compatibility
}
type Language struct {
Id string
Lang string
Name string
Path string
}
type Modules []Module
func (modules Modules) GetById(id int) *Module {
return &modules[id]
}
func (m FoundryStateModel) InsertModule(module *Module, stateId int) 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`
args := []any{stateId, module.TextId, module.Title, module.Description, module.Url, module.Version, module.Availability}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&module.Id, &module.CreatedAt)
if err != nil {
return err
}
for i := range module.Languages {
err = m.InsertModuleLanguages(&module.Languages[i], module.Id)
if err != nil {
return err
}
}
return m.InsertModuleCompatibility(&module.Compatibility, module.Id)
}
func (m FoundryStateModel) InsertModuleCompatibility(compatibility *Compatibility, moduleId int) error {
query := `
INSERT INTO modules_compatibility (model_id, minumum, 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()
return m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id)
}
func (m FoundryStateModel) InsertModuleLanguages(lang *Language, moduleId int) error {
query := `
INSERT INTO modules_compatibility (model_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()
return m.DB.QueryRowContext(ctx, query, args...).Scan(&lang.Id)
}

View File

@@ -0,0 +1,53 @@
package db
import (
"context"
"time"
)
type System struct {
Id int
TextId string
Title string
Description string
Url string
Download string
CreatedAt time.Time
Compatibility Compatibility
}
type Systems []System
func (systems Systems) GetById(id int) *System {
return &systems[id]
}
func (m FoundryStateModel) InsertSystem(system *System, stateId int) error {
query := `
INSERT INTO systems (state_id, text_id, title, description, url, download)
VALUES ($1, $2, $3, $4, $5, $6)`
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)
if err != nil {
return err
}
return m.InsertModuleCompatibility(&system.Compatibility, system.Id)
}
func (m FoundryStateModel) InsertSystenCompatibility(compatibility *Compatibility, systemId int) error {
query := `
INSERT INTO systems_compatibility (system_id, minumum, verified, maximum)
VALUES ($1, $2, $3, $4)`
args := []any{systemId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
return m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id)
}

View File

@@ -0,0 +1,91 @@
package db
import (
"context"
"time"
)
type User struct {
Id int
Name string
Role int
Character string
Color string
Pronouns string
CreatedAt time.Time
Hotbar map[string]string
Stats UserStats
}
type UserStats struct {
Id int
CoreVersion string
SystemId string
SystemVersion string
CreatedTime int64
ModifiedTime int64
LastModifiedBy string
}
type Users []User
func (users Users) GetById(id int) *User {
return &users[id]
}
func (m FoundryStateModel) InsertUser(user *User, stateId int) error {
query := `
INSERT INTO users (state_id, name, role, character, color, pronouns)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, created_at`
args := []any{stateId, user.Name, user.Role, user.Character, user.Color, user.Pronouns}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&user.Id, &user.CreatedAt)
if err != nil {
return err
}
for k, v := range user.Hotbar {
err = m.InsertUserHotbar(k, v, user.Id)
if err != nil {
return err
}
}
return m.InsertUserStats(&user.Stats, user.Id)
}
func (m FoundryStateModel) InsertUserHotbar(key string, value string, userId int) error {
query := `
INSERT INTO users_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 {
return err
}
return nil
}
func (m FoundryStateModel) InsertUserStats(stats *UserStats, userId int) 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)
}

View File

@@ -0,0 +1,81 @@
package db
import (
"context"
"time"
)
type World struct {
Id int
TextId string
Title string
Description string
System string
CoreVersion string
SystemVersion string
LastPlayed string
PlayTime int64
NextSession time.Time
CreatedAt time.Time
Compatibility Compatibility
}
type Worlds []World
func (worlds Worlds) GetById(id int) *World {
return &worlds[id]
}
func (m FoundryStateModel) InsertWorld(world *World, stateId int) 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`
args := []any{stateId, world.TextId, world.Title, world.Description, world.System, world.System, world.CoreVersion, world.SystemVersion, world.PlayTime, world.NextSession}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := m.DB.QueryRowContext(ctx, query, args...).Scan(&world.Id, &world.CreatedAt)
if err != nil {
return err
}
return m.InsertWorldCompatibility(&world.Compatibility, world.Id)
}
func (m FoundryStateModel) InsertWorldCompatibility(compatibility *Compatibility, worldId int) error {
query := `
INSERT INTO worlds_compatibility (world_id, minumum, 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()
return m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id)
}
// 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

@@ -0,0 +1,181 @@
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)
}
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

@@ -0,0 +1,8 @@
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,4 +1,4 @@
package models
package json
type Language struct {
Id string `json:"id"`

View File

@@ -1,4 +1,4 @@
package models
package json
type Release struct {
Generation int `json:"generation"`

View File

@@ -1,4 +1,4 @@
package models
package json
type Status struct {
Active bool `json:"active"`

View File

@@ -1,4 +1,4 @@
package models
package json
import "time"

View File

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

View File

@@ -1,49 +0,0 @@
package models
type Module struct {
Id string
Title string
Description string
Url string
Version string
Compatibility Compatibility
Languages []DataLanguage
Availability int
}
type Modules []Module
func (modules Modules) GetById(id int) *Module {
return &modules[id]
}
func (state *FoundryState) GetModules() Modules {
modulesCopy := make([]Module, 0, 8)
for i := range state.Modules {
module := &(state.Modules[i])
moduleCopy := Module{
Id: module.Id,
Title: module.Title,
Description: module.Description,
Compatibility: Compatibility{
Minimum: module.Compatibility.Minimum,
Maximum: module.Compatibility.Maximum,
},
Url: module.Url,
Version: module.CoreVersion,
Availability: module.Availability,
}
for j := range module.Languages {
lang := DataLanguage{
Lang: module.Languages[j].Lang,
Name: module.Languages[j].Name,
Path: module.Languages[j].Path,
Flags: module.Languages[j].Flags,
}
moduleCopy.Languages = append(moduleCopy.Languages, lang)
}
modulesCopy = append(modulesCopy, moduleCopy)
}
return modulesCopy
}

View File

@@ -1,36 +0,0 @@
package models
type System struct {
Id string
Title string
Description string
Url string
Compatibility Compatibility
Download string
}
type Systems []System
func (systems Systems) GetById(id int) *System {
return &systems[id]
}
func (state *FoundryState) GetSystems() Systems {
systemsCopy := make([]System, 0, 8)
for i := range state.Systems {
system := &(state.Systems[i])
systemCopy := System{
Id: system.Id,
Title: system.Title,
Description: system.Description,
Url: system.Url,
Compatibility: Compatibility{
Minimum: system.Compatibility.Minimum,
Maximum: system.Compatibility.Maximum,
},
Download: system.Download,
}
systemsCopy = append(systemsCopy, systemCopy)
}
return systemsCopy
}

View File

@@ -1,88 +0,0 @@
package models
import (
"time"
)
type World struct {
Id string
Title string
Description string
Compatibility Compatibility
System string
CoreVersion string
SystemVersion string
LastPlayed string
PlayTime int64
NextSession time.Time
}
type Worlds []World
func (worlds Worlds) GetById(id int) *World {
return &worlds[id]
}
func (state *FoundryState) GetWorld() World {
return World{
Id: state.World.Id,
Title: state.World.Title,
Description: state.World.Description,
Compatibility: 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() Worlds {
worldsCopy := make([]World, 0, 8)
for i := range state.Worlds {
world := &(state.Worlds[i])
worldCopy := World{
Id: world.Id,
Title: world.Title,
Description: world.Description,
Compatibility: 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)
}
return worldsCopy
}
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
}