Add raw implementation of notifications interactive channel

This commit is contained in:
Eugene Burmakin 2024-11-03 14:37:01 +01:00
parent 34c12a9536
commit 14b7397840
12 changed files with 125 additions and 1 deletions

View file

@ -1,4 +1,21 @@
# frozen_string_literal: true
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if (verified_user = env['warden'].user)
verified_user
else
reject_unauthorized_connection
end
end
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class NotificationsChannel < ApplicationCable::Channel
def subscribed
stream_for current_user
end
end

View file

@ -8,3 +8,4 @@ import "leaflet"
import "leaflet-providers"
import "chartkick"
import "Chart.bundle"
import "./channels"

View file

@ -0,0 +1,6 @@
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()

View file

@ -0,0 +1,2 @@
// Import all the channels to be used by Action Cable
import "notifications_channel"

View file

@ -0,0 +1,15 @@
import consumer from "./consumer"
consumer.subscriptions.create("NotificationsChannel", {
connected() {
console.log("Connected to the notifications channel!");
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
// Called when there's incoming data on the websocket for this channel
}
});

View file

@ -0,0 +1,47 @@
import { Controller } from "@hotwired/stimulus"
import consumer from "../channels/consumer"
export default class extends Controller {
static targets = ["container"]
static values = { userId: Number }
connect() {
console.log("Controller connecting...")
// Ensure we clean up any existing subscription
if (this.subscription) {
console.log("Cleaning up existing subscription")
this.subscription.unsubscribe()
}
this.subscription = consumer.subscriptions.create("NotificationsChannel", {
connected: () => {
console.log("Connected to NotificationsChannel", this.subscription)
},
disconnected: () => {
console.log("Disconnected from NotificationsChannel")
},
received: (data) => {
console.log("Received notification:", data, "Subscription:", this.subscription)
this.displayNotification(data)
}
})
}
disconnect() {
console.log("Controller disconnecting...")
if (this.subscription) {
this.subscription.unsubscribe()
this.subscription = null
}
}
displayNotification(data) {
console.log("Notification received:", data) // For debugging
const notification = document.createElement("div")
notification.classList.add("notification", `notification-${data.kind}`)
notification.innerHTML = `<strong>${data.title}</strong>: ${data.content}`
this.containerTarget.appendChild(notification)
setTimeout(() => notification.remove(), 5000) // Auto-hide after 5 seconds
}
}

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Notification < ApplicationRecord
after_create_commit :broadcast_notification
belongs_to :user
validates :title, :content, :kind, presence: true
@ -12,4 +14,18 @@ class Notification < ApplicationRecord
def read?
read_at.present?
end
private
def broadcast_notification
Rails.logger.debug "Broadcasting notification to #{user.id}"
NotificationsChannel.broadcast_to(
user,
{
title: title,
content: content,
kind: kind
}
)
end
end

View file

@ -3,6 +3,7 @@
<head>
<title><%= full_title(yield(:title)) %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= action_cable_meta_tag %>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
@ -20,6 +21,9 @@
<div class='container mx-auto'>
<%= render 'shared/navbar' %>
<%= render 'shared/flash' %>
<div data-controller="notifications" data-notifications-user-id-value="<%= current_user.id %>">
<div data-notifications-target="container" class="notifications-container"></div>
</div>
<div class="flex flex-row gap-5 w-full px-5">
<%= yield %>
</div>

View file

@ -16,4 +16,7 @@ pin 'leaflet-providers' # @2.0.0
pin 'chartkick', to: 'chartkick.js'
pin 'Chart.bundle', to: 'Chart.bundle.js'
pin 'leaflet.heat' # @0.2.0
pin "leaflet-draw" # @1.0.4
pin 'leaflet-draw' # @1.0.4
pin '@rails/actioncable', to: 'actioncable.esm.js'
pin_all_from 'app/javascript/channels', under: 'channels'
pin 'notifications_channel', to: 'channels/notifications_channel.js'

View file

@ -3,6 +3,7 @@
require 'sidekiq/web'
Rails.application.routes.draw do
mount ActionCable.server => '/cable'
mount Rswag::Api::Engine => '/api-docs'
mount Rswag::Ui::Engine => '/api-docs'
authenticate :user, ->(u) { u.admin? } do

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe NotificationsChannel, type: :channel do
pending "add some examples to (or delete) #{__FILE__}"
end