268 lines
7.1 KiB
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
|
|
} |