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:
@@ -1,14 +1,14 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/neoirc/internal/db"
|
||||
"sneak.berlin/go/neoirc/internal/service"
|
||||
"sneak.berlin/go/neoirc/pkg/irc"
|
||||
)
|
||||
|
||||
@@ -471,7 +471,10 @@ func (hdlr *Handlers) handleWallops(
|
||||
}
|
||||
|
||||
// handleUserMode handles user mode queries and changes
|
||||
// (e.g., MODE nick, MODE nick +w).
|
||||
// (e.g., MODE nick, MODE nick +w). Delegates to the
|
||||
// shared service.ApplyUserMode / service.QueryUserMode so
|
||||
// that mode string processing is identical for both the
|
||||
// HTTP API and IRC wire protocol.
|
||||
func (hdlr *Handlers) handleUserMode(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
@@ -496,16 +499,46 @@ func (hdlr *Handlers) handleUserMode(
|
||||
return
|
||||
}
|
||||
|
||||
hdlr.applyUserModeChange(
|
||||
writer, request,
|
||||
sessionID, clientID, nick, lines[0],
|
||||
newModes, err := hdlr.svc.ApplyUserMode(
|
||||
ctx, sessionID, lines[0],
|
||||
)
|
||||
if err != nil {
|
||||
var ircErr *service.IRCError
|
||||
if errors.As(err, &ircErr) {
|
||||
hdlr.respondIRCError(
|
||||
writer, request,
|
||||
clientID, sessionID,
|
||||
ircErr.Code, nick, ircErr.Params,
|
||||
ircErr.Message,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
hdlr.respondError(
|
||||
writer, request,
|
||||
"internal error",
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
hdlr.enqueueNumeric(
|
||||
ctx, clientID, irc.RplUmodeIs, nick, nil,
|
||||
newModes,
|
||||
)
|
||||
|
||||
hdlr.broker.Notify(sessionID)
|
||||
hdlr.respondJSON(writer, request,
|
||||
map[string]string{"status": "ok"},
|
||||
http.StatusOK)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Mode query — build the current mode string.
|
||||
modeStr := hdlr.buildUserModeString(ctx, sessionID)
|
||||
// Mode query — delegate to shared service.
|
||||
modeStr := hdlr.svc.QueryUserMode(ctx, sessionID)
|
||||
|
||||
hdlr.enqueueNumeric(
|
||||
ctx, clientID, irc.RplUmodeIs, nick, nil,
|
||||
@@ -516,144 +549,3 @@ func (hdlr *Handlers) handleUserMode(
|
||||
map[string]string{"status": "ok"},
|
||||
http.StatusOK)
|
||||
}
|
||||
|
||||
// buildUserModeString constructs the mode string for a
|
||||
// user (e.g., "+ow" for oper+wallops).
|
||||
func (hdlr *Handlers) buildUserModeString(
|
||||
ctx context.Context,
|
||||
sessionID int64,
|
||||
) string {
|
||||
modes := "+"
|
||||
|
||||
isOper, err := hdlr.params.Database.IsSessionOper(
|
||||
ctx, sessionID,
|
||||
)
|
||||
if err == nil && isOper {
|
||||
modes += "o"
|
||||
}
|
||||
|
||||
isWallops, err := hdlr.params.Database.IsSessionWallops(
|
||||
ctx, sessionID,
|
||||
)
|
||||
if err == nil && isWallops {
|
||||
modes += "w"
|
||||
}
|
||||
|
||||
return modes
|
||||
}
|
||||
|
||||
// applyUserModeChange applies a user mode change string
|
||||
// (e.g., "+w", "-w").
|
||||
func (hdlr *Handlers) applyUserModeChange(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
sessionID, clientID int64,
|
||||
nick, modeStr string,
|
||||
) {
|
||||
ctx := request.Context()
|
||||
|
||||
if len(modeStr) < 2 { //nolint:mnd // +/- and mode char
|
||||
hdlr.respondIRCError(
|
||||
writer, request, clientID, sessionID,
|
||||
irc.ErrUmodeUnknownFlag, nick, nil,
|
||||
"Unknown MODE flag",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
adding := modeStr[0] == '+'
|
||||
modeChar := modeStr[1:]
|
||||
|
||||
applied, err := hdlr.applyModeChar(
|
||||
ctx, writer, request,
|
||||
sessionID, clientID, nick,
|
||||
modeChar, adding,
|
||||
)
|
||||
if err != nil || !applied {
|
||||
return
|
||||
}
|
||||
|
||||
newModes := hdlr.buildUserModeString(ctx, sessionID)
|
||||
|
||||
hdlr.enqueueNumeric(
|
||||
ctx, clientID, irc.RplUmodeIs, nick, nil,
|
||||
newModes,
|
||||
)
|
||||
|
||||
hdlr.broker.Notify(sessionID)
|
||||
hdlr.respondJSON(writer, request,
|
||||
map[string]string{"status": "ok"},
|
||||
http.StatusOK)
|
||||
}
|
||||
|
||||
// applyModeChar applies a single user mode character.
|
||||
// Returns (applied, error).
|
||||
func (hdlr *Handlers) applyModeChar(
|
||||
ctx context.Context,
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
sessionID, clientID int64,
|
||||
nick, modeChar string,
|
||||
adding bool,
|
||||
) (bool, error) {
|
||||
switch modeChar {
|
||||
case "w":
|
||||
err := hdlr.params.Database.SetSessionWallops(
|
||||
ctx, sessionID, adding,
|
||||
)
|
||||
if err != nil {
|
||||
hdlr.log.Error(
|
||||
"set wallops mode failed", "error", err,
|
||||
)
|
||||
hdlr.respondError(
|
||||
writer, request,
|
||||
"internal error",
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
|
||||
return false, fmt.Errorf(
|
||||
"set wallops: %w", err,
|
||||
)
|
||||
}
|
||||
case "o":
|
||||
// +o cannot be set via MODE, only via OPER.
|
||||
if adding {
|
||||
hdlr.respondIRCError(
|
||||
writer, request, clientID, sessionID,
|
||||
irc.ErrUmodeUnknownFlag, nick, nil,
|
||||
"Unknown MODE flag",
|
||||
)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err := hdlr.params.Database.SetSessionOper(
|
||||
ctx, sessionID, false,
|
||||
)
|
||||
if err != nil {
|
||||
hdlr.log.Error(
|
||||
"clear oper mode failed", "error", err,
|
||||
)
|
||||
hdlr.respondError(
|
||||
writer, request,
|
||||
"internal error",
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
|
||||
return false, fmt.Errorf(
|
||||
"clear oper: %w", err,
|
||||
)
|
||||
}
|
||||
default:
|
||||
hdlr.respondIRCError(
|
||||
writer, request, clientID, sessionID,
|
||||
irc.ErrUmodeUnknownFlag, nick, nil,
|
||||
"Unknown MODE flag",
|
||||
)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user