350 lines
9.5 KiB
Go
350 lines
9.5 KiB
Go
package login
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"eq2emu/internal/database"
|
|
"zombiezen.com/go/sqlite"
|
|
"zombiezen.com/go/sqlite/sqlitex"
|
|
)
|
|
|
|
// 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(dbType, dsn string) (*LoginDB, error) {
|
|
var db *database.Database
|
|
var err error
|
|
|
|
switch strings.ToLower(dbType) {
|
|
case "sqlite":
|
|
db, err = database.NewSQLite(dsn)
|
|
case "mysql":
|
|
db, err = database.NewMySQL(dsn)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported database type: %s", dbType)
|
|
}
|
|
|
|
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 = ?"
|
|
|
|
if db.GetType() == database.SQLite {
|
|
found := false
|
|
err := db.ExecTransient(query,
|
|
func(stmt *sqlite.Stmt) error {
|
|
account.ID = int32(stmt.ColumnInt64(0))
|
|
account.Username = stmt.ColumnText(1)
|
|
account.Password = stmt.ColumnText(2)
|
|
account.Email = stmt.ColumnText(3)
|
|
account.Status = stmt.ColumnText(4)
|
|
account.AccessLevel = int16(stmt.ColumnInt64(5))
|
|
account.CreatedDate = stmt.ColumnInt64(6)
|
|
account.LastLogin = stmt.ColumnInt64(7)
|
|
account.LastIP = stmt.ColumnText(8)
|
|
found = true
|
|
return nil
|
|
},
|
|
username, hashedPassword,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("database query error: %w", err)
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("account not found")
|
|
}
|
|
} else {
|
|
// MySQL implementation
|
|
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 {
|
|
return nil, fmt.Errorf("account not found or database error: %w", err)
|
|
}
|
|
}
|
|
|
|
return &account, nil
|
|
}
|
|
|
|
// GetCharacters retrieves all characters for an account
|
|
func (db *LoginDB) GetCharacters(accountID int32) ([]*Character, error) {
|
|
var characters []*Character
|
|
|
|
err := db.ExecTransient(
|
|
`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`,
|
|
func(stmt *sqlite.Stmt) error {
|
|
char := &Character{
|
|
ID: int32(stmt.ColumnInt64(0)),
|
|
AccountID: int32(stmt.ColumnInt64(1)),
|
|
Name: stmt.ColumnText(2),
|
|
Race: int8(stmt.ColumnInt64(3)),
|
|
Class: int8(stmt.ColumnInt64(4)),
|
|
Gender: int8(stmt.ColumnInt64(5)),
|
|
Level: int16(stmt.ColumnInt64(6)),
|
|
Zone: int32(stmt.ColumnInt64(7)),
|
|
ZoneInstance: int32(stmt.ColumnInt64(8)),
|
|
ServerID: int16(stmt.ColumnInt64(9)),
|
|
LastPlayed: stmt.ColumnInt64(10),
|
|
CreatedDate: stmt.ColumnInt64(11),
|
|
DeletedDate: stmt.ColumnInt64(12),
|
|
}
|
|
characters = append(characters, char)
|
|
return nil
|
|
},
|
|
accountID,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load 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 = ?"
|
|
|
|
if db.GetType() == database.SQLite {
|
|
return db.Execute(query, &sqlitex.ExecOptions{
|
|
Args: []any{now, ipAddress, accountID},
|
|
})
|
|
} else {
|
|
// MySQL implementation
|
|
_, 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()
|
|
|
|
if db.GetType() == database.SQLite {
|
|
return db.Execute(
|
|
`INSERT OR REPLACE INTO server_stats (server_type, client_count, world_count, last_update)
|
|
VALUES (?, ?, ?, ?)`,
|
|
&sqlitex.ExecOptions{
|
|
Args: []any{serverType, clientCount, worldCount, now},
|
|
},
|
|
)
|
|
} else {
|
|
// 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 := false
|
|
err := db.ExecTransient(
|
|
"SELECT 1 FROM login_accounts WHERE username = ?",
|
|
func(stmt *sqlite.Stmt) error {
|
|
exists = true
|
|
return nil
|
|
},
|
|
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
|
|
var accountID int32
|
|
err = db.Execute(
|
|
`INSERT INTO login_accounts (username, password, email, access_level, created_date, status)
|
|
VALUES (?, ?, ?, ?, ?, 'Active')`,
|
|
&sqlitex.ExecOptions{
|
|
Args: []any{username, hashedPassword, email, accessLevel, now},
|
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
|
accountID = int32(stmt.ColumnInt64(0))
|
|
return nil
|
|
},
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create account: %w", err)
|
|
}
|
|
|
|
// Return the created account
|
|
return &LoginAccount{
|
|
ID: 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
|
|
found := false
|
|
|
|
err := db.ExecTransient(
|
|
`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 = ?`,
|
|
func(stmt *sqlite.Stmt) error {
|
|
character.ID = int32(stmt.ColumnInt64(0))
|
|
character.AccountID = int32(stmt.ColumnInt64(1))
|
|
character.Name = stmt.ColumnText(2)
|
|
character.Race = int8(stmt.ColumnInt64(3))
|
|
character.Class = int8(stmt.ColumnInt64(4))
|
|
character.Gender = int8(stmt.ColumnInt64(5))
|
|
character.Level = int16(stmt.ColumnInt64(6))
|
|
character.Zone = int32(stmt.ColumnInt64(7))
|
|
character.ZoneInstance = int32(stmt.ColumnInt64(8))
|
|
character.ServerID = int16(stmt.ColumnInt64(9))
|
|
character.LastPlayed = stmt.ColumnInt64(10)
|
|
character.CreatedDate = stmt.ColumnInt64(11)
|
|
character.DeletedDate = stmt.ColumnInt64(12)
|
|
found = true
|
|
return nil
|
|
},
|
|
characterID,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("database query error: %w", err)
|
|
}
|
|
|
|
if !found {
|
|
return nil, fmt.Errorf("character not found")
|
|
}
|
|
|
|
return &character, nil
|
|
}
|
|
|
|
// DeleteCharacter marks a character as deleted
|
|
func (db *LoginDB) DeleteCharacter(characterID int32) error {
|
|
now := time.Now().Unix()
|
|
|
|
return db.Execute(
|
|
"UPDATE characters SET deleted_date = ? WHERE id = ?",
|
|
&sqlitex.ExecOptions{
|
|
Args: []any{now, characterID},
|
|
},
|
|
)
|
|
}
|
|
|
|
// GetAccountStats retrieves statistics about login accounts
|
|
func (db *LoginDB) GetAccountStats() (map[string]int, error) {
|
|
stats := make(map[string]int)
|
|
|
|
// Count total accounts
|
|
err := db.ExecTransient(
|
|
"SELECT COUNT(*) FROM login_accounts",
|
|
func(stmt *sqlite.Stmt) error {
|
|
stats["total_accounts"] = int(stmt.ColumnInt64(0))
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Count active accounts
|
|
err = db.ExecTransient(
|
|
"SELECT COUNT(*) FROM login_accounts WHERE status = 'Active'",
|
|
func(stmt *sqlite.Stmt) error {
|
|
stats["active_accounts"] = int(stmt.ColumnInt64(0))
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Count total characters
|
|
err = db.ExecTransient(
|
|
"SELECT COUNT(*) FROM characters WHERE deleted_date = 0",
|
|
func(stmt *sqlite.Stmt) error {
|
|
stats["total_characters"] = int(stmt.ColumnInt64(0))
|
|
return nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return stats, nil
|
|
} |