eq2go/internal/guilds/database.go

741 lines
24 KiB
Go

package guilds
import (
"context"
"fmt"
"time"
"eq2emu/internal/database"
)
// DatabaseGuildManager implements GuildDatabase interface using the existing database wrapper
type DatabaseGuildManager struct {
db database.DBInterface
}
// NewDatabaseGuildManager creates a new database guild manager
func NewDatabaseGuildManager(db database.DBInterface) *DatabaseGuildManager {
return &DatabaseGuildManager{
db: db,
}
}
// LoadGuilds retrieves all guilds from database
func (dgm *DatabaseGuildManager) LoadGuilds(ctx context.Context) ([]GuildData, error) {
query := "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds`"
var guilds []GuildData
err := dgm.db.Query(query, func(row *database.Row) error {
var guild GuildData
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)
guilds = append(guilds, guild)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to query guilds: %w", err)
}
return guilds, nil
}
// LoadGuild retrieves a specific guild from database
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` = ?"
row, err := dgm.db.QueryRow(query, guildID)
if err != nil {
return nil, fmt.Errorf("failed to load guild %d: %w", guildID, err)
}
if row == nil {
return nil, fmt.Errorf("guild %d not found", guildID)
}
defer row.Close()
var guild GuildData
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
}
// LoadGuildMembers retrieves all members for a guild
func (dgm *DatabaseGuildManager) LoadGuildMembers(ctx context.Context, guildID int32) ([]GuildMemberData, error) {
query := `SELECT char_id, guild_id, account_id, recruiter_id, name, guild_status, points,
adventure_class, adventure_level, tradeskill_class, tradeskill_level, rank,
member_flags, zone, join_date, last_login_date, note, officer_note,
recruiter_description, recruiter_picture_data, recruiting_show_adventure_class
FROM guild_members WHERE guild_id = ?`
// Use the main implementation with zero-copy optimization
var members []GuildMemberData
err := dgm.db.Query(query, func(row *database.Row) error {
var member GuildMemberData
member.CharacterID = int32(row.Int(0))
member.GuildID = int32(row.Int(1))
member.AccountID = int32(row.Int(2))
member.RecruiterID = int32(row.Int(3))
member.Name = row.Text(4)
member.GuildStatus = int32(row.Int(5))
member.Points = row.Float(6)
member.AdventureClass = int8(row.Int(7))
member.AdventureLevel = int8(row.Int(8))
member.TradeskillClass = int8(row.Int(9))
member.TradeskillLevel = int8(row.Int(10))
member.Rank = int8(row.Int(11))
member.MemberFlags = int8(row.Int(12))
member.Zone = row.Text(13)
member.JoinDate = time.Unix(row.Int64(14), 0)
member.LastLoginDate = time.Unix(row.Int64(15), 0)
if !row.IsNull(16) {
member.Note = row.Text(16)
}
if !row.IsNull(17) {
member.OfficerNote = row.Text(17)
}
if !row.IsNull(18) {
member.RecruiterDescription = row.Text(18)
}
// TODO: Handle blob data for recruiter_picture_data (column 19)
member.RecruitingShowAdventureClass = int8(row.Int(20))
members = append(members, member)
return nil
}, guildID)
if err != nil {
return nil, fmt.Errorf("failed to query guild members for guild %d: %w", guildID, err)
}
return members, nil
}
// LoadGuildEvents retrieves events for a guild
func (dgm *DatabaseGuildManager) LoadGuildEvents(ctx context.Context, guildID int32) ([]GuildEventData, error) {
query := `SELECT event_id, guild_id, date, type, description, locked
FROM guild_events WHERE guild_id = ?
ORDER BY event_id DESC LIMIT ?`
var events []GuildEventData
err := dgm.db.Query(query, func(row *database.Row) error {
var event GuildEventData
event.EventID = row.Int64(0)
event.GuildID = int32(row.Int(1))
event.Date = time.Unix(row.Int64(2), 0)
event.Type = int32(row.Int(3))
event.Description = row.Text(4)
event.Locked = int8(row.Int(5))
events = append(events, event)
return nil
}, guildID, MaxEvents)
if err != nil {
return nil, fmt.Errorf("failed to query guild events for guild %d: %w", guildID, err)
}
return events, nil
}
// LoadGuildRanks retrieves custom rank names for a guild
func (dgm *DatabaseGuildManager) LoadGuildRanks(ctx context.Context, guildID int32) ([]GuildRankData, error) {
query := "SELECT guild_id, rank, name FROM guild_ranks WHERE guild_id = ?"
var ranks []GuildRankData
err := dgm.db.Query(query, func(row *database.Row) error {
var rank GuildRankData
rank.GuildID = int32(row.Int(0))
rank.Rank = int8(row.Int(1))
rank.Name = row.Text(2)
ranks = append(ranks, rank)
return nil
}, guildID)
if err != nil {
return nil, fmt.Errorf("failed to query guild ranks for guild %d: %w", guildID, err)
}
return ranks, nil
}
// LoadGuildPermissions retrieves permissions for a guild
func (dgm *DatabaseGuildManager) LoadGuildPermissions(ctx context.Context, guildID int32) ([]GuildPermissionData, error) {
query := "SELECT guild_id, rank, permission, value FROM guild_permissions WHERE guild_id = ?"
var permissions []GuildPermissionData
err := dgm.db.Query(query, func(row *database.Row) error {
var permission GuildPermissionData
permission.GuildID = int32(row.Int(0))
permission.Rank = int8(row.Int(1))
permission.Permission = int8(row.Int(2))
permission.Value = int8(row.Int(3))
permissions = append(permissions, permission)
return nil
}, guildID)
if err != nil {
return nil, fmt.Errorf("failed to query guild permissions for guild %d: %w", guildID, err)
}
return permissions, nil
}
// LoadGuildEventFilters retrieves event filters for a guild
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 = ?"
var filters []GuildEventFilterData
err := dgm.db.Query(query, func(row *database.Row) error {
var filter GuildEventFilterData
filter.GuildID = int32(row.Int(0))
filter.EventID = int8(row.Int(1))
filter.Category = int8(row.Int(2))
filter.Value = int8(row.Int(3))
filters = append(filters, filter)
return nil
}, guildID)
if err != nil {
return nil, fmt.Errorf("failed to query guild event filters for guild %d: %w", guildID, err)
}
return filters, nil
}
// LoadGuildRecruiting retrieves recruiting settings for a guild
func (dgm *DatabaseGuildManager) LoadGuildRecruiting(ctx context.Context, guildID int32) ([]GuildRecruitingData, error) {
query := "SELECT guild_id, flag, value FROM guild_recruiting WHERE guild_id = ?"
var recruiting []GuildRecruitingData
err := dgm.db.Query(query, func(row *database.Row) error {
var recruit GuildRecruitingData
recruit.GuildID = int32(row.Int(0))
recruit.Flag = int8(row.Int(1))
recruit.Value = int8(row.Int(2))
recruiting = append(recruiting, recruit)
return nil
}, guildID)
if err != nil {
return nil, fmt.Errorf("failed to query guild recruiting for guild %d: %w", guildID, err)
}
return recruiting, nil
}
// LoadPointHistory retrieves point history for a member
func (dgm *DatabaseGuildManager) LoadPointHistory(ctx context.Context, characterID int32) ([]PointHistoryData, error) {
query := `SELECT char_id, date, modified_by, comment, points
FROM guild_point_history WHERE char_id = ?
ORDER BY date DESC LIMIT ?`
var history []PointHistoryData
err := dgm.db.Query(query, func(row *database.Row) error {
var entry PointHistoryData
entry.CharacterID = int32(row.Int(0))
entry.Date = time.Unix(row.Int64(1), 0)
entry.ModifiedBy = row.Text(2)
entry.Comment = row.Text(3)
entry.Points = row.Float(4)
history = append(history, entry)
return nil
}, characterID, MaxPointHistory)
if err != nil {
return nil, fmt.Errorf("failed to query point history for character %d: %w", characterID, err)
}
return history, nil
}
// SaveGuild saves guild basic data
func (dgm *DatabaseGuildManager) SaveGuild(ctx context.Context, guild *Guild) error {
query := `INSERT OR REPLACE INTO guilds
(id, name, motd, level, xp, xp_needed, formed_on)
VALUES (?, ?, ?, ?, ?, ?, ?)`
formedTimestamp := guild.GetFormedDate().Unix()
err := dgm.db.Exec(query,
guild.GetID(),
guild.GetName(),
guild.GetMOTD(),
guild.GetLevel(),
guild.GetEXPCurrent(),
guild.GetEXPToNextLevel(),
formedTimestamp,
)
if err != nil {
return fmt.Errorf("failed to save guild %d: %w", guild.GetID(), err)
}
return nil
}
// SaveGuildMembers saves all guild members
func (dgm *DatabaseGuildManager) SaveGuildMembers(ctx context.Context, guildID int32, members []*GuildMember) error {
if len(members) == 0 {
return nil
}
// Use a transaction for atomic updates
return dgm.db.Transaction(func(db database.DBInterface) error {
// Delete existing members for this guild
if err := db.Exec("DELETE FROM guild_members WHERE guild_id = ?", guildID); err != nil {
return fmt.Errorf("failed to delete existing guild members: %w", err)
}
// Insert all members
insertQuery := `INSERT INTO guild_members
(char_id, guild_id, account_id, recruiter_id, name, guild_status, points,
adventure_class, adventure_level, tradeskill_class, tradeskill_level, rank,
member_flags, zone, join_date, last_login_date, note, officer_note,
recruiter_description, recruiter_picture_data, recruiting_show_adventure_class)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
for _, member := range members {
joinTimestamp := member.GetJoinDate().Unix()
lastLoginTimestamp := member.GetLastLoginDate().Unix()
if err := db.Exec(insertQuery,
member.GetCharacterID(),
guildID,
member.AccountID,
member.GetRecruiterID(),
member.GetName(),
member.GuildStatus,
member.GetPoints(),
member.GetAdventureClass(),
member.GetAdventureLevel(),
member.GetTradeskillClass(),
member.GetTradeskillLevel(),
member.GetRank(),
member.GetMemberFlags(),
member.GetZone(),
joinTimestamp,
lastLoginTimestamp,
member.GetNote(),
member.GetOfficerNote(),
member.GetRecruiterDescription(),
member.GetRecruiterPictureData(),
member.RecruitingShowAdventureClass,
); err != nil {
return fmt.Errorf("failed to insert guild member %d: %w", member.GetCharacterID(), err)
}
}
return nil
})
}
// SaveGuildEvents saves guild events
func (dgm *DatabaseGuildManager) SaveGuildEvents(ctx context.Context, guildID int32, events []GuildEvent) error {
if len(events) == 0 {
return nil
}
query := `INSERT OR REPLACE INTO guild_events
(event_id, guild_id, date, type, description, locked)
VALUES (?, ?, ?, ?, ?, ?)`
for _, event := range events {
if !event.SaveNeeded {
continue
}
dateTimestamp := event.Date.Unix()
if err := dgm.db.Exec(query,
event.EventID,
guildID,
dateTimestamp,
event.Type,
event.Description,
event.Locked,
); err != nil {
return fmt.Errorf("failed to save guild event %d: %w", event.EventID, err)
}
}
return nil
}
// SaveGuildRanks saves guild rank names
func (dgm *DatabaseGuildManager) SaveGuildRanks(ctx context.Context, guildID int32, ranks map[int8]string) error {
// Use a transaction for atomic updates
return dgm.db.Transaction(func(db database.DBInterface) error {
// Delete existing ranks for this guild
if err := db.Exec("DELETE FROM guild_ranks WHERE guild_id = ?", guildID); err != nil {
return fmt.Errorf("failed to delete existing guild ranks: %w", err)
}
// Insert all ranks
insertQuery := "INSERT INTO guild_ranks (guild_id, rank, name) VALUES (?, ?, ?)"
for rank, name := range ranks {
// Only save non-default rank names
if defaultName, exists := DefaultRankNames[rank]; !exists || name != defaultName {
if err := db.Exec(insertQuery, guildID, rank, name); err != nil {
return fmt.Errorf("failed to insert guild rank %d: %w", rank, err)
}
}
}
return nil
})
}
// SaveGuildPermissions saves guild permissions
func (dgm *DatabaseGuildManager) SaveGuildPermissions(ctx context.Context, guildID int32, permissions map[int8]map[int8]int8) error {
// Use a transaction for atomic updates
return dgm.db.Transaction(func(db database.DBInterface) error {
// Delete existing permissions for this guild
if err := db.Exec("DELETE FROM guild_permissions WHERE guild_id = ?", guildID); err != nil {
return fmt.Errorf("failed to delete existing guild permissions: %w", err)
}
// Insert all permissions
insertQuery := "INSERT INTO guild_permissions (guild_id, rank, permission, value) VALUES (?, ?, ?, ?)"
for rank, rankPermissions := range permissions {
for permission, value := range rankPermissions {
if err := db.Exec(insertQuery, guildID, rank, permission, value); err != nil {
return fmt.Errorf("failed to insert guild permission %d/%d: %w", rank, permission, err)
}
}
}
return nil
})
}
// SaveGuildEventFilters saves guild event filters
func (dgm *DatabaseGuildManager) SaveGuildEventFilters(ctx context.Context, guildID int32, filters map[int8]map[int8]int8) error {
// Use a transaction for atomic updates
return dgm.db.Transaction(func(db database.DBInterface) error {
// Delete existing filters for this guild
if err := db.Exec("DELETE FROM guild_event_filters WHERE guild_id = ?", guildID); err != nil {
return fmt.Errorf("failed to delete existing guild event filters: %w", err)
}
// Insert all filters
insertQuery := "INSERT INTO guild_event_filters (guild_id, event_id, category, value) VALUES (?, ?, ?, ?)"
for eventID, eventFilters := range filters {
for category, value := range eventFilters {
if err := db.Exec(insertQuery, guildID, eventID, category, value); err != nil {
return fmt.Errorf("failed to insert guild event filter %d/%d: %w", eventID, category, err)
}
}
}
return nil
})
}
// SaveGuildRecruiting saves guild recruiting settings
func (dgm *DatabaseGuildManager) SaveGuildRecruiting(ctx context.Context, guildID int32, flags, descTags map[int8]int8) error {
// Use a transaction for atomic updates
return dgm.db.Transaction(func(db database.DBInterface) error {
// Delete existing recruiting settings for this guild
if err := db.Exec("DELETE FROM guild_recruiting WHERE guild_id = ?", guildID); err != nil {
return fmt.Errorf("failed to delete existing guild recruiting: %w", err)
}
// Insert recruiting flags
insertQuery := "INSERT INTO guild_recruiting (guild_id, flag, value) VALUES (?, ?, ?)"
for flag, value := range flags {
if err := db.Exec(insertQuery, guildID, flag, value); err != nil {
return fmt.Errorf("failed to insert guild recruiting flag %d: %w", flag, err)
}
}
// Insert description tags (with negative flag values to distinguish)
for tag, value := range descTags {
if err := db.Exec(insertQuery, guildID, -tag-1, value); err != nil { // Negative to distinguish from flags
return fmt.Errorf("failed to insert guild recruiting desc tag %d: %w", tag, err)
}
}
return nil
})
}
// SavePointHistory saves point history for a member
func (dgm *DatabaseGuildManager) SavePointHistory(ctx context.Context, characterID int32, history []PointHistory) error {
if len(history) == 0 {
return nil
}
// Use a transaction for atomic updates
return dgm.db.Transaction(func(db database.DBInterface) error {
// Delete existing history for this character
if err := db.Exec("DELETE FROM guild_point_history WHERE char_id = ?", characterID); err != nil {
return fmt.Errorf("failed to delete existing point history: %w", err)
}
// Insert all history entries
insertQuery := "INSERT INTO guild_point_history (char_id, date, modified_by, comment, points) VALUES (?, ?, ?, ?, ?)"
for _, entry := range history {
dateTimestamp := entry.Date.Unix()
if err := db.Exec(insertQuery,
characterID,
dateTimestamp,
entry.ModifiedBy,
entry.Comment,
entry.Points,
); err != nil {
return fmt.Errorf("failed to insert point history entry: %w", err)
}
}
return nil
})
}
// GetGuildIDByCharacterID returns guild ID for a character
func (dgm *DatabaseGuildManager) GetGuildIDByCharacterID(ctx context.Context, characterID int32) (int32, error) {
query := "SELECT guild_id FROM guild_members WHERE char_id = ?"
row, err := dgm.db.QueryRow(query, characterID)
if err != nil {
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
}
// CreateGuild creates a new guild
func (dgm *DatabaseGuildManager) CreateGuild(ctx context.Context, guildData GuildData) (int32, error) {
query := `INSERT INTO guilds (name, motd, level, xp, xp_needed, formed_on)
VALUES (?, ?, ?, ?, ?, ?)`
formedTimestamp := guildData.FormedDate.Unix()
id, err := dgm.db.ExecAndGetLastInsertID(query,
guildData.Name,
guildData.MOTD,
guildData.Level,
guildData.EXPCurrent,
guildData.EXPToNextLevel,
formedTimestamp,
)
if err != nil {
return 0, fmt.Errorf("failed to create guild: %w", err)
}
return int32(id), nil
}
// DeleteGuild removes a guild and all related data
func (dgm *DatabaseGuildManager) DeleteGuild(ctx context.Context, guildID int32) error {
// Use a transaction for atomic deletion
return dgm.db.Transaction(func(db database.DBInterface) error {
// Delete related data first (foreign key constraints)
tables := []string{
"guild_point_history",
"guild_members",
"guild_events",
"guild_ranks",
"guild_permissions",
"guild_event_filters",
"guild_recruiting",
}
for _, table := range tables {
var query string
if table == "guild_point_history" {
// Special case: delete point history for all members of this guild
query = "DELETE FROM guild_point_history WHERE character_id IN (SELECT char_id FROM guild_members WHERE guild_id = ?)"
} else {
query = fmt.Sprintf("DELETE FROM %s WHERE guild_id = ?", table)
}
if err := db.Exec(query, guildID); err != nil {
return fmt.Errorf("failed to delete from %s: %w", table, err)
}
}
// Finally delete the guild itself
if err := db.Exec("DELETE FROM guilds WHERE id = ?", guildID); err != nil {
return fmt.Errorf("failed to delete guild: %w", err)
}
return nil
})
}
// GetNextGuildID returns the next available guild ID
func (dgm *DatabaseGuildManager) GetNextGuildID(ctx context.Context) (int32, error) {
query := "SELECT COALESCE(MAX(id), 0) + 1 FROM guilds"
row, err := dgm.db.QueryRow(query)
if err != nil {
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
}
// GetNextEventID returns the next available event ID for a guild
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 = ?"
row, err := dgm.db.QueryRow(query, guildID)
if err != nil {
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
}
// EnsureGuildTables creates the guild tables if they don't exist
func (dgm *DatabaseGuildManager) EnsureGuildTables(ctx context.Context) error {
queries := []string{
`CREATE TABLE IF NOT EXISTS guilds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
motd TEXT,
level INTEGER NOT NULL DEFAULT 1,
xp INTEGER NOT NULL DEFAULT 111,
xp_needed INTEGER NOT NULL DEFAULT 2521,
formed_on INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS guild_members (
char_id INTEGER NOT NULL,
guild_id INTEGER NOT NULL,
account_id INTEGER NOT NULL DEFAULT 0,
recruiter_id INTEGER NOT NULL DEFAULT 0,
name TEXT NOT NULL,
guild_status INTEGER NOT NULL DEFAULT 0,
points REAL NOT NULL DEFAULT 0.0,
adventure_class INTEGER NOT NULL DEFAULT 0,
adventure_level INTEGER NOT NULL DEFAULT 1,
tradeskill_class INTEGER NOT NULL DEFAULT 0,
tradeskill_level INTEGER NOT NULL DEFAULT 1,
rank INTEGER NOT NULL DEFAULT 7,
member_flags INTEGER NOT NULL DEFAULT 0,
zone TEXT NOT NULL DEFAULT '',
join_date INTEGER NOT NULL,
last_login_date INTEGER NOT NULL,
note TEXT NOT NULL DEFAULT '',
officer_note TEXT NOT NULL DEFAULT '',
recruiter_description TEXT NOT NULL DEFAULT '',
recruiter_picture_data BLOB,
recruiting_show_adventure_class INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (char_id),
FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE
)`,
`CREATE TABLE IF NOT EXISTS guild_events (
event_id INTEGER NOT NULL,
guild_id INTEGER NOT NULL,
date INTEGER NOT NULL,
type INTEGER NOT NULL,
description TEXT NOT NULL,
locked INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (event_id, guild_id),
FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE
)`,
`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) ON DELETE CASCADE
)`,
`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) ON DELETE CASCADE
)`,
`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) ON DELETE CASCADE
)`,
`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) ON DELETE CASCADE
)`,
`CREATE TABLE IF NOT EXISTS guild_point_history (
char_id INTEGER NOT NULL,
date INTEGER NOT NULL,
modified_by TEXT NOT NULL,
comment TEXT NOT NULL,
points REAL NOT NULL,
PRIMARY KEY (char_id, date),
FOREIGN KEY (char_id) REFERENCES guild_members(char_id) ON DELETE CASCADE
)`,
}
for i, query := range queries {
if err := dgm.db.Exec(query); err != nil {
return fmt.Errorf("failed to create guild table %d: %w", i+1, err)
}
}
// Create indexes for better performance
indexes := []string{
`CREATE INDEX IF NOT EXISTS idx_guild_members_guild_id ON guild_members(guild_id)`,
`CREATE INDEX IF NOT EXISTS idx_guild_members_char_id ON guild_members(char_id)`,
`CREATE INDEX IF NOT EXISTS idx_guild_events_guild_id ON guild_events(guild_id)`,
`CREATE INDEX IF NOT EXISTS idx_guild_events_date ON guild_events(date)`,
`CREATE INDEX IF NOT EXISTS idx_guild_point_history_char_id ON guild_point_history(char_id)`,
`CREATE INDEX IF NOT EXISTS idx_guilds_name ON guilds(name)`,
`CREATE INDEX IF NOT EXISTS idx_guild_members_rank ON guild_members(rank)`,
`CREATE INDEX IF NOT EXISTS idx_guild_members_last_login ON guild_members(last_login_date)`,
}
for i, query := range indexes {
if err := dgm.db.Exec(query); err != nil {
return fmt.Errorf("failed to create guild index %d: %w", i+1, err)
}
}
return nil
}