mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
An MMO server without multiplayer defeats the purpose. PostgreSQL is the right choice and Docker Compose already solves the setup pain. This reverts the common/db wrapper, SQLite schema, config Driver field, modernc.org/sqlite dependency, and all repo type changes while keeping the dashboard, wizard, and CI improvements from the previous commit.
246 lines
9.6 KiB
Go
246 lines
9.6 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
// CharacterRepository centralizes all database access for the characters table.
|
|
type CharacterRepository struct {
|
|
db *sqlx.DB
|
|
}
|
|
|
|
// NewCharacterRepository creates a new CharacterRepository.
|
|
func NewCharacterRepository(db *sqlx.DB) *CharacterRepository {
|
|
return &CharacterRepository{db: db}
|
|
}
|
|
|
|
// LoadColumn reads a single []byte column by character ID.
|
|
func (r *CharacterRepository) LoadColumn(charID uint32, column string) ([]byte, error) {
|
|
var data []byte
|
|
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id = $1", charID).Scan(&data)
|
|
return data, err
|
|
}
|
|
|
|
// SaveColumn writes a single []byte column by character ID.
|
|
func (r *CharacterRepository) SaveColumn(charID uint32, column string, data []byte) error {
|
|
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", data, charID)
|
|
return err
|
|
}
|
|
|
|
// ReadInt reads a single integer column (0 for NULL) by character ID.
|
|
func (r *CharacterRepository) ReadInt(charID uint32, column string) (int, error) {
|
|
var value int
|
|
err := r.db.QueryRow("SELECT COALESCE("+column+", 0) FROM characters WHERE id=$1", charID).Scan(&value)
|
|
return value, err
|
|
}
|
|
|
|
// AdjustInt atomically adds delta to an integer column and returns the new value.
|
|
func (r *CharacterRepository) AdjustInt(charID uint32, column string, delta int) (int, error) {
|
|
var value int
|
|
err := r.db.QueryRow(
|
|
"UPDATE characters SET "+column+"=COALESCE("+column+", 0)+$1 WHERE id=$2 RETURNING "+column,
|
|
delta, charID,
|
|
).Scan(&value)
|
|
return value, err
|
|
}
|
|
|
|
// GetName returns the character name by ID.
|
|
func (r *CharacterRepository) GetName(charID uint32) (string, error) {
|
|
var name string
|
|
err := r.db.QueryRow("SELECT name FROM characters WHERE id=$1", charID).Scan(&name)
|
|
return name, err
|
|
}
|
|
|
|
// GetUserID returns the owning user_id for a character.
|
|
func (r *CharacterRepository) GetUserID(charID uint32) (uint32, error) {
|
|
var userID uint32
|
|
err := r.db.QueryRow("SELECT user_id FROM characters WHERE id=$1", charID).Scan(&userID)
|
|
return userID, err
|
|
}
|
|
|
|
// UpdateLastLogin sets the last_login timestamp.
|
|
func (r *CharacterRepository) UpdateLastLogin(charID uint32, timestamp int64) error {
|
|
_, err := r.db.Exec("UPDATE characters SET last_login=$1 WHERE id=$2", timestamp, charID)
|
|
return err
|
|
}
|
|
|
|
// UpdateTimePlayed sets the time_played value.
|
|
func (r *CharacterRepository) UpdateTimePlayed(charID uint32, timePlayed int) error {
|
|
_, err := r.db.Exec("UPDATE characters SET time_played=$1 WHERE id=$2", timePlayed, charID)
|
|
return err
|
|
}
|
|
|
|
// GetCharIDsByUserID returns all character IDs belonging to a user.
|
|
func (r *CharacterRepository) GetCharIDsByUserID(userID uint32) ([]uint32, error) {
|
|
var ids []uint32
|
|
err := r.db.Select(&ids, "SELECT id FROM characters WHERE user_id=$1", userID)
|
|
return ids, err
|
|
}
|
|
|
|
// ReadTime reads a single time.Time column by character ID.
|
|
// Returns the provided default if the column is NULL.
|
|
func (r *CharacterRepository) ReadTime(charID uint32, column string, defaultVal time.Time) (time.Time, error) {
|
|
var t sql.NullTime
|
|
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&t)
|
|
if err != nil {
|
|
return defaultVal, err
|
|
}
|
|
if !t.Valid {
|
|
return defaultVal, nil
|
|
}
|
|
return t.Time, nil
|
|
}
|
|
|
|
// SaveTime writes a single time.Time column by character ID.
|
|
func (r *CharacterRepository) SaveTime(charID uint32, column string, value time.Time) error {
|
|
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
|
return err
|
|
}
|
|
|
|
// SaveInt writes a single integer column by character ID.
|
|
func (r *CharacterRepository) SaveInt(charID uint32, column string, value int) error {
|
|
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
|
return err
|
|
}
|
|
|
|
// SaveBool writes a single boolean column by character ID.
|
|
func (r *CharacterRepository) SaveBool(charID uint32, column string, value bool) error {
|
|
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
|
return err
|
|
}
|
|
|
|
// SaveString writes a single string column by character ID.
|
|
func (r *CharacterRepository) SaveString(charID uint32, column string, value string) error {
|
|
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
|
return err
|
|
}
|
|
|
|
// ReadBool reads a single boolean column by character ID.
|
|
func (r *CharacterRepository) ReadBool(charID uint32, column string) (bool, error) {
|
|
var value bool
|
|
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&value)
|
|
return value, err
|
|
}
|
|
|
|
// ReadString reads a single string column by character ID (empty string for NULL).
|
|
func (r *CharacterRepository) ReadString(charID uint32, column string) (string, error) {
|
|
var value sql.NullString
|
|
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return value.String, nil
|
|
}
|
|
|
|
// LoadColumnWithDefault reads a []byte column, returning defaultVal if NULL.
|
|
func (r *CharacterRepository) LoadColumnWithDefault(charID uint32, column string, defaultVal []byte) ([]byte, error) {
|
|
var data []byte
|
|
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&data)
|
|
if err != nil {
|
|
return defaultVal, err
|
|
}
|
|
if data == nil {
|
|
return defaultVal, nil
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
// SetDeleted marks a character as deleted.
|
|
func (r *CharacterRepository) SetDeleted(charID uint32) error {
|
|
_, err := r.db.Exec("UPDATE characters SET deleted=true WHERE id=$1", charID)
|
|
return err
|
|
}
|
|
|
|
// UpdateDailyCafe sets daily_time, bonus_quests, and daily_quests atomically.
|
|
func (r *CharacterRepository) UpdateDailyCafe(charID uint32, dailyTime time.Time, bonusQuests, dailyQuests uint32) error {
|
|
_, err := r.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests=$2, daily_quests=$3 WHERE id=$4",
|
|
dailyTime, bonusQuests, dailyQuests, charID)
|
|
return err
|
|
}
|
|
|
|
// ResetDailyQuests zeroes bonus_quests and daily_quests.
|
|
func (r *CharacterRepository) ResetDailyQuests(charID uint32) error {
|
|
_, err := r.db.Exec("UPDATE characters SET bonus_quests=0, daily_quests=0 WHERE id=$1", charID)
|
|
return err
|
|
}
|
|
|
|
// ReadEtcPoints reads bonus_quests, daily_quests, and promo_points.
|
|
func (r *CharacterRepository) ReadEtcPoints(charID uint32) (bonusQuests, dailyQuests, promoPoints uint32, err error) {
|
|
err = r.db.QueryRow("SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id=$1", charID).
|
|
Scan(&bonusQuests, &dailyQuests, &promoPoints)
|
|
return
|
|
}
|
|
|
|
// ResetCafeTime zeroes cafe_time and sets cafe_reset.
|
|
func (r *CharacterRepository) ResetCafeTime(charID uint32, cafeReset time.Time) error {
|
|
_, err := r.db.Exec("UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2", cafeReset, charID)
|
|
return err
|
|
}
|
|
|
|
// UpdateGuildPostChecked sets guild_post_checked to now().
|
|
func (r *CharacterRepository) UpdateGuildPostChecked(charID uint32) error {
|
|
_, err := r.db.Exec("UPDATE characters SET guild_post_checked=now() WHERE id=$1", charID)
|
|
return err
|
|
}
|
|
|
|
// ReadGuildPostChecked reads guild_post_checked timestamp.
|
|
func (r *CharacterRepository) ReadGuildPostChecked(charID uint32) (time.Time, error) {
|
|
var t time.Time
|
|
err := r.db.QueryRow("SELECT guild_post_checked FROM characters WHERE id=$1", charID).Scan(&t)
|
|
return t, err
|
|
}
|
|
|
|
// SaveMercenary updates savemercenary and optionally rasta_id.
|
|
// When rastaID is 0, only the mercenary blob is saved — the existing rasta_id
|
|
// (typically NULL for characters without a mercenary) is preserved. Writing 0
|
|
// would pollute GetMercenaryLoans queries that match on pact_id.
|
|
func (r *CharacterRepository) SaveMercenary(charID uint32, data []byte, rastaID uint32) error {
|
|
if rastaID == 0 {
|
|
_, err := r.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", data, charID)
|
|
return err
|
|
}
|
|
_, err := r.db.Exec("UPDATE characters SET savemercenary=$1, rasta_id=$2 WHERE id=$3", data, rastaID, charID)
|
|
return err
|
|
}
|
|
|
|
// UpdateGCPAndPact updates gcp and pact_id atomically.
|
|
func (r *CharacterRepository) UpdateGCPAndPact(charID uint32, gcp uint32, pactID uint32) error {
|
|
_, err := r.db.Exec("UPDATE characters SET gcp=$1, pact_id=$2 WHERE id=$3", gcp, pactID, charID)
|
|
return err
|
|
}
|
|
|
|
// FindByRastaID looks up name and id by rasta_id.
|
|
func (r *CharacterRepository) FindByRastaID(rastaID int) (charID uint32, name string, err error) {
|
|
err = r.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id=$1", rastaID).Scan(&name, &charID)
|
|
return
|
|
}
|
|
|
|
// SaveCharacterData updates the core save fields on a character.
|
|
func (r *CharacterRepository) SaveCharacterData(charID uint32, compSave []byte, hr, gr uint16, isFemale bool, weaponType uint8, weaponID uint16) error {
|
|
_, err := r.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hr=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7`,
|
|
compSave, hr, gr, isFemale, weaponType, weaponID, charID)
|
|
return err
|
|
}
|
|
|
|
// SaveHouseData updates house-related fields in user_binary.
|
|
func (r *CharacterRepository) SaveHouseData(charID uint32, houseTier []byte, houseData, bookshelf, gallery, tore, garden []byte) error {
|
|
_, err := r.db.Exec(`UPDATE user_binary SET house_tier=$1, house_data=$2, bookshelf=$3, gallery=$4, tore=$5, garden=$6 WHERE id=$7`,
|
|
houseTier, houseData, bookshelf, gallery, tore, garden, charID)
|
|
return err
|
|
}
|
|
|
|
// LoadSaveData reads the core save columns for a character.
|
|
// Returns charID, savedata, isNewCharacter, name, and any error.
|
|
func (r *CharacterRepository) LoadSaveData(charID uint32) (uint32, []byte, bool, string, error) {
|
|
var id uint32
|
|
var savedata []byte
|
|
var isNew bool
|
|
var name string
|
|
err := r.db.QueryRow("SELECT id, savedata, is_new_character, name FROM characters WHERE id = $1", charID).
|
|
Scan(&id, &savedata, &isNew, &name)
|
|
return id, savedata, isNew, name, err
|
|
}
|