293 lines
7.6 KiB
Go
293 lines
7.6 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 (C++ compatible)
|
|
func (db *LoginDB) GetLoginAccount(username, password string) (*LoginAccount, error) {
|
|
var account LoginAccount
|
|
// Using the same query as C++ version: SHA512 hash comparison
|
|
query := "SELECT id, name, passwd, email_address, created_date, last_update, ip_address FROM account WHERE name = ? AND passwd = sha2(?, 512)"
|
|
|
|
row := db.QueryRow(query, username, password)
|
|
|
|
// Variables to handle nullable fields
|
|
var email, lastIP sql.NullString
|
|
var lastLogin sql.NullInt64
|
|
|
|
err := row.Scan(
|
|
&account.ID,
|
|
&account.Username,
|
|
&account.Password, // This will be the hash from DB
|
|
&email,
|
|
&account.CreatedDate,
|
|
&lastLogin,
|
|
&lastIP,
|
|
)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("account not found")
|
|
}
|
|
return nil, fmt.Errorf("database query error: %w", err)
|
|
}
|
|
|
|
// Handle nullable fields
|
|
if email.Valid {
|
|
account.Email = email.String
|
|
} else {
|
|
account.Email = "Unknown"
|
|
}
|
|
|
|
if lastLogin.Valid {
|
|
account.LastLogin = lastLogin.Int64
|
|
}
|
|
|
|
if lastIP.Valid {
|
|
account.LastIP = lastIP.String
|
|
} else {
|
|
account.LastIP = "0.0.0.0"
|
|
}
|
|
|
|
// Set default values
|
|
account.Status = "Active"
|
|
account.AccessLevel = 0
|
|
|
|
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 (C++ compatible)
|
|
func (db *LoginDB) UpdateLastLogin(accountID int32, ipAddress string) error {
|
|
now := time.Now().Unix()
|
|
query := "UPDATE account SET last_update = ?, ip_address = ? 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 (C++ compatible)
|
|
func (db *LoginDB) CreateAccount(username, password, email string) (*LoginAccount, error) {
|
|
now := time.Now().Unix()
|
|
|
|
// Check if username already exists
|
|
exists, err := db.Exists("SELECT 1 FROM account WHERE name = ?", 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 using same schema as C++ version
|
|
accountID, err := db.InsertReturningID(
|
|
`INSERT INTO account (name, passwd, email_address, created_date, account_enabled)
|
|
VALUES (?, sha2(?, 512), ?, ?, 1)`,
|
|
username, password, email, 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: "", // Don't return hash
|
|
Email: email,
|
|
Status: "Active",
|
|
AccessLevel: 0,
|
|
CreatedDate: now,
|
|
LastLogin: 0,
|
|
LastIP: "0.0.0.0",
|
|
}, 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 (C++ compatible)
|
|
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 account").Scan(&totalAccounts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["total_accounts"] = totalAccounts
|
|
|
|
// Count active accounts
|
|
var activeAccounts int
|
|
err = db.QueryRow("SELECT COUNT(*) FROM account WHERE account_enabled = 1").Scan(&activeAccounts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["active_accounts"] = activeAccounts
|
|
|
|
// Count total characters
|
|
var totalCharacters int
|
|
err = db.QueryRow("SELECT COUNT(*) FROM login_characters WHERE deleted = 0").Scan(&totalCharacters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["total_characters"] = totalCharacters
|
|
|
|
return stats, nil
|
|
} |