diff --git a/Makefile b/Makefile index 4c73fcf..7bd71fd 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ db/migration/new: db/migrations/up: @echo 'Running up migrations...' - migrate -path ./db/migrations -database ${DB_DSN} up + migrate -path ./db/migrations -database ${DB_DSN_MIGRATE} up help: go run ./cmd/api -help diff --git a/cmd/api/main.go b/cmd/api/main.go index 9632d52..cb0ac94 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -142,6 +142,8 @@ func main() { app.parseFlags() app.foundryApp.SetLogger(app.slogger) + app.slogger.Info("", "dsn", app.cfg.db.dsn) + db, err := openDB(app.cfg) if err != nil { app.slogger.Error("Error when opening database connection", "err", err) diff --git a/internal/foundry/errors.go b/internal/foundry/errors.go index 02585d7..6efd861 100644 --- a/internal/foundry/errors.go +++ b/internal/foundry/errors.go @@ -11,9 +11,10 @@ var ( ErrorNotAuth = errors.New("Admin is not authenticated") ErrorIsNotReady = errors.New("Connection is not ready for communication") - ErrorMsgNotHaveNumber = errors.New("Message doesn't have dataCode and id") - ErrorTimeout = errors.New("Answer has not been received after timeout") - ErrorReadChannel = errors.New("Error when reading from msg channel") + ErrorStateTypeNotExist = errors.New("State type does not exists") + ErrorMsgNotHaveNumber = errors.New("Message doesn't have dataCode and id") + ErrorTimeout = errors.New("Answer has not been received after timeout") + ErrorReadChannel = errors.New("Error when reading from msg channel") ListenIsDone = errors.New("Listen for websocket data in foundry is stopped") ) diff --git a/internal/foundry/foundry.go b/internal/foundry/foundry.go index 69b00d5..46506c4 100644 --- a/internal/foundry/foundry.go +++ b/internal/foundry/foundry.go @@ -11,45 +11,16 @@ import ( "strconv" "strings" - data "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" "github.com/gorilla/websocket" ) -const ( - RespSessionData = "0" - RespPingCode = "2" - RespSessionId = "40" - RespCreateSessionCode = "42" - RespDataCode = "43" - - ReqPongCode = "3" - ReqCreateSessionCode = "40" - ReqDataCode = "42" -) - -var RequestCodes = []string{ - RespSessionData, - RespPingCode, - RespSessionId, - RespCreateSessionCode, - RespDataCode, -} - -var CodesRespToReq = map[string]string{ - RespSessionData: ReqCreateSessionCode, - RespPingCode: ReqPongCode, - RespCreateSessionCode: ReqDataCode, - //ReqDataCode: RespCreateSessionCode, -} - type Foundry struct { //TODO: make check of admin's authentication isAuth bool logger *slog.Logger - models data.Models config requests.Config ws *webSocketUtil } @@ -129,6 +100,7 @@ func (foundry *Foundry) StartListen() error { foundry.CloseWebSocketConn() return foundryErr } + foundry.logger.Warn("Got error when listening or served websocket", "err", err.Error()) } case <-wsChannels.Done(): foundry.CloseWebSocketConn() @@ -195,7 +167,7 @@ func (foundry *Foundry) ListenAndServeWS() { } func (foundry *Foundry) HandleWSRequest(msgType string) ([]byte, error) { - msg := foundry.ws.CreateWSMessageByPage("/setup") + msg := foundry.ws.CreateWSMessageByPage(msgType) return foundry.ws.HandleWebsocketRequest(msg) } @@ -218,7 +190,7 @@ func (foundry *Foundry) SetLogger(slogger *slog.Logger) { } func (foundry *Foundry) CreateNewDataModels(db *sql.DB) { - foundry.models = data.NewModels(db) + foundry.ws.CreateNewDataModels(db) } // /** diff --git a/internal/foundry/models/db/foundry_state.go b/internal/foundry/models/db/foundry_state.go index 2b86761..a345805 100644 --- a/internal/foundry/models/db/foundry_state.go +++ b/internal/foundry/models/db/foundry_state.go @@ -64,34 +64,34 @@ func (m FoundryStateModel) Insert(state *FoundryState) error { } err = m.InsertOptions(&state.Options, state.Id) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } for i := range state.Modules { err = m.InsertModule(&state.Modules[i], state.Id) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } } for i := range state.Systems { err = m.InsertSystem(&state.Systems[i], state.Id) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } } for i := range state.Worlds { err = m.InsertWorld(&state.Worlds[i], state.Id) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } } for i := range state.Users { err = m.InsertUser(&state.Users[i], state.Id) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } } @@ -258,3 +258,49 @@ func (m FoundryStateModel) Delete(id int64) error { return nil } + +func (m FoundryStateModel) DeleteAll() error { + query := ` + DELETE FROM foundry_state` + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + result, err := m.DB.ExecContext(ctx, query) + if err != nil { + return err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + return ErrorRecordNotFound + } + + return nil +} + +func (m FoundryStateModel) DeleteAllSeq() error { + query := ` + DELETE FROM sqlite_sequence` + + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + result, err := m.DB.ExecContext(ctx, query) + if err != nil { + return err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + return ErrorRecordNotFound + } + + return nil +} diff --git a/internal/foundry/models/db/module.go b/internal/foundry/models/db/module.go index 4763e2e..ddcfb39 100644 --- a/internal/foundry/models/db/module.go +++ b/internal/foundry/models/db/module.go @@ -50,7 +50,7 @@ func (m FoundryStateModel) InsertModule(module *Module, stateId int64) error { } for i := range module.Languages { err = m.InsertModuleLanguages(&module.Languages[i], module.Id) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } } @@ -59,7 +59,7 @@ func (m FoundryStateModel) InsertModule(module *Module, stateId int64) error { func (m FoundryStateModel) InsertModuleCompatibility(compatibility *Compatibility, moduleId int64) error { query := ` - INSERT INTO modules_compatibility (module_id, minumum, verified, maximum) + INSERT INTO modules_compatibility (module_id, minimum, verified, maximum) VALUES ($1, $2, $3, $4) RETURNING id` @@ -68,12 +68,16 @@ func (m FoundryStateModel) InsertModuleCompatibility(compatibility *Compatibilit ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - return m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) + 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_compatibility (module_id, language, name, path) + INSERT INTO modules_languages (module_id, language, name, path) VALUES ($1, $2, $3, $4) RETURNING id` @@ -82,7 +86,11 @@ func (m FoundryStateModel) InsertModuleLanguages(lang *Language, moduleId int64) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - return m.DB.QueryRowContext(ctx, query, args...).Scan(&lang.Id) + 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) { diff --git a/internal/foundry/models/db/system.go b/internal/foundry/models/db/system.go index 3a1da63..c2d3889 100644 --- a/internal/foundry/models/db/system.go +++ b/internal/foundry/models/db/system.go @@ -27,23 +27,24 @@ func (systems Systems) GetById(id int) *System { 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)` + 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) - if err != nil { + err := m.DB.QueryRowContext(ctx, query, args...).Scan(&system.Id, &system.CreatedAt) + if err != nil && err != sql.ErrNoRows { return err } - return m.InsertModuleCompatibility(&system.Compatibility, system.Id) + return m.InsertSystemCompatibility(&system.Compatibility, system.Id) } -func (m FoundryStateModel) InsertSystenCompatibility(compatibility *Compatibility, systemId int64) error { +func (m FoundryStateModel) InsertSystemCompatibility(compatibility *Compatibility, systemId int64) error { query := ` - INSERT INTO systems_compatibility (system_id, minumum, verified, maximum) + INSERT INTO systems_compatibility (system_id, minimum, verified, maximum) VALUES ($1, $2, $3, $4)` args := []any{systemId, compatibility.Minimum, compatibility.Verified, compatibility.Maximum} @@ -51,7 +52,11 @@ func (m FoundryStateModel) InsertSystenCompatibility(compatibility *Compatibilit ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - return m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) + err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) + if err != nil && err != sql.ErrNoRows { + return err + } + return nil } func (m FoundryStateModel) GetSystems(idState int64) (Systems, error) { diff --git a/internal/foundry/models/db/user.go b/internal/foundry/models/db/user.go index ef5a7d6..9a39cc1 100644 --- a/internal/foundry/models/db/user.go +++ b/internal/foundry/models/db/user.go @@ -72,7 +72,7 @@ func (m FoundryStateModel) InsertUserHotbar(key string, value string, userId int defer cancel() _, err := m.DB.ExecContext(ctx, query, args...) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } return nil diff --git a/internal/foundry/models/db/world.go b/internal/foundry/models/db/world.go index 3a70a81..7e617d3 100644 --- a/internal/foundry/models/db/world.go +++ b/internal/foundry/models/db/world.go @@ -40,7 +40,7 @@ func (m FoundryStateModel) InsertWorld(world *World, stateId int64) error { defer cancel() err := m.DB.QueryRowContext(ctx, query, args...).Scan(&world.Id, &world.CreatedAt) - if err != nil { + if err != nil && err != sql.ErrNoRows { return err } return m.InsertWorldCompatibility(&world.Compatibility, world.Id) @@ -48,7 +48,7 @@ func (m FoundryStateModel) InsertWorld(world *World, stateId int64) error { func (m FoundryStateModel) InsertWorldCompatibility(compatibility *Compatibility, worldId int64) error { query := ` - INSERT INTO worlds_compatibility (world_id, minumum, verified, maximum) + INSERT INTO worlds_compatibility (world_id, minimum, verified, maximum) VALUES ($1, $2, $3, $4) RETURNING id` @@ -57,7 +57,11 @@ func (m FoundryStateModel) InsertWorldCompatibility(compatibility *Compatibility ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - return m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) + err := m.DB.QueryRowContext(ctx, query, args...).Scan(&compatibility.Id) + if err != nil && err != sql.ErrNoRows { + return err + } + return nil } func (m FoundryStateModel) GetWorlds(idState int64) (Worlds, error) { diff --git a/internal/foundry/requests/config.go b/internal/foundry/requests/config.go index 1900ad4..59737e7 100644 --- a/internal/foundry/requests/config.go +++ b/internal/foundry/requests/config.go @@ -4,6 +4,8 @@ import ( "errors" "net/http" "strings" + + "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" ) var ( @@ -31,6 +33,18 @@ var WsTypeData = map[string]string{ UpdatePath: "getUpdateData", } +var PathToSetupState = map[string]db.StateType{ + SetupPath: db.SetupState, + // AuthPath: db.AuthState, + // UpdatePath: db.UpdateState, + // LicensePath: db.LicenseState, +} + +var PathToWorldState = map[string]db.StateType{ + JoinPath: db.JoinState, + PlayersPath: db.PlayersState, +} + type Config struct { Host string Password string diff --git a/internal/foundry/websocket.go b/internal/foundry/websocket.go index 53deb77..31f9005 100644 --- a/internal/foundry/websocket.go +++ b/internal/foundry/websocket.go @@ -2,6 +2,7 @@ package foundry import ( "context" + "database/sql" "fmt" "log/slog" "strconv" @@ -9,6 +10,8 @@ import ( "sync" "time" + db_model "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/db" + json_model "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/models/json" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/requests" "gitea.local.lab/Lbenedar/foundry_helper_service/internal/foundry/types" "github.com/gorilla/websocket" @@ -21,6 +24,33 @@ const ( ReaderCode = TransportCode(1) ) +const ( + RespSessionData = "0" + RespPingCode = "2" + RespSessionId = "40" + RespCreateSessionCode = "42" + RespDataCode = "43" + + ReqPongCode = "3" + ReqCreateSessionCode = "40" + ReqDataCode = "42" +) + +var RequestCodes = []string{ + RespSessionData, + RespPingCode, + RespSessionId, + RespCreateSessionCode, + RespDataCode, +} + +var CodesRespToReq = map[string]string{ + RespSessionData: ReqCreateSessionCode, + RespPingCode: ReqPongCode, + RespCreateSessionCode: ReqDataCode, + //ReqDataCode: RespCreateSessionCode, +} + type webSocketUtil struct { wsConn *websocket.Conn currWsId int @@ -30,6 +60,7 @@ type webSocketUtil struct { msgMap map[int](chan []byte) chanMutex sync.Mutex channels types.Channels + models db_model.Models } func NewWebSocketUtil() *webSocketUtil { @@ -193,6 +224,7 @@ func (ws *webSocketUtil) ServeWebSocket() { } case RespCreateSessionCode: ws.isReadReady = true + go ws.GetInitialData() case RespDataCode: ws.msgMap[message.Id] = make(chan []byte, 1) ws.msgMap[message.Id] <- []byte(message.MsgJson) @@ -209,6 +241,56 @@ func (ws *webSocketUtil) IsReadReady() bool { return ws.isReadReady } +func (ws *webSocketUtil) GetJsonData(stateType string) ([]byte, error) { + msg := ws.CreateWSMessageByPage(stateType) + + return ws.HandleWebsocketRequest(msg) +} + +func (ws *webSocketUtil) InsertJsonDataToDB(statePath string) error { + _, ok1 := requests.PathToSetupState[statePath] + _, ok2 := requests.PathToWorldState[statePath] + if !ok1 && !ok2 { + return ErrorStateTypeNotExist + } + + msgJson, err := ws.GetJsonData(statePath) + if err != nil { + return err + } + + foundryStateJson, err := json_model.ParseSetupModel(msgJson) + if err != nil { + return err + } + + foundryStateDb := foundryStateJson.GetFoundryStateDB(requests.PathToSetupState[statePath]) + err = ws.models.FoundryState.Insert(foundryStateDb) + if err != nil { + return err + } + return nil +} + +func (ws *webSocketUtil) GetInitialData() { + ws.models.FoundryState.DeleteAll() + ws.models.FoundryState.DeleteAllSeq() + for k := range requests.PathToSetupState { + err := ws.InsertJsonDataToDB(k) + if err != nil { + ws.channels.Err() <- &FoundryError{Type: WriterCode, Err: err} + break + } + } + // for k := range requests.PathToWorldState { + + // } +} + +func (ws *webSocketUtil) CreateNewDataModels(db *sql.DB) { + ws.models = db_model.NewModels(db) +} + func (ws *webSocketUtil) CreateWSMessageByPage(page string) *types.WsMessage { msgToSend := &types.WsMessage{Code: CodesRespToReq[RespCreateSessionCode], Id: ws.currWsId, MsgJson: fmt.Sprintf("[\"%s\"]", requests.WsTypeData[page])} ws.currWsId++