diff --git a/api/ban.go b/api/ban.go index 4a4661a..ca0ae3d 100644 --- a/api/ban.go +++ b/api/ban.go @@ -6,7 +6,6 @@ import ( "net/http" "strconv" "time" - "wwfc/database" "wwfc/gpcm" "wwfc/logging" @@ -102,7 +101,7 @@ func handleBanImpl(r *http.Request) (bool, string, int) { logging.Notice("API:"+moderator, "Ban profile:", aurora.Cyan(req.ProfileID), "TOS:", aurora.Cyan(req.Tos), "Length:", aurora.Cyan(length), "Reason:", aurora.BrightCyan(req.Reason), "Reason (Hidden):", aurora.BrightCyan(req.ReasonHidden)) - if !database.BanUser(pool, ctx, req.ProfileID, req.Tos, length, req.Reason, req.ReasonHidden, moderator) { + if !db.BanUser(req.ProfileID, req.Tos, length, req.Reason, req.ReasonHidden, moderator) { return false, "Failed to ban user", http.StatusInternalServerError } diff --git a/api/main.go b/api/main.go index 33928eb..1b0dca6 100644 --- a/api/main.go +++ b/api/main.go @@ -1,16 +1,13 @@ package api import ( - "context" - "fmt" "wwfc/common" - - "github.com/jackc/pgx/v4/pgxpool" + "wwfc/database" ) var ( - ctx = context.Background() - pool *pgxpool.Pool + db database.Connection + apiSecret string ) @@ -21,16 +18,7 @@ func StartServer(reload bool) { apiSecret = config.APISecret // Start SQL - dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) - dbConf, err := pgxpool.ParseConfig(dbString) - if err != nil { - panic(err) - } - - pool, err = pgxpool.ConnectConfig(ctx, dbConf) - if err != nil { - panic(err) - } + db = database.Start(config) } func Shutdown() { diff --git a/api/unban.go b/api/unban.go index 33eb180..1775740 100644 --- a/api/unban.go +++ b/api/unban.go @@ -5,7 +5,6 @@ import ( "io" "net/http" "strconv" - "wwfc/database" ) func HandleUnban(w http.ResponseWriter, r *http.Request) { @@ -72,7 +71,7 @@ func handleUnbanImpl(r *http.Request) (bool, string, int) { return false, "pid missing or 0 in request", http.StatusBadRequest } - if !database.UnbanUser(pool, ctx, req.ProfileID) { + if !db.UnbanUser(req.ProfileID) { return false, "Failed to unban user", http.StatusInternalServerError } diff --git a/database/connection.go b/database/connection.go new file mode 100644 index 0000000..0b34756 --- /dev/null +++ b/database/connection.go @@ -0,0 +1,37 @@ +package database + +import ( + "context" + "fmt" + "wwfc/common" + + "github.com/jackc/pgx/v4/pgxpool" +) + +type Connection struct { + pool *pgxpool.Pool + ctx context.Context +} + +func Start(config common.Config) Connection { + conn := Connection{ + ctx: context.Background(), + } + + dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) + dbConf, err := pgxpool.ParseConfig(dbString) + if err != nil { + panic(err) + } + + conn.pool, err = pgxpool.ConnectConfig(conn.ctx, dbConf) + if err != nil { + panic(err) + } + + return conn +} + +func (c *Connection) Close() { + c.pool.Close() +} diff --git a/database/gamestats.go b/database/gamestats.go index c2c778e..9735b99 100644 --- a/database/gamestats.go +++ b/database/gamestats.go @@ -1,10 +1,7 @@ package database import ( - "context" "time" - - "github.com/jackc/pgx/v4/pgxpool" ) const ( @@ -13,17 +10,17 @@ const ( queryGsUpdatePublicData = `UPDATE gamestats_public_data SET pdata = $4, modified_time = CURRENT_TIMESTAMP WHERE profile_id = $1 AND dindex = $2 AND ptype = $3 RETURNING modified_time` ) -func GetGameStatsPublicData(pool *pgxpool.Pool, ctx context.Context, profileId uint32, dindex string, ptype string) (modifiedTime time.Time, publicData string, err error) { - err = pool.QueryRow(ctx, queryGsGetPublicData, profileId, dindex, ptype).Scan(&modifiedTime, &publicData) +func (c *Connection) GetGameStatsPublicData(profileId uint32, dindex string, ptype string) (modifiedTime time.Time, publicData string, err error) { + err = c.pool.QueryRow(c.ctx, queryGsGetPublicData, profileId, dindex, ptype).Scan(&modifiedTime, &publicData) return } -func CreateGameStatsPublicData(pool *pgxpool.Pool, ctx context.Context, profileId uint32, dindex string, ptype string, publicData string) (modifiedTime time.Time, err error) { - err = pool.QueryRow(ctx, queryGsInsertPublicData, profileId, dindex, ptype, publicData).Scan(&modifiedTime) +func (c *Connection) CreateGameStatsPublicData(profileId uint32, dindex string, ptype string, publicData string) (modifiedTime time.Time, err error) { + err = c.pool.QueryRow(c.ctx, queryGsInsertPublicData, profileId, dindex, ptype, publicData).Scan(&modifiedTime) return } -func UpdateGameStatsPublicData(pool *pgxpool.Pool, ctx context.Context, profileId uint32, dindex string, ptype string, publicData string) (modifiedTime time.Time, err error) { - err = pool.QueryRow(ctx, queryGsUpdatePublicData, profileId, dindex, ptype, publicData).Scan(&modifiedTime) +func (c *Connection) UpdateGameStatsPublicData(profileId uint32, dindex string, ptype string, publicData string) (modifiedTime time.Time, err error) { + err = c.pool.QueryRow(c.ctx, queryGsUpdatePublicData, profileId, dindex, ptype, publicData).Scan(&modifiedTime) return } diff --git a/database/login.go b/database/login.go index c3386c0..88a1eb5 100644 --- a/database/login.go +++ b/database/login.go @@ -1,7 +1,6 @@ package database import ( - "context" "errors" "fmt" "time" @@ -9,7 +8,6 @@ import ( "wwfc/logging" "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/pgxpool" "github.com/logrusorgru/aurora/v3" ) @@ -43,9 +41,9 @@ var ( ErrProfileBannedTOS = errors.New("profile is banned for violating the Terms of Service") ) -func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32, defaultKey bool, ngDeviceId uint32, ipAddress string, ingamesn string, deviceAuth bool) (User, error) { +func (c *Connection) LoginUserToGPCM(userId uint64, gsbrcd string, profileId uint32, defaultKey bool, ngDeviceId uint32, ipAddress string, ingamesn string, deviceAuth bool) (User, error) { var exists bool - err := pool.QueryRow(ctx, DoesUserExist, userId, gsbrcd).Scan(&exists) + err := c.pool.QueryRow(c.ctx, DoesUserExist, userId, gsbrcd).Scan(&exists) if err != nil { return User{}, err } @@ -67,7 +65,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb user.Email = user.UniqueNick + "@nds" // Create the GPCM account - err := user.CreateUser(pool, ctx) + err := c.CreateUser(&user) if err != nil { logging.Error("DATABASE", "Error creating user:", aurora.Cyan(userId), aurora.Cyan(gsbrcd), aurora.Cyan(user.ProfileId), "\nerror:", err.Error()) return User{}, err @@ -79,7 +77,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb var lastName *string var allowDefaultKeys bool - err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost, &lastIPAddress, &allowDefaultKeys) + err := c.pool.QueryRow(c.ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost, &lastIPAddress, &allowDefaultKeys) if err != nil { return User{}, err } @@ -106,7 +104,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb if !validDeviceId && id == 0 { // Replace the 0 with the actual device ID user.NgDeviceId[index] = ngDeviceId - _, err = pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId) + _, err = c.pool.Exec(c.ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId) validDeviceId = true } @@ -126,7 +124,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb } user.NgDeviceId = append(user.NgDeviceId, ngDeviceId) - _, err = pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId) + _, err = c.pool.Exec(c.ctx, UpdateUserNGDeviceID, user.ProfileId, user.NgDeviceId) } else if deviceAuth && !validDeviceId && ngDeviceId == 0 { if len(user.NgDeviceId) > 0 && !common.GetConfig().AllowConnectWithoutDeviceID { logging.Error("DATABASE", "NG device ID not provided for profile", aurora.Cyan(user.ProfileId), "- expected one of {", deviceIdList[:len(deviceIdList)-2], "} but got", aurora.Cyan("00000000")) @@ -139,7 +137,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb } if profileId != 0 && user.ProfileId != profileId { - err := user.UpdateProfileID(pool, ctx, profileId) + err := c.UpdateProfileID(&user, profileId) if err != nil { logging.Warn("DATABASE", "Could not update", aurora.Cyan(userId), aurora.Cyan(gsbrcd), "profile ID from", aurora.Cyan(user.ProfileId), "to", aurora.Cyan(profileId)) } else { @@ -152,14 +150,14 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb // This should be set if the user already knows its own profile ID if profileId != 0 && user.LastName == "" { - user.UpdateProfile(pool, ctx, map[string]string{ + c.UpdateProfile(&user, map[string]string{ "lastname": "000000000" + gsbrcd, }) } // Update the user's last IP address and ingamesn if deviceAuth { - _, err = pool.Exec(ctx, UpdateUserLastIPAddress, user.ProfileId, ipAddress, ingamesn) + _, err = c.pool.Exec(c.ctx, UpdateUserLastIPAddress, user.ProfileId, ipAddress, ingamesn) if err != nil { return User{}, err } @@ -176,7 +174,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb var bannedDeviceIdList []uint32 var banReason string timeNow := time.Now().UTC() - err = pool.QueryRow(ctx, SearchUserBan, user.NgDeviceId, user.ProfileId, ipAddress, *lastIPAddress, timeNow).Scan(&banExists, &banTOS, &bannedDeviceIdList, &banReason) + err = c.pool.QueryRow(c.ctx, SearchUserBan, user.NgDeviceId, user.ProfileId, ipAddress, *lastIPAddress, timeNow).Scan(&banExists, &banTOS, &bannedDeviceIdList, &banReason) if err != nil { if err != pgx.ErrNoRows { @@ -220,7 +218,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb return user, nil } -func LoginUserToGameStats(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string) (User, error) { +func (c *Connection) LoginUserToGameStats(userId uint64, gsbrcd string) (User, error) { user := User{ UserId: userId, GsbrCode: gsbrcd, @@ -231,7 +229,7 @@ func LoginUserToGameStats(pool *pgxpool.Pool, ctx context.Context, userId uint64 var lastIPAddress *string var allowDefaultKeys bool - err := pool.QueryRow(ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost, &lastIPAddress, &allowDefaultKeys) + err := c.pool.QueryRow(c.ctx, GetUserProfileID, userId, gsbrcd).Scan(&user.ProfileId, &user.NgDeviceId, &user.Email, &user.UniqueNick, &firstName, &lastName, &user.OpenHost, &lastIPAddress, &allowDefaultKeys) if err != nil { return User{}, err } diff --git a/database/mario_kart_wii.go b/database/mario_kart_wii.go index 32673fd..537223c 100644 --- a/database/mario_kart_wii.go +++ b/database/mario_kart_wii.go @@ -1,11 +1,9 @@ package database import ( - "context" "wwfc/common" "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/pgxpool" ) type MarioKartWiiTopTenRanking struct { @@ -56,9 +54,9 @@ const ( "SET regionid = EXCLUDED.regionid, score = EXCLUDED.score, playerinfo = EXCLUDED.playerinfo, ghost = EXCLUDED.ghost, upload_time = CURRENT_TIMESTAMP" ) -func GetMarioKartWiiTopTenRankings(pool *pgxpool.Pool, ctx context.Context, regionId common.MarioKartWiiLeaderboardRegionId, +func (c *Connection) GetMarioKartWiiTopTenRankings(regionId common.MarioKartWiiLeaderboardRegionId, courseId common.MarioKartWiiCourseId) ([]MarioKartWiiTopTenRanking, error) { - rows, err := pool.Query(ctx, getTopTenRankingsQuery, regionId, courseId) + rows, err := c.pool.Query(c.ctx, getTopTenRankingsQuery, regionId, courseId) if err != nil { return nil, err } @@ -81,8 +79,8 @@ func GetMarioKartWiiTopTenRankings(pool *pgxpool.Pool, ctx context.Context, regi return topTenRankings, nil } -func GetMarioKartWiiGhostData(pool *pgxpool.Pool, ctx context.Context, courseId common.MarioKartWiiCourseId, time int) (int, error) { - row := pool.QueryRow(ctx, getGhostDataQuery, courseId, time) +func (c *Connection) GetMarioKartWiiGhostData(courseId common.MarioKartWiiCourseId, time int) (int, error) { + row := c.pool.QueryRow(c.ctx, getGhostDataQuery, courseId, time) var fileId int if err := row.Scan(&fileId); err != nil { @@ -92,9 +90,9 @@ func GetMarioKartWiiGhostData(pool *pgxpool.Pool, ctx context.Context, courseId return fileId, nil } -func GetMarioKartWiiStoredGhostData(pool *pgxpool.Pool, ctx context.Context, regionId common.MarioKartWiiLeaderboardRegionId, +func (c *Connection) GetMarioKartWiiStoredGhostData(regionId common.MarioKartWiiLeaderboardRegionId, courseId common.MarioKartWiiCourseId) (int, int, error) { - row := pool.QueryRow(ctx, getStoredGhostDataQuery, regionId, courseId) + row := c.pool.QueryRow(c.ctx, getStoredGhostDataQuery, regionId, courseId) var pid int var fileId int @@ -105,8 +103,8 @@ func GetMarioKartWiiStoredGhostData(pool *pgxpool.Pool, ctx context.Context, reg return pid, fileId, nil } -func GetMarioKartWiiFile(pool *pgxpool.Pool, ctx context.Context, fileId int) ([]byte, error) { - row := pool.QueryRow(ctx, getFileQuery, fileId) +func (c *Connection) GetMarioKartWiiFile(fileId int) ([]byte, error) { + row := c.pool.QueryRow(c.ctx, getFileQuery, fileId) var file []byte if err := row.Scan(&file); err != nil { @@ -116,9 +114,8 @@ func GetMarioKartWiiFile(pool *pgxpool.Pool, ctx context.Context, fileId int) ([ return file, nil } -func GetMarioKartWiiGhostFile(pool *pgxpool.Pool, ctx context.Context, courseId common.MarioKartWiiCourseId, - time int, pid int) ([]byte, error) { - row := pool.QueryRow(ctx, getGhostFileQuery, courseId, time, pid) +func (c *Connection) GetMarioKartWiiGhostFile(courseId common.MarioKartWiiCourseId, time int, pid int) ([]byte, error) { + row := c.pool.QueryRow(c.ctx, getGhostFileQuery, courseId, time, pid) var ghost []byte if err := row.Scan(&ghost); err != nil { @@ -128,17 +125,17 @@ func GetMarioKartWiiGhostFile(pool *pgxpool.Pool, ctx context.Context, courseId return ghost, nil } -func InsertMarioKartWiiGhostFile(pool *pgxpool.Pool, ctx context.Context, regionId common.MarioKartWiiLeaderboardRegionId, +func (c *Connection) InsertMarioKartWiiGhostFile(regionId common.MarioKartWiiLeaderboardRegionId, courseId common.MarioKartWiiCourseId, score int, pid int, playerInfo string, ghost []byte) error { - _, err := pool.Exec(ctx, insertGhostFileStatement, regionId, courseId, score, pid, playerInfo, ghost) + _, err := c.pool.Exec(c.ctx, insertGhostFileStatement, regionId, courseId, score, pid, playerInfo, ghost) return err } // Mario Kart Wii friend info functions for API compatibility -func GetMKWFriendInfo(pool *pgxpool.Pool, ctx context.Context, profileId uint32) string { - records, err := GetSakeRecords(pool, ctx, 1687, []int32{int32(profileId)}, "FriendInfo", nil, []string{"info"}, "") +func (c *Connection) GetMKWFriendInfo(profileId uint32) string { + records, err := c.GetSakeRecords(1687, []int32{int32(profileId)}, "FriendInfo", nil, []string{"info"}, "") if err != nil || len(records) == 0 { return "" } @@ -151,8 +148,8 @@ func GetMKWFriendInfo(pool *pgxpool.Pool, ctx context.Context, profileId uint32) return infoField.Value } -func UpdateMKWFriendInfo(pool *pgxpool.Pool, ctx context.Context, profileId uint32, info string) { - records, err := GetSakeRecords(pool, ctx, 1687, []int32{int32(profileId)}, "FriendInfo", nil, []string{"info"}, "") +func (c *Connection) UpdateMKWFriendInfo(profileId uint32, info string) { + records, err := c.GetSakeRecords(1687, []int32{int32(profileId)}, "FriendInfo", nil, []string{"info"}, "") if err == pgx.ErrNoRows || (err == nil && len(records) == 0) { // No existing record, insert new one record := SakeRecord{ @@ -166,14 +163,14 @@ func UpdateMKWFriendInfo(pool *pgxpool.Pool, ctx context.Context, profileId uint }, }, } - _, err = InsertSakeRecord(pool, ctx, record) + _, err = c.InsertSakeRecord(record) } else if err == nil { // Update existing record records[0].Fields["info"] = SakeField{ Type: SakeFieldTypeBinaryData, Value: info, } - err = UpdateSakeRecord(pool, ctx, records[0], int32(profileId)) + err = c.UpdateSakeRecord(records[0], int32(profileId)) } diff --git a/database/sake.go b/database/sake.go index a6a4f6b..794052e 100644 --- a/database/sake.go +++ b/database/sake.go @@ -1,7 +1,6 @@ package database import ( - "context" "encoding/json" "errors" "wwfc/filter" @@ -63,6 +62,13 @@ const ( VALUES ($1, $2, $3, $4) RETURNING record_id` + deleteSakeRecordQuery = ` + DELETE FROM sake_records + WHERE game_id = $1 + AND table_id = $2 + AND record_id = $3 + AND owner_id = $4` + checkMaxSakeRecordsQuery = ` SELECT COUNT(*) FROM sake_records @@ -84,7 +90,7 @@ func parseSakeFieldsFromJson(fieldsJson []byte) (map[string]SakeField, error) { return fields, nil } -func GetSakeRecords(pool *pgxpool.Pool, ctx context.Context, gameId int, ownerIds []int32, tableId string, recordIds []int32, fields []string, filterExpr string) ([]SakeRecord, error) { +func (c *Connection) GetSakeRecords(gameId int, ownerIds []int32, tableId string, recordIds []int32, fields []string, filterExpr string) ([]SakeRecord, error) { if fields == nil { fields = []string{} } @@ -103,7 +109,7 @@ func GetSakeRecords(pool *pgxpool.Pool, ctx context.Context, gameId int, ownerId } var filterQuery string - err = pool.AcquireFunc(ctx, func(conn *pgxpool.Conn) error { + err = c.pool.AcquireFunc(c.ctx, func(conn *pgxpool.Conn) error { filterQuery, err = createSqlFilter(conn.Conn().PgConn(), tree) return err }) @@ -116,7 +122,7 @@ func GetSakeRecords(pool *pgxpool.Pool, ctx context.Context, gameId int, ownerId query += " AND (" + filterQuery + ")" } - rows, err := pool.Query(ctx, query, gameId, tableId, ownerIds, recordIds) + rows, err := c.pool.Query(c.ctx, query, gameId, tableId, ownerIds, recordIds) if err != nil { return nil, err } @@ -144,13 +150,13 @@ func GetSakeRecords(pool *pgxpool.Pool, ctx context.Context, gameId int, ownerId return records, nil } -func UpdateSakeRecord(pool *pgxpool.Pool, ctx context.Context, record SakeRecord, ownerId int32) error { +func (c *Connection) UpdateSakeRecord(record SakeRecord, ownerId int32) error { fieldsJson, err := json.Marshal(record.Fields) if err != nil { return err } var existingOwnerId int32 - err = pool.QueryRow(ctx, updateSakeRecordQuery, record.GameId, record.TableId, record.RecordId, ownerId, fieldsJson).Scan(&existingOwnerId) + err = c.pool.QueryRow(c.ctx, updateSakeRecordQuery, record.GameId, record.TableId, record.RecordId, ownerId, fieldsJson).Scan(&existingOwnerId) if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.CheckViolation { @@ -165,14 +171,14 @@ func UpdateSakeRecord(pool *pgxpool.Pool, ctx context.Context, record SakeRecord return nil } -func InsertSakeRecord(pool *pgxpool.Pool, ctx context.Context, record SakeRecord) (recordId int32, err error) { +func (c *Connection) InsertSakeRecord(record SakeRecord) (recordId int32, err error) { fieldsJson, err := json.Marshal(record.Fields) if err != nil { return 0, err } for i := 0; i < 10; i++ { - err = pool.QueryRow(ctx, insertSakeRecordQuery, record.GameId, record.TableId, record.OwnerId, fieldsJson).Scan(&recordId) + err = c.pool.QueryRow(c.ctx, insertSakeRecordQuery, record.GameId, record.TableId, record.OwnerId, fieldsJson).Scan(&recordId) if err == nil { break } @@ -188,9 +194,9 @@ func InsertSakeRecord(pool *pgxpool.Pool, ctx context.Context, record SakeRecord return recordId, err } -func IsMaxSakeRecordsReached(pool *pgxpool.Pool, ctx context.Context, profileId uint32, maxRecords int) (bool, error) { +func (c *Connection) IsMaxSakeRecordsReached(profileId uint32, maxRecords int) (bool, error) { var count int - err := pool.QueryRow(ctx, checkMaxSakeRecordsQuery, profileId).Scan(&count) + err := c.pool.QueryRow(c.ctx, checkMaxSakeRecordsQuery, profileId).Scan(&count) if err != nil { return false, err } diff --git a/database/schema.go b/database/schema.go index 7d69421..94516f7 100644 --- a/database/schema.go +++ b/database/schema.go @@ -1,13 +1,7 @@ package database -import ( - "context" - - "github.com/jackc/pgx/v4/pgxpool" -) - -func UpdateTables(pool *pgxpool.Pool, ctx context.Context) { - pool.Exec(ctx, ` +func (c *Connection) UpdateTables() { + _, _ = c.pool.Exec(c.ctx, ` ALTER TABLE ONLY public.users ADD IF NOT EXISTS last_ip_address character varying DEFAULT ''::character varying, @@ -23,7 +17,7 @@ func UpdateTables(pool *pgxpool.Pool, ctx context.Context) { ADD IF NOT EXISTS allow_default_keys boolean DEFAULT false; `) - pool.Exec(ctx, ` + _, _ = c.pool.Exec(c.ctx, ` DO $$ BEGIN @@ -35,7 +29,7 @@ func UpdateTables(pool *pgxpool.Pool, ctx context.Context) { `) - pool.Exec(ctx, ` + _, _ = c.pool.Exec(c.ctx, ` ALTER TABLE ONLY public.mario_kart_wii_sake ADD IF NOT EXISTS id serial PRIMARY KEY, diff --git a/database/user.go b/database/user.go index 8de25b6..821a428 100644 --- a/database/user.go +++ b/database/user.go @@ -1,12 +1,9 @@ package database import ( - "context" "errors" "math/rand" "time" - - "github.com/jackc/pgx/v4/pgxpool" ) const ( @@ -48,9 +45,9 @@ var ( ErrReservedProfileIDRange = errors.New("profile ID is in reserved range") ) -func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) error { +func (c *Connection) CreateUser(user *User) error { if user.ProfileId == 0 { - return pool.QueryRow(ctx, InsertUser, user.UserId, user.GsbrCode, "", user.NgDeviceId, user.Email, user.UniqueNick).Scan(&user.ProfileId) + return c.pool.QueryRow(c.ctx, InsertUser, user.UserId, user.GsbrCode, "", user.NgDeviceId, user.Email, user.UniqueNick).Scan(&user.ProfileId) } if user.ProfileId >= 1000000000 { @@ -58,7 +55,7 @@ func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) error { } var exists bool - err := pool.QueryRow(ctx, IsProfileIDInUse, user.ProfileId).Scan(&exists) + err := c.pool.QueryRow(c.ctx, IsProfileIDInUse, user.ProfileId).Scan(&exists) if err != nil { return err } @@ -67,17 +64,17 @@ func (user *User) CreateUser(pool *pgxpool.Pool, ctx context.Context) error { return ErrProfileIDInUse } - _, err = pool.Exec(ctx, InsertUserWithProfileID, user.ProfileId, user.UserId, user.GsbrCode, "", user.NgDeviceId, user.Email, user.UniqueNick) + _, err = c.pool.Exec(c.ctx, InsertUserWithProfileID, user.ProfileId, user.UserId, user.GsbrCode, "", user.NgDeviceId, user.Email, user.UniqueNick) return err } -func (user *User) UpdateProfileID(pool *pgxpool.Pool, ctx context.Context, newProfileId uint32) error { +func (c *Connection) UpdateProfileID(user *User, newProfileId uint32) error { if newProfileId >= 1000000000 { return ErrReservedProfileIDRange } var exists bool - err := pool.QueryRow(ctx, IsProfileIDInUse, newProfileId).Scan(&exists) + err := c.pool.QueryRow(c.ctx, IsProfileIDInUse, newProfileId).Scan(&exists) if err != nil { return err } @@ -86,7 +83,7 @@ func (user *User) UpdateProfileID(pool *pgxpool.Pool, ctx context.Context, newPr return ErrProfileIDInUse } - _, err = pool.Exec(ctx, UpdateUserProfileID, user.UserId, user.GsbrCode, newProfileId) + _, err = c.pool.Exec(c.ctx, UpdateUserProfileID, user.UserId, user.GsbrCode, newProfileId) if err == nil { user.ProfileId = newProfileId } @@ -99,7 +96,7 @@ func GetUniqueUserID() uint64 { return uint64(rand.Int63n(0x80000000000)) } -func (user *User) UpdateProfile(pool *pgxpool.Pool, ctx context.Context, data map[string]string) { +func (c *Connection) UpdateProfile(user *User, data map[string]string) { firstName, firstNameExists := data["firstname"] lastName, lastNameExists := data["lastname"] openHost, openHostExists := data["wl:oh"] @@ -108,7 +105,7 @@ func (user *User) UpdateProfile(pool *pgxpool.Pool, ctx context.Context, data ma openHostBool = true } - _, err := pool.Exec(ctx, UpdateUserTable, user.ProfileId, firstName, firstNameExists, lastName, lastNameExists, openHostBool, openHostExists) + _, err := c.pool.Exec(c.ctx, UpdateUserTable, user.ProfileId, firstName, firstNameExists, lastName, lastNameExists, openHostBool, openHostExists) if err != nil { panic(err) } @@ -126,9 +123,9 @@ func (user *User) UpdateProfile(pool *pgxpool.Pool, ctx context.Context, data ma } } -func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (User, bool) { +func (c *Connection) GetProfile(profileId uint32) (User, bool) { user := User{} - row := pool.QueryRow(ctx, GetUser, profileId) + row := c.pool.QueryRow(c.ctx, GetUser, profileId) err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName, &user.OpenHost, &user.LastIPAddress, &user.LastInGameSn) if err != nil { return User{}, false @@ -138,9 +135,9 @@ func GetProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (User return user, true } -func ClearProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (User, bool) { +func (c *Connection) ClearProfile(profileId uint32) (User, bool) { user := User{} - row := pool.QueryRow(ctx, ClearProfileQuery, profileId) + row := c.pool.QueryRow(c.ctx, ClearProfileQuery, profileId) err := row.Scan(&user.UserId, &user.GsbrCode, &user.Email, &user.UniqueNick, &user.FirstName, &user.LastName, &user.OpenHost, &user.LastIPAddress, &user.LastInGameSn) if err != nil { @@ -151,12 +148,12 @@ func ClearProfile(pool *pgxpool.Pool, ctx context.Context, profileId uint32) (Us return user, true } -func BanUser(pool *pgxpool.Pool, ctx context.Context, profileId uint32, tos bool, length time.Duration, reason string, reasonHidden string, moderator string) bool { - _, err := pool.Exec(ctx, UpdateUserBan, profileId, time.Now().UTC(), time.Now().UTC().Add(length), reason, reasonHidden, moderator, tos) +func (c *Connection) BanUser(profileId uint32, tos bool, length time.Duration, reason string, reasonHidden string, moderator string) bool { + _, err := c.pool.Exec(c.ctx, UpdateUserBan, profileId, time.Now().UTC(), time.Now().UTC().Add(length), reason, reasonHidden, moderator, tos) return err == nil } -func UnbanUser(pool *pgxpool.Pool, ctx context.Context, profileId uint32) bool { - _, err := pool.Exec(ctx, DisableUserBan, profileId) +func (c *Connection) UnbanUser(profileId uint32) bool { + _, err := c.pool.Exec(c.ctx, DisableUserBan, profileId) return err == nil } diff --git a/gamestats/auth.go b/gamestats/auth.go index 02c4faa..85bbcca 100644 --- a/gamestats/auth.go +++ b/gamestats/auth.go @@ -5,7 +5,6 @@ import ( "strconv" "time" "wwfc/common" - "wwfc/database" "wwfc/gpcm" "wwfc/logging" @@ -77,7 +76,7 @@ func (g *GameStatsSession) authp(command common.GameSpyCommand) { return } - g.User, err = database.LoginUserToGameStats(pool, ctx, userId, gsbrcd) + g.User, err = db.LoginUserToGameStats(userId, gsbrcd) if err != nil { logging.Error(g.ModuleName, "Error logging in user:", err.Error()) g.Write(errorCmd) diff --git a/gamestats/getpd.go b/gamestats/getpd.go index 0de2aa9..473e49a 100644 --- a/gamestats/getpd.go +++ b/gamestats/getpd.go @@ -3,7 +3,6 @@ package gamestats import ( "strconv" "wwfc/common" - "wwfc/database" "wwfc/logging" "github.com/jackc/pgx/v4" @@ -54,7 +53,7 @@ func (g *GameStatsSession) getpd(command common.GameSpyCommand) { logging.Info(g.ModuleName, "Get public data: PID:", aurora.Cyan(profileId), "Index:", aurora.Cyan(dindex), "Type:", aurora.Cyan(ptype)) - modifiedTime, data, err := database.GetGameStatsPublicData(pool, ctx, uint32(profileId), dindex, ptype) + modifiedTime, data, err := db.GetGameStatsPublicData(uint32(profileId), dindex, ptype) if err != nil { if err != pgx.ErrNoRows { logging.Error(g.ModuleName, "GetGameStatsPublicData returned", err) diff --git a/gamestats/main.go b/gamestats/main.go index e8efd69..2e6e2ea 100644 --- a/gamestats/main.go +++ b/gamestats/main.go @@ -1,9 +1,7 @@ package gamestats import ( - "context" "encoding/gob" - "fmt" "os" "strings" "wwfc/common" @@ -11,7 +9,6 @@ import ( "wwfc/gpcm" "wwfc/logging" - "github.com/jackc/pgx/v4/pgxpool" "github.com/linkdata/deadlock" "github.com/logrusorgru/aurora/v3" ) @@ -37,8 +34,7 @@ type GameStatsSession struct { } var ( - ctx = context.Background() - pool *pgxpool.Pool + db database.Connection serverName string webSalt string @@ -57,16 +53,7 @@ func StartServer(reload bool) { common.ReadGameList() // Start SQL - dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) - dbConf, err := pgxpool.ParseConfig(dbString) - if err != nil { - panic(err) - } - - pool, err = pgxpool.ConnectConfig(ctx, dbConf) - if err != nil { - panic(err) - } + db = database.Start(config) if reload { // Load state @@ -114,7 +101,7 @@ func Shutdown() { panic(err) } - pool.Close() + db.Close() logging.Notice("GSTATS", "Saved", aurora.Cyan(len(sessionsByConnIndex)), "sessions") } diff --git a/gamestats/setpd.go b/gamestats/setpd.go index 20a2cf3..a611fc3 100644 --- a/gamestats/setpd.go +++ b/gamestats/setpd.go @@ -5,7 +5,6 @@ import ( "strings" "time" "wwfc/common" - "wwfc/database" "wwfc/logging" "github.com/jackc/pgx/v4" @@ -78,7 +77,7 @@ func (g *GameStatsSession) setpd(command common.GameSpyCommand) { } var modifiedTime time.Time - _, _, err := database.GetGameStatsPublicData(pool, ctx, g.User.ProfileId, dindex, ptype) + _, _, err := db.GetGameStatsPublicData(g.User.ProfileId, dindex, ptype) if err != nil { if err != pgx.ErrNoRows { logging.Error(g.ModuleName, "GetGameStatsPublicData returned", err) @@ -86,14 +85,14 @@ func (g *GameStatsSession) setpd(command common.GameSpyCommand) { return } - modifiedTime, err = database.CreateGameStatsPublicData(pool, ctx, g.User.ProfileId, dindex, ptype, newData) + modifiedTime, err = db.CreateGameStatsPublicData(g.User.ProfileId, dindex, ptype, newData) if err != nil { logging.Error(g.ModuleName, "GetGameStatsPublicData returned", err) g.Write(errMsg) return } } else { - modifiedTime, err = database.UpdateGameStatsPublicData(pool, ctx, g.User.ProfileId, dindex, ptype, newData) + modifiedTime, err = db.UpdateGameStatsPublicData(g.User.ProfileId, dindex, ptype, newData) if err != nil { logging.Error(g.ModuleName, "UpdateGameStatsPublicData returned", err) g.Write(errMsg) diff --git a/gpcm/login.go b/gpcm/login.go index 38bae59..ccf4fc9 100644 --- a/gpcm/login.go +++ b/gpcm/login.go @@ -456,7 +456,7 @@ func (g *GameSpySession) performLoginWithDatabase(userId uint64, gsbrCode string ipAddress = ipAddress[:strings.Index(ipAddress, ":")] } - user, err := database.LoginUserToGPCM(pool, ctx, userId, gsbrCode, profileId, defaultKey, deviceId, ipAddress, g.InGameName, deviceAuth) + user, err := db.LoginUserToGPCM(userId, gsbrCode, profileId, defaultKey, deviceId, ipAddress, g.InGameName, deviceAuth) g.User = user if err != nil { diff --git a/gpcm/main.go b/gpcm/main.go index 1517182..ac0d626 100644 --- a/gpcm/main.go +++ b/gpcm/main.go @@ -1,9 +1,7 @@ package gpcm import ( - "context" "encoding/gob" - "fmt" "os" "strings" "wwfc/common" @@ -11,12 +9,11 @@ import ( "wwfc/logging" "wwfc/qr2" - "github.com/jackc/pgx/v4/pgxpool" "github.com/linkdata/deadlock" "github.com/logrusorgru/aurora/v3" ) -var ServerName = "gpcm" +const ServerName = "gpcm" type GameSpySession struct { ConnIndex uint64 @@ -60,8 +57,8 @@ type GameSpySession struct { } var ( - ctx = context.Background() - pool *pgxpool.Pool + db database.Connection + // I would use a sync.Map instead of the map mutex combo, but this performs better. sessions = map[uint32]*GameSpySession{} sessionsByConnIndex = map[uint64]*GameSpySession{} @@ -77,18 +74,8 @@ func StartServer(reload bool) { config := common.GetConfig() // Start SQL - dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) - dbConf, err := pgxpool.ParseConfig(dbString) - if err != nil { - panic(err) - } - - pool, err = pgxpool.ConnectConfig(ctx, dbConf) - if err != nil { - panic(err) - } - - database.UpdateTables(pool, ctx) + db = database.Start(config) + db.UpdateTables() allowDefaultDolphinKeys = config.AllowDefaultDolphinKeys @@ -108,7 +95,11 @@ func Shutdown() { if err != nil { logging.Error("GPCM", "Failed to save state:", err) } + + db.Close() + logging.Notice("GPCM", "Saved", aurora.Cyan(len(sessions)), "sessions") + } func CloseConnection(index uint64) { diff --git a/gpcm/profile.go b/gpcm/profile.go index 96231f3..ffc7a86 100644 --- a/gpcm/profile.go +++ b/gpcm/profile.go @@ -30,7 +30,7 @@ func (g *GameSpySession) getProfile(command common.GameSpyCommand) { mutex.Unlock() } else { mutex.Unlock() - user, ok = database.GetProfile(pool, ctx, uint32(profileId)) + user, ok = db.GetProfile(uint32(profileId)) if !ok { // The profile info was requested on is invalid. g.replyError(ErrGetProfileBadProfile) @@ -91,7 +91,7 @@ func (g *GameSpySession) updateProfile(command common.GameSpyCommand) { } } - g.User.UpdateProfile(pool, ctx, command.OtherValues) + db.UpdateProfile(&g.User, command.OtherValues) } func VerifyPlayerSearch(profileId uint32, sessionKey int32, gameName string) (string, bool) { diff --git a/race/main.go b/race/main.go index 790d697..58dc6f1 100644 --- a/race/main.go +++ b/race/main.go @@ -1,20 +1,17 @@ package race import ( - "context" - "fmt" "net/http" "strings" "wwfc/common" + "wwfc/database" "wwfc/logging" - "github.com/jackc/pgx/v4/pgxpool" "github.com/logrusorgru/aurora/v3" ) var ( - ctx = context.Background() - pool *pgxpool.Pool + db database.Connection ) func StartServer(reload bool) { @@ -24,19 +21,11 @@ func StartServer(reload bool) { common.ReadGameList() // Start SQL - dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) - dbConf, err := pgxpool.ParseConfig(dbString) - if err != nil { - panic(err) - } - - pool, err = pgxpool.ConnectConfig(ctx, dbConf) - if err != nil { - panic(err) - } + db = database.Start(config) } func Shutdown() { + db.Close() } func HandleRequest(responseWriter http.ResponseWriter, request *http.Request) { diff --git a/race/nintendo_racing_service.go b/race/nintendo_racing_service.go index 9be0aca..b675553 100644 --- a/race/nintendo_racing_service.go +++ b/race/nintendo_racing_service.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" "wwfc/common" - "wwfc/database" "wwfc/logging" "github.com/logrusorgru/aurora/v3" @@ -144,7 +143,7 @@ func handleGetTopTenRankingsRequest(moduleName string, responseWriter http.Respo return } - topTenRankings, err := database.GetMarioKartWiiTopTenRankings(pool, ctx, regionId, courseId) + topTenRankings, err := db.GetMarioKartWiiTopTenRankings(regionId, courseId) if err != nil { logging.Error(moduleName, "Failed to get the Top 10 rankings:", err) writeErrorResponse(raceServiceResultDatabaseError, responseWriter) diff --git a/sake/main.go b/sake/main.go index c310d99..39bbe22 100644 --- a/sake/main.go +++ b/sake/main.go @@ -1,20 +1,17 @@ package sake import ( - "context" - "fmt" "net/http" "strings" "wwfc/common" + "wwfc/database" "wwfc/logging" - "github.com/jackc/pgx/v4/pgxpool" "github.com/logrusorgru/aurora/v3" ) var ( - ctx = context.Background() - pool *pgxpool.Pool + db database.Connection ) func StartServer(reload bool) { @@ -24,19 +21,11 @@ func StartServer(reload bool) { common.ReadGameList() // Start SQL - dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) - dbConf, err := pgxpool.ParseConfig(dbString) - if err != nil { - panic(err) - } - - pool, err = pgxpool.ConnectConfig(ctx, dbConf) - if err != nil { - panic(err) - } + db = database.Start(config) } func Shutdown() { + db.Close() } func HandleRequest(w http.ResponseWriter, r *http.Request) { diff --git a/sake/mario_kart_wii.go b/sake/mario_kart_wii.go index 25af3fb..efdca5f 100644 --- a/sake/mario_kart_wii.go +++ b/sake/mario_kart_wii.go @@ -85,7 +85,7 @@ func getMarioKartWiiGhostDataRecord(moduleName string, request StorageRequestCom return []database.SakeRecord{}, false } - fileId, err := database.GetMarioKartWiiGhostData(pool, ctx, courseId, time) + fileId, err := db.GetMarioKartWiiGhostData(courseId, time) if err != nil { logging.Error(moduleName, "mariokartwii/GhostData: Failed to get the ghost data from the database:", err) return []database.SakeRecord{}, false @@ -157,7 +157,7 @@ func getMarioKartWiiStoredGhostDataRecord(moduleName string, request StorageRequ regionId = common.Worldwide } - pid, fileId, err := database.GetMarioKartWiiStoredGhostData(pool, ctx, regionId, courseId) + pid, fileId, err := db.GetMarioKartWiiStoredGhostData(regionId, courseId) if err != nil { logging.Error(moduleName, "mariokartwii/StoredGhostData: Failed to get the stored ghost data from the database:", err) return []database.SakeRecord{}, false @@ -200,7 +200,7 @@ func handleMarioKartWiiFileDownloadRequest(moduleName string, responseWriter htt return } - file, err := database.GetMarioKartWiiFile(pool, ctx, fileId) + file, err := db.GetMarioKartWiiFile(fileId) if err != nil { logging.Error(moduleName, "Failed to get the file from the database:", err) responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultServerError)) @@ -259,7 +259,7 @@ func handleMarioKartWiiGhostDownloadRequest(moduleName string, responseWriter ht return } - ghost, err := database.GetMarioKartWiiGhostFile(pool, ctx, courseId, time, pid) + ghost, err := db.GetMarioKartWiiGhostFile(courseId, time, pid) if err != nil { logging.Error(moduleName, "Failed to get a ghost file from the database:", err) responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultServerError)) @@ -395,7 +395,7 @@ func handleMarioKartWiiGhostUploadRequest(moduleName string, responseWriter http ghostFile = nil } - err = database.InsertMarioKartWiiGhostFile(pool, ctx, regionId, courseId, score, pid, playerInfo, []byte(ghostData)) + err = db.InsertMarioKartWiiGhostFile(regionId, courseId, score, pid, playerInfo, []byte(ghostData)) if err != nil { logging.Error(moduleName, "Failed to insert the ghost file into the database:", err) responseWriter.Header().Set(SakeFileResultHeader, strconv.Itoa(SakeFileResultServerError)) diff --git a/sake/storage.go b/sake/storage.go index f6b98b4..dff35be 100644 --- a/sake/storage.go +++ b/sake/storage.go @@ -261,7 +261,7 @@ func getRequestIdentity(moduleName string, request StorageRequestCommon) (uint32 } func createRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, request StorageRequestCommon) StorageResponseBody { - if reached, err := database.IsMaxSakeRecordsReached(pool, ctx, profileId, MaxSakeRecordsPerProfile); err != nil { + if reached, err := db.IsMaxSakeRecordsReached(profileId, MaxSakeRecordsPerProfile); err != nil { logging.Error(moduleName, "Failed to check max sake records:", err) return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{ CreateRecordResult: ResultDatabaseUnavailable, @@ -311,7 +311,7 @@ func createRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, record.OwnerId = int32(profileId) // TODO: Limit number of records or fields a user can have - recordId, err := database.InsertSakeRecord(pool, ctx, record) + recordId, err := db.InsertSakeRecord(record) if err != nil { logging.Error(moduleName, "Failed to insert sake record into the database:", err) return StorageResponseBody{CreateRecordResponse: &CreateRecordResponse{ @@ -349,7 +349,7 @@ func getMyRecords(moduleName string, profileId uint32, gameInfo common.GameInfo, }} } - records, err := database.GetSakeRecords(pool, ctx, gameInfo.GameID, []int32{int32(profileId)}, request.TableID, nil, request.Fields.String, request.Filter) + records, err := db.GetSakeRecords(gameInfo.GameID, []int32{int32(profileId)}, request.TableID, nil, request.Fields.String, request.Filter) if err != nil { logging.Error(moduleName, "Failed to get sake records from the database:", err) if err == pgx.ErrNoRows { @@ -410,7 +410,7 @@ func updateRecord(moduleName string, profileId uint32, gameInfo common.GameInfo, record.RecordId = int32(request.RecordID) record.OwnerId = int32(profileId) - err := database.UpdateSakeRecord(pool, ctx, record, int32(profileId)) + err := db.UpdateSakeRecord(record, int32(profileId)) if err != nil { logging.Error(moduleName, "Failed to update sake record in the database:", err) if err == database.ErrSakeNotOwned { @@ -467,7 +467,7 @@ func searchForRecords(moduleName string, profileId uint32, gameInfo common.GameI } var err error - records, err = database.GetSakeRecords(pool, ctx, gameInfo.GameID, ownerIds, request.TableID, nil, request.Fields.String, request.Filter) + records, err = db.GetSakeRecords(gameInfo.GameID, ownerIds, request.TableID, nil, request.Fields.String, request.Filter) if err != nil { logging.Error(moduleName, "Failed to get sake records from the database:", err) return StorageResponseBody{SearchForRecordsResponse: &SearchForRecordsResponse{