2024-04-29 17:26:46 -04:00
/ *
Copyright ( C ) 2024 Pagefault Games
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2024-04-29 15:32:58 -04:00
2023-12-05 13:28:08 -05:00
package api
2023-12-31 17:39:11 -05:00
import (
2024-05-09 14:26:54 -04:00
"database/sql"
2024-07-27 23:41:44 +01:00
"encoding/base64"
2024-04-08 20:44:36 -04:00
"encoding/json"
2024-05-12 20:05:46 +02:00
"errors"
2024-04-08 20:44:36 -04:00
"fmt"
2024-09-14 03:32:49 +01:00
"log"
2023-12-31 17:39:11 -05:00
"net/http"
2024-04-08 20:44:36 -04:00
"strconv"
2024-07-27 23:41:44 +01:00
"strings"
"time"
2024-04-08 20:44:36 -04:00
2024-04-29 15:22:27 -04:00
"github.com/pagefaultgames/rogueserver/api/account"
"github.com/pagefaultgames/rogueserver/api/daily"
"github.com/pagefaultgames/rogueserver/api/savedata"
"github.com/pagefaultgames/rogueserver/db"
"github.com/pagefaultgames/rogueserver/defs"
2023-12-31 17:39:11 -05:00
)
2023-12-29 14:30:47 -05:00
2024-04-08 20:44:36 -04:00
/ *
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 .
Handlers should not return serialized JSON , instead return the struct itself .
* /
2024-04-24 20:05:57 -04:00
// account
2023-12-29 14:30:47 -05:00
2024-04-24 20:05:57 -04:00
func handleAccountInfo ( w http . ResponseWriter , r * http . Request ) {
2024-05-05 16:12:10 -04:00
uuid , err := uuidFromRequest ( r )
2024-04-24 20:05:57 -04:00
if err != nil {
2024-06-07 23:16:27 -04:00
httpError ( w , r , err , http . StatusUnauthorized )
2024-04-24 20:05:57 -04:00
return
2024-04-19 03:27:47 -04:00
}
2024-04-08 20:44:36 -04:00
2024-05-05 16:12:10 -04:00
username , err := db . FetchUsernameFromUUID ( uuid )
2024-04-24 20:05:57 -04:00
if err != nil {
2024-05-05 16:12:10 -04:00
httpError ( w , r , err , http . StatusInternalServerError )
2024-04-24 20:05:57 -04:00
return
}
2024-07-27 23:41:44 +01:00
discordId , err := db . FetchDiscordIdByUsername ( username )
if err != nil {
if ! errors . Is ( err , sql . ErrNoRows ) {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
}
googleId , err := db . FetchGoogleIdByUsername ( username )
if err != nil {
if ! errors . Is ( err , sql . ErrNoRows ) {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
}
2024-09-14 03:32:49 +01:00
var hasAdminRole bool
if discordId != "" {
hasAdminRole , _ = account . IsUserDiscordAdmin ( discordId , account . DiscordGuildID )
}
response , err := account . Info ( username , discordId , googleId , uuid , hasAdminRole )
2024-04-24 20:05:57 -04:00
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-05-23 14:15:44 -04:00
writeJSON ( w , r , response )
2024-04-24 20:05:57 -04:00
}
2024-04-20 16:58:04 -04:00
2024-04-24 20:05:57 -04:00
func handleAccountRegister ( w http . ResponseWriter , r * http . Request ) {
err := r . ParseForm ( )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to parse request form: %s" , err ) , http . StatusBadRequest )
return
}
2024-04-08 20:44:36 -04:00
2024-04-24 20:05:57 -04:00
err = account . Register ( r . Form . Get ( "username" ) , r . Form . Get ( "password" ) )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2023-12-29 14:30:47 -05:00
2024-04-24 20:05:57 -04:00
w . WriteHeader ( http . StatusOK )
}
2024-04-08 20:44:36 -04:00
2024-04-24 20:05:57 -04:00
func handleAccountLogin ( w http . ResponseWriter , r * http . Request ) {
err := r . ParseForm ( )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to parse request form: %s" , err ) , http . StatusBadRequest )
return
}
2024-04-19 03:27:47 -04:00
2024-04-24 20:05:57 -04:00
response , err := account . Login ( r . Form . Get ( "username" ) , r . Form . Get ( "password" ) )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-04-20 16:58:04 -04:00
2024-05-23 14:15:44 -04:00
writeJSON ( w , r , response )
2024-04-24 20:05:57 -04:00
}
2024-04-19 03:27:47 -04:00
2024-04-28 17:27:58 -04:00
func handleAccountChangePW ( w http . ResponseWriter , r * http . Request ) {
err := r . ParseForm ( )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to parse request form: %s" , err ) , http . StatusBadRequest )
return
}
uuid , err := uuidFromRequest ( r )
if err != nil {
2024-06-07 23:16:27 -04:00
httpError ( w , r , err , http . StatusUnauthorized )
2024-04-28 17:27:58 -04:00
return
}
err = account . ChangePW ( uuid , r . Form . Get ( "password" ) )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusOK )
}
2024-04-24 20:05:57 -04:00
func handleAccountLogout ( w http . ResponseWriter , r * http . Request ) {
2024-04-25 15:31:53 -04:00
token , err := tokenFromRequest ( r )
2024-04-24 20:05:57 -04:00
if err != nil {
2024-04-25 15:31:53 -04:00
httpError ( w , r , err , http . StatusBadRequest )
2024-04-24 20:05:57 -04:00
return
}
2024-04-19 03:27:47 -04:00
2024-04-24 20:05:57 -04:00
err = account . Logout ( token )
if err != nil {
2024-06-07 23:16:27 -04:00
// also possible for InternalServerError but that's unlikely unless the server blew up
httpError ( w , r , err , http . StatusUnauthorized )
2024-04-24 20:05:57 -04:00
return
}
2024-04-20 16:58:04 -04:00
2024-04-24 20:05:57 -04:00
w . WriteHeader ( http . StatusOK )
}
2024-03-23 21:34:18 -04:00
2024-04-24 20:05:57 -04:00
// game
func handleGameTitleStats ( w http . ResponseWriter , r * http . Request ) {
2024-05-12 20:05:46 +02:00
stats := defs . TitleStats {
2024-04-24 20:05:57 -04:00
PlayerCount : playerCount ,
BattleCount : battleCount ,
}
2024-04-08 20:44:36 -04:00
2024-05-23 14:15:44 -04:00
writeJSON ( w , r , stats )
2024-04-24 20:05:57 -04:00
}
func handleGameClassicSessionCount ( w http . ResponseWriter , r * http . Request ) {
2024-06-02 18:15:24 -04:00
w . Write ( [ ] byte ( strconv . Itoa ( classicSessionCount ) ) )
2024-04-24 20:05:57 -04:00
}
2024-06-07 22:23:02 -04:00
func handleSession ( w http . ResponseWriter , r * http . Request ) {
2024-05-14 14:30:04 +02:00
uuid , err := uuidFromRequest ( r )
2024-05-14 12:54:06 +02:00
if err != nil {
2024-06-07 23:16:27 -04:00
httpError ( w , r , err , http . StatusUnauthorized )
2024-05-14 12:54:06 +02:00
return
}
2024-06-07 22:38:24 -04:00
slot , err := strconv . Atoi ( r . URL . Query ( ) . Get ( "slot" ) )
if err != nil {
httpError ( w , r , err , http . StatusBadRequest )
return
}
if slot < 0 || slot >= defs . SessionSlotCount {
httpError ( w , r , fmt . Errorf ( "slot id %d out of range" , slot ) , http . StatusBadRequest )
2024-06-07 23:11:09 -04:00
return
2024-05-14 12:54:06 +02:00
}
2024-06-02 18:23:51 -04:00
if ! r . URL . Query ( ) . Has ( "clientSessionId" ) {
2024-05-14 12:54:06 +02:00
httpError ( w , r , fmt . Errorf ( "missing clientSessionId" ) , http . StatusBadRequest )
2024-06-02 18:23:51 -04:00
return
2024-05-14 12:54:06 +02:00
}
2024-06-02 18:23:51 -04:00
err = db . UpdateActiveSession ( uuid , r . URL . Query ( ) . Get ( "clientSessionId" ) )
2024-05-14 12:54:06 +02:00
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to update active session: %s" , err ) , http . StatusBadRequest )
return
}
2024-06-09 20:03:27 -04:00
switch r . PathValue ( "action" ) {
case "get" :
2024-06-07 22:23:02 -04:00
save , err := savedata . GetSession ( uuid , slot )
if errors . Is ( err , sql . ErrNoRows ) {
http . Error ( w , err . Error ( ) , http . StatusNotFound )
return
}
2024-06-07 22:38:24 -04:00
2024-06-07 22:23:02 -04:00
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-06-07 22:38:24 -04:00
2024-06-07 22:23:02 -04:00
writeJSON ( w , r , save )
2024-06-09 20:03:27 -04:00
case "update" :
2024-06-07 22:23:02 -04:00
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
}
2024-05-14 12:54:06 +02:00
2024-06-22 22:28:14 +01:00
existingSave , err := savedata . GetSession ( uuid , slot )
if err != nil && ! errors . Is ( err , sql . ErrNoRows ) {
httpError ( w , r , fmt . Errorf ( "failed to retrieve session save data: %s" , err ) , http . StatusInternalServerError )
return
} else {
if existingSave . Seed == session . Seed && existingSave . WaveIndex > session . WaveIndex {
httpError ( w , r , fmt . Errorf ( "session out of date: existing wave index is greater" ) , http . StatusBadRequest )
return
}
}
2024-06-15 22:21:14 -04:00
err = savedata . UpdateSession ( uuid , slot , session )
2024-06-07 22:23:02 -04:00
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to put session data: %s" , err ) , http . StatusInternalServerError )
return
}
2024-05-14 12:54:06 +02:00
2024-06-09 20:03:27 -04:00
w . WriteHeader ( http . StatusOK )
case "clear" :
var session defs . SessionSaveData
err = json . NewDecoder ( r . Body ) . Decode ( & session )
2024-06-07 22:23:02 -04:00
if err != nil {
2024-06-09 20:03:27 -04:00
httpError ( w , r , fmt . Errorf ( "failed to decode request body: %s" , err ) , http . StatusBadRequest )
2024-06-07 22:23:02 -04:00
return
}
2024-06-09 20:03:27 -04:00
seed , err := db . GetDailyRunSeed ( )
2024-05-12 20:42:07 +02:00
if err != nil {
2024-06-09 20:03:27 -04:00
httpError ( w , r , err , http . StatusInternalServerError )
2024-05-12 20:42:07 +02:00
return
}
2024-06-09 20:03:27 -04:00
resp , err := savedata . Clear ( uuid , slot , seed , session )
2024-05-12 20:42:07 +02:00
if err != nil {
2024-06-09 20:03:27 -04:00
httpError ( w , r , err , http . StatusInternalServerError )
2024-05-12 20:42:07 +02:00
return
}
2024-06-09 20:03:27 -04:00
writeJSON ( w , r , resp )
case "newclear" :
resp , err := savedata . NewClear ( uuid , slot )
2024-05-12 20:42:07 +02:00
if err != nil {
2024-06-09 20:03:27 -04:00
httpError ( w , r , fmt . Errorf ( "failed to read new clear: %s" , err ) , http . StatusInternalServerError )
2024-05-12 20:42:07 +02:00
return
}
2024-06-09 20:03:27 -04:00
writeJSON ( w , r , resp )
case "delete" :
err := savedata . DeleteSession ( uuid , slot )
2024-05-12 20:42:07 +02:00
if err != nil {
2024-06-09 20:03:27 -04:00
httpError ( w , r , err , http . StatusInternalServerError )
2024-05-12 20:42:07 +02:00
return
}
2024-06-09 20:03:27 -04:00
w . WriteHeader ( http . StatusOK )
2024-06-15 22:21:14 -04:00
default :
httpError ( w , r , fmt . Errorf ( "unknown action" ) , http . StatusBadRequest )
2024-04-24 20:05:57 -04:00
return
}
}
2024-04-08 20:44:36 -04:00
2024-05-12 03:34:08 +02:00
type CombinedSaveData struct {
2024-05-14 12:54:06 +02:00
System defs . SystemSaveData ` json:"system" `
Session defs . SessionSaveData ` json:"session" `
SessionSlotId int ` json:"sessionSlotId" `
ClientSessionId string ` json:"clientSessionId" `
2024-05-12 03:34:08 +02:00
}
2024-05-12 03:42:35 +02:00
// TODO wrap this in a transaction
2024-05-12 20:42:07 +02:00
func handleUpdateAll ( w http . ResponseWriter , r * http . Request ) {
2024-05-14 14:30:04 +02:00
uuid , err := uuidFromRequest ( r )
2024-05-12 03:34:08 +02:00
if err != nil {
2024-06-07 23:16:27 -04:00
httpError ( w , r , err , http . StatusUnauthorized )
2024-05-12 03:34:08 +02:00
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
}
2024-06-07 18:05:41 -04:00
2024-05-15 04:08:42 +02:00
if data . ClientSessionId == "" {
2024-06-15 22:21:14 -04:00
httpError ( w , r , fmt . Errorf ( "missing clientSessionId" ) , http . StatusBadRequest )
return
2024-05-15 04:08:42 +02:00
}
2024-05-12 03:34:08 +02:00
var active bool
2024-05-15 04:07:47 +02:00
active , err = db . IsActiveSession ( uuid , data . ClientSessionId )
2024-05-12 03:34:08 +02:00
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to check active session: %s" , err ) , http . StatusBadRequest )
return
}
if ! active {
2024-05-15 07:28:49 +02:00
httpError ( w , r , fmt . Errorf ( "session out of date: not active" ) , http . StatusBadRequest )
2024-05-12 03:34:08 +02:00
return
}
storedTrainerId , storedSecretId , err := db . FetchTrainerIds ( uuid )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
if storedTrainerId > 0 || storedSecretId > 0 {
2024-06-07 18:05:41 -04:00
if data . System . TrainerId != storedTrainerId || data . System . SecretId != storedSecretId {
2024-05-15 07:28:49 +02:00
httpError ( w , r , fmt . Errorf ( "session out of date: stored trainer or secret ID does not match" ) , http . StatusBadRequest )
2024-05-12 03:34:08 +02:00
return
}
} else {
2024-06-07 18:05:41 -04:00
err = db . UpdateTrainerIds ( data . System . TrainerId , data . System . SecretId , uuid )
if err != nil {
2024-05-12 03:34:08 +02:00
httpError ( w , r , err , http . StatusInternalServerError )
return
}
}
2024-06-22 22:28:14 +01:00
existingPlaytime , err := db . RetrievePlaytime ( uuid )
if err != nil && ! errors . Is ( err , sql . ErrNoRows ) {
httpError ( w , r , fmt . Errorf ( "failed to retrieve playtime: %s" , err ) , http . StatusInternalServerError )
return
} else {
playtime , ok := data . System . GameStats . ( map [ string ] interface { } ) [ "playTime" ] . ( float64 )
if ! ok {
httpError ( w , r , fmt . Errorf ( "no playtime found" ) , http . StatusBadRequest )
return
}
if float64 ( existingPlaytime ) > playtime {
httpError ( w , r , fmt . Errorf ( "session out of date: existing playtime is greater" ) , http . StatusBadRequest )
return
}
}
existingSave , err := savedata . GetSession ( uuid , data . SessionSlotId )
if err != nil && ! errors . Is ( err , sql . ErrNoRows ) {
httpError ( w , r , fmt . Errorf ( "failed to retrieve session save data: %s" , err ) , http . StatusInternalServerError )
return
} else {
if existingSave . Seed == data . Session . Seed && existingSave . WaveIndex > data . Session . WaveIndex {
httpError ( w , r , fmt . Errorf ( "session out of date: existing wave index is greater" ) , http . StatusBadRequest )
return
}
}
2024-05-12 03:34:08 +02:00
err = savedata . Update ( uuid , data . SessionSlotId , data . Session )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-06-07 18:05:41 -04:00
2024-05-12 03:34:08 +02:00
err = savedata . Update ( uuid , 0 , data . System )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-06-07 18:05:41 -04:00
2024-05-12 03:34:08 +02:00
w . WriteHeader ( http . StatusOK )
}
2024-05-15 00:00:38 +02:00
type SystemVerifyResponse struct {
2024-06-09 20:03:27 -04:00
Valid bool ` json:"valid" `
SystemData defs . SystemSaveData ` json:"systemData" `
2024-05-14 12:54:06 +02:00
}
2024-06-07 22:23:02 -04:00
func handleSystem ( w http . ResponseWriter , r * http . Request ) {
2024-05-14 14:30:04 +02:00
uuid , err := uuidFromRequest ( r )
2024-05-14 12:54:06 +02:00
if err != nil {
2024-06-07 23:16:27 -04:00
httpError ( w , r , err , http . StatusUnauthorized )
2024-05-14 12:54:06 +02:00
return
}
2024-06-10 14:56:15 -04:00
var active bool
2024-06-15 22:21:14 -04:00
if ! r . URL . Query ( ) . Has ( "clientSessionId" ) {
httpError ( w , r , fmt . Errorf ( "missing clientSessionId" ) , http . StatusBadRequest )
return
}
active , err = db . IsActiveSession ( uuid , r . URL . Query ( ) . Get ( "clientSessionId" ) )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to check active session: %s" , err ) , http . StatusBadRequest )
return
2024-05-14 12:54:06 +02:00
}
2024-06-09 20:03:27 -04:00
switch r . PathValue ( "action" ) {
case "get" :
if ! active {
err = db . UpdateActiveSession ( uuid , r . URL . Query ( ) . Get ( "clientSessionId" ) )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to update active session: %s" , err ) , http . StatusBadRequest )
return
}
}
2024-06-07 22:23:02 -04:00
save , err := savedata . GetSystem ( uuid )
if err != nil {
if errors . Is ( err , sql . ErrNoRows ) {
http . Error ( w , err . Error ( ) , http . StatusNotFound )
} else {
httpError ( w , r , err , http . StatusInternalServerError )
}
2024-06-07 22:38:24 -04:00
2024-06-07 22:23:02 -04:00
return
}
2024-06-07 22:38:24 -04:00
2024-06-07 22:23:02 -04:00
writeJSON ( w , r , save )
2024-06-09 20:03:27 -04:00
case "update" :
if ! active {
httpError ( w , r , fmt . Errorf ( "session out of date: not active" ) , http . StatusBadRequest )
return
}
2024-06-07 22:23:02 -04:00
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
2024-05-14 12:54:06 +02:00
}
2024-06-22 22:28:14 +01:00
existingPlaytime , err := db . RetrievePlaytime ( uuid )
if err != nil && ! errors . Is ( err , sql . ErrNoRows ) {
httpError ( w , r , fmt . Errorf ( "failed to retrieve playtime: %s" , err ) , http . StatusInternalServerError )
return
} else {
playtime , ok := system . GameStats . ( map [ string ] interface { } ) [ "playTime" ] . ( float64 )
if ! ok {
httpError ( w , r , fmt . Errorf ( "no playtime found" ) , http . StatusBadRequest )
return
}
if float64 ( existingPlaytime ) > playtime {
httpError ( w , r , fmt . Errorf ( "session out of date: existing playtime is greater" ) , http . StatusBadRequest )
return
}
}
2024-06-15 22:21:14 -04:00
err = savedata . UpdateSystem ( uuid , system )
2024-06-07 22:23:02 -04:00
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to put system data: %s" , err ) , http . StatusInternalServerError )
return
}
2024-06-07 18:24:55 -04:00
2024-06-07 23:10:27 -04:00
w . WriteHeader ( http . StatusNoContent )
2024-06-09 20:03:27 -04:00
case "verify" :
response := SystemVerifyResponse {
Valid : active ,
}
// not valid, send server state
if ! active {
2024-06-15 22:21:14 -04:00
err = db . UpdateActiveSession ( uuid , r . URL . Query ( ) . Get ( "clientSessionId" ) )
2024-06-09 20:03:27 -04:00
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
}
writeJSON ( w , r , response )
case "delete" :
2024-06-07 22:23:02 -04:00
err := savedata . DeleteSystem ( uuid )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-05-14 12:54:06 +02:00
2024-06-09 20:03:27 -04:00
w . WriteHeader ( http . StatusOK )
2024-06-15 22:21:14 -04:00
default :
httpError ( w , r , fmt . Errorf ( "unknown action" ) , http . StatusBadRequest )
2024-05-10 18:07:14 -04:00
return
}
}
2024-04-24 20:05:57 -04:00
// daily
func handleDailySeed ( w http . ResponseWriter , r * http . Request ) {
2024-05-10 21:30:47 +02:00
seed , err := db . GetDailyRunSeed ( )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-05-10 15:33:37 -04:00
2024-05-12 20:42:07 +02:00
_ , err = w . Write ( [ ] byte ( seed ) )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to write seed: %s" , err ) , http . StatusInternalServerError )
}
2024-04-24 20:05:57 -04:00
}
func handleDailyRankings ( w http . ResponseWriter , r * http . Request ) {
var err error
var category int
if r . URL . Query ( ) . Has ( "category" ) {
category , err = strconv . Atoi ( r . URL . Query ( ) . Get ( "category" ) )
2024-04-08 20:44:36 -04:00
if err != nil {
2024-04-24 20:05:57 -04:00
httpError ( w , r , fmt . Errorf ( "failed to convert category: %s" , err ) , http . StatusBadRequest )
2024-04-08 20:44:36 -04:00
return
}
2024-04-24 20:05:57 -04:00
}
2024-04-08 20:44:36 -04:00
2024-04-24 20:05:57 -04:00
page := 1
if r . URL . Query ( ) . Has ( "page" ) {
page , err = strconv . Atoi ( r . URL . Query ( ) . Get ( "page" ) )
2024-04-08 20:44:36 -04:00
if err != nil {
2024-04-24 20:05:57 -04:00
httpError ( w , r , fmt . Errorf ( "failed to convert page: %s" , err ) , http . StatusBadRequest )
2024-04-08 20:44:36 -04:00
return
}
2024-04-24 20:05:57 -04:00
}
2024-04-20 16:58:04 -04:00
2024-04-24 20:05:57 -04:00
rankings , err := daily . Rankings ( category , page )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
2024-05-23 14:15:44 -04:00
writeJSON ( w , r , rankings )
2024-04-24 20:05:57 -04:00
}
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" ) )
2024-04-08 20:44:36 -04:00
if err != nil {
2024-04-24 20:05:57 -04:00
httpError ( w , r , fmt . Errorf ( "failed to convert category: %s" , err ) , http . StatusBadRequest )
return
2024-04-08 20:44:36 -04:00
}
2024-04-24 20:05:57 -04:00
}
2024-04-08 20:44:36 -04:00
2024-04-24 20:05:57 -04:00
count , err := daily . RankingPageCount ( category )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
2023-12-29 14:30:47 -05:00
}
2024-06-02 18:15:24 -04:00
w . Write ( [ ] byte ( strconv . Itoa ( count ) ) )
2024-04-10 02:41:58 -04:00
}
2024-07-27 23:41:44 +01:00
// redirect link after authorizing application link
func handleProviderCallback ( w http . ResponseWriter , r * http . Request ) {
provider := r . PathValue ( "provider" )
state := r . URL . Query ( ) . Get ( "state" )
var externalAuthId string
var err error
switch provider {
case "discord" :
externalAuthId , err = account . HandleDiscordCallback ( w , r )
case "google" :
externalAuthId , err = account . HandleGoogleCallback ( w , r )
default :
http . Error ( w , "invalid provider" , http . StatusBadRequest )
return
}
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
if state != "" {
state = strings . Replace ( state , " " , "+" , - 1 )
stateByte , err := base64 . StdEncoding . DecodeString ( state )
if err != nil {
2024-07-27 20:38:32 -04:00
http . Redirect ( w , r , account . GameURL , http . StatusSeeOther )
2024-07-27 23:41:44 +01:00
return
}
userName , err := db . FetchUsernameBySessionToken ( stateByte )
if err != nil {
2024-07-27 20:38:32 -04:00
http . Redirect ( w , r , account . GameURL , http . StatusSeeOther )
2024-07-27 23:41:44 +01:00
return
}
switch provider {
case "discord" :
err = db . AddDiscordIdByUsername ( externalAuthId , userName )
case "google" :
err = db . AddGoogleIdByUsername ( externalAuthId , userName )
}
if err != nil {
2024-07-27 20:38:32 -04:00
http . Redirect ( w , r , account . GameURL , http . StatusSeeOther )
2024-07-27 23:41:44 +01:00
return
}
} else {
var userName string
switch provider {
case "discord" :
userName , err = db . FetchUsernameByDiscordId ( externalAuthId )
case "google" :
userName , err = db . FetchUsernameByGoogleId ( externalAuthId )
}
if err != nil {
2024-07-27 20:38:32 -04:00
http . Redirect ( w , r , account . GameURL , http . StatusSeeOther )
2024-07-27 23:41:44 +01:00
return
}
sessionToken , err := account . GenerateTokenForUsername ( userName )
if err != nil {
2024-07-27 20:38:32 -04:00
http . Redirect ( w , r , account . GameURL , http . StatusSeeOther )
2024-07-27 23:41:44 +01:00
return
}
http . SetCookie ( w , & http . Cookie {
Name : "pokerogue_sessionId" ,
Value : sessionToken ,
Path : "/" ,
Secure : true ,
SameSite : http . SameSiteStrictMode ,
2024-07-27 20:38:32 -04:00
Domain : "pokerogue.net" ,
2024-07-27 23:41:44 +01:00
Expires : time . Now ( ) . Add ( time . Hour * 24 * 30 * 3 ) , // 3 months
} )
}
2024-07-27 20:58:17 -04:00
http . Redirect ( w , r , account . GameURL , http . StatusSeeOther )
2024-07-27 23:41:44 +01:00
}
func handleProviderLogout ( w http . ResponseWriter , r * http . Request ) {
uuid , err := uuidFromRequest ( r )
if err != nil {
httpError ( w , r , err , http . StatusBadRequest )
return
}
switch r . PathValue ( "provider" ) {
case "discord" :
err = db . RemoveDiscordIdByUUID ( uuid )
case "google" :
err = db . RemoveGoogleIdByUUID ( uuid )
default :
http . Error ( w , "invalid provider" , http . StatusBadRequest )
return
}
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusOK )
}
2024-09-14 03:32:49 +01:00
func handleAdminDiscordLink ( w http . ResponseWriter , r * http . Request ) {
err := r . ParseForm ( )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to parse request form: %s" , err ) , http . StatusBadRequest )
return
}
uuid , err := uuidFromRequest ( r )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
userDiscordId , err := db . FetchDiscordIdByUUID ( uuid )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
hasRole , err := account . IsUserDiscordAdmin ( userDiscordId , account . DiscordGuildID )
if ! hasRole || err != nil {
httpError ( w , r , fmt . Errorf ( "user does not have the required role" ) , http . StatusForbidden )
return
}
err = db . AddDiscordIdByUsername ( r . Form . Get ( "discordId" ) , r . Form . Get ( "username" ) )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
log . Printf ( "%s: %s added discord id %s to username %s" , r . URL . Path , userDiscordId , r . Form . Get ( "discordId" ) , r . Form . Get ( "username" ) )
w . WriteHeader ( http . StatusOK )
}
2024-09-25 18:26:30 +10:00
func handleAdminDiscordUnlink ( w http . ResponseWriter , r * http . Request ) {
err := r . ParseForm ( )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to parse request form: %s" , err ) , http . StatusBadRequest )
return
}
uuid , err := uuidFromRequest ( r )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
userDiscordId , err := db . FetchDiscordIdByUUID ( uuid )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
hasRole , err := account . IsUserDiscordAdmin ( userDiscordId , account . DiscordGuildID )
if ! hasRole || err != nil {
httpError ( w , r , fmt . Errorf ( "user does not have the required role" ) , http . StatusForbidden )
return
}
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
username := r . Form . Get ( "username" )
discordId := r . Form . Get ( "discordId" )
if username != "" {
log . Printf ( "Username given, removing discordId" )
err = db . RemoveDiscordIdByUsername ( username )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
}
if discordId != "" {
log . Printf ( "DiscordID given, removing discordId" )
err = db . RemoveDiscordIdByDiscordId ( discordId )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
}
log . Printf ( "%s: %s removed discord id %s from username %s" , userDiscordId , r . URL . Path , r . Form . Get ( "discordId" ) , r . Form . Get ( "username" ) )
w . WriteHeader ( http . StatusOK )
}
2024-09-28 18:26:59 +10:00
// this is for the output for the admin search, but should probably be moved elsewhere, though not sure where
// account/info has its own version under api/account/info.ts, but not sure if we want a new folder/file for admin stuff or to put it elsewhere?
type AdminSearchResponse struct {
Username string ` json:"username" `
DiscordId string ` json:"discordId" `
GoogleId string ` json:"googleId" `
LastLoggedIn string ` json:"lastLoggedIn" `
}
func handleAdminSearch ( w http . ResponseWriter , r * http . Request ) {
err := r . ParseForm ( )
if err != nil {
httpError ( w , r , fmt . Errorf ( "failed to parse request form: %s" , err ) , http . StatusBadRequest )
return
}
uuid , err := uuidFromRequest ( r )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
userDiscordId , err := db . FetchDiscordIdByUUID ( uuid )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
hasRole , err := account . IsUserDiscordAdmin ( userDiscordId , account . DiscordGuildID )
if ! hasRole || err != nil {
httpError ( w , r , fmt . Errorf ( "user does not have the required role" ) , http . StatusForbidden )
return
}
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
username := r . Form . Get ( "username" )
log . Printf ( "USERNAME SEARCH STARTING" )
/ *
// this way does a single call that does a query for multiple columns from our database and makes an object out of it, which is returned to us
/ adminSearchResult , err := db . FetchAdminDetailsByUsername ( username )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
log . Printf ( "Username is: %s" , adminSearchResult . Username . String )
writeJSON ( w , r , adminSearchResult )
* /
// this way does multiple calls to get individual things (for example, a single call for username, a single call for discord Id, a single call for google Id etc)
// once we have all the single fields we need, it then makes an object out of them with the info that we want
dbUsername , err := db . CheckUsernameExists ( username )
if err != nil {
httpError ( w , r , err , http . StatusInternalServerError )
return
}
log . Printf ( "Username is: %s" , dbUsername )
discordId , err := db . FetchDiscordIdByUsername ( username )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
log . Printf ( "Discord Id is: %s" , discordId )
googleId , err := db . FetchGoogleIdByUsername ( username )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
log . Printf ( "Google Id is: %s" , googleId )
lastLoggedIn , err := db . FetchLastLoggedInDateByUsername ( username )
if err != nil {
httpError ( w , r , err , http . StatusUnauthorized )
return
}
log . Printf ( "Last Logged in date is: %s" , lastLoggedIn )
adminResponse := AdminSearchResponse {
Username : username ,
DiscordId : discordId ,
GoogleId : googleId ,
LastLoggedIn : lastLoggedIn ,
}
writeJSON ( w , r , adminResponse )
log . Printf ( "%s: %s searched for username %s" , userDiscordId , r . URL . Path , username )
}