effective_messaging 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +131 -0
  4. data/Rakefile +18 -0
  5. data/app/assets/config/effective_messaging_manifest.js +3 -0
  6. data/app/assets/javascripts/effective_messaging/base.js +0 -0
  7. data/app/assets/javascripts/effective_messaging.js +1 -0
  8. data/app/assets/stylesheets/effective_messaging/base.scss +0 -0
  9. data/app/assets/stylesheets/effective_messaging.scss +1 -0
  10. data/app/controllers/admin/chat_messages_controller.rb +13 -0
  11. data/app/controllers/admin/chats_controller.rb +13 -0
  12. data/app/controllers/effective/chats_controller.rb +16 -0
  13. data/app/datatables/admin/effective_chat_messages_datatable.rb +30 -0
  14. data/app/datatables/admin/effective_chats_datatable.rb +51 -0
  15. data/app/datatables/effective_chats_datatable.rb +21 -0
  16. data/app/helpers/effective_messaging_helper.rb +3 -0
  17. data/app/mailers/effective/messaging_mailer.rb +59 -0
  18. data/app/models/concerns/effective_messaging_user.rb +47 -0
  19. data/app/models/effective/chat.rb +133 -0
  20. data/app/models/effective/chat_message.rb +42 -0
  21. data/app/models/effective/chat_user.rb +48 -0
  22. data/app/views/admin/chat_messages/_chat_message.html.haml +1 -0
  23. data/app/views/admin/chats/_chat.html.haml +1 -0
  24. data/app/views/admin/chats/_form.html.haml +8 -0
  25. data/app/views/admin/chats/_form_chat.html.haml +17 -0
  26. data/app/views/admin/chats/_layout.html.haml +2 -0
  27. data/app/views/effective/chats/_chat.html.haml +10 -0
  28. data/app/views/effective/chats/_chat_message.html.haml +10 -0
  29. data/app/views/effective/chats/_form.html.haml +27 -0
  30. data/app/views/effective/chats/_layout.html.haml +2 -0
  31. data/app/views/effective/chats/_summary.html.haml +13 -0
  32. data/app/views/effective/messaging/_dashboard.html.haml +10 -0
  33. data/app/views/effective/messaging_mailer/chat_new_message.liquid +11 -0
  34. data/config/effective_messaging.rb +27 -0
  35. data/config/routes.rb +18 -0
  36. data/db/migrate/01_create_effective_messaging.rb.erb +44 -0
  37. data/db/seeds.rb +1 -0
  38. data/lib/effective_messaging/engine.rb +18 -0
  39. data/lib/effective_messaging/version.rb +3 -0
  40. data/lib/effective_messaging.rb +22 -0
  41. data/lib/generators/effective_messaging/install_generator.rb +32 -0
  42. data/lib/generators/templates/effective_messaging_mailer_preview.rb +4 -0
  43. data/lib/tasks/effective_messaging_tasks.rake +8 -0
  44. metadata +281 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6193093b822303bfc098d13ddaa7fe17787571ba0f65a5531ac0978272381320
4
+ data.tar.gz: b305d1836ab2a6f37b1236a986f36753d9194b969cc1105431733720a4d68535
5
+ SHA512:
6
+ metadata.gz: b9b841f95b963670a6471bc6f28ae379ca99e43c7ee686f6d368c9853e4db1aaa2cd50dd7c1fdf15f20fefdb5f43a2d062e103074fbe01a9a9335b88bb465b20
7
+ data.tar.gz: e9e6c2cd7a336d3bf1fb547bceca70c49a2089728e4927b806ae77d78c9ec04836b8217b6a854be56f8e8af3cc96a01d2ee44a1973c104bef5642a8e5a2030cd
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # Effective Messaging
2
+
3
+ Centralize all communications between one or more users.
4
+
5
+ ## Getting Started
6
+
7
+ This requires Rails 6+ and Twitter Bootstrap 4 and just works with Devise.
8
+
9
+ Please first install the [effective_datatables](https://github.com/code-and-effect/effective_datatables) gem.
10
+
11
+ Please download and install the [Twitter Bootstrap4](http://getbootstrap.com)
12
+
13
+ Add to your Gemfile:
14
+
15
+ ```ruby
16
+ gem 'effective_messaging'
17
+ ```
18
+
19
+ Run the bundle command to install it:
20
+
21
+ ```console
22
+ bundle install
23
+ ```
24
+
25
+ Then run the generator:
26
+
27
+ ```ruby
28
+ rails generate effective_messaging:install
29
+ ```
30
+
31
+ The generator will install an initializer which describes all configuration options and creates a database migration.
32
+
33
+ If you want to tweak the table names, manually adjust both the configuration file and the migration now.
34
+
35
+ Then migrate the database:
36
+
37
+ ```ruby
38
+ rake db:migrate
39
+ ```
40
+
41
+ Please add the following to your User model:
42
+
43
+ ```ruby
44
+ effective_messaging_user # effective_messaging_user
45
+
46
+ # Effective messaging
47
+ def effective_messaging_display_name
48
+ to_s
49
+ end
50
+
51
+ # An anonymous token for your users
52
+ def effective_messaging_anonymous_name
53
+ 'Anonymous' + Base64::encode64("#{id}-#{created_at.strftime('%F')}").chomp.first(8)
54
+ end
55
+ ```
56
+
57
+ Add dashboard:
58
+
59
+ ```haml
60
+ .card.card-dashboard.mb-4
61
+ .card-body= render 'effective/messaging/dashboard'
62
+ ```
63
+
64
+ Add a link to the admin menu:
65
+
66
+ ```haml
67
+ - if can?(:admin, :effective_messaging) && can?(:index, Effective::Chat)
68
+ = nav_link_to 'Chats', effective_messaging.admin_chats_path
69
+
70
+ - if can?(:admin, :effective_messaging) && can?(:index, Effective::ChatMessage)
71
+ = nav_link_to 'Chat Messages', effective_messaging.admin_chat_messages_path
72
+ ```
73
+
74
+ ## Authorization
75
+
76
+ All authorization checks are handled via the effective_resources gem found in the `config/initializers/effective_resources.rb` file.
77
+
78
+ ## Permissions
79
+
80
+ The permissions you actually want to define are as follows (using CanCan):
81
+
82
+ ```ruby
83
+ can(:index, Effective::Chat)
84
+ can([:show, :update], Effective::Chat) { |chat| user.chat_user(chat: chat).present? }
85
+
86
+ if user.admin?
87
+ can :admin, :effective_messaging
88
+
89
+ can(crud, Effective::Chat)
90
+ can(crud, Effective::ChatUser)
91
+ can(crud - [:new, :create], Effective::ChatMessage)
92
+ end
93
+ ```
94
+
95
+ ## Creating a chat
96
+
97
+ ```ruby
98
+ users = User.where(id: [1,2,3])
99
+
100
+ chat = Effective::Chat.new(title: 'A cool chat', anonymous: true, users: users)
101
+ chat.save!
102
+ ```
103
+
104
+ and you can just render it *outside of a form*:
105
+
106
+ ```haml
107
+ - chat = Effective::Chat.first
108
+ = render(chat)
109
+ = render('effective/chats/chat', chat: chat)
110
+ ```
111
+
112
+ ## License
113
+
114
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
115
+
116
+ ## Testing
117
+
118
+ Run tests by:
119
+
120
+ ```ruby
121
+ rails test
122
+ ```
123
+
124
+ ## Contributing
125
+
126
+ 1. Fork it
127
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
128
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
129
+ 4. Push to the branch (`git push origin my-new-feature`)
130
+ 5. Bonus points for test coverage
131
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
9
+
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,3 @@
1
+ //= link_directory ../javascripts .js
2
+ //= link_directory ../stylesheets .css
3
+ //= link_tree ../images
@@ -0,0 +1 @@
1
+ //= require_tree ./effective_messaging
@@ -0,0 +1 @@
1
+ @import 'effective_messaging/base';
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class ChatMessagesController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_messaging) }
5
+
6
+ include Effective::CrudController
7
+
8
+ def permitted_params
9
+ params.require(:effective_chat_message).permit!
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Admin
2
+ class ChatsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+ before_action { EffectiveResources.authorize!(self, :admin, :effective_messaging) }
5
+
6
+ include Effective::CrudController
7
+
8
+ def permitted_params
9
+ params.require(:effective_chat).permit!
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Effective
2
+ class ChatsController < ApplicationController
3
+ before_action(:authenticate_user!) if defined?(Devise)
4
+
5
+ include Effective::CrudController
6
+
7
+ resource_scope -> { Effective::Chat.all.deep }
8
+
9
+ private
10
+
11
+ def permitted_params
12
+ params.require(:effective_chat).except(:user_type).permit(chat_messages_attributes: [:body])
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module Admin
2
+ class EffectiveChatMessagesDatatable < Effective::Datatable
3
+
4
+ datatable do
5
+ col :id, visible: false
6
+ col :token, visible: false
7
+
8
+ col :created_at, label: 'Date'
9
+ col :chat, search: :string
10
+
11
+ col :name
12
+ col :body, label: 'Message'
13
+
14
+ actions_col
15
+ end
16
+
17
+ collection do
18
+ scope = Effective::ChatMessage.deep.all
19
+
20
+ if attributes[:user_id].present? && attributes[:user_type].present?
21
+ user = attributes[:user_type].constantize.find(attributes[:user_id])
22
+ scope = Effective::ChatMessage.where(user: user)
23
+ end
24
+
25
+ scope
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ module Admin
2
+ class EffectiveChatsDatatable < Effective::Datatable
3
+
4
+ datatable do
5
+ col :id, visible: false
6
+ col :token, visible: false
7
+
8
+ col :created_at, visible: false
9
+ col :updated_at
10
+
11
+ col :parent, visible: false
12
+ col :title
13
+ col :anonymous
14
+
15
+ col :chat_users
16
+
17
+ col(:chat_users, search: :string) do |chat|
18
+ if chat.anonymous?
19
+ chat.chat_users.map do |chat_user|
20
+ content_tag(:div, chat_user.name, class_name: 'col-resource_item')
21
+ end
22
+ else
23
+ chat.chat_users.map do |chat_user|
24
+ link = link_to(chat_user.name, "/admin/users/#{chat_user.user_id}/edit")
25
+ content_tag(:div, link, class_name: 'col-resource_item')
26
+ end
27
+ end.join
28
+ end.search do |collection, term|
29
+ Effective::Chat.search_by_chat_user_name(term)
30
+ end
31
+
32
+ col :chat_messages_count, label: 'Num Messages'
33
+
34
+ col :last_notified_at, visible: false
35
+
36
+ actions_col(new: false)
37
+ end
38
+
39
+ collection do
40
+ scope = Effective::Chat.deep.all
41
+
42
+ if attributes[:user_id].present? && attributes[:user_type].present?
43
+ user = attributes[:user_type].constantize.find(attributes[:user_id])
44
+ scope = Effective::Chat.for_user(user)
45
+ end
46
+
47
+ scope
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,21 @@
1
+ # Dashboard Chats
2
+ class EffectiveChatsDatatable < Effective::Datatable
3
+ datatable do
4
+ order :updated_at
5
+
6
+ col :token, visible: false
7
+ col :created_at, visible: false
8
+ col :updated_at, label: 'Last message'
9
+
10
+ col :title
11
+ col :chat_users, label: 'Participants'
12
+ col :chat_messages_count, label: 'Messages'
13
+
14
+ actions_col
15
+ end
16
+
17
+ collection do
18
+ Effective::Chat.deep.where(id: current_user.chats)
19
+ end
20
+
21
+ end
@@ -0,0 +1,3 @@
1
+ module EffectiveMessagingHelper
2
+
3
+ end
@@ -0,0 +1,59 @@
1
+ module Effective
2
+ class MessagingMailer < EffectiveMessaging.parent_mailer_class
3
+
4
+ include EffectiveMailer
5
+ include EffectiveEmailTemplatesMailer if EffectiveMessaging.use_effective_email_templates
6
+
7
+ def chat_new_message(chat, chat_user, opts = {})
8
+ raise('expected a chat') unless chat.kind_of?(Chat)
9
+ raise('expected a chat user') unless chat_user.kind_of?(ChatUser)
10
+
11
+ user = chat_user.user
12
+ raise('expected user to have an email') unless user.try(:email).present?
13
+
14
+ @assigns = assigns_for(chat).merge(assigns_for(chat_user))
15
+
16
+ subject = subject_for(__method__, "New Message - #{chat}", chat, opts)
17
+ headers = headers_for(chat, opts)
18
+
19
+ mail(to: user.email, subject: subject, **headers)
20
+ end
21
+
22
+ protected
23
+
24
+ def assigns_for(resource)
25
+ if resource.kind_of?(Effective::Chat)
26
+ return chat_assigns(resource)
27
+ end
28
+
29
+ if resource.kind_of?(Effective::ChatUser)
30
+ return chat_user_assigns(resource)
31
+ end
32
+
33
+ raise('unexpected resource')
34
+ end
35
+
36
+ def chat_assigns(chat)
37
+ raise('expected a chat') unless chat.kind_of?(Chat)
38
+
39
+ values = {
40
+ date: chat.created_at.strftime('%F'),
41
+ title: chat.title,
42
+ url: effective_messaging.chat_url(chat)
43
+ }.compact
44
+
45
+ { chat: values }
46
+ end
47
+
48
+ def chat_user_assigns(chat_user)
49
+ raise('expected a chat_user') unless chat_user.kind_of?(ChatUser)
50
+
51
+ values = {
52
+ name: chat_user.name
53
+ }.compact
54
+
55
+ { user: values }
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,47 @@
1
+ # EffectiveMessagingUser
2
+ #
3
+ # Mark your user model with effective_messaging_user to get all the includes
4
+
5
+ module EffectiveMessagingUser
6
+ extend ActiveSupport::Concern
7
+
8
+ module Base
9
+ def effective_messaging_user
10
+ include ::EffectiveMessagingUser
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def effective_messaging_user?; true; end
16
+ end
17
+
18
+ included do
19
+ has_many :chat_users, -> { Effective::ChatUser.sorted },
20
+ class_name: 'Effective::ChatUser', inverse_of: :user, dependent: :nullify
21
+
22
+ accepts_nested_attributes_for :chat_users, allow_destroy: true
23
+
24
+ has_many :chats, -> { Effective::Chat.sorted }, through: :chat_users, class_name: 'Effective::Chat'
25
+ accepts_nested_attributes_for :chats, allow_destroy: true
26
+ end
27
+
28
+ # Instance Methods
29
+ def chat_user(chat:)
30
+ chat_users.find { |cu| cu.chat_id == chat.id }
31
+ end
32
+
33
+ def effective_messaging_display_name
34
+ to_s
35
+ end
36
+
37
+ def effective_messaging_anonymous_name
38
+ raise('to be implemented by the app')
39
+ # Base64::encode64("#{id}-#{created_at.strftime('%F')}").chomp.first(8)
40
+ end
41
+
42
+ # Find or build
43
+ # def build_chat_user(chat:)
44
+ # chat_user(chat: chat) || chat_users.build(chat: chat)
45
+ # end
46
+
47
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Effective
4
+ class Chat < ActiveRecord::Base
5
+ self.table_name = EffectiveMessaging.chats_table_name.to_s
6
+
7
+ NOTIFY_AFTER = 2.minutes
8
+
9
+ attr_accessor :current_user
10
+ attr_accessor :user_type # Must be set when creating a chat by admin/new form, and passing user_ids
11
+
12
+ acts_as_tokened
13
+ log_changes if respond_to?(:log_changes)
14
+
15
+ # If we want to use chats in a has_many way
16
+ belongs_to :parent, polymorphic: true, optional: true
17
+
18
+ has_many :chat_users, -> { ChatUser.sorted }, inverse_of: :chat, dependent: :destroy
19
+ accepts_nested_attributes_for :chat_users, allow_destroy: true
20
+
21
+ has_many :chat_messages, -> { ChatMessage.sorted }, inverse_of: :chat, dependent: :destroy
22
+ accepts_nested_attributes_for :chat_messages, allow_destroy: true
23
+
24
+ effective_resource do
25
+ title :string
26
+ anonymous :boolean
27
+
28
+ chat_messages_count :integer
29
+ token :string
30
+
31
+ timestamps
32
+ end
33
+
34
+ scope :sorted, -> { order(id: :desc) }
35
+ scope :deep, -> { includes(:chat_users, :chat_messages) }
36
+
37
+ scope :for_user, -> (user) { where(id: ChatUser.where(user: user).select(:chat_id)) }
38
+ scope :search_by_chat_user_name, -> (name) { where(id: ChatUser.with_name(name).select(:chat_id)) }
39
+
40
+ validates :title, presence: true, length: { maximum: 255 }
41
+
42
+ def to_s
43
+ title.presence || 'New Chat'
44
+ end
45
+
46
+ # This is an API for sending a message.
47
+ def send_message!(user:, body:)
48
+ raise('expected an effective_messaging_user') unless user.class.respond_to?(:effective_messaging_user?)
49
+
50
+ chat_user = chat_user(user: user)
51
+ raise('expected user to be a chat_user in this chat') unless chat_user.present?
52
+
53
+ # Build and send message
54
+ chat_message = chat_messages.build(body: body, user: user, chat_user: chat_user, name: chat_user.name)
55
+
56
+ # Creates message, which also calls notify! below
57
+ save!
58
+
59
+ chat_message
60
+ end
61
+
62
+ # Called by an after_commit on chat message
63
+ def notify!(except: nil, force: false)
64
+ raise('expected a ChatUser') if except.present? && !except.kind_of?(ChatUser)
65
+
66
+ # Notify everyone in the chat except the user that created this message
67
+ notified = (chat_users - [except]).map do |chat_user|
68
+
69
+ # Only notify once every 5 minutes unless force
70
+ if chat_user.last_notified_at.present? && !force
71
+ next if (Time.zone.now - chat_user.last_notified_at) < NOTIFY_AFTER
72
+ end
73
+
74
+ EffectiveMessaging.send_email(:chat_new_message, self, chat_user)
75
+ chat_user
76
+ end
77
+
78
+ ChatUser.where(id: notified).update_all(last_notified_at: Time.zone.now) if notified.present?
79
+
80
+ # updated_at is when last message was sent
81
+ touch
82
+ end
83
+
84
+ def chat_user(user:)
85
+ raise('expected an effective_messaging_user') unless user.class.respond_to?(:effective_messaging_user?)
86
+ chat_users.find { |cu| cu.user_id == user.id && cu.user_type == user.class.name }
87
+ end
88
+
89
+ def build_chat_user(user:)
90
+ chat_user(user: user) || chat_users.build(user: user)
91
+ end
92
+
93
+ # Always build
94
+ def build_chat_message(user:, body: nil)
95
+ raise('expected an effective_messaging_user') unless user.class.respond_to?(:effective_messaging_user?)
96
+ chat_messages.build(user: user, body: body)
97
+ end
98
+
99
+ # Builders for polymorphic users form
100
+ def users
101
+ chat_users.map(&:user)
102
+ end
103
+
104
+ def user_ids
105
+ chat_users.map(&:user_id)
106
+ end
107
+
108
+ def user_ids=(user_ids)
109
+ raise('expected a user_type') unless user_type.present?
110
+
111
+ # The users in this chat
112
+ self.users = user_type.constantize.where(id: user_ids)
113
+
114
+ # Return user_ids
115
+ user_ids
116
+ end
117
+
118
+ def users=(users)
119
+ users = Array(users)
120
+ raise('expected a effective_messaging_user') unless users.all? { |user| user.class.respond_to?(:effective_messaging_user?) }
121
+
122
+ # Mark for destruction
123
+ chat_users.each { |cu| cu.mark_for_destruction unless users.include?(cu.user) }
124
+
125
+ # Find or build chat_users
126
+ users.each { |user| build_chat_user(user: user) }
127
+
128
+ # Return users
129
+ users
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Effective
4
+ class ChatMessage < ActiveRecord::Base
5
+ self.table_name = EffectiveMessaging.chat_messages_table_name.to_s
6
+
7
+ log_changes(to: :chat) if respond_to?(:log_changes)
8
+
9
+ belongs_to :chat, counter_cache: true
10
+
11
+ # Who sent this message
12
+ belongs_to :chat_user
13
+ belongs_to :user, polymorphic: true
14
+
15
+ effective_resource do
16
+ name :string # The name, anonymous or display, when sent
17
+ body :text
18
+
19
+ timestamps
20
+ end
21
+
22
+ scope :sorted, -> { order(:id) }
23
+ scope :deep, -> { includes(:chat) }
24
+
25
+ # Use the controller's current_user to initialize the chat_user, user and name
26
+ before_validation(if: -> { new_record? && chat.present? }) do
27
+ self.user ||= chat.current_user
28
+ self.chat_user ||= chat.chat_user(user: self.user)
29
+ self.name ||= self.chat_user&.name
30
+ end
31
+
32
+ after_commit(on: :create) { chat.notify!(except: chat_user) }
33
+
34
+ validates :name, presence: true
35
+ validates :body, presence: true
36
+
37
+ def to_s
38
+ body.presence || 'New Chat Message'
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Effective
4
+ class ChatUser < ActiveRecord::Base
5
+ self.table_name = EffectiveMessaging.chat_users_table_name.to_s
6
+
7
+ log_changes(to: :chat) if respond_to?(:log_changes)
8
+
9
+ belongs_to :chat
10
+ belongs_to :user, polymorphic: true
11
+
12
+ scope :with_name, -> (name) {
13
+ anonymous = where(chat_id: Chat.where(anonymous: true)).where('anonymous_name ILIKE ?', "%#{name}%")
14
+ displayed = where(chat_id: Chat.where(anonymous: false)).where('display_name ILIKE ?', "%#{name}%")
15
+
16
+ anonymous.or(displayed)
17
+ }
18
+
19
+ effective_resource do
20
+ display_name :string
21
+ anonymous_name :string
22
+
23
+ last_notified_at :datetime
24
+
25
+ timestamps
26
+ end
27
+
28
+ scope :sorted, -> { order(:id) }
29
+ scope :deep, -> { includes(:chat) }
30
+
31
+ before_validation do
32
+ self.display_name ||= user.effective_messaging_display_name
33
+ self.anonymous_name ||= user.effective_messaging_anonymous_name
34
+ end
35
+
36
+ validates :display_name, presence: true, length: { maximum: 255 }
37
+ validates :anonymous_name, presence: true, length: { maximum: 255 }
38
+
39
+ def to_s
40
+ name.presence || 'New Chat User'
41
+ end
42
+
43
+ def name
44
+ chat&.anonymous? ? anonymous_name : display_name
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1 @@
1
+ = render 'effective/chats/chat_message', chat_message: :chat_message
@@ -0,0 +1 @@
1
+ = render 'effective/chats/chat', chat: chat, admin: true
@@ -0,0 +1,8 @@
1
+ = tabs do
2
+ = tab "Chat" do
3
+ = render 'admin/chats/form_chat', chat: chat
4
+
5
+ - if chat.persisted?
6
+ - if chat.respond_to?(:log_changes_datatable)
7
+ = tab "Logs" do
8
+ = render_inline_datatable(chat.log_changes_datatable)
@@ -0,0 +1,17 @@
1
+ = effective_form_with(model: [:admin, chat], engine: true) do |f|
2
+ = f.text_field :title, label: "Title"
3
+
4
+ - if f.object.anonymous?
5
+ = f.static_field :anonymous do
6
+ %p Yes, this is an anonymous chat. User names and emails will not be displayed
7
+ - else
8
+ = f.check_box :anonymous, label: 'Yes, this is an anonymous chat', hint: 'user names and emails will not be displayed'
9
+
10
+ = f.hidden_field :user_type, value: current_user.class.name
11
+
12
+ - ajax_url = (@select2_ajax_path || effective_resources.users_admin_select2_ajax_index_path) unless Rails.env.test?
13
+
14
+ = f.select :user_ids, current_user.class.all, required: true,
15
+ ajax_url: ajax_url, hint: 'The users that can participate in this chat'
16
+
17
+ = effective_submit(f)
@@ -0,0 +1,2 @@
1
+ .effective-chat
2
+ = yield
@@ -0,0 +1,10 @@
1
+ = render 'layout' do
2
+ - chat_user = chat.chat_user(user: current_user)
3
+
4
+ - # New message form
5
+ - if chat_user.present? && EffectiveResources.authorized?(self, :update, chat)
6
+ = render('effective/chats/form', chat: chat)
7
+
8
+ - elsif EffectiveResources.authorized?(self, :show, chat)
9
+ = render('effective/chats/summary', chat: chat)
10
+ = render(partial: 'effective/chats/chat_message', collection: chat.chat_messages, chat: chat)
@@ -0,0 +1,10 @@
1
+ = render 'layout' do
2
+
3
+ - label = content_tag(:strong, chat_message.name) + ' commented ' + time_ago_in_words(chat_message.created_at) + ' ago'
4
+
5
+ .card
6
+ .card-header
7
+ = label
8
+ Yea
9
+ .card-body
10
+ = simple_format chat_message.body
@@ -0,0 +1,27 @@
1
+ = effective_form_with(model: chat, engine: true, remote: true) do |f|
2
+ -# Doesn't do anything, we just need something to submit thats not the ID
3
+ = f.hidden_field :user_type
4
+
5
+ - # This is a remote form so we refresh all the chat messages each time we submit
6
+ = render('effective/chats/summary', chat: chat)
7
+ = render(partial: 'effective/chats/chat_message', collection: chat.chat_messages, chat: chat)
8
+
9
+ - if chat.chat_messages.blank?
10
+ %p There are no messages so far. Please send one!
11
+
12
+ %hr
13
+
14
+ - chat_message = chat.build_chat_message(user: current_user)
15
+ - chat_user = chat.chat_user(user: current_user)
16
+
17
+ .card
18
+ .card-header.bg-secondary
19
+ Send message
20
+ .card-body
21
+ %p Your name is displayed as <strong>#{chat_user.name}</strong>.
22
+
23
+ = f.fields_for :chat_messages, chat_message do |fcm|
24
+ - # The user and name are set by controller's current_user
25
+ = fcm.text_area :body, required: true, label: 'Send message...'
26
+
27
+ = f.submit 'Send Message'
@@ -0,0 +1,2 @@
1
+ .effective-chat
2
+ = yield
@@ -0,0 +1,13 @@
1
+ %p
2
+ - if chat.anonymous?
3
+ = badges('anonymous')
4
+
5
+ = succeed('.') do
6
+ A chat with
7
+ = pluralize(chat.chat_users.count, 'participants')
8
+ opened
9
+ = time_ago_in_words(chat.created_at)
10
+ ago
11
+
12
+ = succeed('.') do
13
+ = pluralize(chat.chat_messages_count, 'message')
@@ -0,0 +1,10 @@
1
+ %h2 Messages and Chat
2
+
3
+ - if current_user.chats.present?
4
+ %p You are a member of #{pluralize(current_user.chats.length, 'chat')}.
5
+
6
+ - datatable = EffectiveResources.best('EffectiveChatsDatatable').new(self, namespace: :effective)
7
+ = render_datatable(datatable, simple: true)
8
+
9
+ - else
10
+ %p You haven't received any messages. When you do, we'll show it here.
@@ -0,0 +1,11 @@
1
+ ---
2
+ subject: 'New message {{ chat.title }}'
3
+ from: 'admin@example.com'
4
+ ---
5
+ Hello {{ user.name }},
6
+
7
+ You have received a new message. Please visit:
8
+
9
+ {{ chat.url }}
10
+
11
+ Thank you
@@ -0,0 +1,27 @@
1
+ EffectiveMessaging.setup do |config|
2
+ config.chats_table_name = :chats
3
+ config.chat_users_table_name = :chat_users
4
+ config.chat_messages_table_name = :chat_messages
5
+
6
+ # Layout Settings
7
+ # Configure the Layout per controller, or all at once
8
+ # config.layout = { application: 'application', admin: 'admin' }
9
+
10
+ # Mailer Settings
11
+ # Please see config/initializers/effective_messaging.rb for default effective_* gem mailer settings
12
+ #
13
+ # Configure the class responsible to send e-mails.
14
+ # config.mailer = 'Effective::MessagingMailer'
15
+ #
16
+ # Override effective_resource mailer defaults
17
+ #
18
+ # config.parent_mailer = nil # The parent class responsible for sending emails
19
+ # config.deliver_method = nil # The deliver method, deliver_later or deliver_now
20
+ # config.mailer_layout = nil # Default mailer layout
21
+ # config.mailer_sender = nil # Default From value
22
+ # config.mailer_admin = nil # Default To value for Admin correspondence
23
+ # config.mailer_subject = nil # Proc.new method used to customize Subject
24
+
25
+ # Will work with effective_email_templates gem
26
+ config.use_effective_email_templates = true
27
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ mount EffectiveMessaging::Engine => '/', as: 'effective_messaging'
5
+ end
6
+
7
+ EffectiveMessaging::Engine.routes.draw do
8
+ # Public routes
9
+ scope module: 'effective' do
10
+ resources :chats, only: [:show, :update]
11
+ end
12
+
13
+ namespace :admin do
14
+ resources :chats
15
+ resources :chat_messages, only: [:index, :show, :destroy]
16
+ end
17
+
18
+ end
@@ -0,0 +1,44 @@
1
+ class CreateEffectiveMessaging < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table <%= @chats_table_name %> do |t|
4
+ t.integer :parent_id
5
+ t.string :parent_type
6
+
7
+ t.string :title
8
+ t.boolean :anonymous, default: false
9
+
10
+ t.integer :chat_messages_count, default: 0
11
+ t.string :token
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ create_table <%= @chat_users_table_name %> do |t|
17
+ t.integer :chat_id
18
+
19
+ t.integer :user_id
20
+ t.string :user_type
21
+
22
+ t.string :display_name
23
+ t.string :anonymous_name
24
+
25
+ t.datetime :last_notified_at
26
+
27
+ t.timestamps
28
+ end
29
+
30
+ create_table <%= @chat_messages_table_name %> do |t|
31
+ t.integer :chat_id
32
+ t.integer :chat_user_id
33
+
34
+ t.integer :user_id
35
+ t.string :user_type
36
+
37
+ t.string :name
38
+ t.text :body
39
+
40
+ t.timestamps
41
+ end
42
+
43
+ end
44
+ end
data/db/seeds.rb ADDED
@@ -0,0 +1 @@
1
+ puts "Running effective_messaging seeds"
@@ -0,0 +1,18 @@
1
+ module EffectiveMessaging
2
+ class Engine < ::Rails::Engine
3
+ engine_name 'effective_messaging'
4
+
5
+ # Set up our default configuration options.
6
+ initializer 'effective_messaging.defaults', before: :load_config_initializers do |app|
7
+ eval File.read("#{config.root}/config/effective_messaging.rb")
8
+ end
9
+
10
+ # Include acts_as_messaging concern and allow any ActiveRecord object to call it
11
+ initializer 'effective_messaging.active_record' do |app|
12
+ app.config.to_prepare do
13
+ ActiveRecord::Base.extend(EffectiveMessagingUser::Base)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module EffectiveMessaging
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'effective_resources'
2
+ require 'effective_datatables'
3
+ require 'effective_messaging/engine'
4
+ require 'effective_messaging/version'
5
+
6
+ module EffectiveMessaging
7
+
8
+ def self.config_keys
9
+ [
10
+ :chats_table_name, :chat_users_table_name, :chat_messages_table_name,
11
+ :layout,
12
+ :mailer, :parent_mailer, :deliver_method, :mailer_layout, :mailer_sender, :mailer_admin, :mailer_subject, :use_effective_email_templates
13
+ ]
14
+ end
15
+
16
+ include EffectiveGem
17
+
18
+ def self.mailer_class
19
+ mailer&.constantize || Effective::MessagingMailer
20
+ end
21
+
22
+ end
@@ -0,0 +1,32 @@
1
+ module EffectiveMessaging
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ include Rails::Generators::Migration
5
+
6
+ desc 'Creates an EffectiveMessaging initializer in your application.'
7
+
8
+ source_root File.expand_path('../../templates', __FILE__)
9
+
10
+ def self.next_migration_number(dirname)
11
+ if not ActiveRecord::Base.timestamped_migrations
12
+ Time.new.utc.strftime("%Y%m%d%H%M%S")
13
+ else
14
+ '%.3d' % (current_migration_number(dirname) + 1)
15
+ end
16
+ end
17
+
18
+ def copy_initializer
19
+ template ('../' * 3) + 'config/effective_messaging.rb', 'config/initializers/effective_messaging.rb'
20
+ end
21
+
22
+ def create_migration_file
23
+ @chats_table_name = ':' + EffectiveMessaging.chats_table_name.to_s
24
+ @chat_users_table_name = ':' + EffectiveMessaging.chat_users_table_name.to_s
25
+ @chat_messages_table_name = ':' + EffectiveMessaging.chat_messages_table_name.to_s
26
+
27
+ migration_template ('../' * 3) + 'db/migrate/01_create_effective_messaging.rb.erb', 'db/migrate/create_effective_messaging.rb'
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ # Visit http://localhost:3000/rails/mailers
2
+
3
+ class EffectiveMessagingMailerPreview < ActionMailer::Preview
4
+ end
@@ -0,0 +1,8 @@
1
+ namespace :effective_messaging do
2
+
3
+ # bundle exec rake effective_messaging:seed
4
+ task seed: :environment do
5
+ load "#{__dir__}/../../db/seeds.rb"
6
+ end
7
+
8
+ end
metadata ADDED
@@ -0,0 +1,281 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: effective_messaging
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Code and Effect
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: effective_bootstrap
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: effective_datatables
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 4.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 4.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: effective_orders
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: effective_resources
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: effective_roles
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: wicked
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: devise
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: haml-rails
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pry-byebug
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: effective_test_bot
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: effective_developer
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: effective_email_templates
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ description: Centralize the communication between one or more users.
210
+ email:
211
+ - info@codeandeffect.com
212
+ executables: []
213
+ extensions: []
214
+ extra_rdoc_files: []
215
+ files:
216
+ - MIT-LICENSE
217
+ - README.md
218
+ - Rakefile
219
+ - app/assets/config/effective_messaging_manifest.js
220
+ - app/assets/javascripts/effective_messaging.js
221
+ - app/assets/javascripts/effective_messaging/base.js
222
+ - app/assets/stylesheets/effective_messaging.scss
223
+ - app/assets/stylesheets/effective_messaging/base.scss
224
+ - app/controllers/admin/chat_messages_controller.rb
225
+ - app/controllers/admin/chats_controller.rb
226
+ - app/controllers/effective/chats_controller.rb
227
+ - app/datatables/admin/effective_chat_messages_datatable.rb
228
+ - app/datatables/admin/effective_chats_datatable.rb
229
+ - app/datatables/effective_chats_datatable.rb
230
+ - app/helpers/effective_messaging_helper.rb
231
+ - app/mailers/effective/messaging_mailer.rb
232
+ - app/models/concerns/effective_messaging_user.rb
233
+ - app/models/effective/chat.rb
234
+ - app/models/effective/chat_message.rb
235
+ - app/models/effective/chat_user.rb
236
+ - app/views/admin/chat_messages/_chat_message.html.haml
237
+ - app/views/admin/chats/_chat.html.haml
238
+ - app/views/admin/chats/_form.html.haml
239
+ - app/views/admin/chats/_form_chat.html.haml
240
+ - app/views/admin/chats/_layout.html.haml
241
+ - app/views/effective/chats/_chat.html.haml
242
+ - app/views/effective/chats/_chat_message.html.haml
243
+ - app/views/effective/chats/_form.html.haml
244
+ - app/views/effective/chats/_layout.html.haml
245
+ - app/views/effective/chats/_summary.html.haml
246
+ - app/views/effective/messaging/_dashboard.html.haml
247
+ - app/views/effective/messaging_mailer/chat_new_message.liquid
248
+ - config/effective_messaging.rb
249
+ - config/routes.rb
250
+ - db/migrate/01_create_effective_messaging.rb.erb
251
+ - db/seeds.rb
252
+ - lib/effective_messaging.rb
253
+ - lib/effective_messaging/engine.rb
254
+ - lib/effective_messaging/version.rb
255
+ - lib/generators/effective_messaging/install_generator.rb
256
+ - lib/generators/templates/effective_messaging_mailer_preview.rb
257
+ - lib/tasks/effective_messaging_tasks.rake
258
+ homepage: https://github.com/code-and-effect/effective_messaging
259
+ licenses:
260
+ - MIT
261
+ metadata: {}
262
+ post_install_message:
263
+ rdoc_options: []
264
+ require_paths:
265
+ - lib
266
+ required_ruby_version: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: '0'
271
+ required_rubygems_version: !ruby/object:Gem::Requirement
272
+ requirements:
273
+ - - ">="
274
+ - !ruby/object:Gem::Version
275
+ version: '0'
276
+ requirements: []
277
+ rubygems_version: 3.1.2
278
+ signing_key:
279
+ specification_version: 4
280
+ summary: Centralize the communication between one or more users.
281
+ test_files: []