You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rogueserver/db/account.go

481 lines
12 KiB
Go

/*
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/>.
*/
package db
import (
"database/sql"
"errors"
"fmt"
"slices"
_ "github.com/go-sql-driver/mysql"
"github.com/pagefaultgames/rogueserver/defs"
)
func AddAccountRecord(uuid []byte, username string, key, salt []byte) error {
_, err := handle.Exec("INSERT INTO accounts (uuid, username, hash, salt, registered) VALUES (?, ?, ?, ?, UTC_TIMESTAMP())", uuid, username, key, salt)
if err != nil {
return err
}
return nil
}
func AddAccountSession(username string, token []byte) error {
_, err := handle.Exec("INSERT INTO sessions (uuid, token, expire) SELECT a.uuid, ?, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 1 WEEK) FROM accounts a WHERE a.username = ?", token, username)
if err != nil {
return err
}
_, err = handle.Exec("UPDATE accounts SET lastLoggedIn = UTC_TIMESTAMP() WHERE username = ?", username)
if err != nil {
return err
}
return nil
}
func AddDiscordIdByUsername(discordId string, username string) error {
_, err := handle.Exec("UPDATE accounts SET discordId = ? WHERE username = ?", discordId, username)
if err != nil {
return err
}
return nil
}
func AddGoogleIdByUsername(googleId string, username string) error {
_, err := handle.Exec("UPDATE accounts SET googleId = ? WHERE username = ?", googleId, username)
if err != nil {
return err
}
return nil
}
func AddGoogleIdByUUID(googleId string, uuid []byte) error {
_, err := handle.Exec("UPDATE accounts SET googleId = ? WHERE uuid = ?", googleId, uuid)
if err != nil {
return err
}
return nil
}
func AddDiscordIdByUUID(discordId string, uuid []byte) error {
_, err := handle.Exec("UPDATE accounts SET discordId = ? WHERE uuid = ?", discordId, uuid)
if err != nil {
return err
}
return nil
}
func FetchUsernameByDiscordId(discordId string) (string, error) {
var username string
err := handle.QueryRow("SELECT username FROM accounts WHERE discordId = ?", discordId).Scan(&username)
if err != nil {
return "", err
}
return username, nil
}
func FetchUsernameByGoogleId(googleId string) (string, error) {
var username string
err := handle.QueryRow("SELECT username FROM accounts WHERE googleId = ?", googleId).Scan(&username)
if err != nil {
return "", err
}
return username, nil
}
func FetchDiscordIdByUsername(username string) (string, error) {
var discordId sql.NullString
err := handle.QueryRow("SELECT discordId FROM accounts WHERE username = ?", username).Scan(&discordId)
if err != nil {
return "", err
}
if !discordId.Valid {
return "", nil
}
return discordId.String, nil
}
func FetchGoogleIdByUsername(username string) (string, error) {
var googleId sql.NullString
err := handle.QueryRow("SELECT googleId FROM accounts WHERE username = ?", username).Scan(&googleId)
if err != nil {
return "", err
}
if !googleId.Valid {
return "", nil
}
return googleId.String, nil
}
func FetchDiscordIdByUUID(uuid []byte) (string, error) {
var discordId sql.NullString
err := handle.QueryRow("SELECT discordId FROM accounts WHERE uuid = ?", uuid).Scan(&discordId)
if err != nil {
return "", err
}
if !discordId.Valid {
return "", nil
}
return discordId.String, nil
}
func FetchGoogleIdByUUID(uuid []byte) (string, error) {
var googleId sql.NullString
err := handle.QueryRow("SELECT googleId FROM accounts WHERE uuid = ?", uuid).Scan(&googleId)
if err != nil {
return "", err
}
if !googleId.Valid {
return "", nil
}
return googleId.String, nil
}
func FetchUsernameBySessionToken(token []byte) (string, error) {
var username string
err := handle.QueryRow("SELECT a.username FROM accounts a JOIN sessions s ON a.uuid = s.uuid WHERE s.token = ?", token).Scan(&username)
if err != nil {
return "", err
}
return username, nil
}
func CheckUsernameExists(username string) (string, error) {
var dbUsername sql.NullString
err := handle.QueryRow("SELECT username FROM accounts WHERE username = ?", username).Scan(&dbUsername)
if err != nil {
return "", err
}
if !dbUsername.Valid {
return "", nil
}
return dbUsername.String, nil
}
func FetchLastLoggedInDateByUsername(username string) (string, error) {
var lastLoggedIn sql.NullString
err := handle.QueryRow("SELECT lastLoggedIn FROM accounts WHERE username = ?", username).Scan(&lastLoggedIn)
if err != nil {
return "", err
}
if !lastLoggedIn.Valid {
return "", nil
}
return lastLoggedIn.String, nil
}
type AdminSearchResponse struct {
Username string `json:"username"`
DiscordId string `json:"discordId"`
GoogleId string `json:"googleId"`
LastActivity string `json:"lastLoggedIn"` // TODO: this is currently lastLoggedIn to match server PR #54 with pokerogue PR #4198. We're hotfixing the server with this PR to return lastActivity, but we're not hotfixing the client, so are leaving this as lastLoggedIn so that it still talks to the client properly
Registered string `json:"registered"`
}
func FetchAdminDetailsByUsername(dbUsername string) (AdminSearchResponse, error) {
var username, discordId, googleId, lastActivity, registered sql.NullString
var adminResponse AdminSearchResponse
err := handle.QueryRow("SELECT username, discordId, googleId, lastActivity, registered from accounts WHERE username = ?", dbUsername).Scan(&username, &discordId, &googleId, &lastActivity, &registered)
if err != nil {
return adminResponse, err
}
adminResponse = AdminSearchResponse{
Username: username.String,
DiscordId: discordId.String,
GoogleId: googleId.String,
LastActivity: lastActivity.String,
Registered: registered.String,
}
return adminResponse, nil
}
func UpdateAccountPassword(uuid, key, salt []byte) error {
_, err := handle.Exec("UPDATE accounts SET (hash, salt) VALUES (?, ?) WHERE uuid = ?", key, salt, uuid)
if err != nil {
return err
}
return nil
}
func UpdateAccountLastActivity(uuid []byte) error {
_, err := handle.Exec("UPDATE accounts SET lastActivity = UTC_TIMESTAMP() WHERE uuid = ?", uuid)
if err != nil {
return err
}
return nil
}
func UpdateAccountStats(uuid []byte, stats defs.GameStats, voucherCounts map[string]int) error {
var columns = []string{"playTime", "battles", "classicSessionsPlayed", "sessionsWon", "highestEndlessWave", "highestLevel", "pokemonSeen", "pokemonDefeated", "pokemonCaught", "pokemonHatched", "eggsPulled", "regularVouchers", "plusVouchers", "premiumVouchers", "goldenVouchers"}
var statCols []string
var statValues []interface{}
m, ok := stats.(map[string]interface{})
if !ok {
return fmt.Errorf("expected map[string]interface{}, got %T", stats)
}
for k, v := range m {
value, ok := v.(float64)
if !ok {
return fmt.Errorf("expected float64, got %T", v)
}
if slices.Contains(columns, k) {
statCols = append(statCols, k)
statValues = append(statValues, value)
}
}
for k, v := range voucherCounts {
var column string
switch k {
case "0":
column = "regularVouchers"
case "1":
column = "plusVouchers"
case "2":
column = "premiumVouchers"
case "3":
column = "goldenVouchers"
default:
continue
}
statCols = append(statCols, column)
statValues = append(statValues, v)
}
var statArgs []interface{}
statArgs = append(statArgs, uuid)
for range 2 {
statArgs = append(statArgs, statValues...)
}
query := "INSERT INTO accountStats (uuid"
for _, col := range statCols {
query += ", " + col
}
query += ") VALUES (?"
for range len(statCols) {
query += ", ?"
}
query += ") ON DUPLICATE KEY UPDATE "
for i, col := range statCols {
if i > 0 {
query += ", "
}
query += col + " = ?"
}
_, err := handle.Exec(query, statArgs...)
if err != nil {
return err
}
return nil
}
func SetAccountBanned(uuid []byte, banned bool) error {
_, err := handle.Exec("UPDATE accounts SET banned = ? WHERE uuid = ?", banned, uuid)
if err != nil {
return err
}
return nil
}
func FetchAccountKeySaltFromUsername(username string) ([]byte, []byte, error) {
var key, salt []byte
err := handle.QueryRow("SELECT hash, salt FROM accounts WHERE username = ?", username).Scan(&key, &salt)
if err != nil {
return nil, nil, err
}
return key, salt, nil
}
func FetchTrainerIds(uuid []byte) (trainerId, secretId int, err error) {
err = handle.QueryRow("SELECT trainerId, secretId FROM accounts WHERE uuid = ?", uuid).Scan(&trainerId, &secretId)
if err != nil {
return 0, 0, err
}
return trainerId, secretId, nil
}
func UpdateTrainerIds(trainerId, secretId int, uuid []byte) error {
_, err := handle.Exec("UPDATE accounts SET trainerId = ?, secretId = ? WHERE uuid = ?", trainerId, secretId, uuid)
if err != nil {
return err
}
return nil
}
func IsActiveSession(uuid []byte, sessionId string) (bool, error) {
var id string
err := handle.QueryRow("SELECT clientSessionId FROM activeClientSessions WHERE uuid = ?", uuid).Scan(&id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
err = UpdateActiveSession(uuid, sessionId)
if err != nil {
return false, err
}
return true, nil
}
return false, err
}
return id == "" || id == sessionId, nil
}
func UpdateActiveSession(uuid []byte, clientSessionId string) error {
_, err := handle.Exec("INSERT INTO activeClientSessions (uuid, clientSessionId) VALUES (?, ?) ON DUPLICATE KEY UPDATE clientSessionId = ?", uuid, clientSessionId, clientSessionId)
if err != nil {
return err
}
return nil
}
func FetchUUIDFromToken(token []byte) ([]byte, error) {
var uuid []byte
err := handle.QueryRow("SELECT uuid FROM sessions WHERE token = ?", token).Scan(&uuid)
if err != nil {
return nil, err
}
return uuid, nil
}
func RemoveSessionFromToken(token []byte) error {
_, err := handle.Exec("DELETE FROM sessions WHERE token = ?", token)
if err != nil {
return err
}
return nil
}
func FetchUsernameFromUUID(uuid []byte) (string, error) {
var username string
err := handle.QueryRow("SELECT username FROM accounts WHERE uuid = ?", uuid).Scan(&username)
if err != nil {
return "", err
}
return username, nil
}
func FetchUUIDFromUsername(username string) ([]byte, error) {
var uuid []byte
err := handle.QueryRow("SELECT uuid FROM accounts WHERE username = ?", username).Scan(&uuid)
if err != nil {
return nil, err
}
return uuid, nil
}
func RemoveDiscordIdByUUID(uuid []byte) error {
_, err := handle.Exec("UPDATE accounts SET discordId = NULL WHERE uuid = ?", uuid)
if err != nil {
return err
}
return nil
}
func RemoveGoogleIdByUUID(uuid []byte) error {
_, err := handle.Exec("UPDATE accounts SET googleId = NULL WHERE uuid = ?", uuid)
if err != nil {
return err
}
return nil
}
func RemoveGoogleIdByUsername(username string) error {
_, err := handle.Exec("UPDATE accounts SET googleId = NULL WHERE username = ?", username)
if err != nil {
return err
}
return nil
}
func RemoveDiscordIdByUsername(username string) error {
_, err := handle.Exec("UPDATE accounts SET discordId = NULL WHERE username = ?", username)
if err != nil {
return err
}
return nil
}
func RemoveDiscordIdByDiscordId(discordId string) error {
_, err := handle.Exec("UPDATE accounts SET discordId = NULL WHERE discordId = ?", discordId)
if err != nil {
return err
}
return nil
}
func RemoveGoogleIdByDiscordId(discordId string) error {
_, err := handle.Exec("UPDATE accounts SET googleId = NULL WHERE discordId = ?", discordId)
if err != nil {
return err
}
return nil
}