campagne 0.0.2
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.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/README.md +21 -0
- data/Rakefile +1 -0
- data/app/assets/.DS_Store +0 -0
- data/app/assets/javascripts/campagne.js +15 -0
- data/app/assets/stylesheets/campagne.css +118 -0
- data/app/controllers/bulletin/.DS_Store +0 -0
- data/app/controllers/bulletin/campagne_campaigns_controller.rb +62 -0
- data/app/controllers/bulletin/campagne_deliveries_controller.rb +39 -0
- data/app/controllers/bulletin/campagne_lists_controller.rb +45 -0
- data/app/helpers/campagne/error_messages_helper.rb +26 -0
- data/app/helpers/campagne/layout_helper.rb +26 -0
- data/app/models/campagne/campagne_campaign.rb +25 -0
- data/app/models/campagne/campagne_contact.rb +10 -0
- data/app/models/campagne/campagne_delivery.rb +37 -0
- data/app/models/campagne/campagne_list.rb +11 -0
- data/app/models/campagne/sender.rb +19 -0
- data/app/models/campagne/sender_job.rb +29 -0
- data/app/validators/email_validator.rb +56 -0
- data/app/views/campagne/.DS_Store +0 -0
- data/app/views/campagne/campagne_campaigns/_form.html.erb +23 -0
- data/app/views/campagne/campagne_campaigns/deliveries.html.erb +30 -0
- data/app/views/campagne/campagne_campaigns/edit.html.erb +8 -0
- data/app/views/campagne/campagne_campaigns/index.html.erb +20 -0
- data/app/views/campagne/campagne_campaigns/new.html.erb +5 -0
- data/app/views/campagne/campagne_campaigns/preview.html.erb +1 -0
- data/app/views/campagne/campagne_campaigns/show.html.erb +35 -0
- data/app/views/campagne/campagne_lists/_form.html.erb +8 -0
- data/app/views/campagne/campagne_lists/import.html.erb +12 -0
- data/app/views/campagne/campagne_lists/index.html.erb +16 -0
- data/app/views/campagne/campagne_lists/new.html.erb +5 -0
- data/app/views/campagne/campagne_lists/show.html.erb +17 -0
- data/app/views/layouts/campagne/campagne.html.erb +30 -0
- data/campagne.gemspec +23 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +25 -0
- data/lib/bulletin.rb +18 -0
- data/lib/campagne/engine.rb +23 -0
- data/lib/campagne/version.rb +3 -0
- data/lib/generators/campagne/campagne_generator.rb +30 -0
- data/lib/generators/campagne/templates/1x1.gif +0 -0
- data/lib/generators/campagne/templates/initializer.rb +11 -0
- data/lib/generators/campagne/templates/migration.rb +48 -0
- data/lib/tasks/resque.rake +100 -0
- data/test/test_helper.rb +0 -0
- metadata +153 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm ruby-1.9.3-p125
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Campagne
|
2
|
+
========
|
3
|
+
|
4
|
+
A simple Rails 3 engine gem to manage and send newsletters.
|
5
|
+
|
6
|
+
|
7
|
+
Use this on campaign body:
|
8
|
+
|
9
|
+
||WB|| - WebBeacon
|
10
|
+
||UNSUB|| - Unsubscribe
|
11
|
+
||LINK|| - Link
|
12
|
+
|
13
|
+
# Add to Gemfile
|
14
|
+
gem 'campagne'
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
$ bundle exec rails g campagne
|
18
|
+
# Change config/initializers/campagne.rb
|
19
|
+
|
20
|
+
$ bundle exec rake db:migrate
|
21
|
+
http://localhost:3000/campagne
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,118 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
14
|
+
|
15
|
+
body {
|
16
|
+
background-color: #CCC;
|
17
|
+
font-family: "Helvetica Nue", Helvetica, Arial;
|
18
|
+
font-size: 14px;
|
19
|
+
}
|
20
|
+
|
21
|
+
a img {
|
22
|
+
border: none;
|
23
|
+
}
|
24
|
+
|
25
|
+
a {
|
26
|
+
color: #0000FF;
|
27
|
+
}
|
28
|
+
|
29
|
+
.clear {
|
30
|
+
clear: both;
|
31
|
+
height: 0;
|
32
|
+
overflow: hidden;
|
33
|
+
}
|
34
|
+
|
35
|
+
#nav {
|
36
|
+
width: 75%;
|
37
|
+
margin: 0 auto;
|
38
|
+
padding: 0 40px;
|
39
|
+
margin-top: 20px;
|
40
|
+
}
|
41
|
+
|
42
|
+
#container {
|
43
|
+
width: 75%;
|
44
|
+
margin: 0 auto;
|
45
|
+
background-color: #FFF;
|
46
|
+
padding: 20px 40px;
|
47
|
+
border: solid 1px black;
|
48
|
+
margin-top: 20px;
|
49
|
+
}
|
50
|
+
|
51
|
+
#flash_notice, #flash_error {
|
52
|
+
padding: 5px 8px;
|
53
|
+
margin: 10px 0;
|
54
|
+
}
|
55
|
+
|
56
|
+
#flash_notice {
|
57
|
+
background-color: #CFC;
|
58
|
+
border: solid 1px #6C6;
|
59
|
+
}
|
60
|
+
|
61
|
+
#flash_error {
|
62
|
+
background-color: #FCC;
|
63
|
+
border: solid 1px #C66;
|
64
|
+
}
|
65
|
+
|
66
|
+
.fieldWithErrors {
|
67
|
+
display: inline;
|
68
|
+
}
|
69
|
+
|
70
|
+
#errorExplanation {
|
71
|
+
width: 400px;
|
72
|
+
border: 2px solid #CF0000;
|
73
|
+
padding: 0px;
|
74
|
+
padding-bottom: 12px;
|
75
|
+
margin-bottom: 20px;
|
76
|
+
background-color: #f0f0f0;
|
77
|
+
}
|
78
|
+
|
79
|
+
#errorExplanation h2 {
|
80
|
+
text-align: left;
|
81
|
+
font-weight: bold;
|
82
|
+
padding: 5px 5px 5px 15px;
|
83
|
+
font-size: 12px;
|
84
|
+
margin: 0;
|
85
|
+
background-color: #c00;
|
86
|
+
color: #fff;
|
87
|
+
}
|
88
|
+
|
89
|
+
#errorExplanation p {
|
90
|
+
color: #333;
|
91
|
+
margin-bottom: 0;
|
92
|
+
padding: 8px;
|
93
|
+
}
|
94
|
+
|
95
|
+
#errorExplanation ul {
|
96
|
+
margin: 2px 24px;
|
97
|
+
}
|
98
|
+
|
99
|
+
#errorExplanation ul li {
|
100
|
+
font-size: 12px;
|
101
|
+
list-style: disc;
|
102
|
+
}
|
103
|
+
|
104
|
+
table {
|
105
|
+
width: 50%;
|
106
|
+
border-top: 1px solid #CCC;
|
107
|
+
border-bottom: 1px solid #CCC;
|
108
|
+
}
|
109
|
+
|
110
|
+
table td {
|
111
|
+
border-top: 1px solid #CCC;
|
112
|
+
padding: 4px;
|
113
|
+
}
|
114
|
+
|
115
|
+
table th {
|
116
|
+
text-align: left;
|
117
|
+
padding: 4px;
|
118
|
+
}
|
Binary file
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Campagne
|
2
|
+
class CampagneCampaignsController < ApplicationController
|
3
|
+
respond_to :html, :xml
|
4
|
+
layout 'campagne/campagne'
|
5
|
+
|
6
|
+
def index
|
7
|
+
@campaigns = CampagneCampaign.all
|
8
|
+
end
|
9
|
+
|
10
|
+
def show
|
11
|
+
@campaign = CampagneCampaign.find(params[:id])
|
12
|
+
respond_with(@campaign)
|
13
|
+
end
|
14
|
+
|
15
|
+
def new
|
16
|
+
@campaign = CampagneCampaign.new
|
17
|
+
respond_with(@campaign)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@campaign = CampagneCampaign.new(params[:campagne_campagne_campaign])
|
22
|
+
flash[:notice] = 'Campaign was successfully created.' if @campaign.save
|
23
|
+
respond_with(@campaign, :location => campagne_campagne_campaigns_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def edit
|
27
|
+
@campaign = CampagneCampaign.find(params[:id])
|
28
|
+
respond_with(@campaign)
|
29
|
+
end
|
30
|
+
|
31
|
+
def update
|
32
|
+
@campaign = CampagneCampaign.find(params[:id])
|
33
|
+
flash[:notice] = 'Campaign was successfully updated.' if @campaign.update_attributes(params[:campagne_campagne_campaign])
|
34
|
+
respond_with(@campaign)
|
35
|
+
end
|
36
|
+
|
37
|
+
def preview
|
38
|
+
@campaign = CampagneCampaign.find(params[:id])
|
39
|
+
render :layout => nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def deliveries
|
43
|
+
@campaign = CampagneCampaign.find(params[:id])
|
44
|
+
@deliveries = @campaign.campagne_deliveries
|
45
|
+
end
|
46
|
+
|
47
|
+
def schedule
|
48
|
+
@campaign = CampagneCampaign.find(params[:id])
|
49
|
+
datetime = Time.zone.local(
|
50
|
+
params[:schedule][:"at(1i)"].to_i,
|
51
|
+
params[:schedule][:"at(2i)"].to_i,
|
52
|
+
params[:schedule][:"at(3i)"].to_i,
|
53
|
+
params[:schedule][:"at(4i)"].to_i,
|
54
|
+
params[:schedule][:"at(5i)"].to_i
|
55
|
+
)
|
56
|
+
Resque.enqueue_at(datetime, Campagne::SenderJob, @campaign.id)
|
57
|
+
redirect_to campagne_campagne_campaign_path(@campaign), :notice => 'Campaign was successfully scheduled.'
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Campagne
|
2
|
+
class CampagneDeliveriesController < ApplicationController
|
3
|
+
|
4
|
+
def see
|
5
|
+
if delivery = CampagneDelivery.find_by_token(params[:token])
|
6
|
+
delivery.see!(request)
|
7
|
+
end
|
8
|
+
image = File.read(File.join(Rails.root, "public/1x1.gif"))
|
9
|
+
send_data image, :type => "image/gif", :disposition => "inline"
|
10
|
+
end
|
11
|
+
|
12
|
+
def click
|
13
|
+
if delivery = CampagneDelivery.find_by_token(params[:token])
|
14
|
+
delivery.click!(request)
|
15
|
+
end
|
16
|
+
# TODO:
|
17
|
+
redirect_to "http://#{params[:link]}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def unsubscribe
|
21
|
+
if delivery = CampagneDelivery.find_by_token(params[:token])
|
22
|
+
delivery.unsubscribe!(request)
|
23
|
+
render :text => 'Ok'
|
24
|
+
else
|
25
|
+
render :text => 'Error'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def bounce
|
30
|
+
if delivery = CampagneDelivery.find_by_token(params[:token])
|
31
|
+
delivery.bounce!
|
32
|
+
render :text => 'Ok'
|
33
|
+
else
|
34
|
+
render :text => 'Error'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Campagne
|
2
|
+
class CampagneListsController < ApplicationController
|
3
|
+
respond_to :html, :xml
|
4
|
+
layout 'campagne/campagne'
|
5
|
+
|
6
|
+
def index
|
7
|
+
@lists = CampagneList.all
|
8
|
+
end
|
9
|
+
|
10
|
+
def show
|
11
|
+
@list = CampagneList.find(params[:id])
|
12
|
+
respond_with(@list)
|
13
|
+
end
|
14
|
+
|
15
|
+
def new
|
16
|
+
@list = CampagneList.new
|
17
|
+
respond_with(@list)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@list = CampagneList.new(params[:campagne_campagne_list])
|
22
|
+
flash[:notice] = 'List was successfully created.' if @list.save
|
23
|
+
respond_with(@list, :location => campagne_campagne_lists_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def import
|
27
|
+
@list = CampagneList.find(params[:id])
|
28
|
+
if request.post?
|
29
|
+
emails = params[:emails].split("\r\n")
|
30
|
+
emails.each do |email|
|
31
|
+
if contact = CampagneContact.find_by_email(email)
|
32
|
+
if !@list.contacts.exists?(contact.id)
|
33
|
+
@list.contacts << contact
|
34
|
+
@list.save
|
35
|
+
end
|
36
|
+
else
|
37
|
+
@list.contacts.create(:email => email)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
redirect_to campagne_campagne_lists_path, :notice => "Contacts were successfully imported."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Campagne
|
2
|
+
module ErrorMessagesHelper
|
3
|
+
# Render error messages for the given objects. The :message and :header_message options are allowed.
|
4
|
+
def error_messages_for(*objects)
|
5
|
+
options = objects.extract_options!
|
6
|
+
options[:header_message] ||= I18n.t(:"activerecord.errors.header", :default => "Invalid Fields")
|
7
|
+
options[:message] ||= I18n.t(:"activerecord.errors.message", :default => "Correct the following errors and try again.")
|
8
|
+
messages = objects.compact.map { |o| o.errors.full_messages }.flatten
|
9
|
+
unless messages.empty?
|
10
|
+
content_tag(:div, :class => "error_messages") do
|
11
|
+
list_items = messages.map { |msg| content_tag(:li, msg) }
|
12
|
+
content_tag(:h2, options[:header_message]) + content_tag(:p, options[:message]) + content_tag(:ul, list_items.join.html_safe)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module FormBuilderAdditions
|
18
|
+
def error_messages(options = {})
|
19
|
+
@template.error_messages_for(@object, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
ActionView::Helpers::FormBuilder.send(:include, Campagne::ErrorMessagesHelper::FormBuilderAdditions)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# These helper methods can be called in your template to set variables to be used in the layout
|
2
|
+
# This module should be included in all views globally,
|
3
|
+
# to do so you may need to add this line to your ApplicationController
|
4
|
+
# helper :layout
|
5
|
+
module Campagne
|
6
|
+
module LayoutHelper
|
7
|
+
|
8
|
+
def title(page_title, show_title = true)
|
9
|
+
content_for(:title) { h(page_title.to_s) }
|
10
|
+
@show_title = show_title
|
11
|
+
end
|
12
|
+
|
13
|
+
def show_title?
|
14
|
+
@show_title
|
15
|
+
end
|
16
|
+
|
17
|
+
def stylesheet(*args)
|
18
|
+
content_for(:head) { stylesheet_link_tag(*args) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def javascript(*args)
|
22
|
+
content_for(:head) { javascript_include_tag(*args) }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Campagne
|
2
|
+
class CampagneCampaign < ActiveRecord::Base
|
3
|
+
has_and_belongs_to_many :campagne_lists
|
4
|
+
has_many :campagne_deliveries
|
5
|
+
|
6
|
+
attr_accessible :name, :subject, :body, :campagne_list_ids
|
7
|
+
|
8
|
+
validates :name, :presence => true, :uniqueness => true
|
9
|
+
validates :subject, :presence => true
|
10
|
+
validates :body, :presence => true
|
11
|
+
|
12
|
+
def formated_body(token)
|
13
|
+
formated_body = body.gsub('||WB||', "<img src=\"#{Rails.application.config.campagne_base_url}/campagne/see/#{token}\" width=\"1\" height=\"1\" />")
|
14
|
+
formated_body = formated_body.gsub('||UNSUB||', "#{Rails.application.config.campagne_base_url}/campagne/unsubscribe/#{token}")
|
15
|
+
formated_body = formated_body.gsub('||LINK||', "#{Rails.application.config.campagne_base_url}/campagne/click/#{token}?link=")
|
16
|
+
# formated_body = formated_body.gsub('||VIEW||', "#{Rails.application.config.campagne_base_url}/campagne/preview/#{token}")
|
17
|
+
formated_body
|
18
|
+
end
|
19
|
+
|
20
|
+
def preview
|
21
|
+
formated_body('0')
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Campagne
|
2
|
+
class CampagneContact < ActiveRecord::Base
|
3
|
+
has_and_belongs_to_many :campagne_lists
|
4
|
+
|
5
|
+
attr_accessible :email, :name
|
6
|
+
|
7
|
+
validates :email, :presence => true, :uniqueness => true, :email => true, :incorrect_email => true, :denied_email => true
|
8
|
+
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Campagne
|
2
|
+
class CampagneDelivery < ActiveRecord::Base
|
3
|
+
belongs_to :campagne_campaign
|
4
|
+
|
5
|
+
attr_accessible :contact_id, :campagne_list_id , :email, :token
|
6
|
+
|
7
|
+
def see!(request)
|
8
|
+
self.ip_address = request.remote_ip if ip_address.blank?
|
9
|
+
self.user_agent = request.user_agent if user_agent.blank?
|
10
|
+
self.seen_at = Time.now if seen_at.nil?
|
11
|
+
save
|
12
|
+
end
|
13
|
+
|
14
|
+
def click!(request)
|
15
|
+
self.ip_address = request.remote_ip if ip_address.blank?
|
16
|
+
self.user_agent = request.user_agent if user_agent.blank?
|
17
|
+
self.seen_at = Time.now if seen_at.nil?
|
18
|
+
self.clicked_at = Time.now if clicked_at.nil?
|
19
|
+
save
|
20
|
+
end
|
21
|
+
|
22
|
+
def unsubscribe!(request)
|
23
|
+
CampagneList.find(list_id).contacts.delete(CampagneContact.find(contact_id)) # Remove contact from list
|
24
|
+
self.ip_address = request.remote_ip if ip_address.blank?
|
25
|
+
self.user_agent = request.user_agent if user_agent.blank?
|
26
|
+
self.seen_at = Time.now if seen_at.nil?
|
27
|
+
self.unsubscribed_at = Time.now if unsubscribed_at.nil?
|
28
|
+
save
|
29
|
+
end
|
30
|
+
|
31
|
+
def bounce!
|
32
|
+
CampagneList.find(list_id).contacts.delete(CampagneContact.find(contact_id))
|
33
|
+
update_attribute(:bounced_at, Time.now) if bounced_at.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Campagne
|
2
|
+
class CampagneList < ActiveRecord::Base
|
3
|
+
has_and_belongs_to_many :campagne_contacts
|
4
|
+
has_and_belongs_to_many :campagne_campaigns
|
5
|
+
|
6
|
+
attr_accessible :name, :contact_ids
|
7
|
+
|
8
|
+
validates :name, :presence => true, :uniqueness => true
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Campagne
|
3
|
+
class Sender
|
4
|
+
|
5
|
+
def self.deliver_email(email, subject, email_body, token)
|
6
|
+
sleep 0.5
|
7
|
+
ActionMailer::Base::mail({
|
8
|
+
:from => "#{Rails.application.config.campagne_from_name} <#{Rails.application.config.campagne_from_email}>",
|
9
|
+
:to => email.downcase,
|
10
|
+
:subject => subject,
|
11
|
+
:body => email_body,
|
12
|
+
:content_type => 'text/html; charset=UTF-8'#,
|
13
|
+
:'Return-Path' => "bounce+#{token}@#{Rails.application.config.campagne_domain}",
|
14
|
+
:'List-Unsubscribe' => "<mailto:unsubscribe-#{token}@#{Rails.application.config.campagne_domain}>, <#{Rails.application.config.campagne_base_url}/campagne/unsubscribe/#{token}>"
|
15
|
+
}).deliver
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module Campagne
|
3
|
+
class SenderJob
|
4
|
+
@queue = :default
|
5
|
+
|
6
|
+
def self.generate_token
|
7
|
+
token = rand(36**18).to_s(36)
|
8
|
+
token = token.gsub('+', '')
|
9
|
+
while CampagneDelivery.where(:token => token).first do
|
10
|
+
token = rand(36**18).to_s(36)
|
11
|
+
end
|
12
|
+
token
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.perform(campaign_id)
|
16
|
+
ActiveSupport::BufferedLogger.new(Rails.root.join('log/resque.log')).info([Time.now.iso8601, $$, "I", "---PERFORM---", campaign_id].join("\t"))
|
17
|
+
campaign = Campaign.find(campaign_id)
|
18
|
+
contacts = campaign.lists.map(&:contacts).flatten
|
19
|
+
contacts = contacts.sort_by {rand} # shuffle
|
20
|
+
contacts.each do |contact|
|
21
|
+
next if campaign.deliveries.where(:contact_id => contact.id).first
|
22
|
+
token = generate_token
|
23
|
+
Sender.deliver_email(contact.email, campaign.subject, campaign.formated_body(token), token)
|
24
|
+
campaign.deliveries.create(:contact_id => contact.id, :email => contact.email, :token => token)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'mail'
|
3
|
+
|
4
|
+
class EmailValidator < ActiveModel::EachValidator
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
return if value.blank?
|
7
|
+
begin
|
8
|
+
# http://my.rails-royce.org/2010/07/21/email-validation-in-ruby-on-rails-without-regexp/
|
9
|
+
m = Mail::Address.new(value)
|
10
|
+
# We must check that value contains a domain and that value is an email address
|
11
|
+
r = m.domain && m.address == value
|
12
|
+
t = m.__send__(:tree)
|
13
|
+
# We need to dig into treetop
|
14
|
+
# A valid domain must have dot_atom_text elements size > 1
|
15
|
+
# user@localhost is excluded
|
16
|
+
# treetop must respond to domain
|
17
|
+
# We exclude valid email values like <user@localhost.com>
|
18
|
+
# Hence we use m.__send__(tree).domain
|
19
|
+
r &&= (t.domain.dot_atom_text.elements.size > 1)
|
20
|
+
rescue Exception => e
|
21
|
+
r = false
|
22
|
+
end
|
23
|
+
record.errors[attribute] << (options[:message] || 'inválido') unless r
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class IncorrectEmailValidator < ActiveModel::EachValidator
|
28
|
+
def validate_each(record, attribute, value)
|
29
|
+
domains = %w(
|
30
|
+
hotmail.com.br gmail.com.br
|
31
|
+
hotamil.com hotimail.com hotmail.com.br hotmail.com.com hotmail.con hotmal.com hoymail.com hotmil.com
|
32
|
+
gamil.com gmail.com.br gmal.com gmeil.com.br gmial.com
|
33
|
+
teste.com teste.com.br
|
34
|
+
yaoo.com
|
35
|
+
.com.be
|
36
|
+
)
|
37
|
+
domains.each do |d|
|
38
|
+
if value && value.include?("@#{d}")
|
39
|
+
record.errors[attribute] << 'está incorreto'
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class DeniedEmailValidator < ActiveModel::EachValidator
|
47
|
+
def validate_each(record, attribute, value)
|
48
|
+
domains = %w(mailinator.com dodgit.com uggsrock.com spambox.us spamhole.com spam.la trashymail.com guerrillamailblock.com spamspot.com spamfree tempomail.fr jetable.net maileater.com meltmail.com)
|
49
|
+
domains.each do |d|
|
50
|
+
if value && value.include?("@#{d}")
|
51
|
+
record.errors[attribute] << 'inválido'
|
52
|
+
break
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<%= form_for @campaign do |f| %>
|
2
|
+
<%= f.error_messages %>
|
3
|
+
<p>
|
4
|
+
<%= f.label :name %><br />
|
5
|
+
<%= f.text_field :name %>
|
6
|
+
</p>
|
7
|
+
<p>
|
8
|
+
<%= f.label :subject %><br />
|
9
|
+
<%= f.text_field :subject %>
|
10
|
+
</p>
|
11
|
+
<p>
|
12
|
+
<%= f.label :body %><br />
|
13
|
+
<%= f.text_area :body %>
|
14
|
+
</p>
|
15
|
+
<p>
|
16
|
+
<%= hidden_field_tag "campagne_campagne_campaign[campagne_list_ids][]", nil%>
|
17
|
+
<% Campagne::CampagneList.all.each do |list| %>
|
18
|
+
<%= check_box_tag "campagne_campagne_campaign[campagne_list_ids][]", list.id, @campaign.campagne_list_ids.include?(list.id), id: dom_id(list) %>
|
19
|
+
<%= label_tag dom_id(list), list.name %><br/>
|
20
|
+
<% end %>
|
21
|
+
</p>
|
22
|
+
<p><%= f.submit 'Create Campaign' %></p>
|
23
|
+
<% end %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<% title "Deliveries" %>
|
2
|
+
|
3
|
+
<h2><%= @campaign.name %></h2>
|
4
|
+
|
5
|
+
<table style="width:100%">
|
6
|
+
<tr>
|
7
|
+
<th>Email</th>
|
8
|
+
<th>Token</th>
|
9
|
+
<th>Sent at</th>
|
10
|
+
<th>Seen at</th>
|
11
|
+
<th>Clicked at</th>
|
12
|
+
<th>Unsubscribed at</th>
|
13
|
+
<th>Bounced at</th>
|
14
|
+
<th>IP Address</th>
|
15
|
+
<th>User Agent</th>
|
16
|
+
</tr>
|
17
|
+
<% for delivery in @deliveries %>
|
18
|
+
<tr>
|
19
|
+
<td><%= delivery.email %></td>
|
20
|
+
<td><%= delivery.token %></td>
|
21
|
+
<td><%= delivery.created_at %></td>
|
22
|
+
<td><%= delivery.seen_at %></td>
|
23
|
+
<td><%= delivery.clicked_at %></td>
|
24
|
+
<td><%= delivery.unsubscribed_at %></td>
|
25
|
+
<td><%= delivery.bounced_at %></td>
|
26
|
+
<td><%= delivery.ip_address %></td>
|
27
|
+
<td><%= delivery.user_agent %></td>
|
28
|
+
</tr>
|
29
|
+
<% end %>
|
30
|
+
</table>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<% title "Campaigns" %>
|
2
|
+
|
3
|
+
<p><%= link_to "New Campaign", new_campagne_campagne_campaign_path %></p>
|
4
|
+
|
5
|
+
<table>
|
6
|
+
<tr>
|
7
|
+
<th>Name</th>
|
8
|
+
<th>Subject</th>
|
9
|
+
</tr>
|
10
|
+
<% for campaign in @campaigns %>
|
11
|
+
<tr>
|
12
|
+
<td><%= campaign.name %></td>
|
13
|
+
<td><%= campaign.subject %></td>
|
14
|
+
<td><%= link_to "Preview", preview_campagne_campagne_campaign_path(campaign) %></td>
|
15
|
+
<td><%= link_to "Deliveries", deliveries_campagne_campagne_campaign_path(campaign) %></td>
|
16
|
+
<td><%= link_to "Show", campaign %></td>
|
17
|
+
<td><%= link_to "Edit", edit_campagne_campagne_campaign_path(campaign) %></td>
|
18
|
+
</tr>
|
19
|
+
<% end %>
|
20
|
+
</table>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= raw(@campaign.preview) %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<% title "Campaign" %>
|
2
|
+
|
3
|
+
<%= form_tag schedule_campagne_campagne_campaign_path(@campaign) do %>
|
4
|
+
<%= datetime_select("schedule", 'at', :default => Time.now) %>
|
5
|
+
<%= submit_tag 'Schedule' %>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<p>
|
9
|
+
<%= link_to "Preview", preview_campagne_campagne_campaign_path(@campaign) %> |
|
10
|
+
<%= link_to "Deliveries", deliveries_campagne_campagne_campaign_path(@campaign) %> |
|
11
|
+
<%= link_to "Edit", edit_campagne_campagne_campaign_path(@campaign) %> |
|
12
|
+
<%= link_to "View All", campagne_campagne_campaigns_path %>
|
13
|
+
</p>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
<strong>Name:</strong>
|
17
|
+
<%= @campaign.name %>
|
18
|
+
</p>
|
19
|
+
<p>
|
20
|
+
<strong>Subject:</strong>
|
21
|
+
<%= @campaign.subject %>
|
22
|
+
</p>
|
23
|
+
<p>
|
24
|
+
<strong>Lists:</strong><br/>
|
25
|
+
<% @campaign.campagne_lists.each do |list| %>
|
26
|
+
<%= list.name %><br/>
|
27
|
+
<% end %>
|
28
|
+
</p>
|
29
|
+
<p>
|
30
|
+
<strong>Body:</strong><br />
|
31
|
+
<pre>
|
32
|
+
<%= raw(@campaign.body) %>
|
33
|
+
</pre>
|
34
|
+
</p>
|
35
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<% title "Import contacts" %>
|
2
|
+
|
3
|
+
<h2><%= @list.name %></h2>
|
4
|
+
|
5
|
+
<%= form_tag import_campagne_campagne_list_path(@list) do %>
|
6
|
+
<p>
|
7
|
+
<%= text_area_tag 'emails', nil, :style => 'width:600px;height:400px' %>
|
8
|
+
</p>
|
9
|
+
<p><%= submit_tag 'Import' %></p>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<p><%= link_to "Back", campagne_campagne_lists_path %></p>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% title "Lists" %>
|
2
|
+
|
3
|
+
<p><%= link_to "New List", new_campagne_campagne_list_path %></p>
|
4
|
+
|
5
|
+
<table>
|
6
|
+
<tr>
|
7
|
+
<th>Name</th>
|
8
|
+
</tr>
|
9
|
+
<% for list in @lists %>
|
10
|
+
<tr>
|
11
|
+
<td><%= list.name %></td>
|
12
|
+
<td><%= link_to "Import", import_campagne_campagne_list_path(list) %></td>
|
13
|
+
<td><%= link_to "Show", list %></td>
|
14
|
+
</tr>
|
15
|
+
<% end %>
|
16
|
+
</table>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% title "List" %>
|
2
|
+
|
3
|
+
<p>
|
4
|
+
<strong>Name:</strong>
|
5
|
+
<%= @list.name %>
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<p>
|
9
|
+
<strong>Emails:</strong><br />
|
10
|
+
<% @list.campagne_contacts.each do |contact| %>
|
11
|
+
<%= contact.email %><br />
|
12
|
+
<% end %>
|
13
|
+
</p>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
<%= link_to "View All", campagne_campagne_lists_path %>
|
17
|
+
</p>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= content_for?(:title) ? yield(:title) : "Untitled" %></title>
|
5
|
+
<%= stylesheet_link_tag :campagne %>
|
6
|
+
<%= javascript_include_tag :campagne %>
|
7
|
+
<%= csrf_meta_tag %>
|
8
|
+
<%= yield(:head) %>
|
9
|
+
</head>
|
10
|
+
<body>
|
11
|
+
|
12
|
+
<div id="nav">
|
13
|
+
<%= link_to 'Lists', campagne_campagne_lists_path %> |
|
14
|
+
<%= link_to 'Campaings', campagne_campagne_campaigns_path %>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div id="container">
|
18
|
+
<% if notice %>
|
19
|
+
<%= content_tag :div, notice, :id => "flash_notice" %>
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<% if alert %>
|
23
|
+
<%= content_tag :div, alert, :id => "flash_alert" %>
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
<%= content_tag :h1, yield(:title) if show_title? %>
|
27
|
+
<%= yield %>
|
28
|
+
</div>
|
29
|
+
</body>
|
30
|
+
</html>
|
data/campagne.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/campagne/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "campagne"
|
6
|
+
s.version = Campagne::VERSION
|
7
|
+
s.authors = ["Arley Lobato"]
|
8
|
+
s.email = ["arleylobato@gmail.com"]
|
9
|
+
s.homepage = "http://github.com/alobato/campagne"
|
10
|
+
s.summary = "campagne-#{s.version}"
|
11
|
+
s.description = "A simple Rails 3 engine gem to manage and send newsletters."
|
12
|
+
|
13
|
+
s.add_dependency "rails", "~> 3.2.3"
|
14
|
+
s.add_dependency "mysql2", "~> 0.3.11"
|
15
|
+
s.add_dependency "jquery-rails", "~> 2.0.2"
|
16
|
+
s.add_dependency "resque", "~> 1.20.0"
|
17
|
+
s.add_dependency "resque-scheduler", "~> 1.9.9"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
21
|
+
s.require_path = 'lib'
|
22
|
+
|
23
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Rails.application.routes.draw do
|
2
|
+
|
3
|
+
namespace :campagne do
|
4
|
+
root to: 'campagne_lists#index'
|
5
|
+
mount Resque::Server.new, at: 'resque'
|
6
|
+
resources :campagne_lists do
|
7
|
+
member do
|
8
|
+
get 'import'
|
9
|
+
post 'import'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
resources :campagne_campaigns do
|
13
|
+
member do
|
14
|
+
get 'preview'
|
15
|
+
get 'deliveries'
|
16
|
+
post 'schedule'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
get 'see/:token' => 'campagne_deliveries#see'
|
20
|
+
get 'click/:token' => 'campagne_deliveries#click'
|
21
|
+
get 'unsubscribe/:token' => 'campagne_deliveries#unsubscribe'
|
22
|
+
get 'bounce/:token' => 'campagne_deliveries#bounce'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/bulletin.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Requires
|
2
|
+
require "active_support/dependencies"
|
3
|
+
|
4
|
+
module Campagne
|
5
|
+
|
6
|
+
# Our host application root path
|
7
|
+
# We set this when the engine is initialized
|
8
|
+
mattr_accessor :app_root
|
9
|
+
|
10
|
+
# Yield self on setup for nice config blocks
|
11
|
+
def self.setup
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
# Require our engine
|
18
|
+
require "campagne/engine"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# http://edgeapi.rubyonrails.org/classes/Rails/Engine.html
|
2
|
+
module Campagne
|
3
|
+
class Engine < Rails::Engine
|
4
|
+
|
5
|
+
initializer "campagne.load_app_instance_data" do |app|
|
6
|
+
Campagne.setup do |config|
|
7
|
+
config.app_root = app.root
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer "campagne.load_static_assets" do |app|
|
12
|
+
app.middleware.use ::ActionDispatch::Static, "#{root}/public"
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "campagne.load_resque_config" do |app|
|
16
|
+
require 'resque/server'
|
17
|
+
Resque::Server.use(Rack::Auth::Basic)
|
18
|
+
require 'resque_scheduler'
|
19
|
+
require 'resque_scheduler/server'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
class CampagneGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
def self.source_root
|
7
|
+
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.next_migration_number(dirname)
|
11
|
+
if 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 create_migration_file
|
19
|
+
migration_template 'migration.rb', 'db/migrate/create_campagne_tables.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
def copy_initializer_file
|
23
|
+
copy_file 'initializer.rb', 'config/initializers/campagne.rb'
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy_1_pixel_image
|
27
|
+
copy_file "1x1.gif", "public/1x1.gif"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
Binary file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'resque/server'
|
2
|
+
Resque::Server.use(Rack::Auth::Basic) do |username, password|
|
3
|
+
username == 'admin' && password == "secret"
|
4
|
+
end
|
5
|
+
|
6
|
+
Rails.application.class.configure do
|
7
|
+
config.campagne_from_name = 'Foo'
|
8
|
+
config.campagne_from_email = 'foo@bar.com'
|
9
|
+
config.campagne_domain = 'foobar.com'
|
10
|
+
config.campagne_base_url = 'http://www.foobar.com'
|
11
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class CreateCampagneTables < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
create_table :campagne_contacts do |t|
|
5
|
+
t.string :email
|
6
|
+
t.string :name
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
create_table :campagne_lists do |t|
|
10
|
+
t.string :name
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
create_table :campagne_contacts_campagne_lists, :id => false do |t|
|
14
|
+
t.integer :campagne_contact_id
|
15
|
+
t.integer :campagne_list_id
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
create_table :campagne_campaigns do |t|
|
19
|
+
t.string :name
|
20
|
+
t.string :subject
|
21
|
+
t.text :body, :limit => 2147483647
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
create_table :campagne_campaigns_campagne_lists, :id => false do |t|
|
25
|
+
t.integer :campagne_campaign_id
|
26
|
+
t.integer :campagne_list_id
|
27
|
+
t.timestamps
|
28
|
+
end
|
29
|
+
create_table :campagne_deliveries do |t|
|
30
|
+
t.integer :campagne_campaign_id
|
31
|
+
t.integer :campagne_contact_id
|
32
|
+
t.integer :campagne_list_id
|
33
|
+
t.string :email
|
34
|
+
t.string :token
|
35
|
+
t.datetime :seen_at
|
36
|
+
t.datetime :clicked_at
|
37
|
+
t.datetime :unsubscribed_at
|
38
|
+
t.datetime :bounced_at
|
39
|
+
t.string :ip_address
|
40
|
+
t.string :user_agent
|
41
|
+
t.timestamps
|
42
|
+
end
|
43
|
+
add_index "campagne_deliveries", ["token"], :name => "index_deliveries_on_token", :unique => true
|
44
|
+
add_index "campagne_campaigns_campagne_lists", ["campagne_campaign_id", "campagne_list_id"], :name => "index_campaigns_lists_on_c_and_l", :unique => true
|
45
|
+
add_index "campagne_contacts_campagne_lists", ["campagne_contact_id", "campagne_list_id"], :name => "index_contacts_lists_on_c_and_l", :unique => true
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'resque/tasks'
|
2
|
+
require 'resque_scheduler/tasks'
|
3
|
+
|
4
|
+
def run_worker(queue, count = 1)
|
5
|
+
puts "Starting #{count} worker(s) with QUEUE: #{queue}"
|
6
|
+
ops = {:pgroup => true, :err => [(Rails.root + "log/workers_error.log").to_s, "a"],
|
7
|
+
:out => [(Rails.root + "log/workers.log").to_s, "a"]}
|
8
|
+
env_vars = {
|
9
|
+
"QUEUE" => queue.to_s,
|
10
|
+
"BACKGROUND" => "1",
|
11
|
+
"PIDFILE" => (Rails.root + "tmp/pids/resque.pid").to_s,
|
12
|
+
"VERBOSE" => "1"
|
13
|
+
}
|
14
|
+
count.times {
|
15
|
+
pid = spawn(env_vars, "rake resque:work", ops)
|
16
|
+
Process.detach(pid)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_scheduler
|
21
|
+
puts "Starting resque scheduler"
|
22
|
+
env_vars = {
|
23
|
+
"BACKGROUND" => "1",
|
24
|
+
"PIDFILE" => (Rails.root + "tmp/pids/resque_scheduler.pid").to_s,
|
25
|
+
"VERBOSE" => "1"
|
26
|
+
}
|
27
|
+
ops = {:pgroup => true, :err => [(Rails.root + "log/scheduler_error.log").to_s, "a"],
|
28
|
+
:out => [(Rails.root + "log/scheduler.log").to_s, "a"]}
|
29
|
+
pid = spawn(env_vars, "rake resque:scheduler", ops)
|
30
|
+
Process.detach(pid)
|
31
|
+
end
|
32
|
+
|
33
|
+
namespace :resque do
|
34
|
+
task :setup => :environment
|
35
|
+
|
36
|
+
desc "Restart running workers"
|
37
|
+
task :restart_workers => :environment do
|
38
|
+
Rake::Task['resque:stop_workers'].invoke
|
39
|
+
Rake::Task['resque:start_workers'].invoke
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Quit running workers"
|
43
|
+
task :stop_workers => :environment do
|
44
|
+
pids = Array.new
|
45
|
+
Resque.workers.each do |worker|
|
46
|
+
pids.concat(worker.worker_pids)
|
47
|
+
end
|
48
|
+
if pids.empty?
|
49
|
+
puts "No workers to kill"
|
50
|
+
else
|
51
|
+
syscmd = "kill -s QUIT #{pids.join(' ')}"
|
52
|
+
puts "Running syscmd: #{syscmd}"
|
53
|
+
system(syscmd)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "Start workers"
|
58
|
+
task :start_workers => :environment do
|
59
|
+
run_worker("default", 1)
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "Restart scheduler"
|
63
|
+
task :restart_scheduler => :environment do
|
64
|
+
Rake::Task['resque:stop_scheduler'].invoke
|
65
|
+
Rake::Task['resque:start_scheduler'].invoke
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Quit scheduler"
|
69
|
+
task :stop_scheduler => :environment do
|
70
|
+
pidfile = Rails.root + "tmp/pids/resque_scheduler.pid"
|
71
|
+
if !File.exists?(pidfile)
|
72
|
+
puts "Scheduler not running"
|
73
|
+
else
|
74
|
+
pid = File.read(pidfile).to_i
|
75
|
+
syscmd = "kill -s QUIT #{pid}"
|
76
|
+
puts "Running syscmd: #{syscmd}"
|
77
|
+
system(syscmd)
|
78
|
+
FileUtils.rm_f(pidfile)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
desc "Start scheduler"
|
83
|
+
task :start_scheduler => :environment do
|
84
|
+
run_scheduler
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "Reload schedule"
|
88
|
+
task :reload_schedule => :environment do
|
89
|
+
pidfile = Rails.root + "tmp/pids/resque_scheduler.pid"
|
90
|
+
|
91
|
+
if !File.exists?(pidfile)
|
92
|
+
puts "Scheduler not running"
|
93
|
+
else
|
94
|
+
pid = File.read(pidfile).to_i
|
95
|
+
syscmd = "kill -s USR2 #{pid}"
|
96
|
+
puts "Running syscmd: #{syscmd}"
|
97
|
+
system(syscmd)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/test/test_helper.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: campagne
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Arley Lobato
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &70126380669840 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70126380669840
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mysql2
|
27
|
+
requirement: &70126380668860 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.3.11
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70126380668860
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: jquery-rails
|
38
|
+
requirement: &70126380668040 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.0.2
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70126380668040
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: resque
|
49
|
+
requirement: &70126380667220 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.20.0
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70126380667220
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: resque-scheduler
|
60
|
+
requirement: &70126380666460 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.9.9
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70126380666460
|
69
|
+
description: A simple Rails 3 engine gem to manage and send newsletters.
|
70
|
+
email:
|
71
|
+
- arleylobato@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rvmrc
|
78
|
+
- Gemfile
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- app/assets/.DS_Store
|
82
|
+
- app/assets/javascripts/campagne.js
|
83
|
+
- app/assets/stylesheets/campagne.css
|
84
|
+
- app/controllers/bulletin/.DS_Store
|
85
|
+
- app/controllers/bulletin/campagne_campaigns_controller.rb
|
86
|
+
- app/controllers/bulletin/campagne_deliveries_controller.rb
|
87
|
+
- app/controllers/bulletin/campagne_lists_controller.rb
|
88
|
+
- app/helpers/campagne/error_messages_helper.rb
|
89
|
+
- app/helpers/campagne/layout_helper.rb
|
90
|
+
- app/models/campagne/campagne_campaign.rb
|
91
|
+
- app/models/campagne/campagne_contact.rb
|
92
|
+
- app/models/campagne/campagne_delivery.rb
|
93
|
+
- app/models/campagne/campagne_list.rb
|
94
|
+
- app/models/campagne/sender.rb
|
95
|
+
- app/models/campagne/sender_job.rb
|
96
|
+
- app/validators/email_validator.rb
|
97
|
+
- app/views/campagne/.DS_Store
|
98
|
+
- app/views/campagne/campagne_campaigns/_form.html.erb
|
99
|
+
- app/views/campagne/campagne_campaigns/deliveries.html.erb
|
100
|
+
- app/views/campagne/campagne_campaigns/edit.html.erb
|
101
|
+
- app/views/campagne/campagne_campaigns/index.html.erb
|
102
|
+
- app/views/campagne/campagne_campaigns/new.html.erb
|
103
|
+
- app/views/campagne/campagne_campaigns/preview.html.erb
|
104
|
+
- app/views/campagne/campagne_campaigns/show.html.erb
|
105
|
+
- app/views/campagne/campagne_lists/_form.html.erb
|
106
|
+
- app/views/campagne/campagne_lists/import.html.erb
|
107
|
+
- app/views/campagne/campagne_lists/index.html.erb
|
108
|
+
- app/views/campagne/campagne_lists/new.html.erb
|
109
|
+
- app/views/campagne/campagne_lists/show.html.erb
|
110
|
+
- app/views/layouts/campagne/campagne.html.erb
|
111
|
+
- campagne.gemspec
|
112
|
+
- config/locales/en.yml
|
113
|
+
- config/routes.rb
|
114
|
+
- lib/bulletin.rb
|
115
|
+
- lib/campagne/engine.rb
|
116
|
+
- lib/campagne/version.rb
|
117
|
+
- lib/generators/campagne/campagne_generator.rb
|
118
|
+
- lib/generators/campagne/templates/1x1.gif
|
119
|
+
- lib/generators/campagne/templates/initializer.rb
|
120
|
+
- lib/generators/campagne/templates/migration.rb
|
121
|
+
- lib/tasks/resque.rake
|
122
|
+
- test/test_helper.rb
|
123
|
+
homepage: http://github.com/alobato/campagne
|
124
|
+
licenses: []
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ! '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
segments:
|
136
|
+
- 0
|
137
|
+
hash: 1196017273243914426
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ! '>='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
segments:
|
145
|
+
- 0
|
146
|
+
hash: 1196017273243914426
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 1.8.17
|
150
|
+
signing_key:
|
151
|
+
specification_version: 3
|
152
|
+
summary: campagne-0.0.2
|
153
|
+
test_files: []
|