refactor: unify user mode processing into shared service layer
Some checks failed
check / check (push) Failing after 2m28s
Some checks failed
check / check (push) Failing after 2m28s
Both the HTTP API and IRC wire protocol handlers now call service.ApplyUserMode/service.QueryUserMode for all user mode operations. The service layer iterates mode strings character by character (the correct IRC approach), ensuring identical behavior regardless of transport. Removed duplicate mode logic from internal/handlers/utility.go (buildUserModeString, applyUserModeChange, applyModeChar) and internal/ircserver/commands.go (buildUmodeString, inline iteration). Added service-level tests for QueryUserMode, ApplyUserMode (single-char, multi-char, invalid input, de-oper, +o rejection).
This commit is contained in:
@@ -791,6 +791,109 @@ func (s *Service) QueryChannelMode(
|
||||
return modes + modeParams
|
||||
}
|
||||
|
||||
// QueryUserMode returns the current user mode string for
|
||||
// the given session (e.g. "+ow", "+w", "+").
|
||||
func (s *Service) QueryUserMode(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
) string {
|
||||
modes := "+"
|
||||
|
||||
isOper, err := s.db.IsSessionOper(ctx, sessionID)
|
||||
if err == nil && isOper {
|
||||
modes += "o"
|
||||
}
|
||||
|
||||
isWallops, err := s.db.IsSessionWallops(
|
||||
ctx, sessionID,
|
||||
)
|
||||
if err == nil && isWallops {
|
||||
modes += "w"
|
||||
}
|
||||
|
||||
return modes
|
||||
}
|
||||
|
||||
// ApplyUserMode parses a mode string character by
|
||||
// character (e.g. "+wo", "-w") and applies each mode
|
||||
// change to the session. Returns the resulting mode string
|
||||
// after all changes, or an IRCError on failure.
|
||||
func (s *Service) ApplyUserMode(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
modeStr string,
|
||||
) (string, error) {
|
||||
if len(modeStr) < 2 { //nolint:mnd // +/- prefix + ≥1 char
|
||||
return "", &IRCError{
|
||||
Code: irc.ErrUmodeUnknownFlag,
|
||||
Params: nil,
|
||||
Message: "Unknown MODE flag",
|
||||
}
|
||||
}
|
||||
|
||||
adding := modeStr[0] == '+'
|
||||
|
||||
for _, ch := range modeStr[1:] {
|
||||
if err := s.applySingleUserMode(
|
||||
ctx, sessionID, ch, adding,
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return s.QueryUserMode(ctx, sessionID), nil
|
||||
}
|
||||
|
||||
// applySingleUserMode applies one user mode character.
|
||||
func (s *Service) applySingleUserMode(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
modeChar rune,
|
||||
adding bool,
|
||||
) error {
|
||||
switch modeChar {
|
||||
case 'w':
|
||||
err := s.db.SetSessionWallops(
|
||||
ctx, sessionID, adding,
|
||||
)
|
||||
if err != nil {
|
||||
s.log.Error(
|
||||
"set wallops mode failed", "error", err,
|
||||
)
|
||||
|
||||
return fmt.Errorf("set wallops: %w", err)
|
||||
}
|
||||
case 'o':
|
||||
// +o cannot be set via MODE; use OPER command.
|
||||
if adding {
|
||||
return &IRCError{
|
||||
Code: irc.ErrUmodeUnknownFlag,
|
||||
Params: nil,
|
||||
Message: "Unknown MODE flag",
|
||||
}
|
||||
}
|
||||
|
||||
err := s.db.SetSessionOper(
|
||||
ctx, sessionID, false,
|
||||
)
|
||||
if err != nil {
|
||||
s.log.Error(
|
||||
"clear oper mode failed", "error", err,
|
||||
)
|
||||
|
||||
return fmt.Errorf("clear oper: %w", err)
|
||||
}
|
||||
default:
|
||||
return &IRCError{
|
||||
Code: irc.ErrUmodeUnknownFlag,
|
||||
Params: nil,
|
||||
Message: "Unknown MODE flag",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// broadcastNickChange notifies channel peers of a nick
|
||||
// change.
|
||||
func (s *Service) broadcastNickChange(
|
||||
|
||||
Reference in New Issue
Block a user