eq2go/internal/login/database.go

268 lines
7.1 KiB
Go

package login
import (
"database/sql"
"fmt"
"time"
"eq2emu/internal/database"
)
// LoginAccount represents a login account
type LoginAccount struct {
ID int32 `json:"id"`
Username string `json:"username"`
Password string `json:"password"` // MD5 hash
Email string `json:"email"`
Status string `json:"status"` // Active, Suspended, Banned
AccessLevel int16 `json:"access_level"`
CreatedDate int64 `json:"created_date"`
LastLogin int64 `json:"last_login"`
LastIP string `json:"last_ip"`
}
// Character represents a character
type Character struct {
ID int32 `json:"id"`
AccountID int32 `json:"account_id"`
Name string `json:"name"`
Race int8 `json:"race"`
Class int8 `json:"class"`
Gender int8 `json:"gender"`
Level int16 `json:"level"`
Zone int32 `json:"zone"`
ZoneInstance int32 `json:"zone_instance"`
ServerID int16 `json:"server_id"`
LastPlayed int64 `json:"last_played"`
CreatedDate int64 `json:"created_date"`
DeletedDate int64 `json:"deleted_date"`
}
// LoginDB wraps the base Database with login-specific methods
type LoginDB struct {
*database.Database
}
// NewLoginDB creates a new database connection for login server
func NewLoginDB(dsn string) (*LoginDB, error) {
db, err := database.NewMySQL(dsn)
if err != nil {
return nil, err
}
loginDB := &LoginDB{Database: db}
return loginDB, nil
}
// GetLoginAccount retrieves a login account by username and password
func (db *LoginDB) GetLoginAccount(username, hashedPassword string) (*LoginAccount, error) {
var account LoginAccount
query := "SELECT id, username, password, email, status, access_level, created_date, last_login, last_ip FROM login_accounts WHERE username = ? AND password = ?"
row := db.QueryRow(query, username, hashedPassword)
err := row.Scan(
&account.ID,
&account.Username,
&account.Password,
&account.Email,
&account.Status,
&account.AccessLevel,
&account.CreatedDate,
&account.LastLogin,
&account.LastIP,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("account not found")
}
return nil, fmt.Errorf("database query error: %w", err)
}
return &account, nil
}
// GetCharacters retrieves all characters for an account
func (db *LoginDB) GetCharacters(accountID int32) ([]*Character, error) {
var characters []*Character
rows, err := db.Query(
`SELECT id, account_id, name, race, class, gender, level, zone_id, zone_instance,
server_id, last_played, created_date, deleted_date
FROM characters
WHERE account_id = ? AND deleted_date = 0
ORDER BY last_played DESC`,
accountID,
)
if err != nil {
return nil, fmt.Errorf("failed to load characters: %w", err)
}
defer rows.Close()
for rows.Next() {
char := &Character{}
err := rows.Scan(
&char.ID,
&char.AccountID,
&char.Name,
&char.Race,
&char.Class,
&char.Gender,
&char.Level,
&char.Zone,
&char.ZoneInstance,
&char.ServerID,
&char.LastPlayed,
&char.CreatedDate,
&char.DeletedDate,
)
if err != nil {
return nil, fmt.Errorf("failed to scan character: %w", err)
}
characters = append(characters, char)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error reading characters: %w", err)
}
return characters, nil
}
// UpdateLastLogin updates the last login time and IP for an account
func (db *LoginDB) UpdateLastLogin(accountID int32, ipAddress string) error {
now := time.Now().Unix()
query := "UPDATE login_accounts SET last_login = ?, last_ip = ? WHERE id = ?"
_, err := db.Exec(query, now, ipAddress, accountID)
return err
}
// UpdateServerStats updates server statistics
func (db *LoginDB) UpdateServerStats(serverType string, clientCount, worldCount int) error {
now := time.Now().Unix()
// MySQL implementation using ON DUPLICATE KEY UPDATE
query := `INSERT INTO server_stats (server_type, client_count, world_count, last_update)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
client_count = VALUES(client_count),
world_count = VALUES(world_count),
last_update = VALUES(last_update)`
_, err := db.Exec(query, serverType, clientCount, worldCount, now)
return err
}
// CreateAccount creates a new login account
func (db *LoginDB) CreateAccount(username, hashedPassword, email string, accessLevel int16) (*LoginAccount, error) {
now := time.Now().Unix()
// Check if username already exists
exists, err := db.Exists("SELECT 1 FROM login_accounts WHERE username = ?", username)
if err != nil {
return nil, fmt.Errorf("failed to check username: %w", err)
}
if exists {
return nil, fmt.Errorf("username already exists")
}
// Insert new account
accountID, err := db.InsertReturningID(
`INSERT INTO login_accounts (username, password, email, access_level, created_date, status)
VALUES (?, ?, ?, ?, ?, 'Active')`,
username, hashedPassword, email, accessLevel, now,
)
if err != nil {
return nil, fmt.Errorf("failed to create account: %w", err)
}
// Return the created account
return &LoginAccount{
ID: int32(accountID),
Username: username,
Password: hashedPassword,
Email: email,
Status: "Active",
AccessLevel: accessLevel,
CreatedDate: now,
LastLogin: 0,
LastIP: "",
}, nil
}
// GetCharacterByID retrieves a character by ID
func (db *LoginDB) GetCharacterByID(characterID int32) (*Character, error) {
var character Character
row := db.QueryRow(
`SELECT id, account_id, name, race, class, gender, level, zone_id, zone_instance,
server_id, last_played, created_date, deleted_date
FROM characters WHERE id = ?`,
characterID,
)
err := row.Scan(
&character.ID,
&character.AccountID,
&character.Name,
&character.Race,
&character.Class,
&character.Gender,
&character.Level,
&character.Zone,
&character.ZoneInstance,
&character.ServerID,
&character.LastPlayed,
&character.CreatedDate,
&character.DeletedDate,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("character not found")
}
return nil, fmt.Errorf("database query error: %w", err)
}
return &character, nil
}
// DeleteCharacter marks a character as deleted
func (db *LoginDB) DeleteCharacter(characterID int32) error {
now := time.Now().Unix()
_, err := db.Exec("UPDATE characters SET deleted_date = ? WHERE id = ?", now, characterID)
return err
}
// GetAccountStats retrieves statistics about login accounts
func (db *LoginDB) GetAccountStats() (map[string]int, error) {
stats := make(map[string]int)
// Count total accounts
var totalAccounts int
err := db.QueryRow("SELECT COUNT(*) FROM login_accounts").Scan(&totalAccounts)
if err != nil {
return nil, err
}
stats["total_accounts"] = totalAccounts
// Count active accounts
var activeAccounts int
err = db.QueryRow("SELECT COUNT(*) FROM login_accounts WHERE status = 'Active'").Scan(&activeAccounts)
if err != nil {
return nil, err
}
stats["active_accounts"] = activeAccounts
// Count total characters
var totalCharacters int
err = db.QueryRow("SELECT COUNT(*) FROM characters WHERE deleted_date = 0").Scan(&totalCharacters)
if err != nil {
return nil, err
}
stats["total_characters"] = totalCharacters
return stats, nil
}