mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
Extract business logic from handleMsgMhfOperateGuildMember into GuildService.OperateMember, establishing the handler→service→repo layering pattern. The handler is now ~20 lines of protocol glue (type-assert, map action, call service, send ACK, notify). GuildService owns authorization checks, repo coordination, mail composition, and best-effort mail delivery. It accepts plain Go types (no mhfpacket or Session imports), making it fully testable with mock repos. Cross-channel notification stays in the handler since it requires Session. Adds 7 table-driven service-level tests covering accept/reject/kick, authorization, repo errors, mail errors, and unknown actions.
107 lines
3.2 KiB
Go
107 lines
3.2 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// GuildMemberAction is a domain enum for guild member operations.
|
|
type GuildMemberAction uint8
|
|
|
|
const (
|
|
GuildMemberActionAccept GuildMemberAction = iota + 1
|
|
GuildMemberActionReject
|
|
GuildMemberActionKick
|
|
)
|
|
|
|
// ErrUnauthorized is returned when the actor lacks permission for the operation.
|
|
var ErrUnauthorized = errors.New("unauthorized")
|
|
|
|
// ErrUnknownAction is returned for unrecognized guild member actions.
|
|
var ErrUnknownAction = errors.New("unknown guild member action")
|
|
|
|
// OperateMemberResult holds the outcome of a guild member operation.
|
|
type OperateMemberResult struct {
|
|
MailRecipientID uint32
|
|
Mail Mail
|
|
}
|
|
|
|
// GuildService encapsulates guild business logic, sitting between handlers and repos.
|
|
type GuildService struct {
|
|
guildRepo GuildRepo
|
|
mailRepo MailRepo
|
|
charRepo CharacterRepo
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewGuildService creates a new GuildService.
|
|
func NewGuildService(gr GuildRepo, mr MailRepo, cr CharacterRepo, log *zap.Logger) *GuildService {
|
|
return &GuildService{
|
|
guildRepo: gr,
|
|
mailRepo: mr,
|
|
charRepo: cr,
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
// OperateMember performs a guild member management action (accept/reject/kick).
|
|
// The actor must be the guild leader or a sub-leader. On success, a notification
|
|
// mail is sent (best-effort) and the result is returned for protocol-level notification.
|
|
func (svc *GuildService) OperateMember(actorCharID, targetCharID uint32, action GuildMemberAction) (*OperateMemberResult, error) {
|
|
guild, err := svc.guildRepo.GetByCharID(targetCharID)
|
|
if err != nil || guild == nil {
|
|
return nil, fmt.Errorf("guild lookup for char %d: %w", targetCharID, err)
|
|
}
|
|
|
|
actorMember, err := svc.guildRepo.GetCharacterMembership(actorCharID)
|
|
if err != nil || (!actorMember.IsSubLeader() && guild.LeaderCharID != actorCharID) {
|
|
return nil, ErrUnauthorized
|
|
}
|
|
|
|
var mail Mail
|
|
switch action {
|
|
case GuildMemberActionAccept:
|
|
err = svc.guildRepo.AcceptApplication(guild.ID, targetCharID)
|
|
mail = Mail{
|
|
RecipientID: targetCharID,
|
|
Subject: "Accepted!",
|
|
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
|
|
IsSystemMessage: true,
|
|
}
|
|
case GuildMemberActionReject:
|
|
err = svc.guildRepo.RejectApplication(guild.ID, targetCharID)
|
|
mail = Mail{
|
|
RecipientID: targetCharID,
|
|
Subject: "Rejected",
|
|
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
|
|
IsSystemMessage: true,
|
|
}
|
|
case GuildMemberActionKick:
|
|
err = svc.guildRepo.RemoveCharacter(targetCharID)
|
|
mail = Mail{
|
|
RecipientID: targetCharID,
|
|
Subject: "Kicked",
|
|
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
|
|
IsSystemMessage: true,
|
|
}
|
|
default:
|
|
return nil, ErrUnknownAction
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("guild member action %d: %w", action, err)
|
|
}
|
|
|
|
// Send mail best-effort
|
|
if mailErr := svc.mailRepo.SendMail(mail.SenderID, mail.RecipientID, mail.Subject, mail.Body, 0, 0, false, true); mailErr != nil {
|
|
svc.logger.Warn("Failed to send guild member operation mail", zap.Error(mailErr))
|
|
}
|
|
|
|
return &OperateMemberResult{
|
|
MailRecipientID: targetCharID,
|
|
Mail: mail,
|
|
}, nil
|
|
}
|