mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
refactor(channelserver): extract RengokuRepository and MailRepository
Move all direct DB access from handlers_rengoku.go (11 calls) and handlers_mail.go (10 calls) into dedicated repository types, continuing the established extraction pattern. RengokuRepository provides UpsertScore and GetRanking, replacing a 3-call check/insert/update sequence and an 8-case switch of nearly identical queries respectively. MailRepository provides SendMail, SendMailTx, GetListForCharacter, GetByID, MarkRead, MarkDeleted, SetLocked, and MarkItemReceived. The old Mail.Send(), Mail.MarkRead(), GetMailListForCharacter, and GetMailByID free functions are removed. Guild handlers that sent mail via Mail.Send(s, ...) now call mailRepo directly.
This commit is contained in:
@@ -76,13 +76,9 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
response = 0
|
response = 0
|
||||||
} else {
|
} else {
|
||||||
mail := Mail{
|
_ = s.server.mailRepo.SendMail(0, s.charID, "Withdrawal",
|
||||||
RecipientID: s.charID,
|
fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name),
|
||||||
Subject: "Withdrawal",
|
0, 0, false, true)
|
||||||
Body: fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name),
|
|
||||||
IsSystemMessage: true,
|
|
||||||
}
|
|
||||||
_ = mail.Send(s, nil)
|
|
||||||
}
|
}
|
||||||
bf.WriteUint32(uint32(response))
|
bf.WriteUint32(uint32(response))
|
||||||
case mhfpacket.OperateGuildDonateRank:
|
case mhfpacket.OperateGuildDonateRank:
|
||||||
@@ -303,7 +299,7 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||||
} else {
|
} 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 {
|
if s.server.Registry != nil {
|
||||||
s.server.Registry.NotifyMailToCharID(pkt.CharID, s, &mail)
|
s.server.Registry.NotifyMailToCharID(pkt.CharID, s, &mail)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -62,18 +62,10 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mail := &Mail{
|
err = s.server.mailRepo.SendMailTx(transaction, s.charID, pkt.CharID,
|
||||||
SenderID: s.charID,
|
s.server.i18n.guild.invite.title,
|
||||||
RecipientID: pkt.CharID,
|
fmt.Sprintf(s.server.i18n.guild.invite.body, guildInfo.Name),
|
||||||
Subject: s.server.i18n.guild.invite.title,
|
0, 0, true, false)
|
||||||
Body: fmt.Sprintf(
|
|
||||||
s.server.i18n.guild.invite.body,
|
|
||||||
guildInfo.Name,
|
|
||||||
),
|
|
||||||
IsGuildInvite: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = mail.Send(s, transaction)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = transaction.Rollback()
|
_ = transaction.Rollback()
|
||||||
@@ -151,37 +143,25 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var mail []Mail
|
type mailMsg struct {
|
||||||
|
senderID uint32
|
||||||
|
recipientID uint32
|
||||||
|
subject string
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
var msgs []mailMsg
|
||||||
if pkt.Answer {
|
if pkt.Answer {
|
||||||
err = s.server.guildRepo.AcceptApplication(guild.ID, s.charID)
|
err = s.server.guildRepo.AcceptApplication(guild.ID, s.charID)
|
||||||
mail = append(mail, Mail{
|
msgs = append(msgs,
|
||||||
RecipientID: s.charID,
|
mailMsg{0, s.charID, s.server.i18n.guild.invite.success.title, fmt.Sprintf(s.server.i18n.guild.invite.success.body, guild.Name)},
|
||||||
Subject: s.server.i18n.guild.invite.success.title,
|
mailMsg{s.charID, pkt.LeaderID, s.server.i18n.guild.invite.accepted.title, fmt.Sprintf(s.server.i18n.guild.invite.accepted.body, guild.Name)},
|
||||||
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,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
err = s.server.guildRepo.RejectApplication(guild.ID, s.charID)
|
err = s.server.guildRepo.RejectApplication(guild.ID, s.charID)
|
||||||
mail = append(mail, Mail{
|
msgs = append(msgs,
|
||||||
RecipientID: s.charID,
|
mailMsg{0, s.charID, s.server.i18n.guild.invite.rejected.title, fmt.Sprintf(s.server.i18n.guild.invite.rejected.body, guild.Name)},
|
||||||
Subject: s.server.i18n.guild.invite.rejected.title,
|
mailMsg{s.charID, pkt.LeaderID, s.server.i18n.guild.invite.declined.title, fmt.Sprintf(s.server.i18n.guild.invite.declined.body, guild.Name)},
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bf.WriteUint32(7)
|
bf.WriteUint32(7)
|
||||||
@@ -191,8 +171,8 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf.WriteUint32(0)
|
bf.WriteUint32(0)
|
||||||
bf.WriteUint32(guild.ID)
|
bf.WriteUint32(guild.ID)
|
||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
for _, m := range mail {
|
for _, m := range msgs {
|
||||||
_ = m.Send(s, nil)
|
_ = s.server.mailRepo.SendMail(m.senderID, m.recipientID, m.subject, m.body, 0, 0, false, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"erupe-ce/common/stringsupport"
|
"erupe-ce/common/stringsupport"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -30,146 +29,6 @@ type Mail struct {
|
|||||||
SenderName string `db:"sender_name"`
|
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.
|
// SendMailNotification sends a new mail notification to a player.
|
||||||
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
|
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
@@ -213,13 +72,13 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mail, err := GetMailByID(s, mailId)
|
mail, err := s.server.mailRepo.GetByID(mailId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
|
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
|
||||||
return
|
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))
|
s.logger.Error("Failed to mark mail as read", zap.Error(err))
|
||||||
}
|
}
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
@@ -231,8 +90,9 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfListMail)
|
pkt := p.(*mhfpacket.MsgMhfListMail)
|
||||||
|
|
||||||
mail, err := GetMailListForCharacter(s, s.charID)
|
mail, err := s.server.mailRepo.GetListForCharacter(s.charID)
|
||||||
if err != nil {
|
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})
|
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -304,7 +164,7 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
|
mail, err := s.server.mailRepo.GetByID(s.mailList[pkt.AccIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
@@ -312,19 +172,19 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
switch pkt.Operation {
|
switch pkt.Operation {
|
||||||
case mhfpacket.OperateMailDelete:
|
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))
|
s.logger.Error("Failed to delete mail", zap.Error(err))
|
||||||
}
|
}
|
||||||
case mhfpacket.OperateMailLock:
|
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))
|
s.logger.Error("Failed to lock mail", zap.Error(err))
|
||||||
}
|
}
|
||||||
case mhfpacket.OperateMailUnlock:
|
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))
|
s.logger.Error("Failed to unlock mail", zap.Error(err))
|
||||||
}
|
}
|
||||||
case mhfpacket.OperateMailAcquireItem:
|
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))
|
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) {
|
func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfSendMail)
|
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
|
if pkt.RecipientID == 0 { // Guild mail
|
||||||
g, err := s.server.guildRepo.GetByCharID(s.charID)
|
g, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||||
@@ -352,7 +208,7 @@ func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := 0; i < len(gm); i++ {
|
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 {
|
if err != nil {
|
||||||
s.logger.Error("Failed to send mail")
|
s.logger.Error("Failed to send mail")
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -360,7 +216,7 @@ func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
s.logger.Error("Failed to send mail")
|
s.logger.Error("Failed to send mail")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
ps "erupe-ce/common/pascalstring"
|
ps "erupe-ce/common/pascalstring"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
|
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -125,15 +122,8 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
_, _ = bf.Seek(4, 1)
|
_, _ = bf.Seek(4, 1)
|
||||||
maxStageSp := bf.ReadUint32()
|
maxStageSp := bf.ReadUint32()
|
||||||
maxScoreSp := bf.ReadUint32()
|
maxScoreSp := bf.ReadUint32()
|
||||||
var t int
|
if err := s.server.rengokuRepo.UpsertScore(s.charID, maxStageMp, maxScoreMp, maxStageSp, maxScoreSp); err != nil {
|
||||||
err = s.server.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", s.charID).Scan(&t)
|
s.logger.Error("Failed to upsert rengoku score", zap.Error(err))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
}
|
}
|
||||||
@@ -200,10 +190,6 @@ func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
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.
|
// RengokuScore represents a Rengoku (Hunting Road) ranking score.
|
||||||
type RengokuScore struct {
|
type RengokuScore struct {
|
||||||
Name string `db:"name"`
|
Name string `db:"name"`
|
||||||
@@ -235,26 +221,11 @@ func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
scoreData := byteframe.NewByteFrame()
|
scoreData := byteframe.NewByteFrame()
|
||||||
|
|
||||||
var rows *sqlx.Rows
|
var guildID uint32
|
||||||
var err error
|
if guild != nil {
|
||||||
switch pkt.Leaderboard {
|
guildID = guild.ID
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
rows, err := s.server.rengokuRepo.GetRanking(pkt.Leaderboard, guildID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to query rengoku ranking", zap.Error(err))
|
s.logger.Error("Failed to query rengoku ranking", zap.Error(err))
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 11))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 11))
|
||||||
|
|||||||
130
server/channelserver/repo_mail.go
Normal file
130
server/channelserver/repo_mail.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
76
server/channelserver/repo_rengoku.go
Normal file
76
server/channelserver/repo_rengoku.go
Normal file
@@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -605,6 +605,8 @@ func createTestServerWithDB(t *testing.T, db *sqlx.DB) *Server {
|
|||||||
server.houseRepo = NewHouseRepository(db)
|
server.houseRepo = NewHouseRepository(db)
|
||||||
server.festaRepo = NewFestaRepository(db)
|
server.festaRepo = NewFestaRepository(db)
|
||||||
server.towerRepo = NewTowerRepository(db)
|
server.towerRepo = NewTowerRepository(db)
|
||||||
|
server.rengokuRepo = NewRengokuRepository(db)
|
||||||
|
server.mailRepo = NewMailRepository(db)
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ type Server struct {
|
|||||||
houseRepo *HouseRepository
|
houseRepo *HouseRepository
|
||||||
festaRepo *FestaRepository
|
festaRepo *FestaRepository
|
||||||
towerRepo *TowerRepository
|
towerRepo *TowerRepository
|
||||||
|
rengokuRepo *RengokuRepository
|
||||||
|
mailRepo *MailRepository
|
||||||
erupeConfig *_config.Config
|
erupeConfig *_config.Config
|
||||||
acceptConns chan net.Conn
|
acceptConns chan net.Conn
|
||||||
deleteConns chan net.Conn
|
deleteConns chan net.Conn
|
||||||
@@ -129,6 +131,8 @@ func NewServer(config *Config) *Server {
|
|||||||
s.houseRepo = NewHouseRepository(config.DB)
|
s.houseRepo = NewHouseRepository(config.DB)
|
||||||
s.festaRepo = NewFestaRepository(config.DB)
|
s.festaRepo = NewFestaRepository(config.DB)
|
||||||
s.towerRepo = NewTowerRepository(config.DB)
|
s.towerRepo = NewTowerRepository(config.DB)
|
||||||
|
s.rengokuRepo = NewRengokuRepository(config.DB)
|
||||||
|
s.mailRepo = NewMailRepository(config.DB)
|
||||||
|
|
||||||
// Mezeporta
|
// Mezeporta
|
||||||
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
||||||
|
|||||||
@@ -341,4 +341,6 @@ func SetTestDB(s *Server, db *sqlx.DB) {
|
|||||||
s.houseRepo = NewHouseRepository(db)
|
s.houseRepo = NewHouseRepository(db)
|
||||||
s.festaRepo = NewFestaRepository(db)
|
s.festaRepo = NewFestaRepository(db)
|
||||||
s.towerRepo = NewTowerRepository(db)
|
s.towerRepo = NewTowerRepository(db)
|
||||||
|
s.rengokuRepo = NewRengokuRepository(db)
|
||||||
|
s.mailRepo = NewMailRepository(db)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user