mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
refactor(channelserver): extract GuildRepository for guild table access
Per anti-patterns.md item #9, guild-related SQL was scattered across ~15 handler files with no repository abstraction. Following the same pattern established by CharacterRepository, this centralizes all guilds, guild_characters, and guild_applications table access into a single GuildRepository (~30 methods). guild_model.go and handlers_guild_member.go are trimmed to types and pure business logic only. All handler files (guild_*, festa, mail, house, mercenary, rengoku) now call s.server.guildRepo methods instead of direct DB queries or methods on domain objects.
This commit is contained in:
@@ -1,18 +1,10 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"erupe-ce/common/mhfitem"
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// FestivalColor is a festival color identifier string.
|
||||
@@ -150,486 +142,3 @@ func (g *Guild) Rank(mode _config.Mode) uint16 {
|
||||
}
|
||||
return 17
|
||||
}
|
||||
|
||||
const guildInfoSelectQuery = `
|
||||
SELECT
|
||||
g.id,
|
||||
g.name,
|
||||
rank_rp,
|
||||
event_rp,
|
||||
room_rp,
|
||||
COALESCE(room_expiry, '1970-01-01') AS room_expiry,
|
||||
main_motto,
|
||||
sub_motto,
|
||||
created_at,
|
||||
leader_id,
|
||||
c.name AS leader_name,
|
||||
comment,
|
||||
COALESCE(pugi_name_1, '') AS pugi_name_1,
|
||||
COALESCE(pugi_name_2, '') AS pugi_name_2,
|
||||
COALESCE(pugi_name_3, '') AS pugi_name_3,
|
||||
pugi_outfit_1,
|
||||
pugi_outfit_2,
|
||||
pugi_outfit_3,
|
||||
pugi_outfits,
|
||||
recruiting,
|
||||
COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_color,
|
||||
COALESCE((SELECT SUM(fs.souls) FROM festa_submissions fs WHERE fs.guild_id=g.id), 0) AS souls,
|
||||
COALESCE((
|
||||
SELECT id FROM guild_alliances ga WHERE
|
||||
ga.parent_id = g.id OR
|
||||
ga.sub1_id = g.id OR
|
||||
ga.sub2_id = g.id
|
||||
), 0) AS alliance_id,
|
||||
icon,
|
||||
(SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id) AS member_count
|
||||
FROM guilds g
|
||||
JOIN guild_characters gc ON gc.character_id = leader_id
|
||||
JOIN characters c on leader_id = c.id
|
||||
`
|
||||
|
||||
func (guild *Guild) Save(s *Session) error {
|
||||
_, err := s.server.db.Exec(`
|
||||
UPDATE guilds SET main_motto=$2, sub_motto=$3, comment=$4, pugi_name_1=$5, pugi_name_2=$6, pugi_name_3=$7,
|
||||
pugi_outfit_1=$8, pugi_outfit_2=$9, pugi_outfit_3=$10, pugi_outfits=$11, icon=$12, leader_id=$13 WHERE id=$1
|
||||
`, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3,
|
||||
guild.PugiOutfit1, guild.PugiOutfit2, guild.PugiOutfit3, guild.PugiOutfits, guild.Icon, guild.LeaderCharID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to update guild data", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) CreateApplication(s *Session, charID uint32, applicationType GuildApplicationType, transaction *sql.Tx) error {
|
||||
|
||||
query := `
|
||||
INSERT INTO guild_applications (guild_id, character_id, actor_id, application_type)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`
|
||||
|
||||
var err error
|
||||
|
||||
if transaction == nil {
|
||||
_, err = s.server.db.Exec(query, guild.ID, charID, s.charID, applicationType)
|
||||
} else {
|
||||
_, err = transaction.Exec(query, guild.ID, charID, s.charID, applicationType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to add guild application",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) Disband(s *Session) error {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to begin transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("DELETE FROM guild_characters WHERE guild_id = $1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild characters", zap.Error(err), zap.Uint32("guildId", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("DELETE FROM guilds WHERE id = $1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("DELETE FROM guild_alliances WHERE parent_id=$1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild alliance", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("UPDATE guild_alliances SET sub1_id=sub2_id, sub2_id=NULL WHERE sub1_id=$1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild from alliance", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("UPDATE guild_alliances SET sub2_id=NULL WHERE sub2_id=$1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild from alliance", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("Character disbanded guild", zap.Uint32("charID", s.charID), zap.Uint32("guildID", guild.ID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) RemoveCharacter(s *Session, charID uint32) error {
|
||||
_, err := s.server.db.Exec("DELETE FROM guild_characters WHERE character_id=$1", charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to remove character from guild",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) AcceptApplication(s *Session, charID uint32) error {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to start db transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(`DELETE FROM guild_applications WHERE character_id = $1`, charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to accept character's guild application", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(`
|
||||
INSERT INTO guild_characters (guild_id, character_id, order_index)
|
||||
VALUES ($1, $2, (SELECT MAX(order_index) + 1 FROM guild_characters WHERE guild_id = $1))
|
||||
`, guild.ID, charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to add applicant to guild",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit db transaction", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is relying on the fact that invitation ID is also character ID right now
|
||||
// if invitation ID changes, this will break.
|
||||
func (guild *Guild) CancelInvitation(s *Session, charID uint32) error {
|
||||
_, err := s.server.db.Exec(
|
||||
`DELETE FROM guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = 'invited'`,
|
||||
charID, guild.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to cancel guild invitation",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) RejectApplication(s *Session, charID uint32) error {
|
||||
_, err := s.server.db.Exec(
|
||||
`DELETE FROM guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = 'applied'`,
|
||||
charID, guild.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to reject guild application",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) ArrangeCharacters(s *Session, charIDs []uint32) error {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to start db transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
for i, id := range charIDs {
|
||||
_, err := transaction.Exec("UPDATE guild_characters SET order_index = $1 WHERE character_id = $2", 2+i, id)
|
||||
|
||||
if err != nil {
|
||||
err = transaction.Rollback()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to rollback db transaction", zap.Error(err))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit db transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) GetApplicationForCharID(s *Session, charID uint32, applicationType GuildApplicationType) (*GuildApplication, error) {
|
||||
row := s.server.db.QueryRowx(`
|
||||
SELECT * from guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = $3
|
||||
`, charID, guild.ID, applicationType)
|
||||
|
||||
application := &GuildApplication{}
|
||||
|
||||
err := row.StructScan(application)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve guild application for character",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return application, nil
|
||||
}
|
||||
|
||||
func (guild *Guild) HasApplicationForCharID(s *Session, charID uint32) (bool, error) {
|
||||
row := s.server.db.QueryRowx(`
|
||||
SELECT 1 from guild_applications WHERE character_id = $1 AND guild_id = $2
|
||||
`, charID, guild.ID)
|
||||
|
||||
num := 0
|
||||
|
||||
err := row.Scan(&num)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve guild applications for character",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CreateGuild creates a new guild in the database and adds the session's character as its leader.
|
||||
func CreateGuild(s *Session, guildName string) (int32, error) {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to start db transaction", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
guildResult, err := transaction.Query(
|
||||
"INSERT INTO guilds (name, leader_id) VALUES ($1, $2) RETURNING id",
|
||||
guildName, s.charID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to create guild", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var guildId int32
|
||||
|
||||
guildResult.Next()
|
||||
|
||||
err = guildResult.Scan(&guildId)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild ID", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = guildResult.Close()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to finalise query", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(`
|
||||
INSERT INTO guild_characters (guild_id, character_id)
|
||||
VALUES ($1, $2)
|
||||
`, guildId, s.charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to add character to guild", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit guild creation", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return guildId, nil
|
||||
}
|
||||
|
||||
func rollbackTransaction(s *Session, transaction *sql.Tx) {
|
||||
err := transaction.Rollback()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to rollback transaction", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// GetGuildInfoByID retrieves guild info by guild ID, returning nil if not found.
|
||||
func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE g.id = $1
|
||||
LIMIT 1
|
||||
`, guildInfoSelectQuery), guildID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild", zap.Error(err), zap.Uint32("guildID", guildID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
hasRow := rows.Next()
|
||||
|
||||
if !hasRow {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return buildGuildObjectFromDbResult(rows, err, s)
|
||||
}
|
||||
|
||||
// GetGuildInfoByCharacterId retrieves guild info for a character, including applied guilds.
|
||||
func GetGuildInfoByCharacterId(s *Session, charID uint32) (*Guild, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE EXISTS(
|
||||
SELECT 1
|
||||
FROM guild_characters gc1
|
||||
WHERE gc1.character_id = $1
|
||||
AND gc1.guild_id = g.id
|
||||
)
|
||||
OR EXISTS(
|
||||
SELECT 1
|
||||
FROM guild_applications ga
|
||||
WHERE ga.character_id = $1
|
||||
AND ga.guild_id = g.id
|
||||
AND ga.application_type = 'applied'
|
||||
)
|
||||
LIMIT 1
|
||||
`, guildInfoSelectQuery), charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild for character", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
hasRow := rows.Next()
|
||||
|
||||
if !hasRow {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return buildGuildObjectFromDbResult(rows, err, s)
|
||||
}
|
||||
|
||||
func buildGuildObjectFromDbResult(result *sqlx.Rows, _ error, s *Session) (*Guild, error) {
|
||||
guild := &Guild{}
|
||||
|
||||
err := result.StructScan(guild)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild data from database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return guild, nil
|
||||
}
|
||||
|
||||
func guildGetItems(s *Session, guildID uint32) []mhfitem.MHFItemStack {
|
||||
var data []byte
|
||||
var items []mhfitem.MHFItemStack
|
||||
if err := s.server.db.QueryRow(`SELECT item_box FROM guilds WHERE id=$1`, guildID).Scan(&data); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
s.logger.Error("Failed to get guild item box", zap.Error(err))
|
||||
}
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
box.ReadUint16() // Unused
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
items = append(items, mhfitem.ReadWarehouseItem(box))
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user