Files
OwnCord/Server/ws/handlers_chat.go
T
J3vb 81c121c2bd fix(server): resolve golangci-lint contextcheck, errcheck, gocritic findings
contextcheck: add ctx context.Context as first param to ListVisibleChannels,
BlockUser, CreateDM, CreateInvite, UpdateProfile, SendMessage; pass r.Context()
from HTTP handlers and ctx from WS handler; replace context.Background() in
telemetry spans with the propagated ctx.

errcheck: suppress justified Close() errors — defer func(){ _ = rows.Close() }()
in sqlite_events.go (idiomatic; rows.Err() checked), _ = resp.Body.Close() in
host_http.go (body fully consumed), _ = f.Close() in host_ui.go (read-only fd).

gocritic/rangeValCopy: rewrite for _, ch := range all (line 60) to indexed loop
in ChannelService.ListVisibleChannels to avoid 144-byte per-iteration copy.
2026-04-07 09:10:14 +02:00

148 lines
4.7 KiB
Go

package ws
import (
"context"
"errors"
"github.com/owncord/server/service"
)
// registerChatHandlers registers all chat-related V2 message handlers.
func registerChatHandlers(r *HandlerRegistry, deps ChatDeps) {
r.RegisterV2(MsgTypeChatSend, handleChatSendV2, deps)
r.RegisterV2(MsgTypeChatEdit, handleChatEditV2, deps)
r.RegisterV2(MsgTypeChatDelete, handleChatDeleteV2, deps)
}
// handleChatSendV2 processes a chat_send command via the MessageService.
func handleChatSendV2(ctx context.Context, cmd Command, info ClientInfo, deps any) Result {
d := deps.(ChatDeps)
sendCmd := cmd.(ChatSendCmd)
result, err := d.MessageSvc.SendMessage(ctx, service.SendMessageParams{
ChannelID: sendCmd.ChannelID(),
UserID: info.UserID,
Username: info.Username,
Avatar: info.Avatar,
RoleName: info.RoleName,
Content: sendCmd.Content(),
ReplyTo: sendCmd.ReplyTo(),
AttachmentIDs: sendCmd.Attachments(),
})
if err != nil {
return serviceErrorToResult(err)
}
// Build attachment data for broadcast.
var attData []map[string]any
for _, ai := range result.Attachments {
attData = append(attData, map[string]any{
"id": ai.ID,
"filename": ai.Filename,
"size": ai.Size,
"mime": ai.Mime,
"url": ai.URL,
})
}
reply := buildChatSendOK(info.ReqID, result.MessageID, result.Timestamp)
broadcast := buildChatMessage(result.MessageID, sendCmd.ChannelID(), info.UserID,
info.Username, info.Avatar, info.RoleName, result.Content, result.Timestamp,
sendCmd.ReplyTo(), attData)
if !result.IsDM {
return Result{
Reply: reply,
Events: []Event{MessageSentChannelEvent{channelID: sendCmd.ChannelID(), payload: broadcast}},
}
}
// DM path: build dm_channel_open events + sequenced message.
var events []Event
if result.SenderUser != nil {
for _, pid := range result.OpenedDMFor {
events = append(events, DMChannelOpenEvent{
targetUserID: pid,
payload: buildDMChannelOpen(sendCmd.ChannelID(), result.SenderUser),
})
}
}
events = append(events, MessageSentDMEvent{
channelID: sendCmd.ChannelID(),
participantIDs: result.ParticipantIDs,
payload: broadcast,
})
return Result{Reply: reply, Events: events}
}
// handleChatEditV2 processes a chat_edit command via the MessageService.
func handleChatEditV2(_ context.Context, cmd Command, info ClientInfo, deps any) Result {
d := deps.(ChatDeps)
editCmd := cmd.(ChatEditCmd)
result, err := d.MessageSvc.EditMessage(info.UserID, editCmd.MessageID(), editCmd.Content())
if err != nil {
return serviceErrorToResult(err)
}
editedPayload := buildChatEdited(result.MessageID, result.ChannelID, result.Content, result.EditedAt)
if result.IsDM {
return Result{Events: []Event{MessageEditedDMEvent{
channelID: result.ChannelID,
participantIDs: result.ParticipantIDs,
payload: editedPayload,
}}}
}
return Result{Events: []Event{MessageEditedChannelEvent{
channelID: result.ChannelID,
payload: editedPayload,
}}}
}
// handleChatDeleteV2 processes a chat_delete command via the MessageService.
func handleChatDeleteV2(_ context.Context, cmd Command, info ClientInfo, deps any) Result {
d := deps.(ChatDeps)
deleteCmd := cmd.(ChatDeleteCmd)
result, err := d.MessageSvc.DeleteMessage(info.UserID, deleteCmd.MessageID())
if err != nil {
return serviceErrorToResult(err)
}
deletedPayload := buildChatDeleted(result.MessageID, result.ChannelID)
if result.IsDM {
return Result{Events: []Event{MessageDeletedDMEvent{
channelID: result.ChannelID,
participantIDs: result.ParticipantIDs,
payload: deletedPayload,
}}}
}
return Result{Events: []Event{MessageDeletedChannelEvent{
channelID: result.ChannelID,
payload: deletedPayload,
}}}
}
// serviceErrorToResult converts a service-layer error to a WS Result.
func serviceErrorToResult(err error) Result {
switch {
case errors.Is(err, service.ErrRateLimited):
return Result{Error: ClientError{Code: ErrCodeRateLimited, Message: err.Error()}}
case errors.Is(err, service.ErrSlowMode):
return Result{Error: ClientError{Code: ErrCodeSlowMode, Message: err.Error()}}
case errors.Is(err, service.ErrBadRequest):
return Result{Error: ClientError{Code: ErrCodeBadRequest, Message: err.Error()}}
case errors.Is(err, service.ErrNotFound):
return Result{Error: ClientError{Code: ErrCodeNotFound, Message: err.Error()}}
case errors.Is(err, service.ErrForbidden), errors.Is(err, service.ErrBlocked),
errors.Is(err, service.ErrDeletedMessage):
return Result{Error: ClientError{Code: ErrCodeForbidden, Message: err.Error()}}
case errors.Is(err, service.ErrConflict):
return Result{Error: ClientError{Code: ErrCodeConflict, Message: err.Error()}}
default:
return Result{Error: ClientError{Code: ErrCodeInternal, Message: err.Error()}}
}
}