diff --git a/Server/api/channel_handler.go b/Server/api/channel_handler.go index e976516c..978790e6 100644 --- a/Server/api/channel_handler.go +++ b/Server/api/channel_handler.go @@ -76,7 +76,7 @@ func handleListChannels(svc *service.Services) http.HandlerFunc { return } - channels, err := svc.Channels.ListVisibleChannels(user.ID) + channels, err := svc.Channels.ListVisibleChannels(r.Context(), user.ID) if err != nil { slog.Error("handleListChannels", "err", err) writeJSON(w, http.StatusInternalServerError, errorResponse{ diff --git a/Server/api/dm_handler.go b/Server/api/dm_handler.go index fef4e44e..1b595469 100644 --- a/Server/api/dm_handler.go +++ b/Server/api/dm_handler.go @@ -73,7 +73,7 @@ func handleCreateDM(svc *service.Services) http.HandlerFunc { return } - result, err := svc.DMs.CreateDM(user.ID, req.RecipientID) + result, err := svc.DMs.CreateDM(r.Context(), user.ID, req.RecipientID) if err != nil { writeServiceError(w, err) return @@ -169,7 +169,7 @@ func handleBlockUser(svc *service.Services) http.HandlerFunc { return } - if err := svc.Blocks.BlockUser(user.ID, targetID); err != nil { + if err := svc.Blocks.BlockUser(r.Context(), user.ID, targetID); err != nil { writeServiceError(w, err) return } diff --git a/Server/api/invite_handler.go b/Server/api/invite_handler.go index 4030e810..50a12914 100644 --- a/Server/api/invite_handler.go +++ b/Server/api/invite_handler.go @@ -73,7 +73,7 @@ func handleCreateInvite(svc *service.Services) http.HandlerFunc { return } - inv, err := svc.Invites.CreateInvite(user.ID, req.MaxUses, req.ExpiresInHours) + inv, err := svc.Invites.CreateInvite(r.Context(), user.ID, req.MaxUses, req.ExpiresInHours) if err != nil { writeServiceError(w, err) return diff --git a/Server/api/profile_handler.go b/Server/api/profile_handler.go index 006115fe..00aaa177 100644 --- a/Server/api/profile_handler.go +++ b/Server/api/profile_handler.go @@ -133,7 +133,7 @@ func handleUpdateProfile(svc *service.Services, broadcaster ProfileBroadcaster) req.Avatar = &trimmed } - updated, err := svc.Users.UpdateProfile(user.ID, req.Username, req.Avatar) + updated, err := svc.Users.UpdateProfile(r.Context(), user.ID, req.Username, req.Avatar) if err != nil { writeServiceError(w, err) return diff --git a/Server/plugin/host_http.go b/Server/plugin/host_http.go index 34a9d787..a882f304 100644 --- a/Server/plugin/host_http.go +++ b/Server/plugin/host_http.go @@ -120,7 +120,7 @@ func (r *Registry) HTTPDo(ctx context.Context, inst *Instance, req HTTPRequest) if err != nil { return nil, fmt.Errorf("plugin http: do: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() // Cap body size so a hostile/large response cannot OOM the host. We // LimitReader to maxResponseBytes+1 so we can detect truncation. limited := io.LimitReader(resp.Body, maxResponseBytes+1) diff --git a/Server/plugin/host_ui.go b/Server/plugin/host_ui.go index 7340d17b..97d1e97d 100644 --- a/Server/plugin/host_ui.go +++ b/Server/plugin/host_ui.go @@ -98,7 +98,7 @@ func (r *Registry) AssetHandler(inst *Instance) http.Handler { http.NotFound(w, req) return } - defer f.Close() + defer func() { _ = f.Close() }() http.ServeContent(w, req, rel, info.ModTime(), f) }) } diff --git a/Server/service/block.go b/Server/service/block.go index 8ce52816..f60079f7 100644 --- a/Server/service/block.go +++ b/Server/service/block.go @@ -22,8 +22,8 @@ func NewBlockService(st store.Store) *BlockService { // BlockUser blocks a target user. Validates the target exists and // prevents self-blocking. -func (s *BlockService) BlockUser(blockerID, targetID int64) error { - ctx, span := telemetry.GlobalTracer("service/block").Start(context.Background(), "BlockService.BlockUser", +func (s *BlockService) BlockUser(ctx context.Context, blockerID, targetID int64) error { + ctx, span := telemetry.GlobalTracer("service/block").Start(ctx, "BlockService.BlockUser", telemetry.Int64("blocker_id", blockerID), telemetry.Int64("target_id", targetID), ) diff --git a/Server/service/channel.go b/Server/service/channel.go index 64ddb7af..ff079a61 100644 --- a/Server/service/channel.go +++ b/Server/service/channel.go @@ -29,9 +29,9 @@ func NewChannelService(st store.Store, perms *PermissionService) *ChannelService // ListVisibleChannels returns channels the user has ReadMessages permission for. // DM channels are excluded (they are accessed via DMService). -func (s *ChannelService) ListVisibleChannels(userID int64) ([]db.Channel, error) { +func (s *ChannelService) ListVisibleChannels(ctx context.Context, userID int64) ([]db.Channel, error) { // Phase B Step 8 — span the public service entrypoint. - ctx, span := telemetry.GlobalTracer("service/channel").Start(context.Background(), + ctx, span := telemetry.GlobalTracer("service/channel").Start(ctx, "ChannelService.ListVisibleChannels", telemetry.Int64("user_id", userID), ) @@ -57,9 +57,9 @@ func (s *ChannelService) ListVisibleChannels(userID int64) ([]db.Channel, error) if isAdmin { // Admin sees all non-DM channels. var visible []db.Channel - for _, ch := range all { - if ch.Type != "dm" { - visible = append(visible, ch) + for i := range all { + if all[i].Type != "dm" { + visible = append(visible, all[i]) } } return visible, nil diff --git a/Server/service/dm.go b/Server/service/dm.go index 9c17dea5..0cb4ad75 100644 --- a/Server/service/dm.go +++ b/Server/service/dm.go @@ -30,8 +30,8 @@ type CreateDMResult struct { // CreateDM creates or retrieves a DM channel between two users. // Validates that neither user has blocked the other. -func (s *DMService) CreateDM(userID, recipientID int64) (*CreateDMResult, error) { - ctx, span := telemetry.GlobalTracer("service/dm").Start(context.Background(), "DMService.CreateDM", +func (s *DMService) CreateDM(ctx context.Context, userID, recipientID int64) (*CreateDMResult, error) { + ctx, span := telemetry.GlobalTracer("service/dm").Start(ctx, "DMService.CreateDM", telemetry.Int64("user_id", userID), telemetry.Int64("recipient_id", recipientID), ) diff --git a/Server/service/invite.go b/Server/service/invite.go index ea57a62f..bebbd4f2 100644 --- a/Server/service/invite.go +++ b/Server/service/invite.go @@ -27,8 +27,8 @@ const maxInviteExpiryHoursVal = 720 func MaxInviteExpiryHours() int { return maxInviteExpiryHoursVal } // CreateInvite creates a new invite code with optional max uses and expiry. -func (s *InviteService) CreateInvite(createdBy int64, maxUses int, expiresInHours int) (*db.Invite, error) { - ctx, span := telemetry.GlobalTracer("service/invite").Start(context.Background(), "InviteService.CreateInvite", +func (s *InviteService) CreateInvite(ctx context.Context, createdBy int64, maxUses int, expiresInHours int) (*db.Invite, error) { + ctx, span := telemetry.GlobalTracer("service/invite").Start(ctx, "InviteService.CreateInvite", telemetry.Int64("created_by", createdBy), ) start := time.Now() diff --git a/Server/service/message.go b/Server/service/message.go index af2fe5b5..067b3453 100644 --- a/Server/service/message.go +++ b/Server/service/message.go @@ -116,10 +116,10 @@ func NewMessageService(st store.Store, perms *PermissionService, limiter *auth.R // SendMessage validates, persists, and prepares broadcast data for a new message. // Callers are responsible for emitting the appropriate events. -func (s *MessageService) SendMessage(p SendMessageParams) (*SendMessageResult, error) { +func (s *MessageService) SendMessage(ctx context.Context, p SendMessageParams) (*SendMessageResult, error) { // Phase B Step 8 — wrap the public service entrypoint in a tracing span // and a duration histogram. Both are no-ops in the default build. - ctx, span := telemetry.GlobalTracer("service/message").Start(context.Background(), "MessageService.SendMessage", + ctx, span := telemetry.GlobalTracer("service/message").Start(ctx, "MessageService.SendMessage", telemetry.Int64("user_id", p.UserID), telemetry.Int64("channel_id", p.ChannelID), ) diff --git a/Server/service/message_test.go b/Server/service/message_test.go index 02508097..599164a1 100644 --- a/Server/service/message_test.go +++ b/Server/service/message_test.go @@ -1,6 +1,7 @@ package service import ( + "context" "errors" "strings" "testing" @@ -34,7 +35,7 @@ func newTestMessageService() (*MessageService, *store.MemStore) { func TestSendMessage_Valid(t *testing.T) { svc, _ := newTestMessageService() - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -58,7 +59,7 @@ func TestSendMessage_Valid(t *testing.T) { func TestSendMessage_EmptyContent(t *testing.T) { svc, _ := newTestMessageService() - _, err := svc.SendMessage(SendMessageParams{ + _, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -79,7 +80,7 @@ func TestSendMessage_ExceedsMaxLength(t *testing.T) { // maxMessageLen is 4000 runes; create content that exceeds it. longContent := strings.Repeat("a", 4001) - _, err := svc.SendMessage(SendMessageParams{ + _, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -97,7 +98,7 @@ func TestSendMessage_ExceedsMaxLength(t *testing.T) { func TestSendMessage_ChannelNotFound(t *testing.T) { svc, _ := newTestMessageService() - _, err := svc.SendMessage(SendMessageParams{ + _, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 999, UserID: 1, Username: "alice", @@ -114,7 +115,7 @@ func TestSendMessage_ChannelNotFound(t *testing.T) { func TestSendMessage_InvalidChannelID(t *testing.T) { svc, _ := newTestMessageService() - _, err := svc.SendMessage(SendMessageParams{ + _, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 0, UserID: 1, Username: "alice", @@ -145,7 +146,7 @@ func TestSendMessage_NoPermission(t *testing.T) { permSvc := NewPermissionService(ms, checker) svc := NewMessageService(ms, permSvc, nil) - _, err := svc.SendMessage(SendMessageParams{ + _, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -163,7 +164,7 @@ func TestEditMessage_OwnerCanEdit(t *testing.T) { svc, _ := newTestMessageService() // Send a message first. - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -192,7 +193,7 @@ func TestEditMessage_NonOwnerFails(t *testing.T) { ms.SeedUser(&db.User{ID: 2, Username: "bob"}) // User 1 sends a message. - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -215,7 +216,7 @@ func TestEditMessage_NonOwnerFails(t *testing.T) { func TestEditMessage_EmptyContentFails(t *testing.T) { svc, _ := newTestMessageService() - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -237,7 +238,7 @@ func TestEditMessage_EmptyContentFails(t *testing.T) { func TestDeleteMessage_OwnerCanDelete(t *testing.T) { svc, _ := newTestMessageService() - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -265,7 +266,7 @@ func TestDeleteMessage_NonOwnerWithoutModFails(t *testing.T) { ms.SeedUser(&db.User{ID: 2, Username: "bob"}) // User 1 sends. - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -312,7 +313,7 @@ func TestDeleteMessage_ModCanDeleteOthersMessage(t *testing.T) { svc := NewMessageService(ms, permSvc, nil) // User 1 sends a message. - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", @@ -347,7 +348,7 @@ func TestDeleteMessage_InvalidMessageID(t *testing.T) { func TestSendMessage_HTMLSanitized(t *testing.T) { svc, _ := newTestMessageService() - result, err := svc.SendMessage(SendMessageParams{ + result, err := svc.SendMessage(context.Background(), SendMessageParams{ ChannelID: 10, UserID: 1, Username: "alice", diff --git a/Server/service/user.go b/Server/service/user.go index 4569d1b6..3a267270 100644 --- a/Server/service/user.go +++ b/Server/service/user.go @@ -24,8 +24,8 @@ func NewUserService(st store.Store) *UserService { // UpdateProfile updates a user's username and/or avatar. // Returns the updated user for response building. -func (s *UserService) UpdateProfile(userID int64, username string, avatar *string) (*db.User, error) { - ctx, span := telemetry.GlobalTracer("service/user").Start(context.Background(), "UserService.UpdateProfile", +func (s *UserService) UpdateProfile(ctx context.Context, userID int64, username string, avatar *string) (*db.User, error) { + ctx, span := telemetry.GlobalTracer("service/user").Start(ctx, "UserService.UpdateProfile", telemetry.Int64("user_id", userID), ) start := time.Now() diff --git a/Server/store/sqlite_events.go b/Server/store/sqlite_events.go index 08980396..39af874d 100644 --- a/Server/store/sqlite_events.go +++ b/Server/store/sqlite_events.go @@ -40,7 +40,7 @@ func (s *SQLiteStore) GetEventsSince(ctx context.Context, afterSeq int64, limit if err != nil { return nil, fmt.Errorf("GetEventsSince: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() return scanEventRows(rows) } @@ -61,7 +61,7 @@ func (s *SQLiteStore) GetEventsSinceForChannels(ctx context.Context, afterSeq in if err != nil { return nil, fmt.Errorf("GetEventsSinceForChannels (global only): %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() return scanEventRows(rows) } @@ -87,7 +87,7 @@ func (s *SQLiteStore) GetEventsSinceForChannels(ctx context.Context, afterSeq in if err != nil { return nil, fmt.Errorf("GetEventsSinceForChannels: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() return scanEventRows(rows) } @@ -180,7 +180,7 @@ func (s *SQLiteStore) ListPlugins(ctx context.Context) ([]db.PluginRow, error) { if err != nil { return nil, fmt.Errorf("ListPlugins: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() var out []db.PluginRow for rows.Next() { var p db.PluginRow @@ -233,7 +233,7 @@ func (s *SQLiteStore) PluginKVScan(ctx context.Context, pluginID int64, prefix s if err != nil { return nil, fmt.Errorf("PluginKVScan: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() out := make(map[string][]byte) for rows.Next() { var k string diff --git a/Server/ws/handlers_chat.go b/Server/ws/handlers_chat.go index 1d8d2e9a..02bed897 100644 --- a/Server/ws/handlers_chat.go +++ b/Server/ws/handlers_chat.go @@ -15,11 +15,11 @@ func registerChatHandlers(r *HandlerRegistry, deps ChatDeps) { } // handleChatSendV2 processes a chat_send command via the MessageService. -func handleChatSendV2(_ context.Context, cmd Command, info ClientInfo, deps any) Result { +func handleChatSendV2(ctx context.Context, cmd Command, info ClientInfo, deps any) Result { d := deps.(ChatDeps) sendCmd := cmd.(ChatSendCmd) - result, err := d.MessageSvc.SendMessage(service.SendMessageParams{ + result, err := d.MessageSvc.SendMessage(ctx, service.SendMessageParams{ ChannelID: sendCmd.ChannelID(), UserID: info.UserID, Username: info.Username,