diff --git a/server/channelserver/handlers_guild_ops.go b/server/channelserver/handlers_guild_ops.go index 395ddaee7..b3a928cfa 100644 --- a/server/channelserver/handlers_guild_ops.go +++ b/server/channelserver/handlers_guild_ops.go @@ -76,13 +76,9 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { if err != nil { response = 0 } else { - mail := Mail{ - RecipientID: s.charID, - Subject: "Withdrawal", - Body: fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name), - IsSystemMessage: true, - } - _ = mail.Send(s, nil) + _ = s.server.mailRepo.SendMail(0, s.charID, "Withdrawal", + fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name), + 0, 0, false, true) } bf.WriteUint32(uint32(response)) case mhfpacket.OperateGuildDonateRank: @@ -303,7 +299,7 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) { if err != nil { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) } else { - _ = mail.Send(s, nil) + _ = s.server.mailRepo.SendMail(mail.SenderID, mail.RecipientID, mail.Subject, mail.Body, 0, 0, false, true) if s.server.Registry != nil { s.server.Registry.NotifyMailToCharID(pkt.CharID, s, &mail) } else { diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go index 2779cad6c..8f3c4eb44 100644 --- a/server/channelserver/handlers_guild_scout.go +++ b/server/channelserver/handlers_guild_scout.go @@ -62,18 +62,10 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { return } - mail := &Mail{ - SenderID: s.charID, - RecipientID: pkt.CharID, - Subject: s.server.i18n.guild.invite.title, - Body: fmt.Sprintf( - s.server.i18n.guild.invite.body, - guildInfo.Name, - ), - IsGuildInvite: true, - } - - err = mail.Send(s, transaction) + err = s.server.mailRepo.SendMailTx(transaction, s.charID, pkt.CharID, + s.server.i18n.guild.invite.title, + fmt.Sprintf(s.server.i18n.guild.invite.body, guildInfo.Name), + 0, 0, true, false) if err != nil { _ = transaction.Rollback() @@ -151,37 +143,25 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) { return } - var mail []Mail + type mailMsg struct { + senderID uint32 + recipientID uint32 + subject string + body string + } + var msgs []mailMsg if pkt.Answer { err = s.server.guildRepo.AcceptApplication(guild.ID, s.charID) - mail = append(mail, Mail{ - RecipientID: s.charID, - Subject: s.server.i18n.guild.invite.success.title, - Body: fmt.Sprintf(s.server.i18n.guild.invite.success.body, guild.Name), - IsSystemMessage: true, - }) - mail = append(mail, Mail{ - SenderID: s.charID, - RecipientID: pkt.LeaderID, - Subject: s.server.i18n.guild.invite.accepted.title, - Body: fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, guild.Name), - IsSystemMessage: true, - }) + msgs = append(msgs, + mailMsg{0, s.charID, s.server.i18n.guild.invite.success.title, fmt.Sprintf(s.server.i18n.guild.invite.success.body, guild.Name)}, + mailMsg{s.charID, pkt.LeaderID, s.server.i18n.guild.invite.accepted.title, fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, guild.Name)}, + ) } else { err = s.server.guildRepo.RejectApplication(guild.ID, s.charID) - mail = append(mail, Mail{ - RecipientID: s.charID, - Subject: s.server.i18n.guild.invite.rejected.title, - Body: fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, guild.Name), - IsSystemMessage: true, - }) - mail = append(mail, Mail{ - SenderID: s.charID, - RecipientID: pkt.LeaderID, - Subject: s.server.i18n.guild.invite.declined.title, - Body: fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name), - IsSystemMessage: true, - }) + msgs = append(msgs, + mailMsg{0, s.charID, s.server.i18n.guild.invite.rejected.title, fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, guild.Name)}, + mailMsg{s.charID, pkt.LeaderID, s.server.i18n.guild.invite.declined.title, fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name)}, + ) } if err != nil { bf.WriteUint32(7) @@ -191,8 +171,8 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(0) bf.WriteUint32(guild.ID) doAckBufSucceed(s, pkt.AckHandle, bf.Data()) - for _, m := range mail { - _ = m.Send(s, nil) + for _, m := range msgs { + _ = s.server.mailRepo.SendMail(m.senderID, m.recipientID, m.subject, m.body, 0, 0, false, true) } } } diff --git a/server/channelserver/handlers_mail.go b/server/channelserver/handlers_mail.go index 2564d623c..e4ea05d64 100644 --- a/server/channelserver/handlers_mail.go +++ b/server/channelserver/handlers_mail.go @@ -1,7 +1,6 @@ package channelserver import ( - "database/sql" "erupe-ce/common/stringsupport" "time" @@ -30,146 +29,6 @@ type Mail struct { SenderName string `db:"sender_name"` } -func (m *Mail) Send(s *Session, transaction *sql.Tx) error { - query := ` - INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite, is_sys_message) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - ` - - var err error - - if transaction == nil { - _, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage) - } else { - _, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage) - } - - if err != nil { - s.logger.Error( - "failed to send mail", - zap.Error(err), - zap.Uint32("senderID", m.SenderID), - zap.Uint32("recipientID", m.RecipientID), - zap.String("subject", m.Subject), - zap.String("body", m.Body), - zap.Uint16("itemID", m.AttachedItemID), - zap.Uint16("itemAmount", m.AttachedItemAmount), - zap.Bool("isGuildInvite", m.IsGuildInvite), - zap.Bool("isSystemMessage", m.IsSystemMessage), - ) - return err - } - - return nil -} - -func (m *Mail) MarkRead(s *Session) error { - _, err := s.server.db.Exec(` - UPDATE mail SET read = true WHERE id = $1 - `, m.ID) - - if err != nil { - s.logger.Error( - "failed to mark mail as read", - zap.Error(err), - zap.Int("mailID", m.ID), - ) - return err - } - - return nil -} - -// GetMailListForCharacter loads all mail for a character. -func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) { - rows, err := s.server.db.Queryx(` - SELECT - m.id, - m.sender_id, - m.recipient_id, - m.subject, - m.read, - m.attached_item_received, - m.attached_item, - m.attached_item_amount, - m.created_at, - m.is_guild_invite, - m.is_sys_message, - m.deleted, - m.locked, - c.name as sender_name - FROM mail m - JOIN characters c ON c.id = m.sender_id - WHERE recipient_id = $1 AND m.deleted = false - ORDER BY m.created_at DESC, id DESC - LIMIT 32 - `, charID) - - if err != nil { - s.logger.Error("failed to get mail for character", zap.Error(err), zap.Uint32("charID", charID)) - return nil, err - } - - defer func() { _ = rows.Close() }() - - allMail := make([]Mail, 0) - - for rows.Next() { - mail := Mail{} - - err := rows.StructScan(&mail) - - if err != nil { - return nil, err - } - - allMail = append(allMail, mail) - } - - return allMail, nil -} - -// GetMailByID loads a single mail by ID. -func GetMailByID(s *Session, ID int) (*Mail, error) { - row := s.server.db.QueryRowx(` - SELECT - m.id, - m.sender_id, - m.recipient_id, - m.subject, - m.read, - m.body, - m.attached_item_received, - m.attached_item, - m.attached_item_amount, - m.created_at, - m.is_guild_invite, - m.is_sys_message, - m.deleted, - m.locked, - c.name as sender_name - FROM mail m - JOIN characters c ON c.id = m.sender_id - WHERE m.id = $1 - LIMIT 1 - `, ID) - - mail := &Mail{} - - err := row.StructScan(mail) - - if err != nil { - s.logger.Error( - "failed to retrieve mail", - zap.Error(err), - zap.Int("mailID", ID), - ) - return nil, err - } - - return mail, nil -} - // SendMailNotification sends a new mail notification to a player. func SendMailNotification(s *Session, m *Mail, recipient *Session) { bf := byteframe.NewByteFrame() @@ -213,13 +72,13 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) { return } - mail, err := GetMailByID(s, mailId) + mail, err := s.server.mailRepo.GetByID(mailId) if err != nil { doAckBufSucceed(s, pkt.AckHandle, []byte{0}) return } - if _, err := s.server.db.Exec(`UPDATE mail SET read = true WHERE id = $1`, mail.ID); err != nil { + if err := s.server.mailRepo.MarkRead(mail.ID); err != nil { s.logger.Error("Failed to mark mail as read", zap.Error(err)) } bf := byteframe.NewByteFrame() @@ -231,8 +90,9 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfListMail) - mail, err := GetMailListForCharacter(s, s.charID) + mail, err := s.server.mailRepo.GetListForCharacter(s.charID) if err != nil { + s.logger.Error("failed to get mail for character", zap.Error(err), zap.Uint32("charID", s.charID)) doAckBufSucceed(s, pkt.AckHandle, []byte{0}) return } @@ -304,7 +164,7 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return } - mail, err := GetMailByID(s, s.mailList[pkt.AccIndex]) + mail, err := s.server.mailRepo.GetByID(s.mailList[pkt.AccIndex]) if err != nil { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return @@ -312,19 +172,19 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) { switch pkt.Operation { case mhfpacket.OperateMailDelete: - if _, err := s.server.db.Exec(`UPDATE mail SET deleted = true WHERE id = $1`, mail.ID); err != nil { + if err := s.server.mailRepo.MarkDeleted(mail.ID); err != nil { s.logger.Error("Failed to delete mail", zap.Error(err)) } case mhfpacket.OperateMailLock: - if _, err := s.server.db.Exec(`UPDATE mail SET locked = TRUE WHERE id = $1`, mail.ID); err != nil { + if err := s.server.mailRepo.SetLocked(mail.ID, true); err != nil { s.logger.Error("Failed to lock mail", zap.Error(err)) } case mhfpacket.OperateMailUnlock: - if _, err := s.server.db.Exec(`UPDATE mail SET locked = FALSE WHERE id = $1`, mail.ID); err != nil { + if err := s.server.mailRepo.SetLocked(mail.ID, false); err != nil { s.logger.Error("Failed to unlock mail", zap.Error(err)) } case mhfpacket.OperateMailAcquireItem: - if _, err := s.server.db.Exec(`UPDATE mail SET attached_item_received = TRUE WHERE id = $1`, mail.ID); err != nil { + if err := s.server.mailRepo.MarkItemReceived(mail.ID); err != nil { s.logger.Error("Failed to mark mail item received", zap.Error(err)) } } @@ -333,10 +193,6 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSendMail) - query := ` - INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ` if pkt.RecipientID == 0 { // Guild mail g, err := s.server.guildRepo.GetByCharID(s.charID) @@ -352,7 +208,7 @@ func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) { return } for i := 0; i < len(gm); i++ { - _, err := s.server.db.Exec(query, s.charID, gm[i].CharID, pkt.Subject, pkt.Body, 0, 0, false) + err := s.server.mailRepo.SendMail(s.charID, gm[i].CharID, pkt.Subject, pkt.Body, 0, 0, false, false) if err != nil { s.logger.Error("Failed to send mail") doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) @@ -360,7 +216,7 @@ func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) { } } } else { - _, err := s.server.db.Exec(query, s.charID, pkt.RecipientID, pkt.Subject, pkt.Body, pkt.ItemID, pkt.Quantity, false) + err := s.server.mailRepo.SendMail(s.charID, pkt.RecipientID, pkt.Subject, pkt.Body, pkt.ItemID, pkt.Quantity, false, false) if err != nil { s.logger.Error("Failed to send mail") } diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go index 2d166de8b..ff219a192 100644 --- a/server/channelserver/handlers_rengoku.go +++ b/server/channelserver/handlers_rengoku.go @@ -3,12 +3,9 @@ package channelserver import ( "encoding/binary" ps "erupe-ce/common/pascalstring" - "fmt" "os" "path/filepath" - "github.com/jmoiron/sqlx" - "erupe-ce/common/byteframe" "erupe-ce/network/mhfpacket" "go.uber.org/zap" @@ -125,15 +122,8 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { _, _ = bf.Seek(4, 1) maxStageSp := bf.ReadUint32() maxScoreSp := bf.ReadUint32() - var t int - err = s.server.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", s.charID).Scan(&t) - if err != nil { - if _, err := s.server.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", s.charID); err != nil { - s.logger.Error("Failed to insert rengoku score", zap.Error(err)) - } - } - if _, err := s.server.db.Exec("UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", maxStageMp, maxScoreMp, maxStageSp, maxScoreSp, s.charID); err != nil { - s.logger.Error("Failed to update rengoku score", zap.Error(err)) + if err := s.server.rengokuRepo.UpsertScore(s.charID, maxStageMp, maxScoreMp, maxStageSp, maxScoreSp); err != nil { + s.logger.Error("Failed to upsert rengoku score", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } @@ -200,10 +190,6 @@ func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, data) } -const rengokuScoreQuery = `, c.name FROM rengoku_score rs -LEFT JOIN characters c ON c.id = rs.character_id -LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id ` - // RengokuScore represents a Rengoku (Hunting Road) ranking score. type RengokuScore struct { Name string `db:"name"` @@ -235,26 +221,11 @@ func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() scoreData := byteframe.NewByteFrame() - var rows *sqlx.Rows - var err error - switch pkt.Leaderboard { - case 0: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_mp AS score %s ORDER BY max_stages_mp DESC", rengokuScoreQuery)) - case 1: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_mp AS score %s ORDER BY max_points_mp DESC", rengokuScoreQuery)) - case 2: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_mp AS score %s WHERE guild_id=$1 ORDER BY max_stages_mp DESC", rengokuScoreQuery), guild.ID) - case 3: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_mp AS score %s WHERE guild_id=$1 ORDER BY max_points_mp DESC", rengokuScoreQuery), guild.ID) - case 4: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_sp AS score %s ORDER BY max_stages_sp DESC", rengokuScoreQuery)) - case 5: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_sp AS score %s ORDER BY max_points_sp DESC", rengokuScoreQuery)) - case 6: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_sp AS score %s WHERE guild_id=$1 ORDER BY max_stages_sp DESC", rengokuScoreQuery), guild.ID) - case 7: - rows, err = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_sp AS score %s WHERE guild_id=$1 ORDER BY max_points_sp DESC", rengokuScoreQuery), guild.ID) + var guildID uint32 + if guild != nil { + guildID = guild.ID } + rows, err := s.server.rengokuRepo.GetRanking(pkt.Leaderboard, guildID) if err != nil { s.logger.Error("Failed to query rengoku ranking", zap.Error(err)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 11)) diff --git a/server/channelserver/repo_mail.go b/server/channelserver/repo_mail.go new file mode 100644 index 000000000..24da7543a --- /dev/null +++ b/server/channelserver/repo_mail.go @@ -0,0 +1,130 @@ +package channelserver + +import ( + "database/sql" + + "github.com/jmoiron/sqlx" +) + +// MailRepository centralizes all database access for the mail table. +type MailRepository struct { + db *sqlx.DB +} + +// NewMailRepository creates a new MailRepository. +func NewMailRepository(db *sqlx.DB) *MailRepository { + return &MailRepository{db: db} +} + +const mailInsertQuery = ` + INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite, is_sys_message) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +` + +// SendMail inserts a new mail row. +func (r *MailRepository) SendMail(senderID, recipientID uint32, subject, body string, itemID, itemAmount uint16, isGuildInvite, isSystemMessage bool) error { + _, err := r.db.Exec(mailInsertQuery, senderID, recipientID, subject, body, itemID, itemAmount, isGuildInvite, isSystemMessage) + return err +} + +// SendMailTx inserts a new mail row within an existing transaction. +func (r *MailRepository) SendMailTx(tx *sql.Tx, senderID, recipientID uint32, subject, body string, itemID, itemAmount uint16, isGuildInvite, isSystemMessage bool) error { + _, err := tx.Exec(mailInsertQuery, senderID, recipientID, subject, body, itemID, itemAmount, isGuildInvite, isSystemMessage) + return err +} + +// GetListForCharacter loads all non-deleted mail for a character (max 32). +func (r *MailRepository) GetListForCharacter(charID uint32) ([]Mail, error) { + rows, err := r.db.Queryx(` + SELECT + m.id, + m.sender_id, + m.recipient_id, + m.subject, + m.read, + m.attached_item_received, + m.attached_item, + m.attached_item_amount, + m.created_at, + m.is_guild_invite, + m.is_sys_message, + m.deleted, + m.locked, + c.name as sender_name + FROM mail m + JOIN characters c ON c.id = m.sender_id + WHERE recipient_id = $1 AND m.deleted = false + ORDER BY m.created_at DESC, id DESC + LIMIT 32 + `, charID) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + var allMail []Mail + for rows.Next() { + var mail Mail + if err := rows.StructScan(&mail); err != nil { + return nil, err + } + allMail = append(allMail, mail) + } + return allMail, nil +} + +// GetByID loads a single mail by ID. +func (r *MailRepository) GetByID(id int) (*Mail, error) { + row := r.db.QueryRowx(` + SELECT + m.id, + m.sender_id, + m.recipient_id, + m.subject, + m.read, + m.body, + m.attached_item_received, + m.attached_item, + m.attached_item_amount, + m.created_at, + m.is_guild_invite, + m.is_sys_message, + m.deleted, + m.locked, + c.name as sender_name + FROM mail m + JOIN characters c ON c.id = m.sender_id + WHERE m.id = $1 + LIMIT 1 + `, id) + + mail := &Mail{} + if err := row.StructScan(mail); err != nil { + return nil, err + } + return mail, nil +} + +// MarkRead marks a mail as read. +func (r *MailRepository) MarkRead(id int) error { + _, err := r.db.Exec(`UPDATE mail SET read = true WHERE id = $1`, id) + return err +} + +// MarkDeleted marks a mail as deleted. +func (r *MailRepository) MarkDeleted(id int) error { + _, err := r.db.Exec(`UPDATE mail SET deleted = true WHERE id = $1`, id) + return err +} + +// SetLocked sets the locked state of a mail. +func (r *MailRepository) SetLocked(id int, locked bool) error { + _, err := r.db.Exec(`UPDATE mail SET locked = $1 WHERE id = $2`, locked, id) + return err +} + +// MarkItemReceived marks a mail's attached item as received. +func (r *MailRepository) MarkItemReceived(id int) error { + _, err := r.db.Exec(`UPDATE mail SET attached_item_received = TRUE WHERE id = $1`, id) + return err +} diff --git a/server/channelserver/repo_rengoku.go b/server/channelserver/repo_rengoku.go new file mode 100644 index 000000000..06c454e2c --- /dev/null +++ b/server/channelserver/repo_rengoku.go @@ -0,0 +1,76 @@ +package channelserver + +import ( + "fmt" + + "github.com/jmoiron/sqlx" +) + +// RengokuRepository centralizes all database access for the rengoku_score table. +type RengokuRepository struct { + db *sqlx.DB +} + +// NewRengokuRepository creates a new RengokuRepository. +func NewRengokuRepository(db *sqlx.DB) *RengokuRepository { + return &RengokuRepository{db: db} +} + +// UpsertScore ensures a rengoku_score row exists for the character and updates it. +func (r *RengokuRepository) UpsertScore(charID uint32, maxStagesMp, maxPointsMp, maxStagesSp, maxPointsSp uint32) error { + var t int + err := r.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", charID).Scan(&t) + if err != nil { + if _, err := r.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", charID); err != nil { + return fmt.Errorf("insert rengoku_score: %w", err) + } + } + if _, err := r.db.Exec( + "UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", + maxStagesMp, maxPointsMp, maxStagesSp, maxPointsSp, charID, + ); err != nil { + return fmt.Errorf("update rengoku_score: %w", err) + } + return nil +} + +// rengokuScoreQuery is the shared FROM/JOIN clause for ranking queries. +const rengokuScoreQueryRepo = `, c.name FROM rengoku_score rs +LEFT JOIN characters c ON c.id = rs.character_id +LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id ` + +// rengokuColumnForLeaderboard maps a leaderboard index to the score column name. +func rengokuColumnForLeaderboard(leaderboard uint32) string { + switch leaderboard { + case 0, 2: + return "max_stages_mp" + case 1, 3: + return "max_points_mp" + case 4, 6: + return "max_stages_sp" + case 5, 7: + return "max_points_sp" + default: + return "max_stages_mp" + } +} + +// rengokuIsGuildFiltered returns true if the leaderboard index is guild-scoped. +func rengokuIsGuildFiltered(leaderboard uint32) bool { + return leaderboard == 2 || leaderboard == 3 || leaderboard == 6 || leaderboard == 7 +} + +// GetRanking returns rengoku scores for the given leaderboard. +// For guild-scoped leaderboards (2,3,6,7), guildID filters the results. +func (r *RengokuRepository) GetRanking(leaderboard uint32, guildID uint32) (*sqlx.Rows, error) { + col := rengokuColumnForLeaderboard(leaderboard) + if rengokuIsGuildFiltered(leaderboard) { + return r.db.Queryx( + fmt.Sprintf("SELECT %s AS score %s WHERE guild_id=$1 ORDER BY %s DESC", col, rengokuScoreQueryRepo, col), + guildID, + ) + } + return r.db.Queryx( + fmt.Sprintf("SELECT %s AS score %s ORDER BY %s DESC", col, rengokuScoreQueryRepo, col), + ) +} diff --git a/server/channelserver/session_lifecycle_integration_test.go b/server/channelserver/session_lifecycle_integration_test.go index 307daa160..f276710f3 100644 --- a/server/channelserver/session_lifecycle_integration_test.go +++ b/server/channelserver/session_lifecycle_integration_test.go @@ -605,6 +605,8 @@ func createTestServerWithDB(t *testing.T, db *sqlx.DB) *Server { server.houseRepo = NewHouseRepository(db) server.festaRepo = NewFestaRepository(db) server.towerRepo = NewTowerRepository(db) + server.rengokuRepo = NewRengokuRepository(db) + server.mailRepo = NewMailRepository(db) return server } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 0b310c09b..d5ff1748e 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -52,6 +52,8 @@ type Server struct { houseRepo *HouseRepository festaRepo *FestaRepository towerRepo *TowerRepository + rengokuRepo *RengokuRepository + mailRepo *MailRepository erupeConfig *_config.Config acceptConns chan net.Conn deleteConns chan net.Conn @@ -129,6 +131,8 @@ func NewServer(config *Config) *Server { s.houseRepo = NewHouseRepository(config.DB) s.festaRepo = NewFestaRepository(config.DB) s.towerRepo = NewTowerRepository(config.DB) + s.rengokuRepo = NewRengokuRepository(config.DB) + s.mailRepo = NewMailRepository(config.DB) // Mezeporta s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0") diff --git a/server/channelserver/testhelpers_db.go b/server/channelserver/testhelpers_db.go index cc6335037..0d4871ed5 100644 --- a/server/channelserver/testhelpers_db.go +++ b/server/channelserver/testhelpers_db.go @@ -341,4 +341,6 @@ func SetTestDB(s *Server, db *sqlx.DB) { s.houseRepo = NewHouseRepository(db) s.festaRepo = NewFestaRepository(db) s.towerRepo = NewTowerRepository(db) + s.rengokuRepo = NewRengokuRepository(db) + s.mailRepo = NewMailRepository(db) }