Serve game content and API

pull/4/head
maru 7 months ago
parent a36a8f5be9
commit 6bd7deb3de
No known key found for this signature in database
GPG Key ID: 37689350E9CD0F0D

1
.gitignore vendored

@ -3,3 +3,4 @@
pokerogue-server* pokerogue-server*
userdata/* userdata/*
secret.key secret.key
www/

@ -10,9 +10,31 @@ import (
"github.com/pagefaultgames/pokerogue-server/db" "github.com/pagefaultgames/pokerogue-server/db"
) )
func Init() { func Init(mux *http.ServeMux) {
scheduleStatRefresh() scheduleStatRefresh()
daily.Init() daily.Init()
// account
mux.HandleFunc("GET /api/account/info", handleAccountInfo)
mux.HandleFunc("POST /api/account/register", handleAccountRegister)
mux.HandleFunc("POST /api/account/login", handleAccountLogin)
mux.HandleFunc("GET /api/account/logout", handleAccountLogout)
// game
mux.HandleFunc("GET /api/game/playercount", handleGamePlayerCount)
mux.HandleFunc("GET /api/game/titlestats", handleGameTitleStats)
mux.HandleFunc("GET /api/game/classicsessioncount", handleGameClassicSessionCount)
// savedata
mux.HandleFunc("GET /api/savedata/get", handleSaveData)
mux.HandleFunc("POST /api/savedata/update", handleSaveData)
mux.HandleFunc("GET /api/savedata/delete", handleSaveData)
mux.HandleFunc("POST /api/savedata/clear", handleSaveData)
// daily
mux.HandleFunc("GET /api/daily/seed", handleDailySeed)
mux.HandleFunc("GET /api/daily/rankings", handleDailyRankings)
mux.HandleFunc("GET /api/daily/rankingpagecount", handleDailyRankingPageCount)
} }
func getUsernameFromRequest(r *http.Request) (string, error) { func getUsernameFromRequest(r *http.Request) (string, error) {

@ -7,7 +7,6 @@ import (
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
"sync"
"github.com/pagefaultgames/pokerogue-server/api/account" "github.com/pagefaultgames/pokerogue-server/api/account"
"github.com/pagefaultgames/pokerogue-server/api/daily" "github.com/pagefaultgames/pokerogue-server/api/daily"
@ -15,262 +14,252 @@ import (
"github.com/pagefaultgames/pokerogue-server/defs" "github.com/pagefaultgames/pokerogue-server/defs"
) )
type Server struct {
Debug bool
Exit *sync.RWMutex
}
/* /*
The caller of endpoint handler functions are responsible for extracting the necessary data from the request. The caller of endpoint handler functions are responsible for extracting the necessary data from the request.
Handler functions are responsible for checking the validity of this data and returning a result or error. Handler functions are responsible for checking the validity of this data and returning a result or error.
Handlers should not return serialized JSON, instead return the struct itself. Handlers should not return serialized JSON, instead return the struct itself.
*/ */
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func handleAccountInfo(w http.ResponseWriter, r *http.Request) {
// kind of misusing the RWMutex but it doesn't matter username, err := getUsernameFromRequest(r)
s.Exit.RLock() if err != nil {
defer s.Exit.RUnlock() httpError(w, r, err, http.StatusBadRequest)
return
}
if s.Debug { uuid, err := getUUIDFromRequest(r) // lazy
w.Header().Set("Access-Control-Allow-Headers", "*") if err != nil {
w.Header().Set("Access-Control-Allow-Methods", "*") httpError(w, r, err, http.StatusBadRequest)
w.Header().Set("Access-Control-Allow-Origin", "*") return
}
if r.Method == "OPTIONS" { response, err := account.Info(username, uuid)
w.WriteHeader(http.StatusOK) if err != nil {
return httpError(w, r, err, http.StatusInternalServerError)
} return
} }
switch r.URL.Path { err = json.NewEncoder(w).Encode(response)
// /account if err != nil {
case "/account/info": httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
username, err := getUsernameFromRequest(r) return
if err != nil { }
httpError(w, r, err, http.StatusBadRequest) }
return
}
uuid, err := getUUIDFromRequest(r) // lazy func handleAccountRegister(w http.ResponseWriter, r *http.Request) {
if err != nil { err := r.ParseForm()
httpError(w, r, err, http.StatusBadRequest) if err != nil {
return httpError(w, r, fmt.Errorf("failed to parse request form: %s", err), http.StatusBadRequest)
} return
}
response, err := account.Info(username, uuid) err = account.Register(r.Form.Get("username"), r.Form.Get("password"))
if err != nil { if err != nil {
httpError(w, r, err, http.StatusInternalServerError) httpError(w, r, err, http.StatusInternalServerError)
return return
} }
err = json.NewEncoder(w).Encode(response) w.WriteHeader(http.StatusOK)
if err != nil { }
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
case "/account/register":
err := r.ParseForm()
if err != nil {
httpError(w, r, fmt.Errorf("failed to parse request form: %s", err), http.StatusBadRequest)
return
}
err = account.Register(r.Form.Get("username"), r.Form.Get("password")) func handleAccountLogin(w http.ResponseWriter, r *http.Request) {
if err != nil { err := r.ParseForm()
httpError(w, r, err, http.StatusInternalServerError) if err != nil {
return httpError(w, r, fmt.Errorf("failed to parse request form: %s", err), http.StatusBadRequest)
} return
}
w.WriteHeader(http.StatusOK) response, err := account.Login(r.Form.Get("username"), r.Form.Get("password"))
case "/account/login": if err != nil {
err := r.ParseForm() httpError(w, r, err, http.StatusInternalServerError)
if err != nil { return
httpError(w, r, fmt.Errorf("failed to parse request form: %s", err), http.StatusBadRequest) }
return
}
response, err := account.Login(r.Form.Get("username"), r.Form.Get("password")) err = json.NewEncoder(w).Encode(response)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusInternalServerError) httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return return
} }
}
err = json.NewEncoder(w).Encode(response) func handleAccountLogout(w http.ResponseWriter, r *http.Request) {
if err != nil { token, err := base64.StdEncoding.DecodeString(r.Header.Get("Authorization"))
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError) if err != nil {
return httpError(w, r, fmt.Errorf("failed to decode token: %s", err), http.StatusBadRequest)
} return
case "/account/logout": }
token, err := base64.StdEncoding.DecodeString(r.Header.Get("Authorization"))
if err != nil {
httpError(w, r, fmt.Errorf("failed to decode token: %s", err), http.StatusBadRequest)
return
}
err = account.Logout(token) err = account.Logout(token)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusInternalServerError) httpError(w, r, err, http.StatusInternalServerError)
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
}
// /game func handleGamePlayerCount(w http.ResponseWriter, r *http.Request) {
case "/game/playercount": w.Write([]byte(strconv.Itoa(playerCount)))
w.Write([]byte(strconv.Itoa(playerCount))) }
case "/game/titlestats":
err := json.NewEncoder(w).Encode(defs.TitleStats{ func handleGameTitleStats(w http.ResponseWriter, r *http.Request) {
PlayerCount: playerCount, err := json.NewEncoder(w).Encode(defs.TitleStats{
BattleCount: battleCount, PlayerCount: playerCount,
}) BattleCount: battleCount,
})
if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
}
func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(strconv.Itoa(classicSessionCount)))
}
func handleSaveData(w http.ResponseWriter, r *http.Request) {
uuid, err := getUUIDFromRequest(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 { if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError) httpError(w, r, err, http.StatusBadRequest)
return return
} }
case "/game/classicsessioncount": }
w.Write([]byte(strconv.Itoa(classicSessionCount)))
// /savedata var slot int
case "/savedata/get", "/savedata/update", "/savedata/delete", "/savedata/clear": if r.URL.Query().Has("slot") {
uuid, err := getUUIDFromRequest(r) slot, err = strconv.Atoi(r.URL.Query().Get("slot"))
if err != nil { if err != nil {
httpError(w, r, err, http.StatusBadRequest) httpError(w, r, err, http.StatusBadRequest)
return return
} }
}
datatype := -1 var save any
if r.URL.Query().Has("datatype") { // /savedata/get and /savedata/delete specify datatype, but don't expect data in body
datatype, err = strconv.Atoi(r.URL.Query().Get("datatype")) if r.URL.Path != "/api/savedata/get" && r.URL.Path != "/api/savedata/delete" {
if datatype == 0 {
var system defs.SystemSaveData
err = json.NewDecoder(r.Body).Decode(&system)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusBadRequest) httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
return return
} }
}
var slot int save = system
if r.URL.Query().Has("slot") { // /savedata/clear doesn't specify datatype, it is assumed to be 1 (session)
slot, err = strconv.Atoi(r.URL.Query().Get("slot")) } else if datatype == 1 || r.URL.Path == "/api/savedata/clear" {
var session defs.SessionSaveData
err = json.NewDecoder(r.Body).Decode(&session)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusBadRequest) httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
return return
} }
}
var save any save = session
// /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 datatype == 0 {
var system defs.SystemSaveData
err = json.NewDecoder(r.Body).Decode(&system)
if err != nil {
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
return
}
save = system
// /savedata/clear doesn't specify datatype, it is assumed to be 1 (session)
} else if datatype == 1 || r.URL.Path == "/savedata/clear" {
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
}
} }
}
switch r.URL.Path { switch r.URL.Path {
case "/savedata/get": case "/api/savedata/get":
save, err = savedata.Get(uuid, datatype, slot) save, err = savedata.Get(uuid, datatype, slot)
case "/savedata/update": case "/api/savedata/update":
err = savedata.Update(uuid, slot, save) err = savedata.Update(uuid, slot, save)
case "/savedata/delete": case "/api/savedata/delete":
err = savedata.Delete(uuid, datatype, slot) err = savedata.Delete(uuid, datatype, slot)
case "/savedata/clear": case "/api/savedata/clear":
s, ok := save.(defs.SessionSaveData) s, ok := save.(defs.SessionSaveData)
if !ok { if !ok {
httpError(w, r, fmt.Errorf("save data is not type SessionSaveData"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("save data is not type SessionSaveData"), http.StatusBadRequest)
return
}
// doesn't return a save, but it works
save, err = savedata.Clear(uuid, slot, daily.Seed(), s)
}
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return return
} }
if save == nil || r.URL.Path == "/savedata/update" { // doesn't return a save, but it works
w.WriteHeader(http.StatusOK) save, err = savedata.Clear(uuid, slot, daily.Seed(), s)
return }
} if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
return
}
err = json.NewEncoder(w).Encode(save) if save == nil || r.URL.Path == "/api/savedata/update" {
if err != nil { w.WriteHeader(http.StatusOK)
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError) return
return }
}
// /daily err = json.NewEncoder(w).Encode(save)
case "/daily/seed": if err != nil {
w.Write([]byte(daily.Seed())) httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
case "/daily/rankings": return
uuid, err := getUUIDFromRequest(r) }
if err != nil { }
httpError(w, r, err, http.StatusBadRequest)
return
}
var category int func handleDailySeed(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Has("category") { w.Write([]byte(daily.Seed()))
category, err = strconv.Atoi(r.URL.Query().Get("category")) }
if err != nil {
httpError(w, r, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest)
return
}
}
page := 1 func handleDailyRankings(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Has("page") { uuid, err := getUUIDFromRequest(r)
page, err = strconv.Atoi(r.URL.Query().Get("page")) if err != nil {
if err != nil { httpError(w, r, err, http.StatusBadRequest)
httpError(w, r, fmt.Errorf("failed to convert page: %s", err), http.StatusBadRequest) return
return }
}
}
rankings, err := daily.Rankings(uuid, category, page) var category int
if r.URL.Query().Has("category") {
category, err = strconv.Atoi(r.URL.Query().Get("category"))
if err != nil { if err != nil {
httpError(w, r, err, http.StatusInternalServerError) httpError(w, r, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest)
return return
} }
}
err = json.NewEncoder(w).Encode(rankings) page := 1
if r.URL.Query().Has("page") {
page, err = strconv.Atoi(r.URL.Query().Get("page"))
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError) httpError(w, r, fmt.Errorf("failed to convert page: %s", err), http.StatusBadRequest)
return return
} }
case "/daily/rankingpagecount": }
var category int
if r.URL.Query().Has("category") { rankings, err := daily.Rankings(uuid, category, page)
var err error if err != nil {
category, err = strconv.Atoi(r.URL.Query().Get("category")) httpError(w, r, err, http.StatusInternalServerError)
if err != nil { return
httpError(w, r, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest) }
return
} err = json.NewEncoder(w).Encode(rankings)
} if err != nil {
httpError(w, r, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
return
}
}
count, err := daily.RankingPageCount(category) func handleDailyRankingPageCount(w http.ResponseWriter, r *http.Request) {
var category int
if r.URL.Query().Has("category") {
var err error
category, err = strconv.Atoi(r.URL.Query().Get("category"))
if err != nil { if err != nil {
httpError(w, r, err, http.StatusInternalServerError) httpError(w, r, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest)
return
} }
}
w.Write([]byte(strconv.Itoa(count))) count, err := daily.RankingPageCount(category)
if err != nil {
httpError(w, r, err, http.StatusInternalServerError)
} }
w.Write([]byte(strconv.Itoa(count)))
} }
func httpError(w http.ResponseWriter, r *http.Request, err error, code int) { func httpError(w http.ResponseWriter, r *http.Request, err error, code int) {

@ -4,12 +4,7 @@ import (
"encoding/gob" "encoding/gob"
"flag" "flag"
"log" "log"
"net"
"net/http" "net/http"
"os"
"os/signal"
"sync"
"syscall"
"github.com/pagefaultgames/pokerogue-server/api" "github.com/pagefaultgames/pokerogue-server/api"
"github.com/pagefaultgames/pokerogue-server/db" "github.com/pagefaultgames/pokerogue-server/db"
@ -17,10 +12,10 @@ import (
func main() { func main() {
// flag stuff // flag stuff
debug := flag.Bool("debug", false, "debug mode") addr := flag.String("addr", "0.0.0.0:80", "network address for api to listen on")
wwwpath := flag.String("wwwpath", "www", "path to static content to serve")
proto := flag.String("proto", "tcp", "protocol for api to use (tcp, unix)") tlscert := flag.String("tlscert", "", "path to tls certificate to use for https")
addr := flag.String("addr", "0.0.0.0", "network address for api to listen on") tlskey := flag.String("tlskey", "", "path to tls private key to use for https")
dbuser := flag.String("dbuser", "pokerogue", "database username") dbuser := flag.String("dbuser", "pokerogue", "database username")
dbpass := flag.String("dbpass", "", "database password") dbpass := flag.String("dbpass", "", "database password")
@ -30,7 +25,6 @@ func main() {
flag.Parse() flag.Parse()
// register gob types // register gob types
gob.Register([]interface{}{}) gob.Register([]interface{}{})
gob.Register(map[string]interface{}{}) gob.Register(map[string]interface{}{})
@ -41,55 +35,19 @@ func main() {
log.Fatalf("failed to initialize database: %s", err) log.Fatalf("failed to initialize database: %s", err)
} }
// create listener // start web server
listener, err := createListener(*proto, *addr) mux := http.NewServeMux()
if err != nil {
log.Fatalf("failed to create net listener: %s", err)
}
// create exit handler
var exit sync.RWMutex
createExitHandler(&exit)
// init api api.Init(mux)
api.Init()
// start web server mux.Handle("/", http.FileServer(http.Dir(*wwwpath)))
err = http.Serve(listener, &api.Server{Debug: *debug, Exit: &exit})
if err != nil {
log.Fatalf("failed to create http server or server errored: %s", err)
}
}
func createListener(proto, addr string) (net.Listener, error) { if *tlscert != "" && *tlskey != "" {
if proto == "unix" { err = http.ListenAndServeTLS(*addr, *tlscert, *tlskey, mux)
os.Remove(addr) } else {
err = http.ListenAndServe(*addr, mux)
} }
listener, err := net.Listen(proto, addr)
if err != nil { if err != nil {
return nil, err log.Fatalf("failed to create http server or server errored: %s", err)
}
if proto == "unix" {
os.Chmod(addr, 0777)
} }
return listener, nil
}
func createExitHandler(mtx *sync.RWMutex) {
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGINT, syscall.SIGTERM)
go func() {
// wait for exit signal of some kind
<-s
// block new requests and wait for existing ones to finish
mtx.Lock()
// bail
os.Exit(0)
}()
} }

Loading…
Cancel
Save