diff --git a/Gemfile b/Gemfile index 0d8e8d7a..58e26f01 100644 --- a/Gemfile +++ b/Gemfile @@ -44,6 +44,7 @@ group :test do end group :development, :test do + gem 'puma' gem 'better_errors' gem 'binding_of_caller' gem 'pry-byebug' diff --git a/Gemfile.lock b/Gemfile.lock index d104cb51..a1aafb05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -167,6 +167,7 @@ GEM pry (~> 0.10) pry-rails (0.3.4) pry (>= 0.9.10) + puma (2.15.3) pundit (1.1.0) activesupport (>= 3.0.0) pundit_extra (0.3.0) @@ -298,6 +299,7 @@ DEPENDENCIES pg pry-byebug pry-rails + puma pundit pundit_extra rack-attack diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 11633bea..6af52fbd 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,6 +14,7 @@ //= require jquery //= require jquery-ui //= require jquery_ujs +//= require action_cable //= require_directory ./lib //= require ./webpacked/metamaps.bundle //= require ./Metamaps.ServerData diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 00000000..8eb318cd --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,20 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = find_verified_user + logger.add_tags 'ActionCable', current_user.name + end + + protected + def find_verified_user + verified_user = User.find_by(id: cookies.signed['user.id']) + if verified_user && cookies.signed['user.expires_at'] > Time.now + verified_user + else + reject_unauthorized_connection + end + end + end +end diff --git a/app/channels/topic_channel.rb b/app/channels/topic_channel.rb new file mode 100644 index 00000000..bc5cd834 --- /dev/null +++ b/app/channels/topic_channel.rb @@ -0,0 +1,7 @@ +class TopicChannel < ApplicationCable::Channel + # Called when the consumer has successfully + # become a subscriber of this channel. + def subscribed + stream_from "topic_#{params[:id]}" + end +end diff --git a/app/models/synapse.rb b/app/models/synapse.rb index d14a18f4..f0eab9c6 100644 --- a/app/models/synapse.rb +++ b/app/models/synapse.rb @@ -22,6 +22,7 @@ class Synapse < ApplicationRecord where(topic1_id: topic_id).or(where(topic2_id: topic_id)) } + after_create :after_created after_update :after_updated delegate :name, to: :user, prefix: true @@ -42,6 +43,35 @@ class Synapse < ApplicationRecord super(methods: [:user_name, :user_image, :collaborator_ids]) end + def after_created + filteredSynapse = { + id: id, + permission: permission, + user_id: user_id, + collaborator_ids: collaborator_ids + } + filteredTopic1 = { + id: topic1_id, + permission: topic1.permission, + user_id: topic1.user_id, + collaborator_ids: topic1.collaborator_ids + } + filteredTopic2 = { + id: topic2_id, + permission: topic2.permission, + user_id: topic2.user_id, + collaborator_ids: topic2.collaborator_ids + } + data = { + synapse: filteredSynapse, + topic1: filteredTopic1, + topic2: filteredTopic2 + } + # include the filtered topics here too + ActionCable.server.broadcast 'topic_' + topic1_id.to_s, type: 'newSynapse', data: data + ActionCable.server.broadcast 'topic_' + topic2_id.to_s, type: 'newSynapse', data: data + end + def after_updated attrs = ['desc', 'category', 'permission', 'defer_to_map_id'] if attrs.any? {|k| changed_attributes.key?(k)} diff --git a/config/initializers/warden_hooks.rb b/config/initializers/warden_hooks.rb new file mode 100644 index 00000000..da983955 --- /dev/null +++ b/config/initializers/warden_hooks.rb @@ -0,0 +1,10 @@ +Warden::Manager.after_set_user do |user,auth,opts| + scope = opts[:scope] + auth.cookies.signed["#{scope}.id"] = user.id + auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now +end +Warden::Manager.before_logout do |user, auth, opts| + scope = opts[:scope] + auth.cookies.signed["#{scope}.id"] = nil + auth.cookies.signed["#{scope}.expires_at"] = nil +end diff --git a/config/routes.rb b/config/routes.rb index 000784f6..4dc44c91 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true Metamaps::Application.routes.draw do use_doorkeeper + mount ActionCable.server => '/cable' root to: 'main#home', via: :get get 'request', to: 'main#requestinvite', as: :request diff --git a/frontend/src/Metamaps/Cable.js b/frontend/src/Metamaps/Cable.js new file mode 100644 index 00000000..03ec82d4 --- /dev/null +++ b/frontend/src/Metamaps/Cable.js @@ -0,0 +1,50 @@ +/* global $, ActionCable */ + +import Active from './Active' +import DataModel from './DataModel' + +const Cable = { + topicSubs: {}, + init: () => { + let self = Cable + self.cable = ActionCable.createConsumer() + }, + subAllTopics: () => { + let self = Cable + DataModel.Topics.models.forEach(topic => self.subTopic(topic.id)) + }, + subTopic: id => { + let self = Cable + self.topicSubs[id] = self.cable.subscriptions.create({ + channel: 'TopicChannel', + id: id + }, { + received: event => self[event.type](event.data) + }) + }, + unsubTopic: id => { + let self = Cable + self.topicSubs[id] && self.topicSubs[id].unsubscribe() + delete self.topicSubs[id] + }, + unsubAllTopics: () => { + let self = Cable + Object.keys(self.topicSubs).forEach(id => { + self.topicSubs[id].unsubscribe() + }) + self.topicSubs = {} + }, + // begin event functions + newSynapse: data => { + console.log(data) + const m = Active.Mapper + const s = new DataModel.Synapse(data.synapse) + const t1 = new DataModel.Topic(data.topic1) + const t2 = new DataModel.Topic(data.topic2) + if (t1.authorizeToShow(m) && t2.authorizeToShow(m) && s.authorizeToShow(m)) { + console.log('authorized') + } + } +} + +export default Cable diff --git a/frontend/src/Metamaps/DataModel/Synapse.js b/frontend/src/Metamaps/DataModel/Synapse.js index e6a7f1c7..be37e095 100644 --- a/frontend/src/Metamaps/DataModel/Synapse.js +++ b/frontend/src/Metamaps/DataModel/Synapse.js @@ -87,6 +87,10 @@ const Synapse = Backbone.Model.extend({ if (mapper && (this.get('permission') === 'commons' || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true else return false }, + authorizeToShow: function(mapper) { + if (this.get('permission') !== 'private' || (mapper && this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true + else return false + }, authorizePermissionChange: function(mapper) { if (mapper && this.get('user_id') === mapper.get('id')) return true else return false diff --git a/frontend/src/Metamaps/DataModel/Topic.js b/frontend/src/Metamaps/DataModel/Topic.js index 0d71c973..aaf0937b 100644 --- a/frontend/src/Metamaps/DataModel/Topic.js +++ b/frontend/src/Metamaps/DataModel/Topic.js @@ -88,6 +88,10 @@ const Topic = Backbone.Model.extend({ return false } }, + authorizeToShow: function(mapper) { + if (this.get('permission') !== 'private' || (mapper && this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true + else return false + }, authorizePermissionChange: function(mapper) { if (mapper && this.get('user_id') === mapper.get('id')) return true else return false diff --git a/frontend/src/Metamaps/JIT.js b/frontend/src/Metamaps/JIT.js index 903cc11d..078b8991 100644 --- a/frontend/src/Metamaps/JIT.js +++ b/frontend/src/Metamaps/JIT.js @@ -640,14 +640,14 @@ const JIT = { } }, // this will just be used to patch the ForceDirected graphsettings with the few things which actually differ - background: { + /*background: { levelDistance: 200, numberOfCircles: 4, CanvasStyles: { strokeStyle: '#333', lineWidth: 1.5 } - }, + },*/ levelDistance: 200 }, onMouseEnter: function(edge) { diff --git a/frontend/src/Metamaps/Topic.js b/frontend/src/Metamaps/Topic.js index 6b1aa8c1..c3afaf9d 100644 --- a/frontend/src/Metamaps/Topic.js +++ b/frontend/src/Metamaps/Topic.js @@ -4,6 +4,7 @@ import $jit from '../patched/JIT' import Active from './Active' import AutoLayout from './AutoLayout' +import Cable from './Cable' import Create from './Create' import DataModel from './DataModel' import Filter from './Filter' @@ -43,6 +44,8 @@ const Topic = { DataModel.Synapses = new DataModel.SynapseCollection(data.synapses) DataModel.attachCollectionEvents() + DataModel.Topics.models.forEach(topic => Cable.subTopic(topic.id)) + document.title = Active.Topic.get('name') + ' | Metamaps' // set filter mapper H3 text @@ -78,6 +81,7 @@ const Topic = { TopicCard.hideCard() SynapseCard.hideCard() Filter.close() + Cable.unsubAllTopics() } }, centerOn: function(nodeid, callback) { diff --git a/frontend/src/Metamaps/index.js b/frontend/src/Metamaps/index.js index be218aff..b5604994 100644 --- a/frontend/src/Metamaps/index.js +++ b/frontend/src/Metamaps/index.js @@ -2,9 +2,10 @@ import Account from './Account' import Active from './Active' import Admin from './Admin' import AutoLayout from './AutoLayout' -import DataModel from './DataModel' +import Cable from './Cable' import Control from './Control' import Create from './Create' +import DataModel from './DataModel' import Debug from './Debug' import Filter from './Filter' import GlobalUI, { @@ -38,9 +39,10 @@ Metamaps.Account = Account Metamaps.Active = Active Metamaps.Admin = Admin Metamaps.AutoLayout = AutoLayout -Metamaps.DataModel = DataModel +Metamaps.Cable = Cable Metamaps.Control = Control Metamaps.Create = Create +Metamaps.DataModel = DataModel Metamaps.Debug = Debug Metamaps.Filter = Filter Metamaps.GlobalUI = GlobalUI @@ -106,6 +108,8 @@ document.addEventListener('DOMContentLoaded', function() { JIT.prepareVizData() GlobalUI.showDiv('#infovis') } + + if (Active.Topic) Cable.subAllTopics() }) export default Metamaps