diff --git a/api/account/info.go b/api/account/info.go index 6b3b94e..d76b58f 100644 --- a/api/account/info.go +++ b/api/account/info.go @@ -19,7 +19,6 @@ package account import ( "github.com/pagefaultgames/rogueserver/db" - "github.com/pagefaultgames/rogueserver/defs" ) type InfoResponse struct { @@ -29,24 +28,7 @@ type InfoResponse struct { // /account/info - get account info func Info(username string, uuid []byte) (InfoResponse, error) { - response := InfoResponse{Username: username, LastSessionSlot: -1} + slot, _ := db.GetLatestSessionSaveDataSlot(uuid) - highest := -1 - for i := 0; i < defs.SessionSlotCount; i++ { - data, err := db.ReadSessionSaveData(uuid, i) - if err != nil { - continue - } - - if data.Timestamp > highest { - highest = data.Timestamp - response.LastSessionSlot = i - } - } - - if response.LastSessionSlot < 0 || response.LastSessionSlot >= defs.SessionSlotCount { - response.LastSessionSlot = -1 - } - - return response, nil + return InfoResponse{Username: username, LastSessionSlot: slot}, nil } diff --git a/api/common.go b/api/common.go index e1666f5..246cb27 100644 --- a/api/common.go +++ b/api/common.go @@ -52,17 +52,19 @@ func Init(mux *http.ServeMux) error { mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount) // savedata - mux.HandleFunc("GET /savedata/get", legacyHandleGetSaveData) mux.HandleFunc("POST /savedata/update", legacyHandleSaveData) - mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) // TODO use deleteSystemSave - mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) // TODO use clearSessionData + mux.HandleFunc("GET /savedata/delete", legacyHandleSaveData) + mux.HandleFunc("POST /savedata/clear", legacyHandleSaveData) mux.HandleFunc("GET /savedata/newclear", legacyHandleNewClear) + mux.HandleFunc("/savedata/session/{action}", handleSession) + mux.HandleFunc("/savedata/system/{action}", handleSystem) + // 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) + + mux.HandleFunc("GET /savedata/system", handleSystem) + mux.HandleFunc("GET /savedata/session", handleSession) // daily mux.HandleFunc("GET /daily/seed", handleDailySeed) diff --git a/api/daily/common.go b/api/daily/common.go index 95d59ec..d913acf 100644 --- a/api/daily/common.go +++ b/api/daily/common.go @@ -61,7 +61,7 @@ func Init() error { secret = newSecret } - seed, err := recordNewDaily() + seed, err := db.TryAddDailyRun(Seed()) if err != nil { log.Print(err) } @@ -71,8 +71,7 @@ func Init() error { _, err = scheduler.AddFunc("@daily", func() { time.Sleep(time.Second) - seed, err = recordNewDaily() - + seed, err = db.TryAddDailyRun(Seed()) if err != nil { log.Printf("error while recording new daily: %s", err) } else { @@ -104,7 +103,3 @@ func deriveSeed(seedTime time.Time) []byte { return hashedSeed[:] } - -func recordNewDaily() (string, error) { - return db.TryAddDailyRun(Seed()) -} diff --git a/api/endpoints.go b/api/endpoints.go index 782ae53..7972c6b 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -45,7 +45,7 @@ import ( func handleAccountInfo(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -105,7 +105,7 @@ func handleAccountChangePW(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -127,7 +127,8 @@ func handleAccountLogout(w http.ResponseWriter, r *http.Request) { err = account.Logout(token) if err != nil { - httpError(w, r, err, http.StatusInternalServerError) + // also possible for InternalServerError but that's unlikely unless the server blew up + httpError(w, r, err, http.StatusUnauthorized) return } @@ -145,276 +146,115 @@ func handleGameTitleStats(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 handleSession(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) + if err != nil { + httpError(w, r, err, http.StatusUnauthorized) + return + } + + slot, err := strconv.Atoi(r.URL.Query().Get("slot")) 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 - } + if slot < 0 || slot >= defs.SessionSlotCount { + httpError(w, r, fmt.Errorf("slot id %d out of range", slot), http.StatusBadRequest) + return } - var clientSessionId string - if r.URL.Query().Has("clientSessionId") { - clientSessionId = r.URL.Query().Get("clientSessionId") - } else { + if !r.URL.Query().Has("clientSessionId") { httpError(w, r, fmt.Errorf("missing clientSessionId"), http.StatusBadRequest) + return } - err = db.UpdateActiveSession(uuid, clientSessionId) + 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 } - var save any - save, err = savedata.Get(uuid, 1, slot) - if errors.Is(err, sql.ErrNoRows) { - http.Error(w, err.Error(), http.StatusNotFound) - return - } + switch r.PathValue("action") { + default: + fallthrough + case "get": + save, err := savedata.GetSession(uuid, 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 - } + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } - writeJSON(w, r, save) + writeJSON(w, r, save) + case "update": + 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 + } + + err = savedata.PutSession(uuid, slot, session) + if err != nil { + httpError(w, r, fmt.Errorf("failed to put session data: %s", err), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + case "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 + } + + seed, err := db.GetDailyRunSeed() + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + resp, err := savedata.Clear(uuid, slot, seed, session) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + writeJSON(w, r, resp) + case "newclear": + resp, err := savedata.NewClear(uuid, slot) + if err != nil { + httpError(w, r, fmt.Errorf("failed to read new clear: %s", err), http.StatusInternalServerError) + return + } + + writeJSON(w, r, resp) + case "delete": + err := savedata.DeleteSession(uuid, slot) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } } const legacyClientSessionId = "LEGACY_CLIENT" -func legacyHandleGetSaveData(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 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 - } - - writeJSON(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) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -436,17 +276,14 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { } } - var clientSessionId string - if r.URL.Query().Has("clientSessionId") { - clientSessionId = r.URL.Query().Get("clientSessionId") - } + clientSessionId := r.URL.Query().Get("clientSessionId") if clientSessionId == "" { clientSessionId = legacyClientSessionId } var save any - // /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" { + // /savedata/delete specify datatype, but don't expect data in body + if r.URL.Path != "/savedata/delete" { if datatype == 0 { var system defs.SystemSaveData err = json.NewDecoder(r.Body).Decode(&system) @@ -470,74 +307,58 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { } var active bool - if r.URL.Path == "/savedata/get" { - if datatype == 0 { - err = db.UpdateActiveSession(uuid, clientSessionId) + active, err = db.IsActiveSession(uuid, clientSessionId) + if err != nil { + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + return + } + + // TODO: make this not suck + if !active && r.URL.Path != "/savedata/clear" { + httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) + return + } + + var trainerId, secretId int + + if r.URL.Path != "/savedata/update" || datatype == 1 { + 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, fmt.Errorf("failed to update active session: %s", err), http.StatusBadRequest) + 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 } } } else { - active, err = db.IsActiveSession(uuid, clientSessionId) - if err != nil { - httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + trainerId = save.(defs.SystemSaveData).TrainerId + secretId = save.(defs.SystemSaveData).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 } - - // TODO: make this not suck - if !active && r.URL.Path != "/savedata/clear" { - httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) - return - } - - var trainerId, secretId int - - if r.URL.Path != "/savedata/update" || datatype == 1 { - 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 - } - } - } else { - trainerId = save.(defs.SystemSaveData).TrainerId - secretId = save.(defs.SystemSaveData).SecretId - } - - storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) - if err != nil { + } else { + if err := db.UpdateTrainerIds(trainerId, secretId, uuid); 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 - } - } } switch r.URL.Path { - case "/savedata/get": - save, err = savedata.Get(uuid, datatype, slot) - if errors.Is(err, sql.ErrNoRows) { - http.Error(w, err.Error(), http.StatusNotFound) - return - } case "/savedata/update": err = savedata.Update(uuid, slot, save) case "/savedata/delete": @@ -584,7 +405,7 @@ type CombinedSaveData struct { func handleUpdateAll(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -594,6 +415,7 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) return } + if data.ClientSessionId == "" { data.ClientSessionId = legacyClientSessionId } @@ -610,9 +432,6 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { return } - trainerId := data.System.TrainerId - secretId := data.System.SecretId - storedTrainerId, storedSecretId, err := db.FetchTrainerIds(uuid) if err != nil { httpError(w, r, err, http.StatusInternalServerError) @@ -620,12 +439,13 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { } if storedTrainerId > 0 || storedSecretId > 0 { - if trainerId != storedTrainerId || secretId != storedSecretId { + if data.System.TrainerId != storedTrainerId || data.System.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 { + err = db.UpdateTrainerIds(data.System.TrainerId, data.System.SecretId, uuid) + if err != nil { httpError(w, r, err, http.StatusInternalServerError) return } @@ -636,115 +456,150 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) { 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"` + w.WriteHeader(http.StatusOK) } type SystemVerifyRequest struct { ClientSessionId string `json:"clientSessionId"` } -func handleSystemVerify(w http.ResponseWriter, r *http.Request) { +type SystemVerifyResponse struct { + Valid bool `json:"valid"` + SystemData defs.SystemSaveData `json:"systemData"` +} + +func handleSystem(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) + httpError(w, r, err, http.StatusUnauthorized) 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 r.URL.Path != "/savedata/system/verify" { + 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 update active session: %s", err), http.StatusBadRequest) + httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) + return + } + } + + switch r.PathValue("action") { + default: + fallthrough + 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 + } + } + + 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) + } + 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) + writeJSON(w, r, save) + case "update": + if !active { + httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest) return } - response.SystemData = &storedSaveData - } + 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 + } - err = db.UpdateAccountLastActivity(uuid) - if err != nil { - httpError(w, r, fmt.Errorf("failed to update account last activity: %s", err), http.StatusInternalServerError) - return - } + err = savedata.PutSystem(uuid, system) + if err != nil { + httpError(w, r, fmt.Errorf("failed to put system data: %s", err), http.StatusInternalServerError) + return + } - writeJSON(w, r, response) -} + w.WriteHeader(http.StatusNoContent) + case "verify": + var input SystemVerifyRequest + if !r.URL.Query().Has("clientSessionId") { + 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 + } -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) + 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 + } } else { - httpError(w, r, err, http.StatusInternalServerError) + 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 + } + } + + response := SystemVerifyResponse{ + Valid: active, } - return - } - //TODO apply vouchers + // 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 + } - writeJSON(w, r, save) + 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": + err := savedata.DeleteSystem(uuid) + if err != nil { + httpError(w, r, err, http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } } func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) { uuid, err := uuidFromRequest(r) if err != nil { - httpError(w, r, err, http.StatusBadRequest) + httpError(w, r, err, http.StatusUnauthorized) return } @@ -826,5 +681,5 @@ func handleDailyRankingPageCount(w http.ResponseWriter, r *http.Request) { httpError(w, r, err, http.StatusInternalServerError) } - _, _ = w.Write([]byte(strconv.Itoa(count))) + w.Write([]byte(strconv.Itoa(count))) } diff --git a/api/savedata/delete.go b/api/savedata/delete.go index 7285328..5d98756 100644 --- a/api/savedata/delete.go +++ b/api/savedata/delete.go @@ -33,8 +33,6 @@ func Delete(uuid []byte, datatype, slot int) error { } switch datatype { - case 0: // System - err = db.DeleteSystemSaveData(uuid) case 1: // Session if slot < 0 || slot >= defs.SessionSlotCount { err = fmt.Errorf("slot id %d out of range", slot) @@ -48,6 +46,6 @@ func Delete(uuid []byte, datatype, slot int) error { if err != nil { return err } - + return nil } diff --git a/api/savedata/get.go b/api/savedata/get.go deleted file mode 100644 index dcf8232..0000000 --- a/api/savedata/get.go +++ /dev/null @@ -1,86 +0,0 @@ -/* - 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 . -*/ - -package savedata - -import ( - "fmt" - "strconv" - - "github.com/pagefaultgames/rogueserver/db" - "github.com/pagefaultgames/rogueserver/defs" -) - -// /savedata/get - get save data -func Get(uuid []byte, datatype, slot int) (any, error) { - switch datatype { - case 0: // System - if slot != 0 { - return nil, fmt.Errorf("invalid slot id for system data") - } - - system, err := db.ReadSystemSaveData(uuid) - if err != nil { - return nil, err - } - - // TODO this should be a transaction - compensations, err := db.FetchAndClaimAccountCompensations(uuid) - if err != nil { - return nil, fmt.Errorf("failed to fetch compensations: %s", err) - } - - var needsUpdate bool - for compensationType, amount := range compensations { - 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 - case 1: // Session - if slot < 0 || slot >= defs.SessionSlotCount { - return nil, fmt.Errorf("slot id %d out of range", slot) - } - - session, err := db.ReadSessionSaveData(uuid, slot) - if err != nil { - return nil, err - } - - return session, nil - default: - return nil, fmt.Errorf("invalid data type") - } -} diff --git a/api/savedata/session.go b/api/savedata/session.go new file mode 100644 index 0000000..97cc5d5 --- /dev/null +++ b/api/savedata/session.go @@ -0,0 +1,33 @@ +package savedata + +import ( + "github.com/pagefaultgames/rogueserver/db" + "github.com/pagefaultgames/rogueserver/defs" +) + +func GetSession(uuid []byte, slot int) (defs.SessionSaveData, error) { + session, err := db.ReadSessionSaveData(uuid, slot) + if err != nil { + return session, err + } + + return session, nil +} + +func PutSession(uuid []byte, slot int, data defs.SessionSaveData) error { + err := db.StoreSessionSaveData(uuid, data, slot) + if err != nil { + return err + } + + return nil +} + +func DeleteSession(uuid []byte, slot int) error { + err := db.DeleteSessionSaveData(uuid, slot) + if err != nil { + return err + } + + return nil +} diff --git a/api/savedata/system.go b/api/savedata/system.go new file mode 100644 index 0000000..1f9c5d2 --- /dev/null +++ b/api/savedata/system.go @@ -0,0 +1,79 @@ +package savedata + +import ( + "fmt" + "strconv" + + "github.com/pagefaultgames/rogueserver/db" + "github.com/pagefaultgames/rogueserver/defs" +) + +func GetSystem(uuid []byte) (defs.SystemSaveData, error) { + system, err := db.ReadSystemSaveData(uuid) + if err != nil { + return system, err + } + + // TODO: this should be a transaction + compensations, err := db.FetchAndClaimAccountCompensations(uuid) + if err != nil { + return system, fmt.Errorf("failed to fetch compensations: %s", err) + } + + var needsUpdate bool + for compensationType, amount := range compensations { + system.VoucherCounts[strconv.Itoa(compensationType)] += amount + if amount > 0 { + needsUpdate = true + } + } + + if needsUpdate { + err = db.StoreSystemSaveData(uuid, system) + if err != nil { + return system, fmt.Errorf("failed to update system save data: %s", err) + } + err = db.DeleteClaimedAccountCompensations(uuid) + if err != nil { + return system, fmt.Errorf("failed to delete claimed compensations: %s", err) + } + + err = db.UpdateAccountStats(uuid, system.GameStats, system.VoucherCounts) + if err != nil { + return system, fmt.Errorf("failed to update account stats: %s", err) + } + } + + return system, nil +} + +func PutSystem(uuid []byte, data defs.SystemSaveData) error { + if data.TrainerId == 0 && data.SecretId == 0 { + return fmt.Errorf("invalid system data") + } + + if data.GameVersion != "1.0.4" { + return fmt.Errorf("client version out of date") + } + + err := db.UpdateAccountStats(uuid, data.GameStats, data.VoucherCounts) + if err != nil { + return fmt.Errorf("failed to update account stats: %s", err) + } + + err = db.DeleteClaimedAccountCompensations(uuid) + if err != nil { + return fmt.Errorf("failed to delete claimed compensations: %s", err) + } + + return db.StoreSystemSaveData(uuid, data) +} + +func DeleteSystem(uuid []byte) error { + err := db.DeleteSystemSaveData(uuid) + if err != nil { + return err + } + + return nil +} diff --git a/api/savedata/update.go b/api/savedata/update.go index 59ceb6f..6876b57 100644 --- a/api/savedata/update.go +++ b/api/savedata/update.go @@ -42,13 +42,6 @@ func Update(uuid []byte, slot int, save any) error { return fmt.Errorf("client version out of date") } - if save.VoucherCounts["0"] > 300 || - save.VoucherCounts["1"] > 150 || - save.VoucherCounts["2"] > 100 || - save.VoucherCounts["3"] > 10 { - db.SetAccountBanned(uuid, true) - } - err = db.UpdateAccountStats(uuid, save.GameStats, save.VoucherCounts) if err != nil { return fmt.Errorf("failed to update account stats: %s", err) diff --git a/defs/savedata.go b/defs/savedata.go index 55ed31c..094ffc5 100644 --- a/defs/savedata.go +++ b/defs/savedata.go @@ -33,6 +33,8 @@ type SystemSaveData struct { VoucherUnlocks VoucherUnlocks `json:"voucherUnlocks"` VoucherCounts VoucherCounts `json:"voucherCounts"` Eggs []EggData `json:"eggs"` + EggPity []int `json:"eggPity"` + UnlockPity []int `json:"unlockPity"` GameVersion string `json:"gameVersion"` Timestamp int `json:"timestamp"` } @@ -105,6 +107,13 @@ type SessionSaveData struct { Trainer TrainerData `json:"trainer"` GameVersion string `json:"gameVersion"` Timestamp int `json:"timestamp"` + Challenges []ChallengeData `json:"challenges"` +} + +type ChallengeData struct { + Id int `json:"id"` + Value int `json:"value"` + Severity int `json:"severity"` } type GameMode int