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 }