From 4567108da8cd819e3fcebf3cfded65eb933b8b64 Mon Sep 17 00:00:00 2001 From: gamray Date: Sun, 12 May 2024 22:37:43 +0200 Subject: [PATCH 1/7] Added separate endpoints to handle eggs (get, update and delete) --- .gitignore | 1 + api/common.go | 3 ++ api/endpoints.go | 70 +++++++++++++++++++++++++++++++ db/db.go | 104 ++++++++++++++++++++++++++++++++++++++++++----- db/savedata.go | 57 ++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 1f25c88..8f4c20a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ secret.key # local testing /.data/ +docker-compose.yml # Jetbrains IDEs /.idea/ diff --git a/api/common.go b/api/common.go index b1f11dd..c48a9e4 100644 --- a/api/common.go +++ b/api/common.go @@ -54,6 +54,9 @@ func Init(mux *http.ServeMux) error { mux.HandleFunc("GET /savedata/delete", handleSaveData) // TODO use deleteSystemSave mux.HandleFunc("POST /savedata/clear", handleSaveData) // TODO use clearSessionData mux.HandleFunc("GET /savedata/newclear", handleNewClear) + mux.HandleFunc("GET /savedata/eggs", handleRetrieveEggs) + mux.HandleFunc("POST /savedata/eggs", handleUpdateEggs) + mux.HandleFunc("POST /savedata/deleteeggs", handleDeleteEgg) // new session mux.HandleFunc("POST /savedata/updateall", handleUpdateAll) diff --git a/api/endpoints.go b/api/endpoints.go index be24e33..a459fc6 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -146,6 +146,7 @@ func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(strconv.Itoa(classicSessionCount))) } +// savedata func handleGetSaveData(w http.ResponseWriter, r *http.Request) { token, uuid, err := tokenAndUuidFromRequest(r) if err != nil { @@ -609,6 +610,75 @@ func handleNewClear(w http.ResponseWriter, r *http.Request) { jsonResponse(w, r, newClear) } +func handleRetrieveEggs(w http.ResponseWriter, r *http.Request) { + uuid, err := uuidFromRequest(r) + if err != nil { + httpError(w, r, err, http.StatusBadRequest) + return + } + + eggs, err := db.RetrieveAccountEggs(uuid) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + jsonResponse(w, r, eggs) + w.Header().Set("Content-Type", "application/json") +} + +func handleUpdateEggs(w http.ResponseWriter, r *http.Request) { + uuid, err := uuidFromRequest(r) + if err != nil { + httpError(w, r, err, http.StatusBadRequest) + return + } + + var newEggsInfo []defs.EggData + err = json.NewDecoder(r.Body).Decode(&newEggsInfo) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) + return + } + + err = db.UpdateAccountEggs(uuid, newEggsInfo) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +type DeleteEggId struct { + Id int `json:"id"` +} + +func handleDeleteEgg(w http.ResponseWriter, r *http.Request) { + uuid, err := uuidFromRequest(r) + if err != nil { + httpError(w, r, err, http.StatusBadRequest) + return + } + + var eggsId []DeleteEggId + err = json.NewDecoder(r.Body).Decode(&eggsId) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) + return + } + + for _, egg := range eggsId { + err = db.RemoveAccountEgg(uuid, egg.Id) + if err != nil { + httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) + return + } + } + + w.WriteHeader(http.StatusOK) +} + // daily func handleDailySeed(w http.ResponseWriter, r *http.Request) { diff --git a/db/db.go b/db/db.go index 874a48c..55e9f5b 100644 --- a/db/db.go +++ b/db/db.go @@ -145,30 +145,114 @@ func Init(username, password, protocol, address, database string) error { func setupDb(tx *sql.Tx) error { queries := []string{ - `CREATE TABLE IF NOT EXISTS accounts (uuid BINARY(16) NOT NULL PRIMARY KEY, username VARCHAR(16) UNIQUE NOT NULL, hash BINARY(32) NOT NULL, salt BINARY(16) NOT NULL, registered TIMESTAMP NOT NULL, lastLoggedIn TIMESTAMP DEFAULT NULL, lastActivity TIMESTAMP DEFAULT NULL, banned TINYINT(1) NOT NULL DEFAULT 0, trainerId SMALLINT(5) UNSIGNED DEFAULT 0, secretId SMALLINT(5) UNSIGNED DEFAULT 0)`, + `CREATE TABLE IF NOT EXISTS accounts ( + uuid BINARY(16) NOT NULL PRIMARY KEY, + username VARCHAR(16) UNIQUE NOT NULL, + hash BINARY(32) NOT NULL, + salt BINARY(16) NOT NULL, + registered TIMESTAMP NOT NULL, + lastLoggedIn TIMESTAMP DEFAULT NULL, + lastActivity TIMESTAMP DEFAULT NULL, + banned TINYINT(1) NOT NULL DEFAULT 0, + trainerId SMALLINT(5) UNSIGNED DEFAULT 0, + secretId SMALLINT(5) UNSIGNED DEFAULT 0)`, `CREATE INDEX IF NOT EXISTS accountsByActivity ON accounts (lastActivity)`, - `CREATE TABLE IF NOT EXISTS sessions (token BINARY(32) NOT NULL PRIMARY KEY, uuid BINARY(16) NOT NULL, active TINYINT(1) NOT NULL DEFAULT 0, expire TIMESTAMP DEFAULT NULL, CONSTRAINT sessions_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`, + `CREATE TABLE IF NOT EXISTS sessions ( + token BINARY(32) NOT NULL PRIMARY KEY, + uuid BINARY(16) NOT NULL, + active TINYINT(1) NOT NULL DEFAULT 0, + expire TIMESTAMP DEFAULT NULL, + CONSTRAINT sessions_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) + ON DELETE CASCADE + ON UPDATE CASCADE)`, `CREATE INDEX IF NOT EXISTS sessionsByUuid ON sessions (uuid)`, - `CREATE TABLE IF NOT EXISTS accountStats (uuid BINARY(16) NOT NULL PRIMARY KEY, playTime INT(11) NOT NULL DEFAULT 0, battles INT(11) NOT NULL DEFAULT 0, classicSessionsPlayed INT(11) NOT NULL DEFAULT 0, sessionsWon INT(11) NOT NULL DEFAULT 0, highestEndlessWave INT(11) NOT NULL DEFAULT 0, highestLevel INT(11) NOT NULL DEFAULT 0, pokemonSeen INT(11) NOT NULL DEFAULT 0, pokemonDefeated INT(11) NOT NULL DEFAULT 0, pokemonCaught INT(11) NOT NULL DEFAULT 0, pokemonHatched INT(11) NOT NULL DEFAULT 0, eggsPulled INT(11) NOT NULL DEFAULT 0, regularVouchers INT(11) NOT NULL DEFAULT 0, plusVouchers INT(11) NOT NULL DEFAULT 0, premiumVouchers INT(11) NOT NULL DEFAULT 0, goldenVouchers INT(11) NOT NULL DEFAULT 0, CONSTRAINT accountStats_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`, + `CREATE TABLE IF NOT EXISTS accountStats ( + uuid BINARY(16) NOT NULL PRIMARY KEY, + playTime INT(11) NOT NULL DEFAULT 0, + battles INT(11) NOT NULL DEFAULT 0, + classicSessionsPlayed INT(11) NOT NULL DEFAULT 0, + sessionsWon INT(11) NOT NULL DEFAULT 0, + highestEndlessWave INT(11) NOT NULL DEFAULT 0, + highestLevel INT(11) NOT NULL DEFAULT 0, + pokemonSeen INT(11) NOT NULL DEFAULT 0, + pokemonDefeated INT(11) NOT NULL DEFAULT 0, + pokemonCaught INT(11) NOT NULL DEFAULT 0, + pokemonHatched INT(11) NOT NULL DEFAULT 0, + eggsPulled INT(11) NOT NULL DEFAULT 0, + regularVouchers INT(11) NOT NULL DEFAULT 0, + plusVouchers INT(11) NOT NULL DEFAULT 0, + premiumVouchers INT(11) NOT NULL DEFAULT 0, + goldenVouchers INT(11) NOT NULL DEFAULT 0, + CONSTRAINT accountStats_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) + ON DELETE CASCADE + ON UPDATE CASCADE)`, - `CREATE TABLE IF NOT EXISTS accountCompensations (id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, uuid BINARY(16) NOT NULL, voucherType INT(11) NOT NULL, count INT(11) NOT NULL DEFAULT 1, claimed BIT(1) NOT NULL DEFAULT b'0', CONSTRAINT accountCompensations_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`, + `CREATE TABLE IF NOT EXISTS accountCompensations ( + id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, + uuid BINARY(16) NOT NULL, + voucherType INT(11) NOT NULL, + count INT(11) NOT NULL DEFAULT 1, + claimed BIT(1) NOT NULL DEFAULT b'0', + CONSTRAINT accountCompensations_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) + ON DELETE CASCADE + ON UPDATE CASCADE)`, `CREATE INDEX IF NOT EXISTS accountCompensationsByUuid ON accountCompensations (uuid)`, - `CREATE TABLE IF NOT EXISTS dailyRuns (date DATE NOT NULL PRIMARY KEY, seed CHAR(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL)`, + `CREATE TABLE IF NOT EXISTS dailyRuns ( + date DATE NOT NULL PRIMARY KEY, + seed CHAR(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL)`, `CREATE INDEX IF NOT EXISTS dailyRunsByDateAndSeed ON dailyRuns (date, seed)`, - `CREATE TABLE IF NOT EXISTS dailyRunCompletions (uuid BINARY(16) NOT NULL, seed CHAR(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, mode INT(11) NOT NULL DEFAULT 0, score INT(11) NOT NULL DEFAULT 0, timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (uuid, seed), CONSTRAINT dailyRunCompletions_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`, + `CREATE TABLE IF NOT EXISTS dailyRunCompletions ( + uuid BINARY(16) NOT NULL, + seed CHAR(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, + mode INT(11) NOT NULL DEFAULT 0, + score INT(11) NOT NULL DEFAULT 0, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (uuid, seed), + CONSTRAINT dailyRunCompletions_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) + ON DELETE CASCADE + ON UPDATE CASCADE)`, `CREATE INDEX IF NOT EXISTS dailyRunCompletionsByUuidAndSeed ON dailyRunCompletions (uuid, seed)`, - `CREATE TABLE IF NOT EXISTS accountDailyRuns (uuid BINARY(16) NOT NULL, date DATE NOT NULL, score INT(11) NOT NULL DEFAULT 0, wave INT(11) NOT NULL DEFAULT 0, timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (uuid, date), CONSTRAINT accountDailyRuns_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT accountDailyRuns_ibfk_2 FOREIGN KEY (date) REFERENCES dailyRuns (date) ON DELETE NO ACTION ON UPDATE NO ACTION)`, + `CREATE TABLE IF NOT EXISTS accountDailyRuns ( + uuid BINARY(16) NOT NULL, + date DATE NOT NULL, + score INT(11) NOT NULL DEFAULT 0, + wave INT(11) NOT NULL DEFAULT 0, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (uuid, date), + CONSTRAINT accountDailyRuns_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT accountDailyRuns_ibfk_2 FOREIGN KEY (date) REFERENCES dailyRuns (date) + ON DELETE NO ACTION + ON UPDATE NO ACTION)`, `CREATE INDEX IF NOT EXISTS accountDailyRunsByDate ON accountDailyRuns (date)`, - `CREATE TABLE IF NOT EXISTS systemSaveData (uuid BINARY(16) PRIMARY KEY, data LONGBLOB, timestamp TIMESTAMP)`, + `CREATE TABLE IF NOT EXISTS systemSaveData ( + uuid BINARY(16) PRIMARY KEY, + data LONGBLOB, + timestamp TIMESTAMP)`, - `CREATE TABLE IF NOT EXISTS sessionSaveData (uuid BINARY(16), slot TINYINT, data LONGBLOB, timestamp TIMESTAMP, PRIMARY KEY (uuid, slot))`, - } + `CREATE TABLE IF NOT EXISTS sessionSaveData ( + uuid BINARY(16), + slot TINYINT, + data LONGBLOB, + timestamp TIMESTAMP, + PRIMARY KEY (uuid, slot))`, + + `CREATE TABLE IF NOT EXISTS eggs ( + uuid VARCHAR(32) NOT NULL PRIMARY KEY, + owner BINARY(16) NOT NULL, + gachaType INT(11) NOT NULL, + hatchWaves INT(11) NOT NULL, + timestamp INT NOT NULL, + CONSTRAINT eggs_ibfk_1 FOREIGN KEY (owner) REFERENCES accounts (uuid) + ON DELETE CASCADE + ON UPDATE CASCADE)`} for _, q := range queries { _, err := tx.Exec(q) diff --git a/db/savedata.go b/db/savedata.go index bb792c5..76879e3 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -142,3 +142,60 @@ func DeleteSessionSaveData(uuid []byte, slot int) error { return nil } + +func RetrieveAccountEggs(uuid []byte) ([]defs.EggData, error) { + var accountEggs []defs.EggData + + rows, err := handle.Query("SELECT uuid, gachaType, hatchWaves, timestamp FROM eggs WHERE owner = ?", uuid) + if err != nil { + return accountEggs, err + } + + // For each row, we parse the raw data into an EggData and add it to the result + for rows.Next() { + var egg defs.EggData + err = rows.Scan(&egg.Id, &egg.GachaType, &egg.HatchWaves, &egg.Timestamp) + if err != nil { + return accountEggs, err + } + + accountEggs = append(accountEggs, egg) + } + + return accountEggs, nil +} + +func UpdateAccountEggs(uuid []byte, eggs []defs.EggData) error { + for _, egg := range eggs { + // TODO: find a fix to enforce encoding from body to EggData only if + // it respects the EggData struct so we can get rid of the test + if egg.Id == 0 { + continue + } + + var buf bytes.Buffer + err := gob.NewEncoder(&buf).Encode(egg) + if err != nil { + return err + } + + _, err = handle.Exec(`INSERT INTO eggs (uuid, owner, gachaType, hatchWaves, timestamp) + VALUES (?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE hatchWaves = ?`, + egg.Id, uuid, egg.GachaType, egg.HatchWaves, egg.Timestamp, egg.HatchWaves) + if err != nil { + return err + } + } + + return nil +} + +func RemoveAccountEgg(uuid []byte, eggId int) error { + _, err := handle.Exec("DELETE FROM eggs WHERE owner = ? AND uuid = ?", uuid, eggId) + if err != nil { + return err + } + + return nil +} From cf1b33e170cc75623ccfdf3031489ac54c8b6d6c Mon Sep 17 00:00:00 2001 From: gamray Date: Mon, 13 May 2024 00:21:00 +0200 Subject: [PATCH 2/7] fixed bad copy paste (200 ok should be sent on success) --- api/endpoints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/endpoints.go b/api/endpoints.go index a459fc6..016ebcd 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -624,7 +624,7 @@ func handleRetrieveEggs(w http.ResponseWriter, r *http.Request) { } jsonResponse(w, r, eggs) - w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) } func handleUpdateEggs(w http.ResponseWriter, r *http.Request) { From 64bbaed3ede3de05f4381f32f7e702573785bb57 Mon Sep 17 00:00:00 2001 From: gamray Date: Mon, 13 May 2024 00:24:04 +0200 Subject: [PATCH 3/7] removed unusued code --- db/savedata.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/db/savedata.go b/db/savedata.go index 76879e3..2f1e23f 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -173,12 +173,6 @@ func UpdateAccountEggs(uuid []byte, eggs []defs.EggData) error { continue } - var buf bytes.Buffer - err := gob.NewEncoder(&buf).Encode(egg) - if err != nil { - return err - } - _, err = handle.Exec(`INSERT INTO eggs (uuid, owner, gachaType, hatchWaves, timestamp) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE hatchWaves = ?`, From 81592af770fa9637cad3976763def778eec44be4 Mon Sep 17 00:00:00 2001 From: gamray Date: Mon, 13 May 2024 00:32:54 +0200 Subject: [PATCH 4/7] Using transaction instead of normal handle to do multiple queries --- db/savedata.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/db/savedata.go b/db/savedata.go index 2f1e23f..1f407c9 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -166,6 +166,11 @@ func RetrieveAccountEggs(uuid []byte) ([]defs.EggData, error) { } func UpdateAccountEggs(uuid []byte, eggs []defs.EggData) error { + tx, err := handle.Begin() + if err != nil { + return err + } + for _, egg := range eggs { // TODO: find a fix to enforce encoding from body to EggData only if // it respects the EggData struct so we can get rid of the test @@ -173,7 +178,7 @@ func UpdateAccountEggs(uuid []byte, eggs []defs.EggData) error { continue } - _, err = handle.Exec(`INSERT INTO eggs (uuid, owner, gachaType, hatchWaves, timestamp) + _, err = tx.Exec(`INSERT INTO eggs (uuid, owner, gachaType, hatchWaves, timestamp) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE hatchWaves = ?`, egg.Id, uuid, egg.GachaType, egg.HatchWaves, egg.Timestamp, egg.HatchWaves) @@ -182,6 +187,11 @@ func UpdateAccountEggs(uuid []byte, eggs []defs.EggData) error { } } + err = tx.Commit() + if err != nil { + return err + } + return nil } From 733e01ec16cc7fa9a71fe915df9a592f750d87be Mon Sep 17 00:00:00 2001 From: gamray Date: Mon, 13 May 2024 00:35:17 +0200 Subject: [PATCH 5/7] Using transaction instead of normal handle to do multiple queries --- api/endpoints.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/endpoints.go b/api/endpoints.go index 016ebcd..70f7ab8 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -623,8 +623,8 @@ func handleRetrieveEggs(w http.ResponseWriter, r *http.Request) { return } - jsonResponse(w, r, eggs) w.WriteHeader(http.StatusOK) + jsonResponse(w, r, eggs) } func handleUpdateEggs(w http.ResponseWriter, r *http.Request) { From d1fb726b7a6b9c187541f1ba40a111a9d320f593 Mon Sep 17 00:00:00 2001 From: Gamray <52253862+GamrayW@users.noreply.github.com> Date: Mon, 13 May 2024 21:02:27 +0200 Subject: [PATCH 6/7] Update db/savedata.go Co-authored-by: Krystian Chmura <11900380+slsyy@users.noreply.github.com> --- db/savedata.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/savedata.go b/db/savedata.go index 1f407c9..4c0377b 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -170,7 +170,7 @@ func UpdateAccountEggs(uuid []byte, eggs []defs.EggData) error { if err != nil { return err } - +defer tx.Rollback() for _, egg := range eggs { // TODO: find a fix to enforce encoding from body to EggData only if // it respects the EggData struct so we can get rid of the test From 8d32873f02cee687e8fe39a53c50aa8c74471aef Mon Sep 17 00:00:00 2001 From: gamray Date: Mon, 13 May 2024 21:03:24 +0200 Subject: [PATCH 7/7] Fixing rollback --- db/savedata.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/savedata.go b/db/savedata.go index 4c0377b..e5a46fd 100644 --- a/db/savedata.go +++ b/db/savedata.go @@ -170,7 +170,9 @@ func UpdateAccountEggs(uuid []byte, eggs []defs.EggData) error { if err != nil { return err } -defer tx.Rollback() + + defer tx.Rollback() + for _, egg := range eggs { // TODO: find a fix to enforce encoding from body to EggData only if // it respects the EggData struct so we can get rid of the test