936 lines
27 KiB
Go
936 lines
27 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.DB
|
|
}
|
|
|
|
// NewDatabaseGuildManager creates a new database guild manager
|
|
func NewDatabaseGuildManager(db *database.DB) *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`"
|
|
|
|
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
|
|
for rows.Next() {
|
|
var guild GuildData
|
|
var motd *string
|
|
var formedOnTimestamp int64
|
|
|
|
err := rows.Scan(
|
|
&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)
|
|
}
|
|
|
|
// Handle nullable MOTD field
|
|
if motd != nil {
|
|
guild.MOTD = *motd
|
|
}
|
|
|
|
// Convert timestamp to time
|
|
guild.FormedDate = time.Unix(formedOnTimestamp, 0)
|
|
|
|
guilds = append(guilds, guild)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating guild rows: %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` = ?"
|
|
|
|
var guild GuildData
|
|
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 {
|
|
return nil, fmt.Errorf("failed to load guild %d: %w", guildID, err)
|
|
}
|
|
|
|
// Handle nullable MOTD field
|
|
if motd != nil {
|
|
guild.MOTD = *motd
|
|
}
|
|
|
|
// Convert timestamp to time
|
|
guild.FormedDate = time.Unix(formedOnTimestamp, 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 = ?`
|
|
|
|
rows, err := dgm.db.QueryContext(ctx, query, guildID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query guild members for guild %d: %w", guildID, err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var members []GuildMemberData
|
|
for rows.Next() {
|
|
var member GuildMemberData
|
|
var joinDateTimestamp int64
|
|
var lastLoginTimestamp int64
|
|
var note, officerNote, recruiterDesc *string
|
|
var pictureData []byte
|
|
|
|
err := rows.Scan(
|
|
&member.CharacterID,
|
|
&member.GuildID,
|
|
&member.AccountID,
|
|
&member.RecruiterID,
|
|
&member.Name,
|
|
&member.GuildStatus,
|
|
&member.Points,
|
|
&member.AdventureClass,
|
|
&member.AdventureLevel,
|
|
&member.TradeskillClass,
|
|
&member.TradeskillLevel,
|
|
&member.Rank,
|
|
&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)
|
|
}
|
|
|
|
// Handle nullable fields
|
|
if note != nil {
|
|
member.Note = *note
|
|
}
|
|
if officerNote != nil {
|
|
member.OfficerNote = *officerNote
|
|
}
|
|
if recruiterDesc != nil {
|
|
member.RecruiterDescription = *recruiterDesc
|
|
}
|
|
member.RecruiterPictureData = pictureData
|
|
|
|
// Convert timestamps to time
|
|
member.JoinDate = time.Unix(joinDateTimestamp, 0)
|
|
member.LastLoginDate = time.Unix(lastLoginTimestamp, 0)
|
|
|
|
members = append(members, member)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating guild member rows: %w", 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 ?`
|
|
|
|
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
|
|
for rows.Next() {
|
|
var event GuildEventData
|
|
var dateTimestamp int64
|
|
|
|
err := rows.Scan(
|
|
&event.EventID,
|
|
&event.GuildID,
|
|
&dateTimestamp,
|
|
&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)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating guild event rows: %w", 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 = ?"
|
|
|
|
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
|
|
for rows.Next() {
|
|
var rank GuildRankData
|
|
|
|
err := rows.Scan(
|
|
&rank.GuildID,
|
|
&rank.Rank,
|
|
&rank.Name,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan guild rank row: %w", err)
|
|
}
|
|
|
|
ranks = append(ranks, rank)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating guild rank rows: %w", 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 = ?"
|
|
|
|
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
|
|
for rows.Next() {
|
|
var permission GuildPermissionData
|
|
|
|
err := rows.Scan(
|
|
&permission.GuildID,
|
|
&permission.Rank,
|
|
&permission.Permission,
|
|
&permission.Value,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan guild permission row: %w", err)
|
|
}
|
|
|
|
permissions = append(permissions, permission)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating guild permission rows: %w", 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 = ?"
|
|
|
|
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
|
|
for rows.Next() {
|
|
var filter GuildEventFilterData
|
|
|
|
err := rows.Scan(
|
|
&filter.GuildID,
|
|
&filter.EventID,
|
|
&filter.Category,
|
|
&filter.Value,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan guild event filter row: %w", err)
|
|
}
|
|
|
|
filters = append(filters, filter)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating guild event filter rows: %w", 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 = ?"
|
|
|
|
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
|
|
for rows.Next() {
|
|
var recruit GuildRecruitingData
|
|
|
|
err := rows.Scan(
|
|
&recruit.GuildID,
|
|
&recruit.Flag,
|
|
&recruit.Value,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to scan guild recruiting row: %w", err)
|
|
}
|
|
|
|
recruiting = append(recruiting, recruit)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating guild recruiting rows: %w", 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 ?`
|
|
|
|
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
|
|
for rows.Next() {
|
|
var entry PointHistoryData
|
|
var dateTimestamp int64
|
|
|
|
err := rows.Scan(
|
|
&entry.CharacterID,
|
|
&dateTimestamp,
|
|
&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)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating point history rows: %w", 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 (?, ?, ?, ?, ?, ?, ?)`
|
|
|
|
guildInfo := guild.GetGuildInfo()
|
|
formedTimestamp := guild.GetFormedDate().Unix()
|
|
|
|
_, err := dgm.db.ExecContext(ctx, 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
|
|
tx, err := dgm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Delete existing members for this guild
|
|
_, err = tx.ExecContext(ctx, "DELETE FROM guild_members WHERE guild_id = ?", guildID)
|
|
if 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()
|
|
|
|
_, err = tx.ExecContext(ctx, 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,
|
|
)
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
// 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()
|
|
|
|
_, err := dgm.db.ExecContext(ctx, query,
|
|
event.EventID,
|
|
guildID,
|
|
dateTimestamp,
|
|
event.Type,
|
|
event.Description,
|
|
event.Locked,
|
|
)
|
|
if 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
|
|
tx, err := dgm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Delete existing ranks for this guild
|
|
_, err = tx.ExecContext(ctx, "DELETE FROM guild_ranks WHERE guild_id = ?", guildID)
|
|
if 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 {
|
|
_, err = tx.ExecContext(ctx, insertQuery, guildID, rank, name)
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
tx, err := dgm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Delete existing permissions for this guild
|
|
_, err = tx.ExecContext(ctx, "DELETE FROM guild_permissions WHERE guild_id = ?", guildID)
|
|
if 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 {
|
|
_, err = tx.ExecContext(ctx, insertQuery, guildID, rank, permission, value)
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
tx, err := dgm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Delete existing filters for this guild
|
|
_, err = tx.ExecContext(ctx, "DELETE FROM guild_event_filters WHERE guild_id = ?", guildID)
|
|
if 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 {
|
|
_, err = tx.ExecContext(ctx, insertQuery, guildID, eventID, category, value)
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
tx, err := dgm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Delete existing recruiting settings for this guild
|
|
_, err = tx.ExecContext(ctx, "DELETE FROM guild_recruiting WHERE guild_id = ?", guildID)
|
|
if 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 {
|
|
_, err = tx.ExecContext(ctx, insertQuery, guildID, flag, value)
|
|
if 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 {
|
|
_, err = tx.ExecContext(ctx, insertQuery, guildID, -tag-1, value) // Negative to distinguish from flags
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
// 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
|
|
tx, err := dgm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Delete existing history for this character
|
|
_, err = tx.ExecContext(ctx, "DELETE FROM guild_point_history WHERE char_id = ?", characterID)
|
|
if 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()
|
|
|
|
_, err = tx.ExecContext(ctx, insertQuery,
|
|
characterID,
|
|
dateTimestamp,
|
|
entry.ModifiedBy,
|
|
entry.Comment,
|
|
entry.Points,
|
|
)
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
// 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 = ?"
|
|
|
|
var guildID int32
|
|
err := dgm.db.QueryRowContext(ctx, query, characterID).Scan(&guildID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get guild ID for character %d: %w", characterID, err)
|
|
}
|
|
|
|
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()
|
|
|
|
result, err := dgm.db.ExecContext(ctx, 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)
|
|
}
|
|
|
|
id, err := result.LastInsertId()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get new guild ID: %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
|
|
tx, err := dgm.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// 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: need to join with guild_members to get char_ids
|
|
query = fmt.Sprintf("DELETE ph FROM %s ph JOIN guild_members gm ON ph.char_id = gm.char_id WHERE gm.guild_id = ?", table)
|
|
} else {
|
|
query = fmt.Sprintf("DELETE FROM %s WHERE guild_id = ?", table)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, query, guildID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete from %s: %w", table, err)
|
|
}
|
|
}
|
|
|
|
// Finally delete the guild itself
|
|
_, err = tx.ExecContext(ctx, "DELETE FROM guilds WHERE id = ?", guildID)
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
// 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"
|
|
|
|
var nextID int32
|
|
err := dgm.db.QueryRowContext(ctx, query).Scan(&nextID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get next guild ID: %w", err)
|
|
}
|
|
|
|
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 = ?"
|
|
|
|
var nextID int64
|
|
err := dgm.db.QueryRowContext(ctx, query, guildID).Scan(&nextID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get next event ID for guild %d: %w", guildID, err)
|
|
}
|
|
|
|
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 {
|
|
_, err := dgm.db.ExecContext(ctx, query)
|
|
if 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 {
|
|
_, err := dgm.db.ExecContext(ctx, query)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create guild index %d: %w", i+1, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
} |