// backend/device_events.go package main import ( "context" "encoding/json" ) func (s *Server) publishDeviceChangeEvent( ctx context.Context, actorUserID string, deviceID string, notificationType string, title string, message string, data map[string]any, ) error { if data == nil { data = map[string]any{} } data["deviceId"] = deviceID data["actorUserId"] = actorUserID dataJSON, err := json.Marshal(data) if err != nil { return err } rows, err := s.db.Query( ctx, ` SELECT id::TEXT FROM users WHERE 'admin' = ANY(rights) OR 'devices:read' = ANY(rights) OR 'devices:write' = ANY(rights) `, ) if err != nil { return err } defer rows.Close() for rows.Next() { var userID string if err := rows.Scan(&userID); err != nil { return err } var notification Notification err = s.db.QueryRow( ctx, ` INSERT INTO notifications ( user_id, type, title, message, visible, entity_type, entity_id, data ) VALUES ( $1, $2, $3, $4, false, 'device', $5, $6 ) RETURNING id::TEXT, user_id::TEXT, type, title, message, entity_type, COALESCE(entity_id::TEXT, ''), data, visible, read_at, created_at `, userID, notificationType, title, message, deviceID, dataJSON, ).Scan( ¬ification.ID, ¬ification.UserID, ¬ification.Type, ¬ification.Title, ¬ification.Message, ¬ification.EntityType, ¬ification.EntityID, ¬ification.Data, ¬ification.Visible, ¬ification.ReadAt, ¬ification.CreatedAt, ) if err != nil { return err } notificationsBroker.publish(notification) } return rows.Err() }