georgia_mailer 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 (35) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +45 -0
  4. data/Rakefile +25 -0
  5. data/app/assets/javascripts/georgia_mailer/application.js +1 -0
  6. data/app/assets/javascripts/georgia_mailer/components/message_table.js.coffee +59 -0
  7. data/app/assets/stylesheets/georgia_mailer/application.css.scss +2 -0
  8. data/app/assets/stylesheets/georgia_mailer/modules/_message.scss +53 -0
  9. data/app/assets/stylesheets/georgia_mailer/settings/_color.scss +2 -0
  10. data/app/controllers/georgia/messages_controller.rb +75 -0
  11. data/app/controllers/georgia_mailer/messages_controller.rb +35 -0
  12. data/app/decorators/georgia_mailer/message_decorator.rb +21 -0
  13. data/app/helpers/georgia/messages_helper.rb +9 -0
  14. data/app/mailers/georgia_mailer/notifier.rb +18 -0
  15. data/app/models/georgia/extensions/solr/georgia_mailer_message.rb +32 -0
  16. data/app/models/georgia_mailer/concerns/solr_georgia_mailer_message_extension.rb +42 -0
  17. data/app/models/georgia_mailer/concerns/tire_georgia_mailer_message_extension.rb +41 -0
  18. data/app/models/georgia_mailer/message.rb +34 -0
  19. data/app/presenters/georgia_mailer/message_actions_presenter.rb +53 -0
  20. data/app/uploaders/georgia_mailer/attachment_uploader.rb +16 -0
  21. data/app/views/georgia/georgia_mailer/messages/_message.html.erb +26 -0
  22. data/app/views/georgia/header/_messages.html.erb +3 -0
  23. data/app/views/georgia/messages/destroy.js.erb +3 -0
  24. data/app/views/georgia/messages/search.html.erb +63 -0
  25. data/app/views/georgia/messages/show.html.erb +51 -0
  26. data/app/views/georgia_mailer/messages/create.js.erb +18 -0
  27. data/app/views/georgia_mailer/notifier/new_message_notification.html.erb +211 -0
  28. data/app/workers/georgia_mailer/spam_worker.rb +19 -0
  29. data/config/routes.rb +21 -0
  30. data/db/migrate/001_create_georgia_mailer_messages.rb +21 -0
  31. data/lib/georgia_mailer/engine.rb +10 -0
  32. data/lib/georgia_mailer/version.rb +3 -0
  33. data/lib/georgia_mailer.rb +12 -0
  34. data/lib/tasks/georgia_mailer_tasks.rake +4 -0
  35. metadata +134 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b39fdbd73795a29fb6f1f300d92c333666742a2
4
+ data.tar.gz: b247a8adc76637db9504b5cb62e9f2667ce577ec
5
+ SHA512:
6
+ metadata.gz: c53ae62db423a6e55a004c5fb658e19106529de50b24570b2cc16d1c842cf497a7a384f6179eaabcbd3c5d3c09c3c95d879451f4141e142ba0b907da77b1e798
7
+ data.tar.gz: ae2b705af810ac7ffeac666f26182699d831b06804a22048252dd0b5dcc62fd8fec91173403f253c3a078f638587f0587fa4bfb0eee920e5ec99aa4f247bf80d
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-2014 Motion Eleven
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,45 @@
1
+ ## Georgia Mailer
2
+
3
+ Extension to Georgia CMS to store and send messages. Messages will be treated for spam before being sent to you.
4
+
5
+ Make sure you have a working Georgia installation before proceding with these instructions.
6
+
7
+ ### Installation
8
+
9
+ Add migrations and migrate
10
+
11
+ rake railties:install:migrations
12
+ rake db:migrate
13
+
14
+ If you selected ElasticSearch as your indexer, create the GeorgiaMailer::Message index:
15
+
16
+ rake environment tire:import CLASS=GeorgiaMailer::Message FORCE=true
17
+
18
+ ### Heroku
19
+
20
+ Add Redis addon for Sidekiq
21
+
22
+ heroku addons:add redistogo
23
+
24
+ Generate GeorgiaMailer::Message index:
25
+
26
+ heroku run rake environment tire:import CLASS=GeorgiaMailer::Message FORCE=true
27
+
28
+ Add sidekiq to your Procfile:
29
+
30
+ worker: bundle exec sidekiq
31
+
32
+ ### Spam filtering
33
+
34
+ To avoid pesky spammers, Georgia Mailer uses Akismet system to filter spam messages.
35
+
36
+ 1. Sign up on [http://akismet.com/signup/](http://akismet.com/signup/)
37
+ 2. Configure rakismet `key` and `url` in an initializer:
38
+
39
+ ```ruby
40
+ # config/initializers/rakismet.rb
41
+ Example::Application.config.rakismet.key = '123456789XYZ'
42
+ Example::Application.config.rakismet.url = 'https://www.example.com/'
43
+ ```
44
+
45
+ For more information or alternatives, please read on the [rakismet](https://github.com/joshfrench/rakismet) gem.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'GeorgiaMailer'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ Bundler::GemHelper.install_tasks
24
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
25
+ load 'rails/tasks/engine.rake'
@@ -0,0 +1 @@
1
+ //= require_tree ./components
@@ -0,0 +1,59 @@
1
+ class @MessagesTable
2
+
3
+ constructor: (element) ->
4
+ @element = $(element)
5
+ @checkboxes = @element.find("input:checkbox")
6
+ @deleteBtn = @element.find('.js-delete')
7
+ @spamBtn = @element.find('.js-spam')
8
+ @hamBtn = @element.find('.js-ham')
9
+ @setBindings()
10
+
11
+ setBindings: () =>
12
+ @checkboxes.on('change', @update)
13
+ @deleteBtn.on('click', @delete)
14
+ @spamBtn.on('click', @spam)
15
+ @hamBtn.on('click', @ham)
16
+
17
+ update: () =>
18
+ if @getChecked().length then @enableActions() else @disableActions()
19
+
20
+ delete: (event) =>
21
+ @stopEvent(event)
22
+ $.ajax(
23
+ url: "/admin/messages/#{@getIds()}"
24
+ type: 'DELETE'
25
+ )
26
+
27
+ spam: (event) ->
28
+ @stopEvent(event)
29
+ # TODO: send event to controller
30
+
31
+ ham: (event) ->
32
+ @stopEvent(event)
33
+ # TODO: send event to controller
34
+
35
+ enableActions: () =>
36
+ @spamBtn.removeClass('disabled').addClass('btn-warning')
37
+ @hamBtn.removeClass('disabled').addClass('btn-warning')
38
+ @deleteBtn.removeClass('disabled').addClass('btn-danger')
39
+
40
+ disableActions: () =>
41
+ @spamBtn.addClass('disabled').removeClass('btn-warning')
42
+ @hamBtn.addClass('disabled').removeClass('btn-warning')
43
+ @deleteBtn.addClass('disabled').removeClass('btn-danger')
44
+
45
+ stopEvent: (event) ->
46
+ event.stopPropagation()
47
+ event.preventDefault()
48
+
49
+ getChecked: () => @element.find("input:checkbox:checked[data-checkbox='child']")
50
+ getId: (c) => $(c).data('id')
51
+ getIds: () => $.map(@getChecked(), (c) => @getId(c))
52
+
53
+ $.fn.actsAsMessagesTable = () ->
54
+ @each ->
55
+ new MessagesTable($(this))
56
+
57
+ jQuery ->
58
+ $("table.messages.js-checkboxable").each ->
59
+ $(this).actsAsMessagesTable()
@@ -0,0 +1,2 @@
1
+ @import "settings/*";
2
+ @import "modules/*";
@@ -0,0 +1,53 @@
1
+ %clickable {
2
+ a { display: block; }
3
+ }
4
+ .message-name, .message-message, .message-attachment, .message-phone, .message-timestamp {
5
+ @extend %clickable;
6
+ }
7
+ .message-timestamp {
8
+ @extend .timestamp--long;
9
+ }
10
+
11
+ .message-info {
12
+ line-height: 30px;
13
+ padding: 10px 0;
14
+ margin-bottom: 20px;
15
+ border-top: 1px solid #cbcbcb;
16
+ border-bottom: 1px solid #cbcbcb;
17
+ }
18
+ .message-additional-info {
19
+ padding: 10px 0;
20
+ margin: 20px 0;
21
+ border-top: 1px solid #cbcbcb;
22
+ border-bottom: 1px solid #cbcbcb;
23
+ }
24
+ .message-status {
25
+ width: 1px;
26
+ border-right: 3px solid;
27
+ padding: 0 !important;
28
+ margin: 0;
29
+ &.spam { border-color: $red; }
30
+ &.clean { border-color: $green; }
31
+ }
32
+
33
+ .message-name span {
34
+ @extend .ellipsis;
35
+ width: 160px;
36
+ max-width: 160px;
37
+ }
38
+
39
+ .phone {
40
+ @extend .ellipsis;
41
+ width: 120px;
42
+ max-width: 120px;
43
+ }
44
+
45
+ .message-attachment {
46
+ @extend .tiny;
47
+ }
48
+
49
+ .message-message span {
50
+ @extend .nowrap;
51
+ @extend .ellipsis;
52
+ max-width: 800px;
53
+ }
@@ -0,0 +1,2 @@
1
+ $red: #e02222;
2
+ $green: #00ad61;
@@ -0,0 +1,75 @@
1
+ module Georgia
2
+ class MessagesController < ::Georgia::ApplicationController
3
+
4
+ load_and_authorize_resource class: GeorgiaMailer::Message
5
+
6
+ before_filter :prepare_search, only: [:search, :show, :spam, :ham]
7
+
8
+
9
+ def index
10
+ redirect_to georgia.search_messages_path
11
+ end
12
+
13
+ def search
14
+ end
15
+
16
+ # Destroy multiple assets
17
+ def destroy
18
+ ids = params[:id].split(',')
19
+ if @messages = GeorgiaMailer::Message.destroy(ids)
20
+ respond_to do |format|
21
+ format.html {
22
+ redirect_to georgia.search_messages_path, notice: 'Messages successfully deleted.'
23
+ }
24
+ format.js { render layout: false }
25
+ end
26
+ else
27
+ respond_to do |format|
28
+ format.html {redirect_to georgia.search_messages_path, alert: 'Oups. Something went wrong.'}
29
+ format.js {head :internal_server_error}
30
+ end
31
+ end
32
+ end
33
+
34
+ def show
35
+ @message = GeorgiaMailer::Message.find(params[:id]).decorate
36
+ end
37
+
38
+ def spam
39
+ @message = GeorgiaMailer::Message.find(params[:id])
40
+ if @message.spam!
41
+ @message.update_attribute(:spam, true)
42
+ redirect_to :back, notice: 'Message successfully marked as spam.'
43
+ else
44
+ redirect_to :back, alert: 'Oups. Something went wrong.'
45
+ end
46
+ end
47
+
48
+ def ham
49
+ @message = GeorgiaMailer::Message.find(params[:id])
50
+ if @message.ham! == false
51
+ @message.update_attribute(:spam, false)
52
+ redirect_to :back, notice: 'Message successfully marked as ham.'
53
+ else
54
+ redirect_to :back, alert: 'Oups. Something went wrong.'
55
+ end
56
+ end
57
+
58
+ def resend_notification
59
+ @message = GeorgiaMailer::Message.find(params[:id])
60
+ if GeorgiaMailer::Notifier.new_message_notification(@message, message_path(@message, only_path: false)).deliver
61
+ redirect_to :back, notice: 'Notification successfully sent.'
62
+ else
63
+ redirect_to :back, alert: 'Oups. Something went wrong. Message could not be delivered.'
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def prepare_search
70
+ @results = ::Georgia::Indexer.search(GeorgiaMailer::Message, params)
71
+ @messages = GeorgiaMailer::MessageDecorator.decorate_collection(@results)
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,35 @@
1
+ module GeorgiaMailer
2
+ class MessagesController < ::ApplicationController
3
+
4
+ # Convenient method to create and check for spam
5
+ def create
6
+ @message = GeorgiaMailer::Message.new(message_params)
7
+ if @message.save
8
+ SpamWorker.perform_async(@message.id)
9
+ respond_to do |format|
10
+ format.html { redirect_to :back, notice: 'Message delivered successfully' }
11
+ format.js { render layout: false }
12
+ end
13
+ else
14
+ respond_to do |format|
15
+ format.html { redirect_to :back, alert: 'Oups. Something went wrong.' }
16
+ format.js { render layout: false }
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def message_params
24
+ @message_params = {}
25
+ params[:message].each do |key, value|
26
+ @message_params[key] = value.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
27
+ end
28
+ @message_params[:referrer] = request.referrer
29
+ @message_params[:user_ip] = request.remote_ip
30
+ @message_params[:user_agent] = request.user_agent
31
+ @message_params
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module GeorgiaMailer
2
+ class MessageDecorator < ::Georgia::ApplicationDecorator
3
+
4
+ def phone_or_none
5
+ phone.present? ? phone : h.content_tag(:span, 'no phone', class: 'muted')
6
+ end
7
+
8
+ def subject_truncated
9
+ h.truncate(h.strip_tags(subject), length: 60, separator: ' ').html_safe if subject.present?
10
+ end
11
+
12
+ def message_truncated
13
+ h.truncate(h.strip_tags(message), length: 200, separator: ' ').html_safe if message.present?
14
+ end
15
+
16
+ def name_or_anonymous
17
+ name || 'Anonymous'
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module Georgia
2
+ module MessagesHelper
3
+
4
+ def message_actions_list(message)
5
+ GeorgiaMailer::MessageActionsPresenter.new(self, message)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module GeorgiaMailer
2
+ class Notifier < ActionMailer::Base
3
+
4
+ def new_message_notification(message)
5
+ @message = GeorgiaMailer::MessageDecorator.decorate(message)
6
+ emails_to = Georgia::User.admins.map(&:email)
7
+ unless emails_to.empty?
8
+ mail(
9
+ from: "georgia@motioneleven.com",
10
+ to: "noreply@motioneleven.com",
11
+ bcc: emails_to,
12
+ subject: "New message from #{Georgia.title}"
13
+ )
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support/concern'
2
+
3
+ module Georgia
4
+ module Indexer
5
+ module Extensions
6
+ module Solr
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+
11
+ searchable do
12
+ text :name
13
+ text :email
14
+ text :message
15
+ text :subject
16
+ text :phone
17
+ string :spam do
18
+ status
19
+ end
20
+ string :name
21
+ string :email
22
+ string :phone
23
+ string :subject
24
+ string :message
25
+ time :created_at
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ require 'active_support/concern'
2
+
3
+ module GeorgiaMailer
4
+ module Concerns
5
+ module SolrGeorgiaMailerMessageExtension
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+
10
+ searchable do
11
+ text :name
12
+ text :email
13
+ text :message
14
+ text :subject
15
+ text :phone
16
+ string :spam do
17
+ status
18
+ end
19
+ string :name
20
+ string :email
21
+ string :phone
22
+ string :subject
23
+ string :message
24
+ time :created_at
25
+ end
26
+
27
+ def self.search_index model, params
28
+ @search = Georgia::Message.search do
29
+ fulltext params[:query] do
30
+ fields(:name, :email, :message, :subject, :phone)
31
+ end
32
+ facet :spam
33
+ with(:spam, (params[:s] || 'clean'))
34
+ order_by (params[:o] || :created_at), (params[:dir] || :desc)
35
+ paginate(page: params[:page], per_page: (params[:per] || 25))
36
+ end.results
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ require 'active_support/concern'
2
+
3
+ module GeorgiaMailer
4
+ module Concerns
5
+ module TireGeorgiaMailerMessageExtension
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+
10
+ include ::Tire::Model::Search
11
+ include ::Tire::Model::Callbacks
12
+
13
+ def to_indexed_json
14
+ {
15
+ name: name,
16
+ email: email,
17
+ message: message,
18
+ subject: subject,
19
+ phone: phone,
20
+ status: status,
21
+ created_at: created_at.strftime('%F')
22
+ }.to_json
23
+ end
24
+
25
+ def self.search model, params
26
+ model.tire.search(page: (params[:page] || 1), per_page: (params[:per] || 25)) do
27
+ if params[:query].present?
28
+ query do
29
+ boolean do
30
+ must { string params[:query], default_operator: "AND" }
31
+ end
32
+ end
33
+ sort { by (params[:o] || :created_at), (params[:dir] || :desc) }
34
+ end
35
+ end.results
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ module GeorgiaMailer
2
+ class Message < ActiveRecord::Base
3
+
4
+ attr_accessible :name, :email, :subject, :message, :attachment, :phone
5
+ delegate :url, :current_path, :size, :content_type, :filename, to: :attachment
6
+
7
+ validates :name, presence: true
8
+ validates :email, format: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
9
+ validates :message, presence: true
10
+
11
+ mount_uploader :attachment, Georgia::AttachmentUploader
12
+
13
+ # Anti Spam: check https://github.com/joshfrench/rakismet for more details
14
+ include Rakismet::Model
15
+ rakismet_attrs author: :name, author_email: :email, content: :message, comment_type: 'message'
16
+ attr_accessible :user_ip, :user_agent, :referrer, :spam, :verified_at
17
+ attr_accessor :permalink, :author_url
18
+
19
+ scope :spam, where(spam: true)
20
+ scope :ham, where(spam: false)
21
+ scope :latest, order("created_at DESC")
22
+
23
+ def status
24
+ @status ||= spam ? 'spam' : 'clean'
25
+ end
26
+
27
+ include Georgia::Indexer::Adapter
28
+ is_searchable({
29
+ solr: GeorgiaMailer::Concerns::SolrGeorgiaMailerMessageExtension,
30
+ tire: GeorgiaMailer::Concerns::TireGeorgiaMailerMessageExtension
31
+ })
32
+
33
+ end
34
+ end
@@ -0,0 +1,53 @@
1
+ module GeorgiaMailer
2
+ class MessageActionsPresenter < ::Georgia::Presenter
3
+
4
+ def initialize view_context, message
5
+ @message = (message.decorated? ? message.object : message)
6
+ super
7
+ end
8
+
9
+ def to_s
10
+ html = ActiveSupport::SafeBuffer.new
11
+ html << content_tag(:li, link_to_print)
12
+ if spam?
13
+ html << content_tag(:li, link_to_ham) if can?(:ham, @message)
14
+ else
15
+ html << content_tag(:li, link_to_spam) if can?(:spam, @message)
16
+ end
17
+ html << content_tag(:li, link_to_resend_notification)
18
+ html << content_tag(:li, link_to_trash) if can?(:destroy, @message)
19
+ html
20
+ end
21
+
22
+ private
23
+
24
+ def link_to_print
25
+ link_to "#{icon_tag('print')} Print".html_safe, "javascript:window.print()", target: '_blank'
26
+ end
27
+
28
+ def link_to_reply
29
+ link_to "#{icon_tag('reply')} Reply".html_safe, "mailto:#{@message.email}", target: '_blank'
30
+ end
31
+
32
+ def link_to_ham
33
+ link_to "#{icon_tag('thumbs-up')} Mark as ham".html_safe, [:ham, @message]
34
+ end
35
+
36
+ def link_to_spam
37
+ link_to "#{icon_tag('thumbs-down')} Mark as spam".html_safe, [:spam, @message]
38
+ end
39
+
40
+ def link_to_resend_notification
41
+ link_to "#{icon_tag('envelope')} Resend notification".html_safe, [:resend_notification, @message]
42
+ end
43
+
44
+ def link_to_trash
45
+ link_to "#{icon_tag('trash-o')} Trash".html_safe, @message, method: :delete, data: {confirm: 'Are you sure?'}
46
+ end
47
+
48
+ def spam?
49
+ @message.spam?
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ module GeorgiaMailer
3
+ class AttachmentUploader < CarrierWave::Uploader::Base
4
+
5
+ storage :fog
6
+
7
+ def store_dir
8
+ "/attachments/#{model.id}"
9
+ end
10
+
11
+ def extension_white_list
12
+ ["doc", "docx", "xls", "odt", "ods", "pdf", "rar", "zip", "tar", "tar.gz"]
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ <tr id='<%= dom_id(message) %>' class='message' data-status="<%= message.status %>">
2
+ <td class='checkbox-cell'>
3
+ <%= checkboxable_tag message %>
4
+ </td>
5
+ <td class='message-name'>
6
+ <%= link_to message do %>
7
+ <span><%= message.name %></span>
8
+ <% end -%>
9
+ </td>
10
+ <td class='message-message'>
11
+ <%= link_to message do %>
12
+ <span><%= message.subject_truncated %> <small class='muted'><%= message.message_truncated %></small></span>
13
+ <% end -%>
14
+ </td>
15
+ <td class='message-phone'>
16
+ <%= link_to message do %>
17
+ <span class="phone"><%= message.phone_or_none %></span>
18
+ <% end -%>
19
+ </td>
20
+ <td class='message-attachment'>
21
+ <%= icon_tag('paper-clip') %>
22
+ </td>
23
+ <td class='message-timestamp'>
24
+ <%= message.created_at %>
25
+ </td>
26
+ </tr>
@@ -0,0 +1,3 @@
1
+ <% if can? :index, GeorgiaMailer::Message %>
2
+ <%= sidebar_navigation_link 'Inbox', georgia.search_messages_url, controller: 'messages', icon: 'envelope' %>
3
+ <% end -%>
@@ -0,0 +1,3 @@
1
+ <% @messages.each do |m| %>
2
+ $('tr#message_<%= m.id %>').remove()
3
+ <% end -%>
@@ -0,0 +1,63 @@
1
+ <%= content_for :head do %>
2
+ <%= javascript_include_tag 'georgia_mailer/application' %>
3
+ <%= stylesheet_link_tag 'georgia_mailer/application' %>
4
+ <% end -%>
5
+
6
+ <header class='header-main'>
7
+ <div class="header-title">
8
+ <%= form_for :search, url: search_messages_path, method: :get, html: {role: 'search'} do |f| %>
9
+ <%= hidden_field_tag :s, params[:s] if params[:s] %>
10
+ <div class="input-group">
11
+ <div class="input-group-btn">
12
+ <button class="btn btn-default"><%= icon_tag('search') %></button>
13
+ </div>
14
+ <%= text_field_tag :query, params[:query], class: 'form-control input-search', placeholder: 'Search' %>
15
+ </div>
16
+ <% end -%>
17
+ </div>
18
+ <div class="header-actions">
19
+ <%= link_to "#{icon_tag('reply')} Reply".html_safe, "mailto:#{@message.email}", class: 'btn btn-warning' if @message %>
20
+ </div>
21
+ </header>
22
+
23
+ <section class="body-main">
24
+
25
+ <table class="table table-hover table-results messages js-checkboxable">
26
+ <thead>
27
+ <th colspan='3'>
28
+ <div class="btn-group">
29
+ <div class="btn btn-default dropdown-toggle" data-toggle="dropdown">
30
+ <%= checkboxable_all_tag :messages %>
31
+ <span class="caret"></span>
32
+ </div>
33
+ <ul class="dropdown-menu" role="menu">
34
+ <li><%= link_to 'All', '#', class: 'js-select-all' %></li>
35
+ <li><%= link_to 'None', '#', class: 'js-select-none' %></li>
36
+ <li><%= link_to 'Spam', '#', class: 'js-select-spam' %></li>
37
+ </ul>
38
+ </div>
39
+ <%= link_to "#{icon_tag('thumbs-up')} ham".html_safe, '#', class: 'btn btn-default btn-xs js-ham disabled' %>
40
+ <%= link_to "#{icon_tag('thumbs-down')} spam".html_safe, '#', class: 'btn btn-default btn-xs js-spam disabled' %>
41
+ <%= link_to "#{icon_tag('trash-o')} trash".html_safe, '#', class: 'btn btn-default btn-xs js-delete disabled' %>
42
+ <%#= link_to '#', class: 'btn btn-warning' do %>
43
+ <%#= icon_tag('tags') %> <%#= icon_tag('caret-down') %>
44
+ <%# end -%>
45
+ </th>
46
+ <th colspan='3'>
47
+ <%= pagination_tag(@search) %>
48
+ </th>
49
+ </thead>
50
+ <tbody>
51
+ <% if @messages and @messages.any? %>
52
+ <%= render @messages %>
53
+ <% else %>
54
+ <tr>
55
+ <td colspan="6">
56
+ There are currently no messages.
57
+ </td>
58
+ </tr>
59
+ <% end %>
60
+ </tbody>
61
+ </table>
62
+
63
+ </section>
@@ -0,0 +1,51 @@
1
+ <%= content_for :head do %>
2
+ <%= javascript_include_tag 'georgia_mailer/application' %>
3
+ <%= stylesheet_link_tag 'georgia_mailer/application' %>
4
+ <% end -%>
5
+
6
+ <header class='header-main hidden-print'>
7
+ <div class="header-gutter">
8
+ <%= link_to_back search_messages_path %>
9
+ </div>
10
+ <div class="header-title"></div>
11
+ <div class="header-actions">
12
+ </div>
13
+ </header>
14
+
15
+ <section class="body-main">
16
+ <h2><%= @message.subject %></h2>
17
+ <div class="message-info">
18
+ <div class="btn-group pull-right hidden-print">
19
+ <%= link_to "#{icon_tag('reply')} Reply".html_safe, "mailto:#{@message.email}", class: 'btn btn-warning btn-sm', target: '_blank' %>
20
+ <button type="button" class="btn btn-warning btn-sm dropdown-toggle" data-toggle="dropdown">
21
+ <span class="caret"></span>
22
+ <span class="sr-only">Toggle Dropdown</span>
23
+ </button>
24
+ <ul class="dropdown-menu" role="menu">
25
+ <%= message_actions_list(@message) %>
26
+ </ul>
27
+ </div>
28
+ <strong>
29
+ <%= @message.name_or_anonymous %>
30
+ </strong>
31
+ <span>&lt;<%= @message.email %>&gt;</span>
32
+ <span>on <%= @message.created_at.strftime('%F %H:%M') %></span>
33
+ <span>sent from <%= link_to @message.referrer, @message.referrer %></span>
34
+ </div>
35
+ <div class="message-content">
36
+ <%= @message.message.html_safe %>
37
+ </div>
38
+ <div class="message-additional-info">
39
+ <p>
40
+ <b>Phone</b>: <%= @message.phone_or_none %>
41
+ </p>
42
+ <p>
43
+ <b>Attachment</b>:
44
+ <% if @message.attachment.try(:file) and @message.attachment.file.exists? %>
45
+ <%= link_to @message.attachment.file.filename, @message.attachment.url %>
46
+ <% else -%>
47
+ <span class='muted'>There were no attachment.</span>
48
+ <% end -%>
49
+ </p>
50
+ </div>
51
+ </section>
@@ -0,0 +1,18 @@
1
+ // Clear previous errors and alerts
2
+ $('input, textarea, select').closest('.form-group').removeClass('has-error');
3
+ $('form#new_message .alert').remove();
4
+
5
+ <% if @message.valid? %>
6
+
7
+ $('form#new_message')[0].reset();
8
+ $('form#new_message').prepend("<div class='alert alert-success'>Thank you! Your message has been successfully delivered.</div>");
9
+
10
+ <% else %>
11
+
12
+ <% @message.errors.each do |error, message| %>
13
+ $('form#new_message #message_<%= error.to_s %>').closest('.form-group').addClass('has-error');
14
+ <% end %>
15
+
16
+ $('form#new_message').prepend("<div class='alert alert-danger'>Oups. Something went wrong.<br /><%= @message.errors.full_messages.join('. ') %></div>");
17
+
18
+ <% end %>
@@ -0,0 +1,211 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <meta name="viewport" content="width=device-width" />
5
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6
+ <title>New message from <%= Georgia.title %></title>
7
+ <style>
8
+ /* -------------------------------------
9
+ GLOBAL
10
+ ------------------------------------- */
11
+ * {
12
+ margin:0;
13
+ padding:0;
14
+ font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
15
+ font-size: 100%;
16
+ line-height: 1.6;
17
+ }
18
+
19
+ img {
20
+ max-width: 100%;
21
+ }
22
+
23
+ body {
24
+ -webkit-font-smoothing:antialiased;
25
+ -webkit-text-size-adjust:none;
26
+ width: 100%!important;
27
+ height: 100%;
28
+ }
29
+
30
+
31
+ /* -------------------------------------
32
+ ELEMENTS
33
+ ------------------------------------- */
34
+ a {
35
+ color: #348eda;
36
+ }
37
+
38
+ .btn-primary{
39
+ text-decoration:none;
40
+ color: #FFF;
41
+ background-color: #348eda;
42
+ border:solid #348eda;
43
+ border-width:10px 20px;
44
+ line-height:2;
45
+ font-weight:bold;
46
+ margin-right:10px;
47
+ text-align:center;
48
+ cursor:pointer;
49
+ display: inline-block;
50
+ border-radius: 25px;
51
+ }
52
+
53
+ .btn-secondary {
54
+ text-decoration:none;
55
+ color: #FFF;
56
+ background-color: #aaa;
57
+ border:solid #aaa;
58
+ border-width:10px 20px;
59
+ line-height:2;
60
+ font-weight:bold;
61
+ margin-right:10px;
62
+ text-align:center;
63
+ cursor:pointer;
64
+ display: inline-block;
65
+ border-radius: 25px;
66
+ }
67
+
68
+ .last {
69
+ margin-bottom: 0;
70
+ }
71
+
72
+ .first{
73
+ margin-top: 0;
74
+ }
75
+
76
+ .padding{
77
+ padding:10px 0;
78
+ }
79
+
80
+
81
+ /* -------------------------------------
82
+ BODY
83
+ ------------------------------------- */
84
+ table.body-wrap {
85
+ width: 100%;
86
+ padding: 20px;
87
+ }
88
+
89
+ table.body-wrap .container{
90
+ border: 1px solid #f0f0f0;
91
+ }
92
+
93
+
94
+ /* -------------------------------------
95
+ FOOTER
96
+ ------------------------------------- */
97
+ table.footer-wrap {
98
+ width: 100%;
99
+ clear:both!important;
100
+ }
101
+
102
+ .footer-wrap .container p {
103
+ font-size:12px;
104
+ color:#666;
105
+
106
+ }
107
+
108
+ table.footer-wrap a{
109
+ color: #999;
110
+ }
111
+
112
+
113
+ /* -------------------------------------
114
+ TYPOGRAPHY
115
+ ------------------------------------- */
116
+ h1,h2,h3{
117
+ font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; line-height: 1.1; margin-bottom:15px; color:#000;
118
+ margin: 40px 0 10px;
119
+ line-height: 1.2;
120
+ font-weight:200;
121
+ }
122
+
123
+ h1 {
124
+ font-size: 36px;
125
+ }
126
+ h2 {
127
+ font-size: 28px;
128
+ }
129
+ h3 {
130
+ font-size: 22px;
131
+ }
132
+
133
+ p, ul, ol {
134
+ margin-bottom: 10px;
135
+ font-weight: normal;
136
+ font-size:14px;
137
+ }
138
+
139
+ ul li, ol li {
140
+ margin-left:5px;
141
+ list-style-position: inside;
142
+ }
143
+
144
+ /* ---------------------------------------------------
145
+ RESPONSIVENESS
146
+ Nuke it from orbit. It's the only way to be sure.
147
+ ------------------------------------------------------ */
148
+
149
+ /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
150
+ .container {
151
+ display:block!important;
152
+ max-width:600px!important;
153
+ margin:0 auto!important; /* makes it centered */
154
+ clear:both!important;
155
+ }
156
+
157
+ /* Set the padding on the td rather than the div for Outlook compatibility */
158
+ .body-wrap .container{
159
+ padding:20px;
160
+ }
161
+
162
+ /* This should also be a block element, so that it will fill 100% of the .container */
163
+ .content {
164
+ max-width:600px;
165
+ margin:0 auto;
166
+ display:block;
167
+ }
168
+
169
+ /* Let's make sure tables in the content area are 100% wide */
170
+ .content table {
171
+ width: 100%;
172
+ }
173
+
174
+ </style>
175
+ </head>
176
+
177
+ <body bgcolor="#f6f6f6">
178
+
179
+ <!-- body -->
180
+ <table class="body-wrap">
181
+ <tr>
182
+ <td></td>
183
+ <td class="container" bgcolor="#FFFFFF">
184
+
185
+ <!-- content -->
186
+ <div class="content">
187
+ <table>
188
+ <tr>
189
+ <td>
190
+ <p>Hi there,</p>
191
+ <p>You've received a new message on Georgia CMS from <%= @message.name_or_anonymous %></p>
192
+ <h1><%= @message.subject %></h1>
193
+ <p><%= @message.message %></p>
194
+ <% if @message.phone.present? %>
195
+ <p><%= @message.phone %></p>
196
+ <% end -%>
197
+ <p>Thanks, have a lovely day.</p>
198
+ </td>
199
+ </tr>
200
+ </table>
201
+ </div>
202
+ <!-- /content -->
203
+
204
+ </td>
205
+ <td></td>
206
+ </tr>
207
+ </table>
208
+ <!-- /body -->
209
+
210
+ </body>
211
+ </html>
@@ -0,0 +1,19 @@
1
+ module GeorgiaMailer
2
+ class SpamWorker
3
+ include Sidekiq::Worker
4
+
5
+ def perform(message_id)
6
+ begin
7
+ @message = GeorgiaMailer::Message.find(message_id)
8
+ is_spam = @message.spam?
9
+ @message.update_attributes(spam: is_spam, verified_at: Time.zone.now)
10
+ unless @message.spam or !GeorgiaMailer.turn_on_email_notifications
11
+ GeorgiaMailer::Notifier.new_message_notification(@message).deliver
12
+ end
13
+ rescue ActiveRecord::RecordNotFound
14
+ Rails.logger.info "Message with ID #{message_id} was destroy before it could be processed"
15
+ end
16
+ end
17
+
18
+ end
19
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,21 @@
1
+ GeorgiaMailer::Engine.routes.draw do
2
+
3
+ resources :messages, only: :create
4
+
5
+ end
6
+
7
+ Georgia::Engine.routes.draw do
8
+
9
+ resources :messages do
10
+ collection do
11
+ get :search
12
+ get :destroy_all_spam
13
+ end
14
+ member do
15
+ get :spam
16
+ get :ham
17
+ get :resend_notification
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,21 @@
1
+ class CreateGeorgiaMailerMessages < ActiveRecord::Migration
2
+
3
+ def change
4
+ create_table :georgia_mailer_messages do |t|
5
+ t.string :name
6
+ t.string :email
7
+ t.string :phone
8
+ t.string :subject
9
+ t.string :attachment
10
+ t.text :message
11
+ t.boolean :spam, default: false
12
+ t.datetime :verified_at
13
+ t.string :permalink
14
+ t.string :user_ip
15
+ t.string :user_agent
16
+ t.string :referrer
17
+ t.timestamps
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,10 @@
1
+ module GeorgiaMailer
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace GeorgiaMailer
4
+
5
+ require 'georgia'
6
+ require 'rakismet'
7
+ require 'sidekiq'
8
+
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module GeorgiaMailer
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,12 @@
1
+ require "georgia_mailer/engine"
2
+
3
+ module GeorgiaMailer
4
+
5
+ # Helper to turn off email notifications
6
+ mattr_accessor :turn_on_email_notifications
7
+ @@turn_on_email_notifications = true
8
+
9
+ # Add to Georgia by default
10
+ Georgia.navigation += %w(messages)
11
+
12
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :georgia_mailer do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: georgia_mailer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mathieu Gagné
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-03 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: 3.2.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: georgia
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: rakismet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sidekiq
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
+ description: GeorgiaMailer works essentially with Georgia CMS to provide spam-free
70
+ contact forms, email notifications, Sendgrid integration, and so on.
71
+ email:
72
+ - mathieu@motioneleven.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - app/assets/javascripts/georgia_mailer/components/message_table.js.coffee
78
+ - app/assets/javascripts/georgia_mailer/application.js
79
+ - app/assets/stylesheets/georgia_mailer/settings/_color.scss
80
+ - app/assets/stylesheets/georgia_mailer/modules/_message.scss
81
+ - app/assets/stylesheets/georgia_mailer/application.css.scss
82
+ - app/decorators/georgia_mailer/message_decorator.rb
83
+ - app/models/georgia/extensions/solr/georgia_mailer_message.rb
84
+ - app/models/georgia_mailer/concerns/tire_georgia_mailer_message_extension.rb
85
+ - app/models/georgia_mailer/concerns/solr_georgia_mailer_message_extension.rb
86
+ - app/models/georgia_mailer/message.rb
87
+ - app/controllers/georgia/messages_controller.rb
88
+ - app/controllers/georgia_mailer/messages_controller.rb
89
+ - app/views/georgia/header/_messages.html.erb
90
+ - app/views/georgia/messages/destroy.js.erb
91
+ - app/views/georgia/messages/show.html.erb
92
+ - app/views/georgia/messages/search.html.erb
93
+ - app/views/georgia/georgia_mailer/messages/_message.html.erb
94
+ - app/views/georgia_mailer/messages/create.js.erb
95
+ - app/views/georgia_mailer/notifier/new_message_notification.html.erb
96
+ - app/workers/georgia_mailer/spam_worker.rb
97
+ - app/mailers/georgia_mailer/notifier.rb
98
+ - app/presenters/georgia_mailer/message_actions_presenter.rb
99
+ - app/helpers/georgia/messages_helper.rb
100
+ - app/uploaders/georgia_mailer/attachment_uploader.rb
101
+ - config/routes.rb
102
+ - db/migrate/001_create_georgia_mailer_messages.rb
103
+ - lib/tasks/georgia_mailer_tasks.rake
104
+ - lib/georgia_mailer.rb
105
+ - lib/georgia_mailer/engine.rb
106
+ - lib/georgia_mailer/version.rb
107
+ - MIT-LICENSE
108
+ - Rakefile
109
+ - README.md
110
+ homepage: http://www.motioneleven.com/
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.1.11
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: Handles Georgia Messages, SPAM, contact forms, etc.
134
+ test_files: []