Compare commits

..

No commits in common. "master" and "better-accounts" have entirely different histories.

14 changed files with 143 additions and 738 deletions

View File

@ -33,12 +33,6 @@ jobs:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
- name: Install dependencies - name: Install dependencies
run: go mod download run: go mod download
- name: Lint Codebase
continue-on-error: true
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: --config .golangci.yml
- name: Test - name: Test
run: go test -v run: go test -v
- name: Build - name: Build

View File

@ -1,8 +0,0 @@
run:
timeout: 10m
severity:
default-severity: error
rules:
- linters:
- unused
severity: info

View File

@ -1,83 +1 @@
# rogueserver # rogueserver
# Hosting in Docker
It is advised that you host this in a docker container as it will be much easier to manage.
There is a sample docker-compose file for setting up a docker container to setup this server.
# Self Hosting outside of Docker:
## Required Tools:
- Golang
- Node: **18.3.0**
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
## Installation:
The docker compose file should automatically implement a container with mariadb with an empty database and the default user and password combo of pokerogue:pokerogue
### src/utils.ts:224-225 (in pokerogue)
Replace both URLs (one on each line) with the local API server address from rogueserver.go (0.0.0.0:8001) (or whatever port you picked)
# If you are on Windows
Now that all of the files are configured: start up powershell as administrator:
```
cd C:\api\server\location\
go build .
.\rogueserver.exe --debug --dbuser yourusername --dbpass yourpassword
```
The other available flags are located in rogueserver.go:34-43.
Then in another run this the first time then run `npm run start` from the rogueserver location from then on:
```
powershell -ep bypass
cd C:\server\location\
npm install
npm run start
```
You will need to allow the port youre running the API (8001) on and port 8000 to accept inbound connections through the [Windows Advanced Firewall](https://www.youtube.com/watch?v=9llH5_CON-Y).
# If you are on Linux
In whatever shell you prefer, run the following:
```
cd /api/server/location/
go build .
./rogueserver --debug --dbuser yourusername --dbpass yourpassword &
cd /server/location/
npm run start
```
If you have a firewall running such as ufw on your linux machine, make sure to allow inbound connections on the ports youre running the API and the pokerogue server (8000,8001).
An example to allow incoming connections using UFW:
```
sudo ufw allow 8000,8001/tcp
```
This should allow you to reach the game from other computers on the same network.
## Tying to a Domain
If you want to tie it to a domain like I did and make it publicly accessible, there is some extra work to be done.
I setup caddy and would recommend using it as a reverse proxy.
[caddy installation](https://caddyserver.com/docs/install)
once its installed setup a config file for caddy:
```
pokerogue.exampledomain.com {
reverse_proxy localhost:8000
}
pokeapi.exampledomain.com {
reverse_proxy localhost:8001
}
```
Preferably set up caddy as a service from [here.](https://caddyserver.com/docs/running)
Once this is good to go, take your API url (https://pokeapi.exampledomain.com) and paste it on
### src/utils.ts:224-225
in place of the previous 0.0.0.0:8001 address
Make sure that both 8000 and 8001 are portforwarded on your router.
Test that the server's game and game authentication works from other machines both in and outside of the network. Once this is complete, enjoy!

View File

@ -19,7 +19,6 @@ package account
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
@ -29,7 +28,7 @@ import (
func Logout(token []byte) error { func Logout(token []byte) error {
err := db.RemoveSessionFromToken(token) err := db.RemoveSessionFromToken(token)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if err == sql.ErrNoRows {
return fmt.Errorf("token not found") return fmt.Errorf("token not found")
} }

View File

@ -19,22 +19,18 @@ package api
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"log"
"net/http"
"github.com/pagefaultgames/rogueserver/api/account" "github.com/pagefaultgames/rogueserver/api/account"
"github.com/pagefaultgames/rogueserver/api/daily" "github.com/pagefaultgames/rogueserver/api/daily"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
"log"
"net/http"
) )
func Init(mux *http.ServeMux) error { func Init(mux *http.ServeMux) {
if err := scheduleStatRefresh(); err != nil { scheduleStatRefresh()
return err daily.Init()
}
if err := daily.Init(); err != nil {
return err
}
// account // account
mux.HandleFunc("GET /account/info", handleAccountInfo) mux.HandleFunc("GET /account/info", handleAccountInfo)
@ -48,23 +44,16 @@ func Init(mux *http.ServeMux) error {
mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount) mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount)
// savedata // savedata
mux.HandleFunc("GET /savedata/get", legacyHandleGetSaveData) mux.HandleFunc("GET /savedata/get", handleSaveData)
mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) mux.HandleFunc("POST /savedata/update", handleSaveData)
mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // TODO use deleteSystemSave mux.HandleFunc("GET /savedata/delete", handleSaveData)
mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO use clearSessionData mux.HandleFunc("POST /savedata/clear", handleSaveData)
mux.HandleFunc("GET /savedata/newclear", legacyHandleNewClear) mux.HandleFunc("GET /savedata/newclear", handleNewClear)
// new session
mux.HandleFunc("POST /savedata/updateall", handleUpdateAll)
mux.HandleFunc("POST /savedata/system/verify", handleSystemVerify)
mux.HandleFunc("GET /savedata/system", handleGetSystemData)
mux.HandleFunc("GET /savedata/session", handleGetSessionData)
// daily // daily
mux.HandleFunc("GET /daily/seed", handleDailySeed) mux.HandleFunc("GET /daily/seed", handleDailySeed)
mux.HandleFunc("GET /daily/rankings", handleDailyRankings) mux.HandleFunc("GET /daily/rankings", handleDailyRankings)
mux.HandleFunc("GET /daily/rankingpagecount", handleDailyRankingPageCount) mux.HandleFunc("GET /daily/rankingpagecount", handleDailyRankingPageCount)
return nil
} }
func tokenFromRequest(r *http.Request) ([]byte, error) { func tokenFromRequest(r *http.Request) ([]byte, error) {
@ -85,34 +74,20 @@ func tokenFromRequest(r *http.Request) ([]byte, error) {
} }
func uuidFromRequest(r *http.Request) ([]byte, error) { func uuidFromRequest(r *http.Request) ([]byte, error) {
_, uuid, err := tokenAndUuidFromRequest(r)
return uuid, err
}
func tokenAndUuidFromRequest(r *http.Request) ([]byte, []byte, error) {
token, err := tokenFromRequest(r) token, err := tokenFromRequest(r)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
uuid, err := db.FetchUUIDFromToken(token) uuid, err := db.FetchUUIDFromToken(token)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to validate token: %s", err) return nil, fmt.Errorf("failed to validate token: %s", err)
} }
return token, uuid, nil return uuid, nil
} }
func httpError(w http.ResponseWriter, r *http.Request, err error, code int) { func httpError(w http.ResponseWriter, r *http.Request, err error, code int) {
log.Printf("%s: %s\n", r.URL.Path, err) log.Printf("%s: %s\n", r.URL.Path, err)
http.Error(w, err.Error(), code) http.Error(w, err.Error(), code)
} }
func jsonResponse(w http.ResponseWriter, r *http.Request, data any) {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(data)
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
}

View File

@ -20,7 +20,6 @@ package api
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
@ -59,7 +58,13 @@ func handleAccountInfo(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, response) err = json.NewEncoder(w).Encode(response)
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
} }
func handleAccountRegister(w http.ResponseWriter, r *http.Request) { func handleAccountRegister(w http.ResponseWriter, r *http.Request) {
@ -91,7 +96,13 @@ func handleAccountLogin(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, response) err = json.NewEncoder(w).Encode(response)
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
} }
func handleAccountChangePW(w http.ResponseWriter, r *http.Request) { func handleAccountChangePW(w http.ResponseWriter, r *http.Request) {
@ -133,66 +144,25 @@ func handleAccountLogout(w http.ResponseWriter, r *http.Request) {
} }
// game // game
func handleGameTitleStats(w http.ResponseWriter, r *http.Request) { func handleGameTitleStats(w http.ResponseWriter, r *http.Request) {
stats := defs.TitleStats{ err := json.NewEncoder(w).Encode(defs.TitleStats{
PlayerCount: playerCount, PlayerCount: playerCount,
BattleCount: battleCount, BattleCount: battleCount,
})
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
} }
jsonResponse(w, r, stats) w.Header().Set("Content-Type", "application/json")
} }
func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) { func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(strconv.Itoa(classicSessionCount))) w.Write([]byte(strconv.Itoa(classicSessionCount)))
} }
func handleGetSessionData(w http.ResponseWriter, r *http.Request) { func handleSaveData(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
var slot int
if r.URL.Query().Has("slot") {
slot, err = strconv.Atoi(r.URL.Query().Get("slot"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
var clientSessionId string
if r.URL.Query().Has("clientSessionId") {
clientSessionId = r.URL.Query().Get("clientSessionId")
} else {
httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest)
}
err = db.UpdateActiveSession(uuid, clientSessionId)
if err != nil {
httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest)
return
}
var save any
save, err = savedata.Get(uuid, 1, slot)
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
jsonResponse(w, r, save)
}
const legacyClientSessionId = "LEGACY_CLIENT"
func legacyHandleGetSaveData(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r) uuid, err := uuidFromRequest(r)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusBadRequest) httpError(w, r, err, http.StatusBadRequest)
@ -217,231 +187,6 @@ func legacyHandleGetSaveData(w http.ResponseWriter, r *http.Request) {
} }
} }
var save any
if datatype == 0 {
err = db.UpdateActiveSession(uuid, legacyClientSessionId) // we dont have a client id
if err != nil {
httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest)
return
}
}
save, err = savedata.Get(uuid, datatype, slot)
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
jsonResponse(w, r, save)
}
// FIXME UNFINISHED!!!
func clearSessionData(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
var slot int
if r.URL.Query().Has("slot") {
slot, err = strconv.Atoi(r.URL.Query().Get("slot"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
var save any
var session defs.SessionSaveData
err = json.NewDecoder(r.Body).Decode(&session)
if err != nil {
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
return
}
save = session
var active bool
active, err = db.IsActiveSession(uuid, legacyClientSessionId) //TODO unfinished, read token from query
if err != nil {
httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest)
return
}
var trainerId, secretId int
if r.URL.Query().Has("trainerId") && r.URL.Query().Has("secretId") {
trainerId, err = strconv.Atoi(r.URL.Query().Get("trainerId"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
secretId, err = strconv.Atoi(r.URL.Query().Get("secretId"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid)
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest)
return
}
} else {
err = db.UpdateTrainerIds(trainerId, secretId, uuid)
if err != nil {
httpError(w, r, fmt.Errorf("unable to update trainer ID: %s", err), http.StatusInternalServerError)
return
}
}
if !active {
save = savedata.ClearResponse{Error: "session out of date: not active"}
}
var seed string
seed, err = db.GetDailyRunSeed()
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
response, err := savedata.Clear(uuid, slot, seed, save.(defs.SessionSaveData))
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
jsonResponse(w, r, response)
}
// FIXME UNFINISHED!!!
func deleteSystemSave(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
datatype := 0
if r.URL.Query().Has("datatype") {
datatype, err = strconv.Atoi(r.URL.Query().Get("datatype"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
var slot int
if r.URL.Query().Has("slot") {
slot, err = strconv.Atoi(r.URL.Query().Get("slot"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
var active bool
active, err = db.IsActiveSession(uuid, legacyClientSessionId) //TODO unfinished, read token from query
if err != nil {
httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusInternalServerError)
return
}
if !active {
httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest)
return
}
var trainerId, secretId int
if r.URL.Query().Has("trainerId") && r.URL.Query().Has("secretId") {
trainerId, err = strconv.Atoi(r.URL.Query().Get("trainerId"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
secretId, err = strconv.Atoi(r.URL.Query().Get("secretId"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid)
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest)
return
}
} else {
if err := db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
}
err = savedata.Delete(uuid, datatype, slot)
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
datatype := -1
if r.URL.Query().Has("datatype") {
datatype, err = strconv.Atoi(r.URL.Query().Get("datatype"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
var slot int
if r.URL.Query().Has("slot") {
slot, err = strconv.Atoi(r.URL.Query().Get("slot"))
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
}
var clientSessionId string
if r.URL.Query().Has("clientSessionId") {
clientSessionId = r.URL.Query().Get("clientSessionId")
}
if clientSessionId == "" {
clientSessionId = legacyClientSessionId
}
var save any var save any
// /savedata/get and /savedata/delete specify datatype, but don't expect data in body // /savedata/get and /savedata/delete specify datatype, but don't expect data in body
if r.URL.Path != "/savedata/get" && r.URL.Path != "/savedata/delete" { if r.URL.Path != "/savedata/get" && r.URL.Path != "/savedata/delete" {
@ -467,17 +212,24 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
} }
} }
var token []byte
token, err = tokenFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
var active bool var active bool
if r.URL.Path == "/savedata/get" { if r.URL.Path == "/savedata/get" {
if datatype == 0 { if datatype == 0 {
err = db.UpdateActiveSession(uuid, clientSessionId) err = db.UpdateActiveSession(uuid, token)
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest)
return return
} }
} }
} else { } else {
active, err = db.IsActiveSession(uuid, clientSessionId) active, err = db.IsActiveSession(token)
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest)
return return
@ -485,7 +237,7 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
// TODO: make this not suck // TODO: make this not suck
if !active && r.URL.Path != "/savedata/clear" { if !active && r.URL.Path != "/savedata/clear" {
httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest)
return return
} }
@ -518,14 +270,11 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
if storedTrainerId > 0 || storedSecretId > 0 { if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId { if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest)
return return
} }
} else { } else {
if err := db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil { db.UpdateTrainerIds(trainerId, secretId, uuid)
httpError(w, r, err, http.StatusInternalServerError)
return
}
} }
} }
@ -543,7 +292,7 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
case "/savedata/clear": case "/savedata/clear":
if !active { if !active {
// TODO: make this not suck // TODO: make this not suck
save = savedata.ClearResponse{Error: "session out of date: not active"} save = savedata.ClearResponse{Error: "session out of date"}
break break
} }
@ -567,178 +316,16 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, save) err = json.NewEncoder(w).Encode(save)
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
} }
type CombinedSaveData struct { func handleNewClear(w http.ResponseWriter, r *http.Request) {
System defs.SystemSaveData `json:"system"`
Session defs.SessionSaveData `json:"session"`
SessionSlotId int `json:"sessionSlotId"`
ClientSessionId string `json:"clientSessionId"`
}
// TODO wrap this in a transaction
func handleUpdateAll(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
var data CombinedSaveData
err = json.NewDecoder(r.Body).Decode(&data)
if err != nil {
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
return
}
if data.ClientSessionId == "" {
data.ClientSessionId = legacyClientSessionId
}
var active bool
active, err = db.IsActiveSession(uuid, data.ClientSessionId)
if err != nil {
httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest)
return
}
if !active {
httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest)
return
}
trainerId := data.System.TrainerId
secretId := data.System.SecretId
storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid)
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest)
return
}
} else {
if err = db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
}
err = savedata.Update(uuid, data.SessionSlotId, data.Session)
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
err = savedata.Update(uuid, 0, data.System)
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
type SystemVerifyResponse struct {
Valid bool `json:"valid"`
SystemData *defs.SystemSaveData `json:"systemData"`
}
type SystemVerifyRequest struct {
ClientSessionId string `json:"clientSessionId"`
}
func handleSystemVerify(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
var input SystemVerifyRequest
err = json.NewDecoder(r.Body).Decode(&input)
if err != nil {
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
return
}
var active bool
active, err = db.IsActiveSession(uuid, input.ClientSessionId)
if err != nil {
httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest)
return
}
response := SystemVerifyResponse{
Valid: active,
}
// not valid, send server state
if !active {
err = db.UpdateActiveSession(uuid, input.ClientSessionId)
if err != nil {
httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest)
return
}
var storedSaveData defs.SystemSaveData
storedSaveData, err = db.ReadSystemSaveData(uuid)
if err != nil {
httpError(w, r, fmt.Errorf("failed to read session save data: %s", err), http.StatusInternalServerError)
return
}
response.SystemData = &storedSaveData
}
err = db.UpdateAccountLastActivity(uuid)
if err != nil {
httpError(w, r, fmt.Errorf("failed to update account last activity: %s", err), http.StatusInternalServerError)
return
}
jsonResponse(w, r, response)
}
func handleGetSystemData(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r)
if err != nil {
httpError(w, r, err, http.StatusBadRequest)
return
}
var clientSessionId string
if r.URL.Query().Has("clientSessionId") {
clientSessionId = r.URL.Query().Get("clientSessionId")
} else {
httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest)
}
err = db.UpdateActiveSession(uuid, clientSessionId)
if err != nil {
httpError(w, r, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest)
return
}
var save any //TODO this is always system save data
save, err = savedata.Get(uuid, 0, 0)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
httpError(w, r, err, http.StatusInternalServerError)
}
return
}
//TODO apply vouchers
jsonResponse(w, r, save)
}
func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r) uuid, err := uuidFromRequest(r)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusBadRequest) httpError(w, r, err, http.StatusBadRequest)
@ -760,10 +347,17 @@ func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, newClear) err = json.NewEncoder(w).Encode(newClear)
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
} }
// daily // daily
func handleDailySeed(w http.ResponseWriter, r *http.Request) { func handleDailySeed(w http.ResponseWriter, r *http.Request) {
seed, err := db.GetDailyRunSeed() seed, err := db.GetDailyRunSeed()
if err != nil { if err != nil {
@ -771,10 +365,7 @@ func handleDailySeed(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = w.Write([]byte(seed)) w.Write([]byte(seed))
if err != nil {
httpError(w, r, fmt.Errorf("failed to write seed: %s", err), http.StatusInternalServerError)
}
} }
func handleDailyRankings(w http.ResponseWriter, r *http.Request) { func handleDailyRankings(w http.ResponseWriter, r *http.Request) {
@ -804,7 +395,13 @@ func handleDailyRankings(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, rankings) err = json.NewEncoder(w).Encode(rankings)
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
} }
func handleDailyRankingPageCount(w http.ResponseWriter, r *http.Request) { func handleDailyRankingPageCount(w http.ResponseWriter, r *http.Request) {
@ -823,5 +420,5 @@ func handleDailyRankingPageCount(w http.ResponseWriter, r *http.Request) {
httpError(w, r, err, http.StatusInternalServerError) httpError(w, r, err, http.StatusInternalServerError)
} }
_, _ = w.Write([]byte(strconv.Itoa(count))) w.Write([]byte(strconv.Itoa(count)))
} }

View File

@ -38,34 +38,13 @@ func Get(uuid []byte, datatype, slot int) (any, error) {
return nil, err return nil, err
} }
// TODO this should be a transaction
compensations, err := db.FetchAndClaimAccountCompensations(uuid) compensations, err := db.FetchAndClaimAccountCompensations(uuid)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch compensations: %s", err) return nil, fmt.Errorf("failed to fetch compensations: %s", err)
} }
needsUpdate := false
for compensationType, amount := range compensations { for compensationType, amount := range compensations {
system.VoucherCounts[strconv.Itoa(compensationType)] += amount system.VoucherCounts[strconv.Itoa(compensationType)] += amount
if amount > 0 {
needsUpdate = true
}
}
if needsUpdate {
err = db.StoreSystemSaveData(uuid, system)
if err != nil {
return nil, fmt.Errorf("failed to update system save data: %s", err)
}
err = db.DeleteClaimedAccountCompensations(uuid)
if err != nil {
return nil, fmt.Errorf("failed to delete claimed compensations: %s", err)
}
err = db.UpdateAccountStats(uuid, system.GameStats, system.VoucherCounts)
if err != nil {
return nil, fmt.Errorf("failed to update account stats: %s", err)
}
} }
return system, nil return system, nil

View File

@ -20,11 +20,15 @@ package savedata
import ( import (
"fmt" "fmt"
"log" "log"
"strconv"
"github.com/klauspost/compress/zstd"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
"github.com/pagefaultgames/rogueserver/defs" "github.com/pagefaultgames/rogueserver/defs"
) )
var zstdEncoder, _ = zstd.NewWriter(nil)
// /savedata/update - update save data // /savedata/update - update save data
func Update(uuid []byte, slot int, save any) error { func Update(uuid []byte, slot int, save any) error {
err := db.UpdateAccountLastActivity(uuid) err := db.UpdateAccountLastActivity(uuid)
@ -58,6 +62,12 @@ func Update(uuid []byte, slot int, save any) error {
if slot < 0 || slot >= defs.SessionSlotCount { if slot < 0 || slot >= defs.SessionSlotCount {
return fmt.Errorf("slot id %d out of range", slot) return fmt.Errorf("slot id %d out of range", slot)
} }
filename := "session"
if slot != 0 {
filename += strconv.Itoa(slot)
}
return db.StoreSessionSaveData(uuid, save, slot) return db.StoreSessionSaveData(uuid, save, slot)
default: default:

View File

@ -32,19 +32,15 @@ var (
classicSessionCount int classicSessionCount int
) )
func scheduleStatRefresh() error { func scheduleStatRefresh() {
_, err := scheduler.AddFunc("@every 30s", func() { scheduler.AddFunc("@every 30s", func() {
err := updateStats() err := updateStats()
if err != nil { if err != nil {
log.Printf("failed to update stats: %s", err) log.Printf("failed to update stats: %s", err)
} }
}) })
if err != nil {
return err
}
scheduler.Start() scheduler.Start()
return nil
} }
func updateStats() error { func updateStats() error {

View File

@ -18,8 +18,6 @@
package db package db
import ( import (
"database/sql"
"errors"
"fmt" "fmt"
"slices" "slices"
@ -42,6 +40,11 @@ func AddAccountSession(username string, token []byte) error {
return err return err
} }
_, err = handle.Exec("UPDATE sessions s JOIN accounts a ON a.uuid = s.uuid SET s.active = 1 WHERE a.username = ? AND a.lastLoggedIn IS NULL", username)
if err != nil {
return err
}
_, err = handle.Exec("UPDATE accounts SET lastLoggedIn = UTC_TIMESTAMP() WHERE username = ?", username) _, err = handle.Exec("UPDATE accounts SET lastLoggedIn = UTC_TIMESTAMP() WHERE username = ?", username)
if err != nil { if err != nil {
return err return err
@ -210,25 +213,18 @@ func UpdateTrainerIds(trainerId, secretId int, uuid []byte) error {
return nil return nil
} }
func IsActiveSession(uuid []byte, clientSessionId string) (bool, error) { func IsActiveSession(token []byte) (bool, error) {
var storedId string var active int
err := handle.QueryRow("SELECT clientSessionId FROM activeClientSessions WHERE uuid = ?", uuid).Scan(&storedId) err := handle.QueryRow("SELECT `active` FROM sessions WHERE token = ?", token).Scan(&active)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) {
err = UpdateActiveSession(uuid, clientSessionId)
if err != nil {
return false, err
}
return true, nil
}
return false, err return false, err
} }
return storedId == "" || storedId == clientSessionId, nil return active == 1, nil
} }
func UpdateActiveSession(uuid []byte, clientSessionId string) error { func UpdateActiveSession(uuid []byte, token []byte) error {
_, err := handle.Exec("REPLACE INTO activeClientSessions VALUES (?, ?)", uuid, clientSessionId) _, err := handle.Exec("UPDATE sessions SET `active` = CASE WHEN token = ? THEN 1 ELSE 0 END WHERE uuid = ?", token, uuid)
if err != nil { if err != nil {
return err return err
} }

View File

@ -50,33 +50,57 @@ func Init(username, password, protocol, address, database string) error {
tx, err := handle.Begin() tx, err := handle.Begin()
if err != nil { if err != nil {
log.Fatal(err) panic(err)
} }
err = setupDb(tx) // accounts
if err != nil { tx.Exec("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)")
_ = tx.Rollback()
log.Fatal(err) // sessions
} tx.Exec("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)")
tx.Exec("CREATE INDEX IF NOT EXISTS sessionsByUuid ON sessions (uuid)")
// stats
tx.Exec("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)")
// compensations
tx.Exec("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)")
tx.Exec("CREATE INDEX IF NOT EXISTS accountCompensationsByUuid ON accountCompensations (uuid)")
// daily runs
tx.Exec("CREATE TABLE IF NOT EXISTS dailyRuns (date DATE NOT NULL PRIMARY KEY, seed CHAR(24) CHARACTER SET ascii COLLATE ascii_bin NOT NULL)")
tx.Exec("CREATE INDEX IF NOT EXISTS dailyRunsByDateAndSeed ON dailyRuns (date, seed)")
tx.Exec("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)")
tx.Exec("CREATE INDEX IF NOT EXISTS dailyRunCompletionsByUuidAndSeed ON dailyRunCompletions (uuid, seed)")
tx.Exec("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)")
tx.Exec("CREATE INDEX IF NOT EXISTS accountDailyRunsByDate ON accountDailyRuns (date)")
// save data
tx.Exec("CREATE TABLE IF NOT EXISTS systemSaveData (uuid BINARY(16) PRIMARY KEY, data LONGBLOB, timestamp TIMESTAMP)")
tx.Exec("CREATE TABLE IF NOT EXISTS sessionSaveData (uuid BINARY(16), slot TINYINT, data LONGBLOB, timestamp TIMESTAMP, PRIMARY KEY (uuid, slot))")
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
log.Fatal(err) panic(err)
} }
// TODO temp code // TODO temp code
_, err = os.Stat("userdata") _, err = os.Stat("userdata")
if err != nil { if err != nil {
if !os.IsNotExist(err) { // not found, do not migrate if os.IsNotExist(err) { // not found, do not migrate
log.Fatalf("failed to stat userdata directory: %s", err)
}
return nil return nil
} else {
log.Fatalf("failed to stat userdata directory: %s", err)
return err
}
} }
entries, err := os.ReadDir("userdata") entries, err := os.ReadDir("userdata")
if err != nil { if err != nil {
log.Fatal(err) log.Fatalln(err)
return nil
} }
for _, entry := range entries { for _, entry := range entries {
@ -107,6 +131,7 @@ func Init(username, password, protocol, address, database string) error {
err = StoreSystemSaveData(uuid, systemData) err = StoreSystemSaveData(uuid, systemData)
if err != nil { if err != nil {
log.Fatalf("failed to store system save data for %v: %s\n", uuidString, err) log.Fatalf("failed to store system save data for %v: %s\n", uuidString, err)
continue
} }
// delete old system data // delete old system data
@ -142,47 +167,3 @@ func Init(username, password, protocol, address, database string) error {
return nil return nil
} }
func setupDb(tx *sql.Tx) error {
queries := []string{
// MIGRATION 000
`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 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 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 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 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 INDEX IF NOT EXISTS accountDailyRunsByDate ON accountDailyRuns (date)`,
`CREATE TABLE IF NOT EXISTS systemSaveData (uuid BINARY(16) PRIMARY KEY, data LONGBLOB, timestamp TIMESTAMP, FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`,
`CREATE TABLE IF NOT EXISTS sessionSaveData (uuid BINARY(16), slot TINYINT, data LONGBLOB, timestamp TIMESTAMP, PRIMARY KEY (uuid, slot), FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`,
// ----------------------------------
// MIGRATION 001
`ALTER TABLE sessions DROP COLUMN IF EXISTS active`,
`CREATE TABLE IF NOT EXISTS activeClientSessions (uuid BINARY(16) NOT NULL PRIMARY KEY, clientSessionId VARCHAR(32) NOT NULL, FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`,
}
for _, q := range queries {
_, err := tx.Exec(q)
if err != nil {
return fmt.Errorf("failed to execute query: %w, query: %s", err, q)
}
}
return nil
}

View File

@ -29,7 +29,7 @@ func FetchPlayerCount() (int, error) {
func FetchBattleCount() (int, error) { func FetchBattleCount() (int, error) {
var battleCount int var battleCount int
err := handle.QueryRow("SELECT COALESCE(SUM(s.battles), 0) FROM accountStats s JOIN accounts a ON a.uuid = s.uuid WHERE a.banned = 0").Scan(&battleCount) err := handle.QueryRow("SELECT COALESCE(SUM(battles), 0) FROM accountStats").Scan(&battleCount)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -39,7 +39,7 @@ func FetchBattleCount() (int, error) {
func FetchClassicSessionCount() (int, error) { func FetchClassicSessionCount() (int, error) {
var classicSessionCount int var classicSessionCount int
err := handle.QueryRow("SELECT COALESCE(SUM(s.classicSessionsPlayed), 0) FROM accountStats s JOIN accounts a ON a.uuid = s.uuid WHERE a.banned = 0").Scan(&classicSessionCount) err := handle.QueryRow("SELECT COALESCE(SUM(classicSessionsPlayed), 0) FROM accountStats").Scan(&classicSessionCount)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -1,26 +1,17 @@
services: services:
server: server:
command: --debug --dbaddr db --dbuser pokerogue --dbpass pokerogue --dbname pokeroguedb image: ghcr.io/pagefaultgames/pokerogue:latest
image: ghcr.io/pagefaultgames/rogueserver:master command: --debug --dbaddr db:3306 --dbuser pokerogue --dbpass pokerogue --dbname pokeroguedb
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
db: - db
condition: service_healthy
networks: networks:
- internal - internal
ports: ports:
- "8001:8001" - "8001:8001"
db: db:
image: mariadb:11 image: mariadb:11
restart: unless-stopped restart: unless-stopped
healthcheck:
test: [ "CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized" ]
start_period: 10s
start_interval: 10s
interval: 1m
timeout: 5s
retries: 3
environment: environment:
MYSQL_ROOT_PASSWORD: admin MYSQL_ROOT_PASSWORD: admin
MYSQL_DATABASE: pokeroguedb MYSQL_DATABASE: pokeroguedb
@ -28,24 +19,6 @@ services:
MYSQL_PASSWORD: pokerogue MYSQL_PASSWORD: pokerogue
volumes: volumes:
- database:/var/lib/mysql - database:/var/lib/mysql
networks:
- internal
# Watchtower is a service that will automatically update your running containers
# when a new image is available. This is useful for keeping your server up-to-date.
# see https://containrrr.dev/watchtower/ for more information.
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: always
security_opt:
- no-new-privileges:true
environment:
WATCHTOWER_CLEANUP: true
WATCHTOWER_SCHEDULE: "@midnight"
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock
volumes: volumes:
database: database:

View File

@ -65,9 +65,7 @@ func main() {
mux := http.NewServeMux() mux := http.NewServeMux()
// init api // init api
if err := api.Init(mux); err != nil { api.Init(mux)
log.Fatal(err)
}
// start web server // start web server
handler := prodHandler(mux) handler := prodHandler(mux)
@ -96,10 +94,7 @@ func createListener(proto, addr string) (net.Listener, error) {
} }
if proto == "unix" { if proto == "unix" {
if err := os.Chmod(addr, 0777); err != nil { os.Chmod(addr, 0777)
listener.Close()
return nil, err
}
} }
return listener, nil return listener, nil