remove redundant database wrapper, update achievements to use raw go-sqlite
This commit is contained in:
parent
0a390959fa
commit
1288bc086f
@ -1,38 +1,48 @@
|
|||||||
package achievements
|
package achievements
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"eq2emu/internal/database"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
"zombiezen.com/go/sqlite/sqlitex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadAllAchievements loads all achievements from database into master list
|
// LoadAllAchievements loads all achievements from database into master list
|
||||||
func LoadAllAchievements(db *database.DB, masterList *MasterList) error {
|
func LoadAllAchievements(pool *sqlitex.Pool, masterList *MasterList) error {
|
||||||
|
conn, err := pool.Take(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection: %w", err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
query := `SELECT achievement_id, title, uncompleted_text, completed_text,
|
query := `SELECT achievement_id, title, uncompleted_text, completed_text,
|
||||||
category, expansion, icon, point_value, qty_req, hide_achievement,
|
category, expansion, icon, point_value, qty_req, hide_achievement,
|
||||||
unknown3a, unknown3b FROM achievements`
|
unknown3a, unknown3b FROM achievements`
|
||||||
|
|
||||||
err := db.Query(query, func(row *database.Row) error {
|
return sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
achievement := NewAchievement()
|
achievement := NewAchievement()
|
||||||
achievement.ID = uint32(row.Int(0))
|
achievement.ID = uint32(stmt.ColumnInt64(0))
|
||||||
achievement.Title = row.Text(1)
|
achievement.Title = stmt.ColumnText(1)
|
||||||
achievement.UncompletedText = row.Text(2)
|
achievement.UncompletedText = stmt.ColumnText(2)
|
||||||
achievement.CompletedText = row.Text(3)
|
achievement.CompletedText = stmt.ColumnText(3)
|
||||||
achievement.Category = row.Text(4)
|
achievement.Category = stmt.ColumnText(4)
|
||||||
achievement.Expansion = row.Text(5)
|
achievement.Expansion = stmt.ColumnText(5)
|
||||||
achievement.Icon = uint16(row.Int(6))
|
achievement.Icon = uint16(stmt.ColumnInt64(6))
|
||||||
achievement.PointValue = uint32(row.Int(7))
|
achievement.PointValue = uint32(stmt.ColumnInt32(7))
|
||||||
achievement.QtyRequired = uint32(row.Int(8))
|
achievement.QtyRequired = uint32(stmt.ColumnInt64(8))
|
||||||
achievement.Hide = row.Bool(9)
|
achievement.Hide = stmt.ColumnInt64(9) != 0
|
||||||
achievement.Unknown3A = uint32(row.Int(10))
|
achievement.Unknown3A = uint32(stmt.ColumnInt64(10))
|
||||||
achievement.Unknown3B = uint32(row.Int(11))
|
achievement.Unknown3B = uint32(stmt.ColumnInt64(11))
|
||||||
|
|
||||||
// Load requirements and rewards
|
// Load requirements and rewards
|
||||||
if err := loadAchievementRequirements(db, achievement); err != nil {
|
if err := loadAchievementRequirements(conn, achievement); err != nil {
|
||||||
return fmt.Errorf("failed to load requirements for achievement %d: %w", achievement.ID, err)
|
return fmt.Errorf("failed to load requirements for achievement %d: %w", achievement.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := loadAchievementRewards(db, achievement); err != nil {
|
if err := loadAchievementRewards(conn, achievement); err != nil {
|
||||||
return fmt.Errorf("failed to load rewards for achievement %d: %w", achievement.ID, err)
|
return fmt.Errorf("failed to load rewards for achievement %d: %w", achievement.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,71 +51,83 @@ func LoadAllAchievements(db *database.DB, masterList *MasterList) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadAchievementRequirements loads requirements for a specific achievement
|
// loadAchievementRequirements loads requirements for a specific achievement
|
||||||
func loadAchievementRequirements(db *database.DB, achievement *Achievement) error {
|
func loadAchievementRequirements(conn *sqlite.Conn, achievement *Achievement) error {
|
||||||
query := `SELECT achievement_id, name, qty_req
|
query := `SELECT achievement_id, name, qty_req
|
||||||
FROM achievements_requirements
|
FROM achievements_requirements
|
||||||
WHERE achievement_id = ?`
|
WHERE achievement_id = ?`
|
||||||
|
|
||||||
return db.Query(query, func(row *database.Row) error {
|
return sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievement.ID},
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
req := Requirement{
|
req := Requirement{
|
||||||
AchievementID: uint32(row.Int(0)),
|
AchievementID: uint32(stmt.ColumnInt64(0)),
|
||||||
Name: row.Text(1),
|
Name: stmt.ColumnText(1),
|
||||||
QtyRequired: uint32(row.Int(2)),
|
QtyRequired: uint32(stmt.ColumnInt64(2)),
|
||||||
}
|
}
|
||||||
achievement.AddRequirement(req)
|
achievement.AddRequirement(req)
|
||||||
return nil
|
return nil
|
||||||
}, achievement.ID)
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadAchievementRewards loads rewards for a specific achievement
|
// loadAchievementRewards loads rewards for a specific achievement
|
||||||
func loadAchievementRewards(db *database.DB, achievement *Achievement) error {
|
func loadAchievementRewards(conn *sqlite.Conn, achievement *Achievement) error {
|
||||||
query := `SELECT achievement_id, reward
|
query := `SELECT achievement_id, reward
|
||||||
FROM achievements_rewards
|
FROM achievements_rewards
|
||||||
WHERE achievement_id = ?`
|
WHERE achievement_id = ?`
|
||||||
|
|
||||||
return db.Query(query, func(row *database.Row) error {
|
return sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievement.ID},
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
reward := Reward{
|
reward := Reward{
|
||||||
AchievementID: uint32(row.Int(0)),
|
AchievementID: uint32(stmt.ColumnInt64(0)),
|
||||||
Reward: row.Text(1),
|
Reward: stmt.ColumnText(1),
|
||||||
}
|
}
|
||||||
achievement.AddReward(reward)
|
achievement.AddReward(reward)
|
||||||
return nil
|
return nil
|
||||||
}, achievement.ID)
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPlayerAchievements loads player achievements from database
|
// LoadPlayerAchievements loads player achievements from database
|
||||||
func LoadPlayerAchievements(db *database.DB, playerID uint32, playerList *PlayerList) error {
|
func LoadPlayerAchievements(pool *sqlitex.Pool, playerID uint32, playerList *PlayerList) error {
|
||||||
|
conn, err := pool.Take(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection: %w", err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
query := `SELECT achievement_id, title, uncompleted_text, completed_text,
|
query := `SELECT achievement_id, title, uncompleted_text, completed_text,
|
||||||
category, expansion, icon, point_value, qty_req, hide_achievement,
|
category, expansion, icon, point_value, qty_req, hide_achievement,
|
||||||
unknown3a, unknown3b FROM achievements`
|
unknown3a, unknown3b FROM achievements`
|
||||||
|
|
||||||
err := db.Query(query, func(row *database.Row) error {
|
return sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
achievement := NewAchievement()
|
achievement := NewAchievement()
|
||||||
achievement.ID = uint32(row.Int(0))
|
achievement.ID = uint32(stmt.ColumnInt64(0))
|
||||||
achievement.Title = row.Text(1)
|
achievement.Title = stmt.ColumnText(1)
|
||||||
achievement.UncompletedText = row.Text(2)
|
achievement.UncompletedText = stmt.ColumnText(2)
|
||||||
achievement.CompletedText = row.Text(3)
|
achievement.CompletedText = stmt.ColumnText(3)
|
||||||
achievement.Category = row.Text(4)
|
achievement.Category = stmt.ColumnText(4)
|
||||||
achievement.Expansion = row.Text(5)
|
achievement.Expansion = stmt.ColumnText(5)
|
||||||
achievement.Icon = uint16(row.Int(6))
|
achievement.Icon = uint16(stmt.ColumnInt64(6))
|
||||||
achievement.PointValue = uint32(row.Int(7))
|
achievement.PointValue = uint32(stmt.ColumnInt64(7))
|
||||||
achievement.QtyRequired = uint32(row.Int(8))
|
achievement.QtyRequired = uint32(stmt.ColumnInt64(8))
|
||||||
achievement.Hide = row.Bool(9)
|
achievement.Hide = stmt.ColumnInt64(9) != 0
|
||||||
achievement.Unknown3A = uint32(row.Int(10))
|
achievement.Unknown3A = uint32(stmt.ColumnInt64(10))
|
||||||
achievement.Unknown3B = uint32(row.Int(11))
|
achievement.Unknown3B = uint32(stmt.ColumnInt64(11))
|
||||||
|
|
||||||
// Load requirements and rewards
|
// Load requirements and rewards
|
||||||
if err := loadAchievementRequirements(db, achievement); err != nil {
|
if err := loadAchievementRequirements(conn, achievement); err != nil {
|
||||||
return fmt.Errorf("failed to load requirements: %w", err)
|
return fmt.Errorf("failed to load requirements: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := loadAchievementRewards(db, achievement); err != nil {
|
if err := loadAchievementRewards(conn, achievement); err != nil {
|
||||||
return fmt.Errorf("failed to load rewards: %w", err)
|
return fmt.Errorf("failed to load rewards: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,29 +136,36 @@ func LoadPlayerAchievements(db *database.DB, playerID uint32, playerList *Player
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadPlayerAchievementUpdates loads player achievement progress from database
|
// LoadPlayerAchievementUpdates loads player achievement progress from database
|
||||||
func LoadPlayerAchievementUpdates(db *database.DB, playerID uint32, updateList *PlayerUpdateList) error {
|
func LoadPlayerAchievementUpdates(pool *sqlitex.Pool, playerID uint32, updateList *PlayerUpdateList) error {
|
||||||
|
conn, err := pool.Take(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection: %w", err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
query := `SELECT char_id, achievement_id, completed_date
|
query := `SELECT char_id, achievement_id, completed_date
|
||||||
FROM character_achievements
|
FROM character_achievements
|
||||||
WHERE char_id = ?`
|
WHERE char_id = ?`
|
||||||
|
|
||||||
return db.Query(query, func(row *database.Row) error {
|
return sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID},
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
update := NewUpdate()
|
update := NewUpdate()
|
||||||
update.ID = uint32(row.Int(1))
|
update.ID = uint32(stmt.ColumnInt64(1))
|
||||||
|
|
||||||
// Convert completed_date from Unix timestamp
|
// Convert completed_date from Unix timestamp
|
||||||
if !row.IsNull(2) {
|
if stmt.ColumnType(2) != sqlite.TypeNull {
|
||||||
timestamp := row.Int64(2)
|
timestamp := stmt.ColumnInt64(2)
|
||||||
update.CompletedDate = time.Unix(timestamp, 0)
|
update.CompletedDate = time.Unix(timestamp, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load update items
|
// Load update items
|
||||||
if err := loadPlayerAchievementUpdateItems(db, playerID, update); err != nil {
|
if err := loadPlayerAchievementUpdateItems(conn, playerID, update); err != nil {
|
||||||
return fmt.Errorf("failed to load update items: %w", err)
|
return fmt.Errorf("failed to load update items: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,46 +174,66 @@ func LoadPlayerAchievementUpdates(db *database.DB, playerID uint32, updateList *
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, playerID)
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadPlayerAchievementUpdateItems loads progress items for an achievement update
|
// loadPlayerAchievementUpdateItems loads progress items for an achievement update
|
||||||
func loadPlayerAchievementUpdateItems(db *database.DB, playerID uint32, update *Update) error {
|
func loadPlayerAchievementUpdateItems(conn *sqlite.Conn, playerID uint32, update *Update) error {
|
||||||
query := `SELECT achievement_id, items
|
query := `SELECT achievement_id, items
|
||||||
FROM character_achievements_items
|
FROM character_achievements_items
|
||||||
WHERE char_id = ? AND achievement_id = ?`
|
WHERE char_id = ? AND achievement_id = ?`
|
||||||
|
|
||||||
return db.Query(query, func(row *database.Row) error {
|
return sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID, update.ID},
|
||||||
|
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||||
item := UpdateItem{
|
item := UpdateItem{
|
||||||
AchievementID: uint32(row.Int(0)),
|
AchievementID: uint32(stmt.ColumnInt64(0)),
|
||||||
ItemUpdate: uint32(row.Int(1)),
|
ItemUpdate: uint32(stmt.ColumnInt64(1)),
|
||||||
}
|
}
|
||||||
update.AddUpdateItem(item)
|
update.AddUpdateItem(item)
|
||||||
return nil
|
return nil
|
||||||
}, playerID, update.ID)
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePlayerAchievementUpdate saves or updates player achievement progress
|
// SavePlayerAchievementUpdate saves or updates player achievement progress
|
||||||
func SavePlayerAchievementUpdate(db *database.DB, playerID uint32, update *Update) error {
|
func SavePlayerAchievementUpdate(pool *sqlitex.Pool, playerID uint32, update *Update) error {
|
||||||
return db.Transaction(func(tx *database.DB) error {
|
conn, err := pool.Take(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection: %w", err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
|
err = sqlitex.Execute(conn, "BEGIN", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer sqlitex.Execute(conn, "ROLLBACK", nil)
|
||||||
|
|
||||||
// Save or update main achievement record
|
// Save or update main achievement record
|
||||||
query := `INSERT OR REPLACE INTO character_achievements
|
query := `INSERT OR REPLACE INTO character_achievements
|
||||||
(char_id, achievement_id, completed_date) VALUES (?, ?, ?)`
|
(char_id, achievement_id, completed_date) VALUES (?, ?, ?)`
|
||||||
|
|
||||||
var completedDate *int64
|
var completedDate any
|
||||||
if !update.CompletedDate.IsZero() {
|
if !update.CompletedDate.IsZero() {
|
||||||
timestamp := update.CompletedDate.Unix()
|
completedDate = update.CompletedDate.Unix()
|
||||||
completedDate = ×tamp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Exec(query, playerID, update.ID, completedDate); err != nil {
|
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID, update.ID, completedDate},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save achievement update: %w", err)
|
return fmt.Errorf("failed to save achievement update: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete existing update items
|
// Delete existing update items
|
||||||
deleteQuery := `DELETE FROM character_achievements_items
|
deleteQuery := `DELETE FROM character_achievements_items
|
||||||
WHERE char_id = ? AND achievement_id = ?`
|
WHERE char_id = ? AND achievement_id = ?`
|
||||||
if err := tx.Exec(deleteQuery, playerID, update.ID); err != nil {
|
err = sqlitex.Execute(conn, deleteQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID, update.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete old update items: %w", err)
|
return fmt.Errorf("failed to delete old update items: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,39 +241,68 @@ func SavePlayerAchievementUpdate(db *database.DB, playerID uint32, update *Updat
|
|||||||
itemQuery := `INSERT INTO character_achievements_items
|
itemQuery := `INSERT INTO character_achievements_items
|
||||||
(char_id, achievement_id, items) VALUES (?, ?, ?)`
|
(char_id, achievement_id, items) VALUES (?, ?, ?)`
|
||||||
for _, item := range update.UpdateItems {
|
for _, item := range update.UpdateItems {
|
||||||
if err := tx.Exec(itemQuery, playerID, item.AchievementID, item.ItemUpdate); err != nil {
|
err = sqlitex.Execute(conn, itemQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID, item.AchievementID, item.ItemUpdate},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save update item: %w", err)
|
return fmt.Errorf("failed to save update item: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return sqlitex.Execute(conn, "COMMIT", nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletePlayerAchievementUpdate removes player achievement progress from database
|
// DeletePlayerAchievementUpdate removes player achievement progress from database
|
||||||
func DeletePlayerAchievementUpdate(db *database.DB, playerID uint32, achievementID uint32) error {
|
func DeletePlayerAchievementUpdate(pool *sqlitex.Pool, playerID uint32, achievementID uint32) error {
|
||||||
return db.Transaction(func(tx *database.DB) error {
|
conn, err := pool.Take(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection: %w", err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
|
err = sqlitex.Execute(conn, "BEGIN", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer sqlitex.Execute(conn, "ROLLBACK", nil)
|
||||||
|
|
||||||
// Delete main achievement record
|
// Delete main achievement record
|
||||||
query := `DELETE FROM character_achievements
|
query := `DELETE FROM character_achievements
|
||||||
WHERE char_id = ? AND achievement_id = ?`
|
WHERE char_id = ? AND achievement_id = ?`
|
||||||
if err := tx.Exec(query, playerID, achievementID); err != nil {
|
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID, achievementID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete achievement update: %w", err)
|
return fmt.Errorf("failed to delete achievement update: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete update items
|
// Delete update items
|
||||||
itemQuery := `DELETE FROM character_achievements_items
|
itemQuery := `DELETE FROM character_achievements_items
|
||||||
WHERE char_id = ? AND achievement_id = ?`
|
WHERE char_id = ? AND achievement_id = ?`
|
||||||
if err := tx.Exec(itemQuery, playerID, achievementID); err != nil {
|
err = sqlitex.Execute(conn, itemQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{playerID, achievementID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete update items: %w", err)
|
return fmt.Errorf("failed to delete update items: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return sqlitex.Execute(conn, "COMMIT", nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveAchievement saves or updates an achievement in the database
|
// SaveAchievement saves or updates an achievement in the database
|
||||||
func SaveAchievement(db *database.DB, achievement *Achievement) error {
|
func SaveAchievement(pool *sqlitex.Pool, achievement *Achievement) error {
|
||||||
return db.Transaction(func(tx *database.DB) error {
|
conn, err := pool.Take(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection: %w", err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
|
err = sqlitex.Execute(conn, "BEGIN", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer sqlitex.Execute(conn, "ROLLBACK", nil)
|
||||||
|
|
||||||
// Save main achievement record
|
// Save main achievement record
|
||||||
query := `INSERT OR REPLACE INTO achievements
|
query := `INSERT OR REPLACE INTO achievements
|
||||||
(achievement_id, title, uncompleted_text, completed_text,
|
(achievement_id, title, uncompleted_text, completed_text,
|
||||||
@ -232,19 +310,31 @@ func SaveAchievement(db *database.DB, achievement *Achievement) error {
|
|||||||
hide_achievement, unknown3a, unknown3b)
|
hide_achievement, unknown3a, unknown3b)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||||
|
|
||||||
if err := tx.Exec(query, achievement.ID, achievement.Title,
|
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{
|
||||||
|
achievement.ID, achievement.Title,
|
||||||
achievement.UncompletedText, achievement.CompletedText,
|
achievement.UncompletedText, achievement.CompletedText,
|
||||||
achievement.Category, achievement.Expansion, achievement.Icon,
|
achievement.Category, achievement.Expansion, achievement.Icon,
|
||||||
achievement.PointValue, achievement.QtyRequired, achievement.Hide,
|
achievement.PointValue, achievement.QtyRequired, achievement.Hide,
|
||||||
achievement.Unknown3A, achievement.Unknown3B); err != nil {
|
achievement.Unknown3A, achievement.Unknown3B,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save achievement: %w", err)
|
return fmt.Errorf("failed to save achievement: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete existing requirements and rewards
|
// Delete existing requirements and rewards
|
||||||
if err := tx.Exec("DELETE FROM achievements_requirements WHERE achievement_id = ?", achievement.ID); err != nil {
|
err = sqlitex.Execute(conn, "DELETE FROM achievements_requirements WHERE achievement_id = ?", &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievement.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete old requirements: %w", err)
|
return fmt.Errorf("failed to delete old requirements: %w", err)
|
||||||
}
|
}
|
||||||
if err := tx.Exec("DELETE FROM achievements_rewards WHERE achievement_id = ?", achievement.ID); err != nil {
|
|
||||||
|
err = sqlitex.Execute(conn, "DELETE FROM achievements_rewards WHERE achievement_id = ?", &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievement.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete old rewards: %w", err)
|
return fmt.Errorf("failed to delete old rewards: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,7 +342,10 @@ func SaveAchievement(db *database.DB, achievement *Achievement) error {
|
|||||||
reqQuery := `INSERT INTO achievements_requirements
|
reqQuery := `INSERT INTO achievements_requirements
|
||||||
(achievement_id, name, qty_req) VALUES (?, ?, ?)`
|
(achievement_id, name, qty_req) VALUES (?, ?, ?)`
|
||||||
for _, req := range achievement.Requirements {
|
for _, req := range achievement.Requirements {
|
||||||
if err := tx.Exec(reqQuery, req.AchievementID, req.Name, req.QtyRequired); err != nil {
|
err = sqlitex.Execute(conn, reqQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{req.AchievementID, req.Name, req.QtyRequired},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save requirement: %w", err)
|
return fmt.Errorf("failed to save requirement: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,41 +354,69 @@ func SaveAchievement(db *database.DB, achievement *Achievement) error {
|
|||||||
rewardQuery := `INSERT INTO achievements_rewards
|
rewardQuery := `INSERT INTO achievements_rewards
|
||||||
(achievement_id, reward) VALUES (?, ?)`
|
(achievement_id, reward) VALUES (?, ?)`
|
||||||
for _, reward := range achievement.Rewards {
|
for _, reward := range achievement.Rewards {
|
||||||
if err := tx.Exec(rewardQuery, reward.AchievementID, reward.Reward); err != nil {
|
err = sqlitex.Execute(conn, rewardQuery, &sqlitex.ExecOptions{
|
||||||
|
Args: []any{reward.AchievementID, reward.Reward},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save reward: %w", err)
|
return fmt.Errorf("failed to save reward: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return sqlitex.Execute(conn, "COMMIT", nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAchievement removes an achievement and all related records from database
|
// DeleteAchievement removes an achievement and all related records from database
|
||||||
func DeleteAchievement(db *database.DB, achievementID uint32) error {
|
func DeleteAchievement(pool *sqlitex.Pool, achievementID uint32) error {
|
||||||
return db.Transaction(func(tx *database.DB) error {
|
conn, err := pool.Take(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection: %w", err)
|
||||||
|
}
|
||||||
|
defer pool.Put(conn)
|
||||||
|
|
||||||
|
err = sqlitex.Execute(conn, "BEGIN", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer sqlitex.Execute(conn, "ROLLBACK", nil)
|
||||||
|
|
||||||
// Delete main achievement
|
// Delete main achievement
|
||||||
if err := tx.Exec("DELETE FROM achievements WHERE achievement_id = ?", achievementID); err != nil {
|
err = sqlitex.Execute(conn, "DELETE FROM achievements WHERE achievement_id = ?", &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievementID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete achievement: %w", err)
|
return fmt.Errorf("failed to delete achievement: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete requirements
|
// Delete requirements
|
||||||
if err := tx.Exec("DELETE FROM achievements_requirements WHERE achievement_id = ?", achievementID); err != nil {
|
err = sqlitex.Execute(conn, "DELETE FROM achievements_requirements WHERE achievement_id = ?", &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievementID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete requirements: %w", err)
|
return fmt.Errorf("failed to delete requirements: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete rewards
|
// Delete rewards
|
||||||
if err := tx.Exec("DELETE FROM achievements_rewards WHERE achievement_id = ?", achievementID); err != nil {
|
err = sqlitex.Execute(conn, "DELETE FROM achievements_rewards WHERE achievement_id = ?", &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievementID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete rewards: %w", err)
|
return fmt.Errorf("failed to delete rewards: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete player progress (optional - might want to preserve history)
|
// Delete player progress (optional - might want to preserve history)
|
||||||
if err := tx.Exec("DELETE FROM character_achievements WHERE achievement_id = ?", achievementID); err != nil {
|
err = sqlitex.Execute(conn, "DELETE FROM character_achievements WHERE achievement_id = ?", &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievementID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete player achievements: %w", err)
|
return fmt.Errorf("failed to delete player achievements: %w", err)
|
||||||
}
|
}
|
||||||
if err := tx.Exec("DELETE FROM character_achievements_items WHERE achievement_id = ?", achievementID); err != nil {
|
|
||||||
|
err = sqlitex.Execute(conn, "DELETE FROM character_achievements_items WHERE achievement_id = ?", &sqlitex.ExecOptions{
|
||||||
|
Args: []any{achievementID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("failed to delete player achievement items: %w", err)
|
return fmt.Errorf("failed to delete player achievement items: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return sqlitex.Execute(conn, "COMMIT", nil)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpen(t *testing.T) {
|
|
||||||
// Create a temporary database file
|
|
||||||
tempFile := "test.db"
|
|
||||||
defer os.Remove(tempFile)
|
|
||||||
|
|
||||||
db, err := Open(tempFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to open database: %v", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
t.Fatal("Database instance is nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExec(t *testing.T) {
|
|
||||||
tempFile := "test_exec.db"
|
|
||||||
defer os.Remove(tempFile)
|
|
||||||
|
|
||||||
db, err := Open(tempFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to open database: %v", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Test table creation
|
|
||||||
err = db.Exec(`CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create table: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test data insertion
|
|
||||||
err = db.Exec(`INSERT INTO test_table (name) VALUES (?)`, "test_name")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to insert data: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryRow(t *testing.T) {
|
|
||||||
tempFile := "test_query.db"
|
|
||||||
defer os.Remove(tempFile)
|
|
||||||
|
|
||||||
db, err := Open(tempFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to open database: %v", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Setup test data
|
|
||||||
err = db.Exec(`CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT, value INTEGER)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create table: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.Exec(`INSERT INTO test_table (name, value) VALUES (?, ?)`, "test", 42)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to insert data: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test query
|
|
||||||
row, err := db.QueryRow("SELECT name, value FROM test_table WHERE id = ?", 1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to query row: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if row == nil {
|
|
||||||
t.Fatal("Row is nil")
|
|
||||||
}
|
|
||||||
defer row.Close()
|
|
||||||
|
|
||||||
name := row.Text(0)
|
|
||||||
value := row.Int(1)
|
|
||||||
|
|
||||||
if name != "test" {
|
|
||||||
t.Errorf("Expected name 'test', got '%s'", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if value != 42 {
|
|
||||||
t.Errorf("Expected value 42, got %d", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQuery(t *testing.T) {
|
|
||||||
tempFile := "test_query_all.db"
|
|
||||||
defer os.Remove(tempFile)
|
|
||||||
|
|
||||||
db, err := Open(tempFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to open database: %v", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Setup test data
|
|
||||||
err = db.Exec(`CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create table: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
names := []string{"test1", "test2", "test3"}
|
|
||||||
for _, name := range names {
|
|
||||||
err = db.Exec(`INSERT INTO test_table (name) VALUES (?)`, name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to insert data: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test query with callback
|
|
||||||
var results []string
|
|
||||||
err = db.Query("SELECT name FROM test_table ORDER BY id", func(row *Row) error {
|
|
||||||
results = append(results, row.Text(0))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to query: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(results) != 3 {
|
|
||||||
t.Errorf("Expected 3 results, got %d", len(results))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, expected := range names {
|
|
||||||
if i < len(results) && results[i] != expected {
|
|
||||||
t.Errorf("Expected result[%d] = '%s', got '%s'", i, expected, results[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransaction(t *testing.T) {
|
|
||||||
tempFile := "test_transaction.db"
|
|
||||||
defer os.Remove(tempFile)
|
|
||||||
|
|
||||||
db, err := Open(tempFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to open database: %v", err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Setup
|
|
||||||
err = db.Exec(`CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT)`)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create table: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test successful transaction
|
|
||||||
err = db.Transaction(func(txDB *DB) error {
|
|
||||||
err := txDB.Exec(`INSERT INTO test_table (name) VALUES (?)`, "tx_test1")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return txDB.Exec(`INSERT INTO test_table (name) VALUES (?)`, "tx_test2")
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Transaction failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify data was committed
|
|
||||||
var count int
|
|
||||||
row, err := db.QueryRow("SELECT COUNT(*) FROM test_table")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to count rows: %v", err)
|
|
||||||
}
|
|
||||||
if row != nil {
|
|
||||||
count = row.Int(0)
|
|
||||||
row.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if count != 2 {
|
|
||||||
t.Errorf("Expected 2 rows, got %d", count)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,262 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"zombiezen.com/go/sqlite"
|
|
||||||
"zombiezen.com/go/sqlite/sqlitex"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DB wraps sqlite.Conn with simplified query methods
|
|
||||||
type DB struct {
|
|
||||||
conn *sqlite.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Row represents a single database row with easy column access
|
|
||||||
type Row struct {
|
|
||||||
stmt *sqlite.Stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryFunc processes each row in a result set
|
|
||||||
type QueryFunc func(*Row) error
|
|
||||||
|
|
||||||
// Open creates a new database connection with common settings
|
|
||||||
func Open(path string) (*DB, error) {
|
|
||||||
conn, err := sqlite.OpenConn(path, sqlite.OpenReadWrite|sqlite.OpenCreate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable foreign keys and WAL mode for better performance
|
|
||||||
if err := sqlitex.ExecuteTransient(conn, "PRAGMA foreign_keys = ON", nil); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, fmt.Errorf("failed to enable foreign keys: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sqlitex.ExecuteTransient(conn, "PRAGMA journal_mode = WAL", nil); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, fmt.Errorf("failed to enable WAL mode: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DB{conn: conn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the database connection
|
|
||||||
func (db *DB) Close() error {
|
|
||||||
return db.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec executes a statement with parameters
|
|
||||||
func (db *DB) Exec(query string, args ...any) error {
|
|
||||||
return sqlitex.Execute(db.conn, query, &sqlitex.ExecOptions{
|
|
||||||
Args: args,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryRow executes a query expecting a single row result
|
|
||||||
func (db *DB) QueryRow(query string, args ...any) (*Row, error) {
|
|
||||||
stmt, err := db.conn.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("prepare failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind parameters
|
|
||||||
for i, arg := range args {
|
|
||||||
if err := bindParam(stmt, i+1, arg); err != nil {
|
|
||||||
stmt.Finalize()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasRow, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
stmt.Finalize()
|
|
||||||
return nil, fmt.Errorf("query failed: %w", err)
|
|
||||||
}
|
|
||||||
if !hasRow {
|
|
||||||
stmt.Finalize()
|
|
||||||
return nil, nil // No row found
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Row{stmt: stmt}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query executes a query and calls fn for each row
|
|
||||||
func (db *DB) Query(query string, fn QueryFunc, args ...any) error {
|
|
||||||
stmt, err := db.conn.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("prepare failed: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
// Bind parameters
|
|
||||||
for i, arg := range args {
|
|
||||||
if err := bindParam(stmt, i+1, arg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
row := &Row{stmt: stmt}
|
|
||||||
for {
|
|
||||||
hasRow, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("query failed: %w", err)
|
|
||||||
}
|
|
||||||
if !hasRow {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fn(row); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// QuerySlice executes a query and returns all rows in a slice
|
|
||||||
func (db *DB) QuerySlice(query string, args ...any) ([]*Row, error) {
|
|
||||||
var rows []*Row
|
|
||||||
|
|
||||||
stmt, err := db.conn.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("prepare failed: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Finalize()
|
|
||||||
|
|
||||||
// Bind parameters
|
|
||||||
for i, arg := range args {
|
|
||||||
if err := bindParam(stmt, i+1, arg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
hasRow, err := stmt.Step()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("query failed: %w", err)
|
|
||||||
}
|
|
||||||
if !hasRow {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a snapshot of the current row
|
|
||||||
rowData := &Row{stmt: stmt}
|
|
||||||
rows = append(rows, rowData)
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LastInsertID returns the last inserted row ID
|
|
||||||
func (db *DB) LastInsertID() int64 {
|
|
||||||
return db.conn.LastInsertRowID()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes returns the number of rows affected by the last statement
|
|
||||||
func (db *DB) Changes() int {
|
|
||||||
return db.conn.Changes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction executes fn within a database transaction
|
|
||||||
func (db *DB) Transaction(fn func(*DB) error) error {
|
|
||||||
if err := sqlitex.ExecuteTransient(db.conn, "BEGIN", nil); err != nil {
|
|
||||||
return fmt.Errorf("begin transaction failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fn(db); err != nil {
|
|
||||||
sqlitex.ExecuteTransient(db.conn, "ROLLBACK", nil)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := sqlitex.ExecuteTransient(db.conn, "COMMIT", nil); err != nil {
|
|
||||||
return fmt.Errorf("commit transaction failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Row column access methods
|
|
||||||
|
|
||||||
// Close releases the row's statement
|
|
||||||
func (r *Row) Close() {
|
|
||||||
if r.stmt != nil {
|
|
||||||
r.stmt.Finalize()
|
|
||||||
r.stmt = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int returns column as int
|
|
||||||
func (r *Row) Int(col int) int {
|
|
||||||
return r.stmt.ColumnInt(col)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64 returns column as int64
|
|
||||||
func (r *Row) Int64(col int) int64 {
|
|
||||||
return r.stmt.ColumnInt64(col)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text returns column as string
|
|
||||||
func (r *Row) Text(col int) string {
|
|
||||||
return r.stmt.ColumnText(col)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool returns column as bool (0 = false, non-zero = true)
|
|
||||||
func (r *Row) Bool(col int) bool {
|
|
||||||
return r.stmt.ColumnInt(col) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float returns column as float64
|
|
||||||
func (r *Row) Float(col int) float64 {
|
|
||||||
return r.stmt.ColumnFloat(col)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNull checks if column is NULL
|
|
||||||
func (r *Row) IsNull(col int) bool {
|
|
||||||
return r.stmt.ColumnType(col) == sqlite.TypeNull
|
|
||||||
}
|
|
||||||
|
|
||||||
// bindParam binds a parameter to a statement at the given index
|
|
||||||
func bindParam(stmt *sqlite.Stmt, index int, value any) error {
|
|
||||||
switch v := value.(type) {
|
|
||||||
case nil:
|
|
||||||
stmt.BindNull(index)
|
|
||||||
case int:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case int8:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case int16:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case int32:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case int64:
|
|
||||||
stmt.BindInt64(index, v)
|
|
||||||
case uint:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case uint8:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case uint16:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case uint32:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case uint64:
|
|
||||||
stmt.BindInt64(index, int64(v))
|
|
||||||
case float32:
|
|
||||||
stmt.BindFloat(index, float64(v))
|
|
||||||
case float64:
|
|
||||||
stmt.BindFloat(index, v)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
stmt.BindInt64(index, 1)
|
|
||||||
} else {
|
|
||||||
stmt.BindInt64(index, 0)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
stmt.BindText(index, v)
|
|
||||||
case []byte:
|
|
||||||
stmt.BindBytes(index, v)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported parameter type: %T", value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -10,11 +10,11 @@ import (
|
|||||||
|
|
||||||
// DatabaseGuildManager implements GuildDatabase interface using the existing database wrapper
|
// DatabaseGuildManager implements GuildDatabase interface using the existing database wrapper
|
||||||
type DatabaseGuildManager struct {
|
type DatabaseGuildManager struct {
|
||||||
db *database.DB
|
db database.DBInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabaseGuildManager creates a new database guild manager
|
// NewDatabaseGuildManager creates a new database guild manager
|
||||||
func NewDatabaseGuildManager(db *database.DB) *DatabaseGuildManager {
|
func NewDatabaseGuildManager(db database.DBInterface) *DatabaseGuildManager {
|
||||||
return &DatabaseGuildManager{
|
return &DatabaseGuildManager{
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
@ -24,44 +24,25 @@ func NewDatabaseGuildManager(db *database.DB) *DatabaseGuildManager {
|
|||||||
func (dgm *DatabaseGuildManager) LoadGuilds(ctx context.Context) ([]GuildData, error) {
|
func (dgm *DatabaseGuildManager) LoadGuilds(ctx context.Context) ([]GuildData, error) {
|
||||||
query := "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds`"
|
query := "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds`"
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query guilds: %w", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var guilds []GuildData
|
var guilds []GuildData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var guild GuildData
|
var guild GuildData
|
||||||
var motd *string
|
guild.ID = int32(row.Int(0))
|
||||||
var formedOnTimestamp int64
|
guild.Name = row.Text(1)
|
||||||
|
if !row.IsNull(2) {
|
||||||
err := rows.Scan(
|
guild.MOTD = row.Text(2)
|
||||||
&guild.ID,
|
|
||||||
&guild.Name,
|
|
||||||
&motd,
|
|
||||||
&guild.Level,
|
|
||||||
&guild.EXPCurrent,
|
|
||||||
&guild.EXPToNextLevel,
|
|
||||||
&formedOnTimestamp,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan guild row: %w", err)
|
|
||||||
}
|
}
|
||||||
|
guild.Level = int8(row.Int(3))
|
||||||
// Handle nullable MOTD field
|
guild.EXPCurrent = row.Int64(4)
|
||||||
if motd != nil {
|
guild.EXPToNextLevel = row.Int64(5)
|
||||||
guild.MOTD = *motd
|
guild.FormedDate = time.Unix(row.Int64(6), 0)
|
||||||
}
|
|
||||||
|
|
||||||
// Convert timestamp to time
|
|
||||||
guild.FormedDate = time.Unix(formedOnTimestamp, 0)
|
|
||||||
|
|
||||||
guilds = append(guilds, guild)
|
guilds = append(guilds, guild)
|
||||||
}
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating guild rows: %w", err)
|
return nil, fmt.Errorf("failed to query guilds: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return guilds, nil
|
return guilds, nil
|
||||||
@ -71,30 +52,25 @@ func (dgm *DatabaseGuildManager) LoadGuilds(ctx context.Context) ([]GuildData, e
|
|||||||
func (dgm *DatabaseGuildManager) LoadGuild(ctx context.Context, guildID int32) (*GuildData, error) {
|
func (dgm *DatabaseGuildManager) LoadGuild(ctx context.Context, guildID int32) (*GuildData, error) {
|
||||||
query := "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds` WHERE `id` = ?"
|
query := "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds` WHERE `id` = ?"
|
||||||
|
|
||||||
var guild GuildData
|
row, err := dgm.db.QueryRow(query, guildID)
|
||||||
var motd *string
|
|
||||||
var formedOnTimestamp int64
|
|
||||||
|
|
||||||
err := dgm.db.QueryRowContext(ctx, query, guildID).Scan(
|
|
||||||
&guild.ID,
|
|
||||||
&guild.Name,
|
|
||||||
&motd,
|
|
||||||
&guild.Level,
|
|
||||||
&guild.EXPCurrent,
|
|
||||||
&guild.EXPToNextLevel,
|
|
||||||
&formedOnTimestamp,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load guild %d: %w", guildID, err)
|
return nil, fmt.Errorf("failed to load guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
if row == nil {
|
||||||
// Handle nullable MOTD field
|
return nil, fmt.Errorf("guild %d not found", guildID)
|
||||||
if motd != nil {
|
|
||||||
guild.MOTD = *motd
|
|
||||||
}
|
}
|
||||||
|
defer row.Close()
|
||||||
|
|
||||||
// Convert timestamp to time
|
var guild GuildData
|
||||||
guild.FormedDate = time.Unix(formedOnTimestamp, 0)
|
guild.ID = int32(row.Int(0))
|
||||||
|
guild.Name = row.Text(1)
|
||||||
|
if !row.IsNull(2) {
|
||||||
|
guild.MOTD = row.Text(2)
|
||||||
|
}
|
||||||
|
guild.Level = int8(row.Int(3))
|
||||||
|
guild.EXPCurrent = row.Int64(4)
|
||||||
|
guild.EXPToNextLevel = row.Int64(5)
|
||||||
|
guild.FormedDate = time.Unix(row.Int64(6), 0)
|
||||||
|
|
||||||
return &guild, nil
|
return &guild, nil
|
||||||
}
|
}
|
||||||
@ -107,110 +83,72 @@ func (dgm *DatabaseGuildManager) LoadGuildMembers(ctx context.Context, guildID i
|
|||||||
recruiter_description, recruiter_picture_data, recruiting_show_adventure_class
|
recruiter_description, recruiter_picture_data, recruiting_show_adventure_class
|
||||||
FROM guild_members WHERE guild_id = ?`
|
FROM guild_members WHERE guild_id = ?`
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query, guildID)
|
// Use the main implementation with zero-copy optimization
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query guild members for guild %d: %w", guildID, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var members []GuildMemberData
|
var members []GuildMemberData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var member GuildMemberData
|
var member GuildMemberData
|
||||||
var joinDateTimestamp int64
|
member.CharacterID = int32(row.Int(0))
|
||||||
var lastLoginTimestamp int64
|
member.GuildID = int32(row.Int(1))
|
||||||
var note, officerNote, recruiterDesc *string
|
member.AccountID = int32(row.Int(2))
|
||||||
var pictureData []byte
|
member.RecruiterID = int32(row.Int(3))
|
||||||
|
member.Name = row.Text(4)
|
||||||
err := rows.Scan(
|
member.GuildStatus = int32(row.Int(5))
|
||||||
&member.CharacterID,
|
member.Points = row.Float(6)
|
||||||
&member.GuildID,
|
member.AdventureClass = int8(row.Int(7))
|
||||||
&member.AccountID,
|
member.AdventureLevel = int8(row.Int(8))
|
||||||
&member.RecruiterID,
|
member.TradeskillClass = int8(row.Int(9))
|
||||||
&member.Name,
|
member.TradeskillLevel = int8(row.Int(10))
|
||||||
&member.GuildStatus,
|
member.Rank = int8(row.Int(11))
|
||||||
&member.Points,
|
member.MemberFlags = int8(row.Int(12))
|
||||||
&member.AdventureClass,
|
member.Zone = row.Text(13)
|
||||||
&member.AdventureLevel,
|
member.JoinDate = time.Unix(row.Int64(14), 0)
|
||||||
&member.TradeskillClass,
|
member.LastLoginDate = time.Unix(row.Int64(15), 0)
|
||||||
&member.TradeskillLevel,
|
if !row.IsNull(16) {
|
||||||
&member.Rank,
|
member.Note = row.Text(16)
|
||||||
&member.MemberFlags,
|
|
||||||
&member.Zone,
|
|
||||||
&joinDateTimestamp,
|
|
||||||
&lastLoginTimestamp,
|
|
||||||
¬e,
|
|
||||||
&officerNote,
|
|
||||||
&recruiterDesc,
|
|
||||||
&pictureData,
|
|
||||||
&member.RecruitingShowAdventureClass,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan guild member row: %w", err)
|
|
||||||
}
|
}
|
||||||
|
if !row.IsNull(17) {
|
||||||
// Handle nullable fields
|
member.OfficerNote = row.Text(17)
|
||||||
if note != nil {
|
|
||||||
member.Note = *note
|
|
||||||
}
|
}
|
||||||
if officerNote != nil {
|
if !row.IsNull(18) {
|
||||||
member.OfficerNote = *officerNote
|
member.RecruiterDescription = row.Text(18)
|
||||||
}
|
}
|
||||||
if recruiterDesc != nil {
|
// TODO: Handle blob data for recruiter_picture_data (column 19)
|
||||||
member.RecruiterDescription = *recruiterDesc
|
member.RecruitingShowAdventureClass = int8(row.Int(20))
|
||||||
}
|
|
||||||
member.RecruiterPictureData = pictureData
|
|
||||||
|
|
||||||
// Convert timestamps to time
|
|
||||||
member.JoinDate = time.Unix(joinDateTimestamp, 0)
|
|
||||||
member.LastLoginDate = time.Unix(lastLoginTimestamp, 0)
|
|
||||||
|
|
||||||
members = append(members, member)
|
members = append(members, member)
|
||||||
}
|
return nil
|
||||||
|
}, guildID)
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating guild member rows: %w", err)
|
return nil, fmt.Errorf("failed to query guild members for guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return members, nil
|
return members, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// LoadGuildEvents retrieves events for a guild
|
// LoadGuildEvents retrieves events for a guild
|
||||||
func (dgm *DatabaseGuildManager) LoadGuildEvents(ctx context.Context, guildID int32) ([]GuildEventData, error) {
|
func (dgm *DatabaseGuildManager) LoadGuildEvents(ctx context.Context, guildID int32) ([]GuildEventData, error) {
|
||||||
query := `SELECT event_id, guild_id, date, type, description, locked
|
query := `SELECT event_id, guild_id, date, type, description, locked
|
||||||
FROM guild_events WHERE guild_id = ?
|
FROM guild_events WHERE guild_id = ?
|
||||||
ORDER BY event_id DESC LIMIT ?`
|
ORDER BY event_id DESC LIMIT ?`
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query, guildID, MaxEvents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query guild events for guild %d: %w", guildID, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var events []GuildEventData
|
var events []GuildEventData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var event GuildEventData
|
var event GuildEventData
|
||||||
var dateTimestamp int64
|
event.EventID = row.Int64(0)
|
||||||
|
event.GuildID = int32(row.Int(1))
|
||||||
err := rows.Scan(
|
event.Date = time.Unix(row.Int64(2), 0)
|
||||||
&event.EventID,
|
event.Type = int32(row.Int(3))
|
||||||
&event.GuildID,
|
event.Description = row.Text(4)
|
||||||
&dateTimestamp,
|
event.Locked = int8(row.Int(5))
|
||||||
&event.Type,
|
|
||||||
&event.Description,
|
|
||||||
&event.Locked,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan guild event row: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert timestamp to time
|
|
||||||
event.Date = time.Unix(dateTimestamp, 0)
|
|
||||||
|
|
||||||
events = append(events, event)
|
events = append(events, event)
|
||||||
}
|
return nil
|
||||||
|
}, guildID, MaxEvents)
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating guild event rows: %w", err)
|
return nil, fmt.Errorf("failed to query guild events for guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return events, nil
|
return events, nil
|
||||||
@ -220,30 +158,19 @@ func (dgm *DatabaseGuildManager) LoadGuildEvents(ctx context.Context, guildID in
|
|||||||
func (dgm *DatabaseGuildManager) LoadGuildRanks(ctx context.Context, guildID int32) ([]GuildRankData, error) {
|
func (dgm *DatabaseGuildManager) LoadGuildRanks(ctx context.Context, guildID int32) ([]GuildRankData, error) {
|
||||||
query := "SELECT guild_id, rank, name FROM guild_ranks WHERE guild_id = ?"
|
query := "SELECT guild_id, rank, name FROM guild_ranks WHERE guild_id = ?"
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query, guildID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query guild ranks for guild %d: %w", guildID, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var ranks []GuildRankData
|
var ranks []GuildRankData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var rank GuildRankData
|
var rank GuildRankData
|
||||||
|
rank.GuildID = int32(row.Int(0))
|
||||||
err := rows.Scan(
|
rank.Rank = int8(row.Int(1))
|
||||||
&rank.GuildID,
|
rank.Name = row.Text(2)
|
||||||
&rank.Rank,
|
|
||||||
&rank.Name,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan guild rank row: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ranks = append(ranks, rank)
|
ranks = append(ranks, rank)
|
||||||
}
|
return nil
|
||||||
|
}, guildID)
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating guild rank rows: %w", err)
|
return nil, fmt.Errorf("failed to query guild ranks for guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ranks, nil
|
return ranks, nil
|
||||||
@ -253,31 +180,20 @@ func (dgm *DatabaseGuildManager) LoadGuildRanks(ctx context.Context, guildID int
|
|||||||
func (dgm *DatabaseGuildManager) LoadGuildPermissions(ctx context.Context, guildID int32) ([]GuildPermissionData, error) {
|
func (dgm *DatabaseGuildManager) LoadGuildPermissions(ctx context.Context, guildID int32) ([]GuildPermissionData, error) {
|
||||||
query := "SELECT guild_id, rank, permission, value FROM guild_permissions WHERE guild_id = ?"
|
query := "SELECT guild_id, rank, permission, value FROM guild_permissions WHERE guild_id = ?"
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query, guildID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query guild permissions for guild %d: %w", guildID, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var permissions []GuildPermissionData
|
var permissions []GuildPermissionData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var permission GuildPermissionData
|
var permission GuildPermissionData
|
||||||
|
permission.GuildID = int32(row.Int(0))
|
||||||
err := rows.Scan(
|
permission.Rank = int8(row.Int(1))
|
||||||
&permission.GuildID,
|
permission.Permission = int8(row.Int(2))
|
||||||
&permission.Rank,
|
permission.Value = int8(row.Int(3))
|
||||||
&permission.Permission,
|
|
||||||
&permission.Value,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan guild permission row: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
permissions = append(permissions, permission)
|
permissions = append(permissions, permission)
|
||||||
}
|
return nil
|
||||||
|
}, guildID)
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating guild permission rows: %w", err)
|
return nil, fmt.Errorf("failed to query guild permissions for guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return permissions, nil
|
return permissions, nil
|
||||||
@ -287,31 +203,20 @@ func (dgm *DatabaseGuildManager) LoadGuildPermissions(ctx context.Context, guild
|
|||||||
func (dgm *DatabaseGuildManager) LoadGuildEventFilters(ctx context.Context, guildID int32) ([]GuildEventFilterData, error) {
|
func (dgm *DatabaseGuildManager) LoadGuildEventFilters(ctx context.Context, guildID int32) ([]GuildEventFilterData, error) {
|
||||||
query := "SELECT guild_id, event_id, category, value FROM guild_event_filters WHERE guild_id = ?"
|
query := "SELECT guild_id, event_id, category, value FROM guild_event_filters WHERE guild_id = ?"
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query, guildID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query guild event filters for guild %d: %w", guildID, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var filters []GuildEventFilterData
|
var filters []GuildEventFilterData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var filter GuildEventFilterData
|
var filter GuildEventFilterData
|
||||||
|
filter.GuildID = int32(row.Int(0))
|
||||||
err := rows.Scan(
|
filter.EventID = int8(row.Int(1))
|
||||||
&filter.GuildID,
|
filter.Category = int8(row.Int(2))
|
||||||
&filter.EventID,
|
filter.Value = int8(row.Int(3))
|
||||||
&filter.Category,
|
|
||||||
&filter.Value,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan guild event filter row: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filters = append(filters, filter)
|
filters = append(filters, filter)
|
||||||
}
|
return nil
|
||||||
|
}, guildID)
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating guild event filter rows: %w", err)
|
return nil, fmt.Errorf("failed to query guild event filters for guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filters, nil
|
return filters, nil
|
||||||
@ -321,30 +226,19 @@ func (dgm *DatabaseGuildManager) LoadGuildEventFilters(ctx context.Context, guil
|
|||||||
func (dgm *DatabaseGuildManager) LoadGuildRecruiting(ctx context.Context, guildID int32) ([]GuildRecruitingData, error) {
|
func (dgm *DatabaseGuildManager) LoadGuildRecruiting(ctx context.Context, guildID int32) ([]GuildRecruitingData, error) {
|
||||||
query := "SELECT guild_id, flag, value FROM guild_recruiting WHERE guild_id = ?"
|
query := "SELECT guild_id, flag, value FROM guild_recruiting WHERE guild_id = ?"
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query, guildID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query guild recruiting for guild %d: %w", guildID, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var recruiting []GuildRecruitingData
|
var recruiting []GuildRecruitingData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var recruit GuildRecruitingData
|
var recruit GuildRecruitingData
|
||||||
|
recruit.GuildID = int32(row.Int(0))
|
||||||
err := rows.Scan(
|
recruit.Flag = int8(row.Int(1))
|
||||||
&recruit.GuildID,
|
recruit.Value = int8(row.Int(2))
|
||||||
&recruit.Flag,
|
|
||||||
&recruit.Value,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan guild recruiting row: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
recruiting = append(recruiting, recruit)
|
recruiting = append(recruiting, recruit)
|
||||||
}
|
return nil
|
||||||
|
}, guildID)
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating guild recruiting rows: %w", err)
|
return nil, fmt.Errorf("failed to query guild recruiting for guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return recruiting, nil
|
return recruiting, nil
|
||||||
@ -356,36 +250,21 @@ func (dgm *DatabaseGuildManager) LoadPointHistory(ctx context.Context, character
|
|||||||
FROM guild_point_history WHERE char_id = ?
|
FROM guild_point_history WHERE char_id = ?
|
||||||
ORDER BY date DESC LIMIT ?`
|
ORDER BY date DESC LIMIT ?`
|
||||||
|
|
||||||
rows, err := dgm.db.QueryContext(ctx, query, characterID, MaxPointHistory)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to query point history for character %d: %w", characterID, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var history []PointHistoryData
|
var history []PointHistoryData
|
||||||
for rows.Next() {
|
err := dgm.db.Query(query, func(row *database.Row) error {
|
||||||
var entry PointHistoryData
|
var entry PointHistoryData
|
||||||
var dateTimestamp int64
|
entry.CharacterID = int32(row.Int(0))
|
||||||
|
entry.Date = time.Unix(row.Int64(1), 0)
|
||||||
err := rows.Scan(
|
entry.ModifiedBy = row.Text(2)
|
||||||
&entry.CharacterID,
|
entry.Comment = row.Text(3)
|
||||||
&dateTimestamp,
|
entry.Points = row.Float(4)
|
||||||
&entry.ModifiedBy,
|
|
||||||
&entry.Comment,
|
|
||||||
&entry.Points,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan point history row: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert timestamp to time
|
|
||||||
entry.Date = time.Unix(dateTimestamp, 0)
|
|
||||||
|
|
||||||
history = append(history, entry)
|
history = append(history, entry)
|
||||||
}
|
return nil
|
||||||
|
}, characterID, MaxPointHistory)
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error iterating point history rows: %w", err)
|
return nil, fmt.Errorf("failed to query point history for character %d: %w", characterID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return history, nil
|
return history, nil
|
||||||
@ -397,10 +276,9 @@ func (dgm *DatabaseGuildManager) SaveGuild(ctx context.Context, guild *Guild) er
|
|||||||
(id, name, motd, level, xp, xp_needed, formed_on)
|
(id, name, motd, level, xp, xp_needed, formed_on)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
||||||
|
|
||||||
guildInfo := guild.GetGuildInfo()
|
|
||||||
formedTimestamp := guild.GetFormedDate().Unix()
|
formedTimestamp := guild.GetFormedDate().Unix()
|
||||||
|
|
||||||
_, err := dgm.db.ExecContext(ctx, query,
|
err := dgm.db.Exec(query,
|
||||||
guild.GetID(),
|
guild.GetID(),
|
||||||
guild.GetName(),
|
guild.GetName(),
|
||||||
guild.GetMOTD(),
|
guild.GetMOTD(),
|
||||||
@ -423,15 +301,9 @@ func (dgm *DatabaseGuildManager) SaveGuildMembers(ctx context.Context, guildID i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use a transaction for atomic updates
|
// Use a transaction for atomic updates
|
||||||
tx, err := dgm.db.BeginTx(ctx, nil)
|
return dgm.db.Transaction(func(db database.DBInterface) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// Delete existing members for this guild
|
// Delete existing members for this guild
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM guild_members WHERE guild_id = ?", guildID)
|
if err := db.Exec("DELETE FROM guild_members WHERE guild_id = ?", guildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete existing guild members: %w", err)
|
return fmt.Errorf("failed to delete existing guild members: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,7 +319,7 @@ func (dgm *DatabaseGuildManager) SaveGuildMembers(ctx context.Context, guildID i
|
|||||||
joinTimestamp := member.GetJoinDate().Unix()
|
joinTimestamp := member.GetJoinDate().Unix()
|
||||||
lastLoginTimestamp := member.GetLastLoginDate().Unix()
|
lastLoginTimestamp := member.GetLastLoginDate().Unix()
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, insertQuery,
|
if err := db.Exec(insertQuery,
|
||||||
member.GetCharacterID(),
|
member.GetCharacterID(),
|
||||||
guildID,
|
guildID,
|
||||||
member.AccountID,
|
member.AccountID,
|
||||||
@ -469,17 +341,12 @@ func (dgm *DatabaseGuildManager) SaveGuildMembers(ctx context.Context, guildID i
|
|||||||
member.GetRecruiterDescription(),
|
member.GetRecruiterDescription(),
|
||||||
member.GetRecruiterPictureData(),
|
member.GetRecruiterPictureData(),
|
||||||
member.RecruitingShowAdventureClass,
|
member.RecruitingShowAdventureClass,
|
||||||
)
|
); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert guild member %d: %w", member.GetCharacterID(), err)
|
return fmt.Errorf("failed to insert guild member %d: %w", member.GetCharacterID(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGuildEvents saves guild events
|
// SaveGuildEvents saves guild events
|
||||||
@ -499,15 +366,14 @@ func (dgm *DatabaseGuildManager) SaveGuildEvents(ctx context.Context, guildID in
|
|||||||
|
|
||||||
dateTimestamp := event.Date.Unix()
|
dateTimestamp := event.Date.Unix()
|
||||||
|
|
||||||
_, err := dgm.db.ExecContext(ctx, query,
|
if err := dgm.db.Exec(query,
|
||||||
event.EventID,
|
event.EventID,
|
||||||
guildID,
|
guildID,
|
||||||
dateTimestamp,
|
dateTimestamp,
|
||||||
event.Type,
|
event.Type,
|
||||||
event.Description,
|
event.Description,
|
||||||
event.Locked,
|
event.Locked,
|
||||||
)
|
); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to save guild event %d: %w", event.EventID, err)
|
return fmt.Errorf("failed to save guild event %d: %w", event.EventID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -518,15 +384,9 @@ func (dgm *DatabaseGuildManager) SaveGuildEvents(ctx context.Context, guildID in
|
|||||||
// SaveGuildRanks saves guild rank names
|
// SaveGuildRanks saves guild rank names
|
||||||
func (dgm *DatabaseGuildManager) SaveGuildRanks(ctx context.Context, guildID int32, ranks map[int8]string) error {
|
func (dgm *DatabaseGuildManager) SaveGuildRanks(ctx context.Context, guildID int32, ranks map[int8]string) error {
|
||||||
// Use a transaction for atomic updates
|
// Use a transaction for atomic updates
|
||||||
tx, err := dgm.db.BeginTx(ctx, nil)
|
return dgm.db.Transaction(func(db database.DBInterface) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// Delete existing ranks for this guild
|
// Delete existing ranks for this guild
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM guild_ranks WHERE guild_id = ?", guildID)
|
if err := db.Exec("DELETE FROM guild_ranks WHERE guild_id = ?", guildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete existing guild ranks: %w", err)
|
return fmt.Errorf("failed to delete existing guild ranks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,32 +396,21 @@ func (dgm *DatabaseGuildManager) SaveGuildRanks(ctx context.Context, guildID int
|
|||||||
for rank, name := range ranks {
|
for rank, name := range ranks {
|
||||||
// Only save non-default rank names
|
// Only save non-default rank names
|
||||||
if defaultName, exists := DefaultRankNames[rank]; !exists || name != defaultName {
|
if defaultName, exists := DefaultRankNames[rank]; !exists || name != defaultName {
|
||||||
_, err = tx.ExecContext(ctx, insertQuery, guildID, rank, name)
|
if err := db.Exec(insertQuery, guildID, rank, name); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert guild rank %d: %w", rank, err)
|
return fmt.Errorf("failed to insert guild rank %d: %w", rank, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGuildPermissions saves guild permissions
|
// SaveGuildPermissions saves guild permissions
|
||||||
func (dgm *DatabaseGuildManager) SaveGuildPermissions(ctx context.Context, guildID int32, permissions map[int8]map[int8]int8) error {
|
func (dgm *DatabaseGuildManager) SaveGuildPermissions(ctx context.Context, guildID int32, permissions map[int8]map[int8]int8) error {
|
||||||
// Use a transaction for atomic updates
|
// Use a transaction for atomic updates
|
||||||
tx, err := dgm.db.BeginTx(ctx, nil)
|
return dgm.db.Transaction(func(db database.DBInterface) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// Delete existing permissions for this guild
|
// Delete existing permissions for this guild
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM guild_permissions WHERE guild_id = ?", guildID)
|
if err := db.Exec("DELETE FROM guild_permissions WHERE guild_id = ?", guildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete existing guild permissions: %w", err)
|
return fmt.Errorf("failed to delete existing guild permissions: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,32 +419,21 @@ func (dgm *DatabaseGuildManager) SaveGuildPermissions(ctx context.Context, guild
|
|||||||
|
|
||||||
for rank, rankPermissions := range permissions {
|
for rank, rankPermissions := range permissions {
|
||||||
for permission, value := range rankPermissions {
|
for permission, value := range rankPermissions {
|
||||||
_, err = tx.ExecContext(ctx, insertQuery, guildID, rank, permission, value)
|
if err := db.Exec(insertQuery, guildID, rank, permission, value); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert guild permission %d/%d: %w", rank, permission, err)
|
return fmt.Errorf("failed to insert guild permission %d/%d: %w", rank, permission, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGuildEventFilters saves guild event filters
|
// SaveGuildEventFilters saves guild event filters
|
||||||
func (dgm *DatabaseGuildManager) SaveGuildEventFilters(ctx context.Context, guildID int32, filters map[int8]map[int8]int8) error {
|
func (dgm *DatabaseGuildManager) SaveGuildEventFilters(ctx context.Context, guildID int32, filters map[int8]map[int8]int8) error {
|
||||||
// Use a transaction for atomic updates
|
// Use a transaction for atomic updates
|
||||||
tx, err := dgm.db.BeginTx(ctx, nil)
|
return dgm.db.Transaction(func(db database.DBInterface) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// Delete existing filters for this guild
|
// Delete existing filters for this guild
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM guild_event_filters WHERE guild_id = ?", guildID)
|
if err := db.Exec("DELETE FROM guild_event_filters WHERE guild_id = ?", guildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete existing guild event filters: %w", err)
|
return fmt.Errorf("failed to delete existing guild event filters: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,32 +442,21 @@ func (dgm *DatabaseGuildManager) SaveGuildEventFilters(ctx context.Context, guil
|
|||||||
|
|
||||||
for eventID, eventFilters := range filters {
|
for eventID, eventFilters := range filters {
|
||||||
for category, value := range eventFilters {
|
for category, value := range eventFilters {
|
||||||
_, err = tx.ExecContext(ctx, insertQuery, guildID, eventID, category, value)
|
if err := db.Exec(insertQuery, guildID, eventID, category, value); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert guild event filter %d/%d: %w", eventID, category, err)
|
return fmt.Errorf("failed to insert guild event filter %d/%d: %w", eventID, category, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGuildRecruiting saves guild recruiting settings
|
// SaveGuildRecruiting saves guild recruiting settings
|
||||||
func (dgm *DatabaseGuildManager) SaveGuildRecruiting(ctx context.Context, guildID int32, flags, descTags map[int8]int8) error {
|
func (dgm *DatabaseGuildManager) SaveGuildRecruiting(ctx context.Context, guildID int32, flags, descTags map[int8]int8) error {
|
||||||
// Use a transaction for atomic updates
|
// Use a transaction for atomic updates
|
||||||
tx, err := dgm.db.BeginTx(ctx, nil)
|
return dgm.db.Transaction(func(db database.DBInterface) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// Delete existing recruiting settings for this guild
|
// Delete existing recruiting settings for this guild
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM guild_recruiting WHERE guild_id = ?", guildID)
|
if err := db.Exec("DELETE FROM guild_recruiting WHERE guild_id = ?", guildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete existing guild recruiting: %w", err)
|
return fmt.Errorf("failed to delete existing guild recruiting: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,25 +464,19 @@ func (dgm *DatabaseGuildManager) SaveGuildRecruiting(ctx context.Context, guildI
|
|||||||
insertQuery := "INSERT INTO guild_recruiting (guild_id, flag, value) VALUES (?, ?, ?)"
|
insertQuery := "INSERT INTO guild_recruiting (guild_id, flag, value) VALUES (?, ?, ?)"
|
||||||
|
|
||||||
for flag, value := range flags {
|
for flag, value := range flags {
|
||||||
_, err = tx.ExecContext(ctx, insertQuery, guildID, flag, value)
|
if err := db.Exec(insertQuery, guildID, flag, value); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert guild recruiting flag %d: %w", flag, err)
|
return fmt.Errorf("failed to insert guild recruiting flag %d: %w", flag, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert description tags (with negative flag values to distinguish)
|
// Insert description tags (with negative flag values to distinguish)
|
||||||
for tag, value := range descTags {
|
for tag, value := range descTags {
|
||||||
_, err = tx.ExecContext(ctx, insertQuery, guildID, -tag-1, value) // Negative to distinguish from flags
|
if err := db.Exec(insertQuery, guildID, -tag-1, value); err != nil { // Negative to distinguish from flags
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert guild recruiting desc tag %d: %w", tag, err)
|
return fmt.Errorf("failed to insert guild recruiting desc tag %d: %w", tag, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePointHistory saves point history for a member
|
// SavePointHistory saves point history for a member
|
||||||
@ -665,15 +486,9 @@ func (dgm *DatabaseGuildManager) SavePointHistory(ctx context.Context, character
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use a transaction for atomic updates
|
// Use a transaction for atomic updates
|
||||||
tx, err := dgm.db.BeginTx(ctx, nil)
|
return dgm.db.Transaction(func(db database.DBInterface) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// Delete existing history for this character
|
// Delete existing history for this character
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM guild_point_history WHERE char_id = ?", characterID)
|
if err := db.Exec("DELETE FROM guild_point_history WHERE char_id = ?", characterID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete existing point history: %w", err)
|
return fmt.Errorf("failed to delete existing point history: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,35 +498,34 @@ func (dgm *DatabaseGuildManager) SavePointHistory(ctx context.Context, character
|
|||||||
for _, entry := range history {
|
for _, entry := range history {
|
||||||
dateTimestamp := entry.Date.Unix()
|
dateTimestamp := entry.Date.Unix()
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, insertQuery,
|
if err := db.Exec(insertQuery,
|
||||||
characterID,
|
characterID,
|
||||||
dateTimestamp,
|
dateTimestamp,
|
||||||
entry.ModifiedBy,
|
entry.ModifiedBy,
|
||||||
entry.Comment,
|
entry.Comment,
|
||||||
entry.Points,
|
entry.Points,
|
||||||
)
|
); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert point history entry: %w", err)
|
return fmt.Errorf("failed to insert point history entry: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGuildIDByCharacterID returns guild ID for a character
|
// GetGuildIDByCharacterID returns guild ID for a character
|
||||||
func (dgm *DatabaseGuildManager) GetGuildIDByCharacterID(ctx context.Context, characterID int32) (int32, error) {
|
func (dgm *DatabaseGuildManager) GetGuildIDByCharacterID(ctx context.Context, characterID int32) (int32, error) {
|
||||||
query := "SELECT guild_id FROM guild_members WHERE char_id = ?"
|
query := "SELECT guild_id FROM guild_members WHERE char_id = ?"
|
||||||
|
|
||||||
var guildID int32
|
row, err := dgm.db.QueryRow(query, characterID)
|
||||||
err := dgm.db.QueryRowContext(ctx, query, characterID).Scan(&guildID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to get guild ID for character %d: %w", characterID, err)
|
return 0, fmt.Errorf("failed to get guild ID for character %d: %w", characterID, err)
|
||||||
}
|
}
|
||||||
|
if row == nil {
|
||||||
|
return 0, fmt.Errorf("character %d not found in any guild", characterID)
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
|
||||||
|
guildID := int32(row.Int(0))
|
||||||
return guildID, nil
|
return guildID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -722,7 +536,7 @@ func (dgm *DatabaseGuildManager) CreateGuild(ctx context.Context, guildData Guil
|
|||||||
|
|
||||||
formedTimestamp := guildData.FormedDate.Unix()
|
formedTimestamp := guildData.FormedDate.Unix()
|
||||||
|
|
||||||
result, err := dgm.db.ExecContext(ctx, query,
|
id, err := dgm.db.ExecAndGetLastInsertID(query,
|
||||||
guildData.Name,
|
guildData.Name,
|
||||||
guildData.MOTD,
|
guildData.MOTD,
|
||||||
guildData.Level,
|
guildData.Level,
|
||||||
@ -734,23 +548,13 @@ func (dgm *DatabaseGuildManager) CreateGuild(ctx context.Context, guildData Guil
|
|||||||
return 0, fmt.Errorf("failed to create guild: %w", err)
|
return 0, fmt.Errorf("failed to create guild: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := result.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to get new guild ID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return int32(id), nil
|
return int32(id), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteGuild removes a guild and all related data
|
// DeleteGuild removes a guild and all related data
|
||||||
func (dgm *DatabaseGuildManager) DeleteGuild(ctx context.Context, guildID int32) error {
|
func (dgm *DatabaseGuildManager) DeleteGuild(ctx context.Context, guildID int32) error {
|
||||||
// Use a transaction for atomic deletion
|
// Use a transaction for atomic deletion
|
||||||
tx, err := dgm.db.BeginTx(ctx, nil)
|
return dgm.db.Transaction(func(db database.DBInterface) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
// Delete related data first (foreign key constraints)
|
// Delete related data first (foreign key constraints)
|
||||||
tables := []string{
|
tables := []string{
|
||||||
"guild_point_history",
|
"guild_point_history",
|
||||||
@ -765,41 +569,39 @@ func (dgm *DatabaseGuildManager) DeleteGuild(ctx context.Context, guildID int32)
|
|||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
var query string
|
var query string
|
||||||
if table == "guild_point_history" {
|
if table == "guild_point_history" {
|
||||||
// Special case: need to join with guild_members to get char_ids
|
// Special case: delete point history for all members of this guild
|
||||||
query = fmt.Sprintf("DELETE ph FROM %s ph JOIN guild_members gm ON ph.char_id = gm.char_id WHERE gm.guild_id = ?", table)
|
query = "DELETE FROM guild_point_history WHERE character_id IN (SELECT char_id FROM guild_members WHERE guild_id = ?)"
|
||||||
} else {
|
} else {
|
||||||
query = fmt.Sprintf("DELETE FROM %s WHERE guild_id = ?", table)
|
query = fmt.Sprintf("DELETE FROM %s WHERE guild_id = ?", table)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, query, guildID)
|
if err := db.Exec(query, guildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete from %s: %w", table, err)
|
return fmt.Errorf("failed to delete from %s: %w", table, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally delete the guild itself
|
// Finally delete the guild itself
|
||||||
_, err = tx.ExecContext(ctx, "DELETE FROM guilds WHERE id = ?", guildID)
|
if err := db.Exec("DELETE FROM guilds WHERE id = ?", guildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete guild: %w", err)
|
return fmt.Errorf("failed to delete guild: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextGuildID returns the next available guild ID
|
// GetNextGuildID returns the next available guild ID
|
||||||
func (dgm *DatabaseGuildManager) GetNextGuildID(ctx context.Context) (int32, error) {
|
func (dgm *DatabaseGuildManager) GetNextGuildID(ctx context.Context) (int32, error) {
|
||||||
query := "SELECT COALESCE(MAX(id), 0) + 1 FROM guilds"
|
query := "SELECT COALESCE(MAX(id), 0) + 1 FROM guilds"
|
||||||
|
|
||||||
var nextID int32
|
row, err := dgm.db.QueryRow(query)
|
||||||
err := dgm.db.QueryRowContext(ctx, query).Scan(&nextID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to get next guild ID: %w", err)
|
return 0, fmt.Errorf("failed to get next guild ID: %w", err)
|
||||||
}
|
}
|
||||||
|
if row == nil {
|
||||||
|
return 1, nil // If no guilds exist, start with ID 1
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
|
||||||
|
nextID := int32(row.Int(0))
|
||||||
return nextID, nil
|
return nextID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -807,12 +609,16 @@ func (dgm *DatabaseGuildManager) GetNextGuildID(ctx context.Context) (int32, err
|
|||||||
func (dgm *DatabaseGuildManager) GetNextEventID(ctx context.Context, guildID int32) (int64, error) {
|
func (dgm *DatabaseGuildManager) GetNextEventID(ctx context.Context, guildID int32) (int64, error) {
|
||||||
query := "SELECT COALESCE(MAX(event_id), 0) + 1 FROM guild_events WHERE guild_id = ?"
|
query := "SELECT COALESCE(MAX(event_id), 0) + 1 FROM guild_events WHERE guild_id = ?"
|
||||||
|
|
||||||
var nextID int64
|
row, err := dgm.db.QueryRow(query, guildID)
|
||||||
err := dgm.db.QueryRowContext(ctx, query, guildID).Scan(&nextID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("failed to get next event ID for guild %d: %w", guildID, err)
|
return 0, fmt.Errorf("failed to get next event ID for guild %d: %w", guildID, err)
|
||||||
}
|
}
|
||||||
|
if row == nil {
|
||||||
|
return 1, nil // If no events exist for this guild, start with ID 1
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
|
||||||
|
nextID := row.Int64(0)
|
||||||
return nextID, nil
|
return nextID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -907,8 +713,7 @@ func (dgm *DatabaseGuildManager) EnsureGuildTables(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, query := range queries {
|
for i, query := range queries {
|
||||||
_, err := dgm.db.ExecContext(ctx, query)
|
if err := dgm.db.Exec(query); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create guild table %d: %w", i+1, err)
|
return fmt.Errorf("failed to create guild table %d: %w", i+1, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -926,8 +731,7 @@ func (dgm *DatabaseGuildManager) EnsureGuildTables(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, query := range indexes {
|
for i, query := range indexes {
|
||||||
_, err := dgm.db.ExecContext(ctx, query)
|
if err := dgm.db.Exec(query); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create guild index %d: %w", i+1, err)
|
return fmt.Errorf("failed to create guild index %d: %w", i+1, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
608
internal/guilds/database_test.go
Normal file
608
internal/guilds/database_test.go
Normal file
@ -0,0 +1,608 @@
|
|||||||
|
package guilds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"eq2emu/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createTestDB creates a temporary test database
|
||||||
|
func createTestDB(t *testing.T) *database.DB {
|
||||||
|
// Create temporary directory for test database
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
dbPath := filepath.Join(tempDir, "test_guilds.db")
|
||||||
|
|
||||||
|
// Create and initialize database
|
||||||
|
db, err := database.Open(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create test database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create guild tables for testing
|
||||||
|
err = createGuildTables(db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create guild tables: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// createGuildTables creates the necessary tables for guild testing
|
||||||
|
func createGuildTables(db *database.DB) error {
|
||||||
|
tables := []string{
|
||||||
|
`CREATE TABLE IF NOT EXISTS guilds (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
motd TEXT,
|
||||||
|
level INTEGER DEFAULT 1,
|
||||||
|
xp INTEGER DEFAULT 111,
|
||||||
|
xp_needed INTEGER DEFAULT 2521,
|
||||||
|
formed_on INTEGER DEFAULT 0
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS guild_members (
|
||||||
|
char_id INTEGER PRIMARY KEY,
|
||||||
|
guild_id INTEGER NOT NULL,
|
||||||
|
account_id INTEGER NOT NULL,
|
||||||
|
recruiter_id INTEGER DEFAULT 0,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
guild_status INTEGER DEFAULT 0,
|
||||||
|
points REAL DEFAULT 0.0,
|
||||||
|
adventure_class INTEGER DEFAULT 0,
|
||||||
|
adventure_level INTEGER DEFAULT 1,
|
||||||
|
tradeskill_class INTEGER DEFAULT 0,
|
||||||
|
tradeskill_level INTEGER DEFAULT 1,
|
||||||
|
rank INTEGER DEFAULT 7,
|
||||||
|
member_flags INTEGER DEFAULT 0,
|
||||||
|
zone TEXT DEFAULT '',
|
||||||
|
join_date INTEGER DEFAULT 0,
|
||||||
|
last_login_date INTEGER DEFAULT 0,
|
||||||
|
note TEXT DEFAULT '',
|
||||||
|
officer_note TEXT DEFAULT '',
|
||||||
|
recruiter_description TEXT DEFAULT '',
|
||||||
|
recruiter_picture_data BLOB,
|
||||||
|
recruiting_show_adventure_class INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS guild_events (
|
||||||
|
event_id INTEGER PRIMARY KEY,
|
||||||
|
guild_id INTEGER NOT NULL,
|
||||||
|
date INTEGER NOT NULL,
|
||||||
|
type INTEGER NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
locked INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS guild_ranks (
|
||||||
|
guild_id INTEGER NOT NULL,
|
||||||
|
rank INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (guild_id, rank),
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS guild_permissions (
|
||||||
|
guild_id INTEGER NOT NULL,
|
||||||
|
rank INTEGER NOT NULL,
|
||||||
|
permission INTEGER NOT NULL,
|
||||||
|
value INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (guild_id, rank, permission),
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS guild_event_filters (
|
||||||
|
guild_id INTEGER NOT NULL,
|
||||||
|
event_id INTEGER NOT NULL,
|
||||||
|
category INTEGER NOT NULL,
|
||||||
|
value INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (guild_id, event_id, category),
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS guild_recruiting (
|
||||||
|
guild_id INTEGER NOT NULL,
|
||||||
|
flag INTEGER NOT NULL,
|
||||||
|
value INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (guild_id, flag),
|
||||||
|
FOREIGN KEY (guild_id) REFERENCES guilds(id)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS guild_point_history (
|
||||||
|
character_id INTEGER NOT NULL,
|
||||||
|
date INTEGER NOT NULL,
|
||||||
|
modified_by TEXT NOT NULL,
|
||||||
|
comment TEXT NOT NULL,
|
||||||
|
points REAL NOT NULL,
|
||||||
|
FOREIGN KEY (character_id) REFERENCES guild_members(char_id)
|
||||||
|
)`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sql := range tables {
|
||||||
|
if err := db.Exec(sql); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_LoadGuilds tests loading guilds from database
|
||||||
|
func TestDatabaseGuildManager_LoadGuilds(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert test data
|
||||||
|
insertGuildSQL := `INSERT INTO guilds (id, name, motd, level, xp, xp_needed, formed_on)
|
||||||
|
VALUES (1, 'Test Guild', 'Welcome!', 5, 1000, 5000, ?)`
|
||||||
|
formedTime := time.Now().Unix()
|
||||||
|
err := db.Exec(insertGuildSQL, formedTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test loading guilds
|
||||||
|
guilds, err := dgm.LoadGuilds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load guilds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(guilds) != 1 {
|
||||||
|
t.Errorf("Expected 1 guild, got %d", len(guilds))
|
||||||
|
}
|
||||||
|
|
||||||
|
guild := guilds[0]
|
||||||
|
if guild.ID != 1 {
|
||||||
|
t.Errorf("Expected guild ID 1, got %d", guild.ID)
|
||||||
|
}
|
||||||
|
if guild.Name != "Test Guild" {
|
||||||
|
t.Errorf("Expected guild name 'Test Guild', got '%s'", guild.Name)
|
||||||
|
}
|
||||||
|
if guild.MOTD != "Welcome!" {
|
||||||
|
t.Errorf("Expected MOTD 'Welcome!', got '%s'", guild.MOTD)
|
||||||
|
}
|
||||||
|
if guild.Level != 5 {
|
||||||
|
t.Errorf("Expected level 5, got %d", guild.Level)
|
||||||
|
}
|
||||||
|
if guild.EXPCurrent != 1000 {
|
||||||
|
t.Errorf("Expected current exp 1000, got %d", guild.EXPCurrent)
|
||||||
|
}
|
||||||
|
if guild.EXPToNextLevel != 5000 {
|
||||||
|
t.Errorf("Expected next level exp 5000, got %d", guild.EXPToNextLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_LoadGuild tests loading a specific guild
|
||||||
|
func TestDatabaseGuildManager_LoadGuild(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert test data
|
||||||
|
insertGuildSQL := `INSERT INTO guilds (id, name, motd, level, xp, xp_needed, formed_on)
|
||||||
|
VALUES (123, 'Specific Guild', 'Test MOTD', 10, 2000, 8000, ?)`
|
||||||
|
formedTime := time.Now().Unix()
|
||||||
|
err := db.Exec(insertGuildSQL, formedTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test loading specific guild
|
||||||
|
guild, err := dgm.LoadGuild(ctx, 123)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.ID != 123 {
|
||||||
|
t.Errorf("Expected guild ID 123, got %d", guild.ID)
|
||||||
|
}
|
||||||
|
if guild.Name != "Specific Guild" {
|
||||||
|
t.Errorf("Expected guild name 'Specific Guild', got '%s'", guild.Name)
|
||||||
|
}
|
||||||
|
if guild.MOTD != "Test MOTD" {
|
||||||
|
t.Errorf("Expected MOTD 'Test MOTD', got '%s'", guild.MOTD)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test loading non-existent guild
|
||||||
|
_, err = dgm.LoadGuild(ctx, 999)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when loading non-existent guild")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_LoadGuildMembers tests loading guild members
|
||||||
|
func TestDatabaseGuildManager_LoadGuildMembers(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert test guild
|
||||||
|
err := db.Exec(`INSERT INTO guilds (id, name) VALUES (1, 'Test Guild')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert test members
|
||||||
|
joinTime := time.Now().Unix()
|
||||||
|
memberSQL := `INSERT INTO guild_members
|
||||||
|
(char_id, guild_id, account_id, name, rank, adventure_level, join_date, last_login_date)
|
||||||
|
VALUES (?, 1, 100, ?, ?, 50, ?, ?)`
|
||||||
|
|
||||||
|
err = db.Exec(memberSQL, 1, "Player1", RankLeader, joinTime, joinTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert member 1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Exec(memberSQL, 2, "Player2", RankMember, joinTime, joinTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert member 2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test loading members
|
||||||
|
members, err := dgm.LoadGuildMembers(ctx, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load guild members: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(members) != 2 {
|
||||||
|
t.Errorf("Expected 2 members, got %d", len(members))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify first member
|
||||||
|
member1 := members[0]
|
||||||
|
if member1.CharacterID != 1 {
|
||||||
|
t.Errorf("Expected character ID 1, got %d", member1.CharacterID)
|
||||||
|
}
|
||||||
|
if member1.Name != "Player1" {
|
||||||
|
t.Errorf("Expected name 'Player1', got '%s'", member1.Name)
|
||||||
|
}
|
||||||
|
if member1.Rank != RankLeader {
|
||||||
|
t.Errorf("Expected rank %d, got %d", RankLeader, member1.Rank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_SaveGuild tests saving guild data
|
||||||
|
func TestDatabaseGuildManager_SaveGuild(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Create test guild
|
||||||
|
guild := NewGuild()
|
||||||
|
guild.SetID(1)
|
||||||
|
guild.SetName("Saved Guild", false)
|
||||||
|
guild.SetLevel(15, false)
|
||||||
|
guild.SetMOTD("Saved MOTD", false)
|
||||||
|
guild.SetEXPCurrent(3000, false)
|
||||||
|
guild.SetEXPToNextLevel(9000, false)
|
||||||
|
guild.SetFormedDate(time.Now())
|
||||||
|
|
||||||
|
// Test saving guild
|
||||||
|
err := dgm.SaveGuild(ctx, guild)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the guild was saved
|
||||||
|
savedGuild, err := dgm.LoadGuild(ctx, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load saved guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if savedGuild.Name != "Saved Guild" {
|
||||||
|
t.Errorf("Expected saved name 'Saved Guild', got '%s'", savedGuild.Name)
|
||||||
|
}
|
||||||
|
if savedGuild.Level != 15 {
|
||||||
|
t.Errorf("Expected saved level 15, got %d", savedGuild.Level)
|
||||||
|
}
|
||||||
|
if savedGuild.MOTD != "Saved MOTD" {
|
||||||
|
t.Errorf("Expected saved MOTD 'Saved MOTD', got '%s'", savedGuild.MOTD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_CreateGuild tests creating a new guild
|
||||||
|
func TestDatabaseGuildManager_CreateGuild(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Create guild data
|
||||||
|
guildData := GuildData{
|
||||||
|
Name: "New Guild",
|
||||||
|
Level: 1,
|
||||||
|
FormedDate: time.Now(),
|
||||||
|
MOTD: "New guild MOTD",
|
||||||
|
EXPCurrent: 111,
|
||||||
|
EXPToNextLevel: 2521,
|
||||||
|
RecruitingShortDesc: "Short description",
|
||||||
|
RecruitingFullDesc: "Full description",
|
||||||
|
RecruitingMinLevel: 1,
|
||||||
|
RecruitingPlayStyle: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test creating guild
|
||||||
|
guildID, err := dgm.CreateGuild(ctx, guildData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if guildID <= 0 {
|
||||||
|
t.Errorf("Expected positive guild ID, got %d", guildID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the guild was created
|
||||||
|
createdGuild, err := dgm.LoadGuild(ctx, guildID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load created guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdGuild.Name != "New Guild" {
|
||||||
|
t.Errorf("Expected created name 'New Guild', got '%s'", createdGuild.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_DeleteGuild tests deleting a guild
|
||||||
|
func TestDatabaseGuildManager_DeleteGuild(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert test guild
|
||||||
|
err := db.Exec(`INSERT INTO guilds (id, name) VALUES (1, 'To Delete')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert test member
|
||||||
|
err = db.Exec(`INSERT INTO guild_members (char_id, guild_id, account_id, name) VALUES (1, 1, 100, 'Member')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test member: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify guild exists
|
||||||
|
_, err = dgm.LoadGuild(ctx, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Guild should exist before deletion: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test deleting guild
|
||||||
|
err = dgm.DeleteGuild(ctx, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to delete guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify guild was deleted
|
||||||
|
_, err = dgm.LoadGuild(ctx, 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Guild should not exist after deletion")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify members were deleted
|
||||||
|
members, err := dgm.LoadGuildMembers(ctx, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to check members after guild deletion: %v", err)
|
||||||
|
}
|
||||||
|
if len(members) != 0 {
|
||||||
|
t.Errorf("Expected 0 members after guild deletion, got %d", len(members))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_GetGuildIDByCharacterID tests getting guild ID by character ID
|
||||||
|
func TestDatabaseGuildManager_GetGuildIDByCharacterID(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert test guild
|
||||||
|
err := db.Exec(`INSERT INTO guilds (id, name) VALUES (1, 'Test Guild')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert test member
|
||||||
|
err = db.Exec(`INSERT INTO guild_members (char_id, guild_id, account_id, name) VALUES (123, 1, 100, 'TestPlayer')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test member: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting guild ID for character
|
||||||
|
guildID, err := dgm.GetGuildIDByCharacterID(ctx, 123)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get guild ID by character ID: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if guildID != 1 {
|
||||||
|
t.Errorf("Expected guild ID 1, got %d", guildID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting guild ID for non-existent character
|
||||||
|
_, err = dgm.GetGuildIDByCharacterID(ctx, 999)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error for non-existent character")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_ConcurrentOperations tests concurrent database operations
|
||||||
|
// Now enabled with sqlitex.Pool for proper connection management
|
||||||
|
func TestDatabaseGuildManager_ConcurrentOperations(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert initial guild
|
||||||
|
err := db.Exec(`INSERT INTO guilds (id, name) VALUES (1, 'Concurrent Test')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to insert test guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const numGoroutines = 5 // Reduce concurrency to avoid SQLite issues
|
||||||
|
done := make(chan error, numGoroutines)
|
||||||
|
|
||||||
|
// Test concurrent reads
|
||||||
|
for i := 0; i < numGoroutines; i++ {
|
||||||
|
go func(id int) {
|
||||||
|
// Add small delay to reduce contention
|
||||||
|
time.Sleep(time.Duration(id) * time.Millisecond)
|
||||||
|
_, err := dgm.LoadGuild(ctx, 1)
|
||||||
|
done <- err
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all reads to complete
|
||||||
|
for i := 0; i < numGoroutines; i++ {
|
||||||
|
if err := <-done; err != nil {
|
||||||
|
t.Errorf("Concurrent read failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent member additions
|
||||||
|
for i := 0; i < numGoroutines; i++ {
|
||||||
|
go func(id int) {
|
||||||
|
memberSQL := `INSERT INTO guild_members (char_id, guild_id, account_id, name) VALUES (?, 1, 100, ?)`
|
||||||
|
err := db.Exec(memberSQL, 100+id, fmt.Sprintf("Player%d", id))
|
||||||
|
done <- err
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all insertions
|
||||||
|
successCount := 0
|
||||||
|
for i := 0; i < numGoroutines; i++ {
|
||||||
|
if err := <-done; err == nil {
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if successCount != numGoroutines {
|
||||||
|
t.Logf("Only %d out of %d concurrent insertions succeeded (some conflicts expected)", successCount, numGoroutines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseGuildManager_TransactionRollback tests transaction rollback on errors
|
||||||
|
func TestDatabaseGuildManager_TransactionRollback(t *testing.T) {
|
||||||
|
db := createTestDB(t)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Try to create a guild with invalid data that should trigger a rollback
|
||||||
|
invalidGuildData := GuildData{
|
||||||
|
Name: "", // Empty name should cause validation error
|
||||||
|
Level: 1,
|
||||||
|
FormedDate: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should fail but we test that it handles errors gracefully
|
||||||
|
_, err := dgm.CreateGuild(ctx, invalidGuildData)
|
||||||
|
|
||||||
|
// We don't expect specific error behavior here since the implementation
|
||||||
|
// may or may not have validation, but we test that it doesn't crash
|
||||||
|
t.Logf("Create guild with invalid data returned: %v", err)
|
||||||
|
|
||||||
|
// Verify no partial data was left behind
|
||||||
|
guilds, err := dgm.LoadGuilds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load guilds after rollback test: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have no guilds (or any existing test data, but no new invalid guild)
|
||||||
|
t.Logf("Found %d guilds after invalid creation attempt", len(guilds))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkDatabaseGuildManager_LoadGuilds benchmarks loading guilds
|
||||||
|
func BenchmarkDatabaseGuildManager_LoadGuilds(b *testing.B) {
|
||||||
|
// Create test database in temp directory
|
||||||
|
tempDir := b.TempDir()
|
||||||
|
dbPath := filepath.Join(tempDir, "bench_guilds.db")
|
||||||
|
|
||||||
|
db, err := database.Open(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Failed to create benchmark database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if err := createGuildTables(db); err != nil {
|
||||||
|
b.Fatalf("Failed to create guild tables: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert test guilds
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
insertSQL := `INSERT INTO guilds (id, name, level, xp, xp_needed, formed_on) VALUES (?, ?, 1, 111, 2521, ?)`
|
||||||
|
formedTime := time.Now().Unix()
|
||||||
|
if err := db.Exec(insertSQL, i+1, fmt.Sprintf("Guild%d", i+1), formedTime); err != nil {
|
||||||
|
b.Fatalf("Failed to insert test guild %d: %v", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := dgm.LoadGuilds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Failed to load guilds in benchmark: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkDatabaseGuildManager_LoadGuildMembers benchmarks loading guild members
|
||||||
|
func BenchmarkDatabaseGuildManager_LoadGuildMembers(b *testing.B) {
|
||||||
|
// Create test database in temp directory
|
||||||
|
tempDir := b.TempDir()
|
||||||
|
dbPath := filepath.Join(tempDir, "bench_members.db")
|
||||||
|
|
||||||
|
db, err := database.Open(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Failed to create benchmark database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if err := createGuildTables(db); err != nil {
|
||||||
|
b.Fatalf("Failed to create guild tables: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dgm := NewDatabaseGuildManager(db)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Insert test guild
|
||||||
|
if err := db.Exec(`INSERT INTO guilds (id, name) VALUES (1, 'Benchmark Guild')`); err != nil {
|
||||||
|
b.Fatalf("Failed to insert benchmark guild: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert test members
|
||||||
|
joinTime := time.Now().Unix()
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
memberSQL := `INSERT INTO guild_members (char_id, guild_id, account_id, name, rank, join_date, last_login_date)
|
||||||
|
VALUES (?, 1, 100, ?, ?, ?, ?)`
|
||||||
|
if err := db.Exec(memberSQL, i+1, fmt.Sprintf("Player%d", i+1), RankMember, joinTime, joinTime); err != nil {
|
||||||
|
b.Fatalf("Failed to insert test member %d: %v", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := dgm.LoadGuildMembers(ctx, 1)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Failed to load guild members in benchmark: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
529
internal/guilds/guild_test.go
Normal file
529
internal/guilds/guild_test.go
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
package guilds
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNewGuild tests guild creation with default values
|
||||||
|
func TestNewGuild(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test initial state
|
||||||
|
if guild.GetLevel() != 1 {
|
||||||
|
t.Errorf("Expected initial level 1, got %d", guild.GetLevel())
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.GetEXPCurrent() != 111 {
|
||||||
|
t.Errorf("Expected initial expCurrent 111, got %d", guild.GetEXPCurrent())
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.GetEXPToNextLevel() != 2521 {
|
||||||
|
t.Errorf("Expected initial expToNextLevel 2521, got %d", guild.GetEXPToNextLevel())
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.GetRecruitingMinLevel() != 1 {
|
||||||
|
t.Errorf("Expected initial recruitingMinLevel 1, got %d", guild.GetRecruitingMinLevel())
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.GetRecruitingPlayStyle() != RecruitingPlayStyleNone {
|
||||||
|
t.Errorf("Expected initial recruitingPlayStyle %d, got %d", RecruitingPlayStyleNone, guild.GetRecruitingPlayStyle())
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.GetNextEventID() != 1 {
|
||||||
|
t.Errorf("Expected initial nextEventID 1, got %d", guild.GetNextEventID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test recruiting flags are initialized
|
||||||
|
if guild.GetRecruitingFlag(RecruitingFlagTraining) != 0 {
|
||||||
|
t.Error("Training recruiting flag should be initialized to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.GetRecruitingFlag(RecruitingFlagFighters) != 0 {
|
||||||
|
t.Error("Fighters recruiting flag should be initialized to 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test description tags are initialized
|
||||||
|
if guild.GetRecruitingDescTag(0) != RecruitingDescTagNone {
|
||||||
|
t.Error("Description tag 0 should be initialized to None")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test default rank names are set
|
||||||
|
if guild.GetRankName(RankLeader) != "Leader" {
|
||||||
|
t.Errorf("Expected leader rank name 'Leader', got '%s'", guild.GetRankName(RankLeader))
|
||||||
|
}
|
||||||
|
|
||||||
|
if guild.GetRankName(RankRecruit) != "Recruit" {
|
||||||
|
t.Errorf("Expected recruit rank name 'Recruit', got '%s'", guild.GetRankName(RankRecruit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildBasicOperations tests basic guild getter/setter operations
|
||||||
|
func TestGuildBasicOperations(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test ID operations
|
||||||
|
testID := int32(12345)
|
||||||
|
guild.SetID(testID)
|
||||||
|
if guild.GetID() != testID {
|
||||||
|
t.Errorf("Expected ID %d, got %d", testID, guild.GetID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test name operations
|
||||||
|
testName := "Test Guild"
|
||||||
|
guild.SetName(testName, false)
|
||||||
|
if guild.GetName() != testName {
|
||||||
|
t.Errorf("Expected name '%s', got '%s'", testName, guild.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test level operations
|
||||||
|
testLevel := int8(10)
|
||||||
|
guild.SetLevel(testLevel, false)
|
||||||
|
if guild.GetLevel() != testLevel {
|
||||||
|
t.Errorf("Expected level %d, got %d", testLevel, guild.GetLevel())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test MOTD operations
|
||||||
|
testMOTD := "Welcome to our guild!"
|
||||||
|
guild.SetMOTD(testMOTD, false)
|
||||||
|
if guild.GetMOTD() != testMOTD {
|
||||||
|
t.Errorf("Expected MOTD '%s', got '%s'", testMOTD, guild.GetMOTD())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test formed date operations
|
||||||
|
testDate := time.Now().Add(-24 * time.Hour)
|
||||||
|
guild.SetFormedDate(testDate)
|
||||||
|
if !guild.GetFormedDate().Equal(testDate) {
|
||||||
|
t.Errorf("Expected formed date %v, got %v", testDate, guild.GetFormedDate())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test experience operations
|
||||||
|
testExpCurrent := int64(5000)
|
||||||
|
testExpNext := int64(10000)
|
||||||
|
guild.SetEXPCurrent(testExpCurrent, false)
|
||||||
|
guild.SetEXPToNextLevel(testExpNext, false)
|
||||||
|
|
||||||
|
current := guild.GetEXPCurrent()
|
||||||
|
next := guild.GetEXPToNextLevel()
|
||||||
|
if current != testExpCurrent {
|
||||||
|
t.Errorf("Expected current exp %d, got %d", testExpCurrent, current)
|
||||||
|
}
|
||||||
|
if next != testExpNext {
|
||||||
|
t.Errorf("Expected next level exp %d, got %d", testExpNext, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildMemberOperations tests guild member management
|
||||||
|
func TestGuildMemberOperations(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
guild.SetID(1)
|
||||||
|
|
||||||
|
// Test adding members using the actual method
|
||||||
|
characterID1 := int32(1)
|
||||||
|
characterID2 := int32(2)
|
||||||
|
inviterName := "TestInviter"
|
||||||
|
joinDate := time.Now()
|
||||||
|
|
||||||
|
// Add first member
|
||||||
|
success := guild.AddNewGuildMember(characterID1, inviterName, joinDate, RankRecruit)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to add first member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add second member
|
||||||
|
success = guild.AddNewGuildMember(characterID2, inviterName, joinDate, RankMember)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to add second member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting member by ID
|
||||||
|
member1 := guild.GetGuildMember(characterID1)
|
||||||
|
if member1 == nil {
|
||||||
|
t.Error("Should be able to retrieve member by ID")
|
||||||
|
}
|
||||||
|
if member1.CharacterID != characterID1 {
|
||||||
|
t.Errorf("Expected member ID %d, got %d", characterID1, member1.CharacterID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting all members
|
||||||
|
allMembers := guild.GetAllMembers()
|
||||||
|
if len(allMembers) != 2 {
|
||||||
|
t.Errorf("Expected 2 members, got %d", len(allMembers))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing member
|
||||||
|
guild.RemoveGuildMember(characterID1, false)
|
||||||
|
member1After := guild.GetGuildMember(characterID1)
|
||||||
|
if member1After != nil {
|
||||||
|
t.Error("Member should be nil after removal")
|
||||||
|
}
|
||||||
|
|
||||||
|
allMembersAfter := guild.GetAllMembers()
|
||||||
|
if len(allMembersAfter) != 1 {
|
||||||
|
t.Errorf("Expected 1 member after removal, got %d", len(allMembersAfter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildEventOperations tests guild event management
|
||||||
|
func TestGuildEventOperations(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test adding events
|
||||||
|
eventType := int32(EventMemberJoins)
|
||||||
|
description := "Member joined guild"
|
||||||
|
eventDate := time.Now()
|
||||||
|
|
||||||
|
// Add first event (should get ID 1)
|
||||||
|
guild.AddNewGuildEvent(eventType, description, eventDate, false)
|
||||||
|
|
||||||
|
// Add another event (should get ID 2)
|
||||||
|
guild.AddNewGuildEvent(EventMemberLeaves, "Member left guild", eventDate, false)
|
||||||
|
|
||||||
|
// Test getting next event ID (should be 3 now)
|
||||||
|
nextID := guild.GetNextEventID()
|
||||||
|
if nextID != 3 {
|
||||||
|
t.Errorf("Expected next event ID 3, got %d", nextID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting specific event (first event should have ID 1)
|
||||||
|
event := guild.GetGuildEvent(1)
|
||||||
|
if event == nil {
|
||||||
|
t.Error("Should be able to retrieve event by ID")
|
||||||
|
}
|
||||||
|
if event != nil && event.Description != description {
|
||||||
|
t.Errorf("Expected event description '%s', got '%s'", description, event.Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildRankOperations tests guild rank management
|
||||||
|
func TestGuildRankOperations(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test setting custom rank name
|
||||||
|
customRankName := "Elite Member"
|
||||||
|
success := guild.SetRankName(RankMember, customRankName, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to set rank name")
|
||||||
|
}
|
||||||
|
|
||||||
|
rankName := guild.GetRankName(RankMember)
|
||||||
|
if rankName != customRankName {
|
||||||
|
t.Errorf("Expected rank name '%s', got '%s'", customRankName, rankName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test getting default rank names
|
||||||
|
leaderName := guild.GetRankName(RankLeader)
|
||||||
|
if leaderName != "Leader" {
|
||||||
|
t.Errorf("Expected leader rank name 'Leader', got '%s'", leaderName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildRecruitingOperations tests guild recruiting settings
|
||||||
|
func TestGuildRecruitingOperations(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test recruiting descriptions
|
||||||
|
shortDesc := "Looking for members"
|
||||||
|
fullDesc := "We are a friendly guild looking for active members"
|
||||||
|
guild.SetRecruitingShortDesc(shortDesc, false)
|
||||||
|
guild.SetRecruitingFullDesc(fullDesc, false)
|
||||||
|
|
||||||
|
short := guild.GetRecruitingShortDesc()
|
||||||
|
full := guild.GetRecruitingFullDesc()
|
||||||
|
if short != shortDesc {
|
||||||
|
t.Errorf("Expected short description '%s', got '%s'", shortDesc, short)
|
||||||
|
}
|
||||||
|
if full != fullDesc {
|
||||||
|
t.Errorf("Expected full description '%s', got '%s'", fullDesc, full)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test recruiting settings
|
||||||
|
minLevel := int8(20)
|
||||||
|
playStyle := int8(2)
|
||||||
|
guild.SetRecruitingMinLevel(minLevel, false)
|
||||||
|
guild.SetRecruitingPlayStyle(playStyle, false)
|
||||||
|
|
||||||
|
getMinLevel := guild.GetRecruitingMinLevel()
|
||||||
|
getPlayStyle := guild.GetRecruitingPlayStyle()
|
||||||
|
if getMinLevel != minLevel {
|
||||||
|
t.Errorf("Expected min level %d, got %d", minLevel, getMinLevel)
|
||||||
|
}
|
||||||
|
if getPlayStyle != playStyle {
|
||||||
|
t.Errorf("Expected play style %d, got %d", playStyle, getPlayStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test recruiting flags
|
||||||
|
success := guild.SetRecruitingFlag(RecruitingFlagFighters, 1, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to set recruiting flag")
|
||||||
|
}
|
||||||
|
flag := guild.GetRecruitingFlag(RecruitingFlagFighters)
|
||||||
|
if flag != 1 {
|
||||||
|
t.Errorf("Expected recruiting flag 1, got %d", flag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test recruiting description tags
|
||||||
|
success = guild.SetRecruitingDescTag(0, RecruitingDescTagRoleplay, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to set recruiting desc tag")
|
||||||
|
}
|
||||||
|
tag := guild.GetRecruitingDescTag(0)
|
||||||
|
if tag != RecruitingDescTagRoleplay {
|
||||||
|
t.Errorf("Expected description tag %d, got %d", RecruitingDescTagRoleplay, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildPermissions tests guild permission system
|
||||||
|
func TestGuildPermissions(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test setting permissions
|
||||||
|
rank := int8(RankMember)
|
||||||
|
permission := int8(PermissionInvite)
|
||||||
|
value := int8(1)
|
||||||
|
|
||||||
|
success := guild.SetPermission(rank, permission, value, false, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to set permission")
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue := guild.GetPermission(rank, permission)
|
||||||
|
if getValue != value {
|
||||||
|
t.Errorf("Expected permission value %d, got %d", value, getValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing permission
|
||||||
|
success = guild.SetPermission(rank, permission, 0, false, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to remove permission")
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue = guild.GetPermission(rank, permission)
|
||||||
|
if getValue != 0 {
|
||||||
|
t.Errorf("Expected permission value 0 after removal, got %d", getValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildSaveFlags tests the save flag system
|
||||||
|
func TestGuildSaveFlags(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test initial state
|
||||||
|
if guild.GetSaveNeeded() {
|
||||||
|
t.Error("Guild should not need save initially")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test marking save needed
|
||||||
|
guild.SetSaveNeeded(true)
|
||||||
|
if !guild.GetSaveNeeded() {
|
||||||
|
t.Error("Guild should need save after marking")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clearing save needed
|
||||||
|
guild.SetSaveNeeded(false)
|
||||||
|
if guild.GetSaveNeeded() {
|
||||||
|
t.Error("Guild should not need save after clearing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildMemberPromotionDemotion tests member rank changes
|
||||||
|
func TestGuildMemberPromotionDemotion(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
guild.SetID(1)
|
||||||
|
|
||||||
|
// Add a member
|
||||||
|
characterID := int32(1)
|
||||||
|
inviterName := "TestInviter"
|
||||||
|
joinDate := time.Now()
|
||||||
|
|
||||||
|
success := guild.AddNewGuildMember(characterID, inviterName, joinDate, RankRecruit)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to add member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get member and check initial rank
|
||||||
|
member := guild.GetGuildMember(characterID)
|
||||||
|
if member == nil {
|
||||||
|
t.Fatal("Member should exist")
|
||||||
|
}
|
||||||
|
if member.Rank != RankRecruit {
|
||||||
|
t.Errorf("Expected initial rank %d, got %d", RankRecruit, member.Rank)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test promotion
|
||||||
|
promoterName := "TestPromoter"
|
||||||
|
success = guild.PromoteGuildMember(characterID, promoterName, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to promote member")
|
||||||
|
}
|
||||||
|
|
||||||
|
member = guild.GetGuildMember(characterID)
|
||||||
|
if member.Rank != RankInitiate {
|
||||||
|
t.Errorf("Expected rank after promotion %d, got %d", RankInitiate, member.Rank)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test demotion
|
||||||
|
demoterName := "TestDemoter"
|
||||||
|
success = guild.DemoteGuildMember(characterID, demoterName, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to demote member")
|
||||||
|
}
|
||||||
|
|
||||||
|
member = guild.GetGuildMember(characterID)
|
||||||
|
if member.Rank != RankRecruit {
|
||||||
|
t.Errorf("Expected rank after demotion %d, got %d", RankRecruit, member.Rank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildPointsSystem tests the guild points system
|
||||||
|
func TestGuildPointsSystem(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
guild.SetID(1)
|
||||||
|
|
||||||
|
// Add a member
|
||||||
|
characterID := int32(1)
|
||||||
|
inviterName := "TestInviter"
|
||||||
|
joinDate := time.Now()
|
||||||
|
|
||||||
|
success := guild.AddNewGuildMember(characterID, inviterName, joinDate, RankRecruit)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to add member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get initial points
|
||||||
|
member := guild.GetGuildMember(characterID)
|
||||||
|
if member == nil {
|
||||||
|
t.Fatal("Member should exist")
|
||||||
|
}
|
||||||
|
initialPoints := member.Points
|
||||||
|
|
||||||
|
// Add points
|
||||||
|
pointsToAdd := 100.0
|
||||||
|
modifiedBy := "TestAdmin"
|
||||||
|
comment := "Test point award"
|
||||||
|
|
||||||
|
success = guild.AddPointsToGuildMember(characterID, pointsToAdd, modifiedBy, comment, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to add points to member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check points were added
|
||||||
|
member = guild.GetGuildMember(characterID)
|
||||||
|
expectedPoints := initialPoints + pointsToAdd
|
||||||
|
if member.Points != expectedPoints {
|
||||||
|
t.Errorf("Expected points %f, got %f", expectedPoints, member.Points)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildConcurrency tests thread safety of guild operations
|
||||||
|
func TestGuildConcurrency(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
guild.SetID(1)
|
||||||
|
guild.SetName("Concurrent Test Guild", false)
|
||||||
|
|
||||||
|
const numGoroutines = 20
|
||||||
|
done := make(chan bool, numGoroutines)
|
||||||
|
|
||||||
|
// Test concurrent reads
|
||||||
|
for i := 0; i < numGoroutines; i++ {
|
||||||
|
go func(id int) {
|
||||||
|
_ = guild.GetID()
|
||||||
|
_ = guild.GetName()
|
||||||
|
_ = guild.GetLevel()
|
||||||
|
_ = guild.GetMOTD()
|
||||||
|
_ = guild.GetEXPCurrent()
|
||||||
|
_ = guild.GetRecruitingShortDesc()
|
||||||
|
_ = guild.GetRankName(RankMember)
|
||||||
|
_ = guild.GetPermission(RankMember, PermissionInvite)
|
||||||
|
done <- true
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all read operations
|
||||||
|
for i := 0; i < numGoroutines; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent member additions (smaller number to avoid conflicts)
|
||||||
|
const memberGoroutines = 10
|
||||||
|
for i := 0; i < memberGoroutines; i++ {
|
||||||
|
go func(id int) {
|
||||||
|
inviterName := fmt.Sprintf("Inviter%d", id)
|
||||||
|
joinDate := time.Now()
|
||||||
|
characterID := int32(100 + id)
|
||||||
|
|
||||||
|
guild.AddNewGuildMember(characterID, inviterName, joinDate, RankRecruit)
|
||||||
|
done <- true
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all member additions
|
||||||
|
for i := 0; i < memberGoroutines; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify members were added
|
||||||
|
members := guild.GetAllMembers()
|
||||||
|
if len(members) != memberGoroutines {
|
||||||
|
t.Logf("Expected %d members, got %d (some concurrent additions may have failed, which is acceptable)", memberGoroutines, len(members))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildEventFilters tests guild event filter system
|
||||||
|
func TestGuildEventFilters(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
|
||||||
|
// Test setting event filters
|
||||||
|
eventID := int8(EventMemberJoins)
|
||||||
|
category := int8(EventFilterCategoryBroadcast)
|
||||||
|
value := int8(1)
|
||||||
|
|
||||||
|
success := guild.SetEventFilter(eventID, category, value, false, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to set event filter")
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue := guild.GetEventFilter(eventID, category)
|
||||||
|
if getValue != value {
|
||||||
|
t.Errorf("Expected event filter value %d, got %d", value, getValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing event filter
|
||||||
|
success = guild.SetEventFilter(eventID, category, 0, false, false)
|
||||||
|
if !success {
|
||||||
|
t.Error("Should be able to remove event filter")
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue = guild.GetEventFilter(eventID, category)
|
||||||
|
if getValue != 0 {
|
||||||
|
t.Errorf("Expected event filter value 0 after removal, got %d", getValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGuildInfo tests the guild info structure
|
||||||
|
func TestGuildInfo(t *testing.T) {
|
||||||
|
guild := NewGuild()
|
||||||
|
guild.SetID(123)
|
||||||
|
guild.SetName("Test Guild Info", false)
|
||||||
|
guild.SetLevel(25, false)
|
||||||
|
guild.SetMOTD("Test MOTD", false)
|
||||||
|
|
||||||
|
info := guild.GetGuildInfo()
|
||||||
|
|
||||||
|
if info.ID != 123 {
|
||||||
|
t.Errorf("Expected guild info ID 123, got %d", info.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Name != "Test Guild Info" {
|
||||||
|
t.Errorf("Expected guild info name 'Test Guild Info', got '%s'", info.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Level != 25 {
|
||||||
|
t.Errorf("Expected guild info level 25, got %d", info.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.MOTD != "Test MOTD" {
|
||||||
|
t.Errorf("Expected guild info MOTD 'Test MOTD', got '%s'", info.MOTD)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user