email newsletter capability

This commit is contained in:
Kyle Drake 2015-06-28 21:41:08 -07:00
parent d4303c7e46
commit b986c57577
8 changed files with 169 additions and 1 deletions

View file

@ -29,6 +29,7 @@ gem 'rest-client'
gem 'geoip'
gem 'io-extra', require: 'io/extra'
gem 'rye'
gem 'timecop'
platform :mri, :rbx do
gem 'magic' # sudo apt-get install file, For OSX: brew install libmagic

View file

@ -227,6 +227,7 @@ GEM
thread (0.1.4)
thread_safe (0.3.4)
tilt (1.4.1)
timecop (0.7.4)
timers (1.1.0)
treetop (1.4.15)
polyglot
@ -307,6 +308,7 @@ DEPENDENCIES
stripe-ruby-mock (~> 2.0.1)
thread
tilt
timecop
webmock
zipruby

View file

@ -5,6 +5,48 @@ get '/admin' do
erb :'admin'
end
get '/admin/email' do
require_admin
erb :'admin/email'
end
post '/admin/email' do
require_admin
%i{subject body}.each do |k|
if params[k].nil? || params[k].empty?
flash[:error] = "#{k.capitalize} is missing."
redirect '/admin/email'
end
end
sites = Site.newsletter_sites
day = 0
until sites.empty?
queued_sites = []
Site::EMAIL_BLAST_MAXIMUM_PER_DAY.times {
break if sites.empty?
queued_sites << sites.pop
}
queued_sites.each do |site|
EmailWorker.perform_at(day.days.from_now, {
from: 'noreply@neocities.org',
to: site.email,
subject: params[:subject],
body: params[:body]
})
end
day += 1
end
flash[:success] = "#{sites.length} emails have been queued, #{Site::EMAIL_BLAST_MAXIMUM_PER_DAY} per day."
redirect '/'
end
post '/admin/banip' do
require_admin
site = Site[username: params[:username]]

View file

@ -118,6 +118,15 @@ class Site < Sequel::Model
maximum_site_files: 1000
)
def self.newsletter_sites
Site.select(:email).
exclude(email: 'nil').exclude(is_banned: true).
where{updated_at > EMAIL_BLAST_MAXIMUM_AGE}.
where{changed_count > 0}.
order(:updated_at.desc).
all
end
def too_many_files?(file_count=0)
(site_files_dataset.count + file_count) > plan_feature(:maximum_site_files)
end
@ -140,6 +149,14 @@ class Site < Sequel::Model
BROWSE_PAGINATION_LENGTH = 100
EMAIL_BLAST_MAXIMUM_AGE = 6.months.ago
if ENV['RACK_ENV'] == 'test'
EMAIL_BLAST_MAXIMUM_PER_DAY = 2
else
EMAIL_BLAST_MAXIMUM_PER_DAY = 2000
end
many_to_many :tags
one_to_many :profile_comments

View file

@ -51,4 +51,4 @@ describe '/admin' do
end
end
end
end

56
tests/admin_tests.rb Normal file
View file

@ -0,0 +1,56 @@
require_relative './environment.rb'
require 'rack/test'
include Rack::Test::Methods
def app
Sinatra::Application
end
describe 'email blasting' do
before do
EmailWorker.jobs.clear
@admin_site = Fabricate :site, is_admin: true
end
it 'works' do
DB['update sites set changed_count=?', 0].first
relevant_emails = []
sites_emailed_count = Site::EMAIL_BLAST_MAXIMUM_PER_DAY*2
sites_emailed_count.times {
site = Fabricate :site, updated_at: Time.now, changed_count: 1
relevant_emails << site.email
}
EmailWorker.jobs.clear
time = Time.now
Timecop.freeze(time) do
post '/admin/email', {
:csrf_token => 'abcd',
:subject => 'Subject Test',
:body => 'Body Test'}, {
'rack.session' => { 'id' => @admin_site.id, '_csrf_token' => 'abcd' }
}
relevant_jobs = EmailWorker.jobs.select{|j| relevant_emails.include?(j['args'].first['to']) }
relevant_jobs.length.must_equal sites_emailed_count
relevant_jobs.each do |job|
args = job['args'].first
args['from'].must_equal 'noreply@neocities.org'
args['subject'].must_equal 'Subject Test'
args['body'].must_equal 'Body Test'
end
immediate_emails = relevant_jobs.select {|j| j['at'].nil? || j['at'] == Time.now.to_f}
immediate_emails.length.must_equal Site::EMAIL_BLAST_MAXIMUM_PER_DAY
tomorrows_emails = relevant_jobs.select {|j| j['at'] == (time+1.day.to_i).to_f}
tomorrows_emails.length.must_equal Site::EMAIL_BLAST_MAXIMUM_PER_DAY
end
end
end

49
views/admin/email.erb Normal file
View file

@ -0,0 +1,49 @@
<div class="header-Outro">
<div class="row content single-Col">
<h1>Email Mass Send</h1>
<h2 class="subtitle">Use wisely.</h2>
</div>
</div>
<div class="content misc-page single-Col txt-Center" style="padding-top: 20px;">
<% if flash.keys.length > 0 %>
<div class="alert alert-error alert-block">
<% flash.keys.each do |key| %>
<%== flash[key] %>
<% end %>
</div>
<% end %>
<div class="row">
<div class="col col-100">
<p>
This sends to all emails on Neocities that have not opted out. It trickles it out, 4000/day to prevent spam report issues.
</p>
<form method="POST" action="/admin/email">
<%== csrf_token_input_html %>
<p>
Subject:
</p>
<p>
<input name="subject" type="text" style="width: 600px">
</p>
<p>
Body:
</p>
<p>
<textarea name="body" style="width: 600px" rows="10"></textarea>
</p>
<p>
<input type="submit" value="Send" class="btn">
</p>
</form>
</div>
</div>
</div>

View file

@ -3,6 +3,7 @@ class EmailWorker
sidekiq_options queue: :emails, retry: 10, backtrace: true
def perform(args={})
raise 'no' if ENV['RACK_ENV'].nil? || ENV['RACK_ENV'] == 'development'
unsubscribe_token = Site.email_unsubscribe_token args['to']
if args['no_footer']