ses-proxy 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 (57) hide show
  1. data/app/public/bootstrap/css/bootstrap-responsive.css +1092 -0
  2. data/app/public/bootstrap/css/bootstrap-responsive.min.css +9 -0
  3. data/app/public/bootstrap/css/bootstrap.css +6039 -0
  4. data/app/public/bootstrap/css/bootstrap.min.css +9 -0
  5. data/app/public/bootstrap/img/glyphicons-halflings-white.png +0 -0
  6. data/app/public/bootstrap/img/glyphicons-halflings.png +0 -0
  7. data/app/public/bootstrap/js/bootstrap.js +2159 -0
  8. data/app/public/bootstrap/js/bootstrap.min.js +6 -0
  9. data/app/public/css/application.css +16 -0
  10. data/app/public/datepicker/css/datepicker.css +7 -0
  11. data/app/public/datepicker/js/bootstrap-datepicker.js +454 -0
  12. data/app/public/datepicker/less/datepicker.less +119 -0
  13. data/app/public/highcharts/adapters/mootools-adapter.js +13 -0
  14. data/app/public/highcharts/adapters/mootools-adapter.src.js +327 -0
  15. data/app/public/highcharts/adapters/prototype-adapter.js +16 -0
  16. data/app/public/highcharts/adapters/prototype-adapter.src.js +385 -0
  17. data/app/public/highcharts/highcharts-more.js +35 -0
  18. data/app/public/highcharts/highcharts.js +246 -0
  19. data/app/public/highcharts/highcharts.src.js +15111 -0
  20. data/app/public/highcharts/modules/canvas-tools.js +133 -0
  21. data/app/public/highcharts/modules/canvas-tools.src.js +3113 -0
  22. data/app/public/highcharts/modules/data.js +11 -0
  23. data/app/public/highcharts/modules/data.src.js +277 -0
  24. data/app/public/highcharts/modules/exporting.js +23 -0
  25. data/app/public/highcharts/modules/exporting.src.js +736 -0
  26. data/app/public/highcharts/themes/dark-blue.js +263 -0
  27. data/app/public/highcharts/themes/dark-green.js +263 -0
  28. data/app/public/highcharts/themes/gray.js +262 -0
  29. data/app/public/highcharts/themes/grid.js +95 -0
  30. data/app/public/highcharts/themes/skies.js +89 -0
  31. data/app/public/images/loader.gif +0 -0
  32. data/app/public/js/application.js +81 -0
  33. data/app/views/_chart.haml +2 -0
  34. data/app/views/_search_form.haml +23 -0
  35. data/app/views/bounces.haml +23 -0
  36. data/app/views/kaminari/_first_page.html.erb +3 -0
  37. data/app/views/kaminari/_gap.html.erb +3 -0
  38. data/app/views/kaminari/_last_page.html.erb +3 -0
  39. data/app/views/kaminari/_next_page.html.erb +3 -0
  40. data/app/views/kaminari/_page.html.erb +3 -0
  41. data/app/views/kaminari/_paginator.html.erb +17 -0
  42. data/app/views/kaminari/_prev_page.html.erb +3 -0
  43. data/app/views/layout.haml +25 -0
  44. data/app/views/mails.haml +26 -0
  45. data/app/web_panel.rb +149 -0
  46. data/bin/ses_proxy +20 -0
  47. data/lib/ses_proxy/conf.rb +9 -0
  48. data/lib/ses_proxy/main_command.rb +154 -0
  49. data/lib/ses_proxy/models/bounce.rb +14 -0
  50. data/lib/ses_proxy/models/complaint.rb +13 -0
  51. data/lib/ses_proxy/models/email.rb +15 -0
  52. data/lib/ses_proxy/smtp_server.rb +122 -0
  53. data/lib/ses_proxy/sns_endpoint.rb +199 -0
  54. data/ses_proxy.rb +10 -0
  55. data/template/mongoid.yml +12 -0
  56. data/template/ses-proxy.yml +19 -0
  57. metadata +294 -0
@@ -0,0 +1,2 @@
1
+ %div#chartdiv(style="width: 100%;height: 400px;")
2
+ %div.loader-div(style="top: 50%; margin-top: -25px;")
@@ -0,0 +1,23 @@
1
+ %form.form-horizontal(data-ajax-url="#{@chart_url}" data-chart="chartdiv")
2
+ %fieldset(style="text-align: left")
3
+ %legend Search:
4
+ %div(style="display: inline-block;vertical-align: middle;")
5
+ %label Text
6
+ %input.span4(type="text" name="q" value="#{@q}" placeholder="Type something…")
7
+ %div.input-append.date(data-date="#{@s||Time.now.strftime('%d/%m/%Y')}" data-date-format="dd-mm-yyyy")
8
+ %label From
9
+ %input.span2(size="16" type="text" name="s" value="#{@s}" placeholder="dd-mm-yyyy")
10
+ %span.add-on
11
+ %i.icon-th
12
+ %div.input-append.date(data-date="#{@e||Time.now.strftime('%d/%m/%Y')}" data-date-format="dd-mm-yyyy")
13
+ %label To
14
+ %input.span2(size="16" type="text" name="e" value="#{@e}" placeholder="dd-mm-yyyy")
15
+ %span.add-on
16
+ %i.icon-th
17
+ %div(style="display: inline-block;vertical-align: middle;")
18
+ %label Per page
19
+ %select.span2(name="per")
20
+ -5.times do |i|
21
+ %option{:value=>(i+1)*10, :selected=>("selected" if @per.to_i.eql?((i+1)*10))}= (i+1)*10
22
+ %div.form-actions
23
+ %button.btn(type="submit") Search
@@ -0,0 +1,23 @@
1
+ %h1.page-title(style="text-align: center; margin-bottom:20px;") Bounced Addresses
2
+ = haml :_search_form
3
+ = haml :_chart
4
+ %table.table.table-striped(style="table-layout:fixed")
5
+ %colgoup
6
+ %col(width="25%")
7
+ %col(width="15%")
8
+ %col(width="50%")
9
+ %col(width="10%")
10
+ %thead
11
+ %tr
12
+ %th Email
13
+ %th Type
14
+ %th Description
15
+ %th Created At
16
+ %tbody
17
+ - for bounce in @bounces
18
+ %tr
19
+ %td= bounce.email
20
+ %td= bounce.type
21
+ %td= bounce.desc
22
+ %td= bounce.created_at.strftime '%d/%m/%Y'
23
+ = paginate @bounces
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote %>
3
+ </li>
@@ -0,0 +1,3 @@
1
+ <li class="disabled">
2
+ <%= link_to raw(t 'views.pagination.truncate'), '#' %>
3
+ </li>
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} %>
3
+ </li>
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote %>
3
+ </li>
@@ -0,0 +1,3 @@
1
+ <li class="<%= 'active' if page.current? %>">
2
+ <%= link_to page, page.current? ? '#' : url, {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil} %>
3
+ </li>
@@ -0,0 +1,17 @@
1
+ <%= paginator.render do -%>
2
+ <div class="pagination pagination-centered">
3
+ <ul>
4
+ <%= first_page_tag unless current_page.first? %>
5
+ <%= prev_page_tag unless current_page.first? %>
6
+ <% each_page do |page| -%>
7
+ <% if page.left_outer? || page.right_outer? || page.inside_window? -%>
8
+ <%= page_tag page %>
9
+ <% elsif !page.was_truncated? -%>
10
+ <%= gap_tag %>
11
+ <% end -%>
12
+ <% end -%>
13
+ <%= next_page_tag unless current_page.last? %>
14
+ <%= last_page_tag unless current_page.last? %>
15
+ </ul>
16
+ </div>
17
+ <% end -%>
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote %>
3
+ </li>
@@ -0,0 +1,25 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %title SES Proxy WebPanel
5
+ %link(rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css" media="screen")
6
+ %link(rel="stylesheet" type="text/css" href="datepicker/css/datepicker.css" media="screen")
7
+ %link(rel="stylesheet" type="text/css" href="css/application.css" media="screen")
8
+ %link(rel="icon" type="image/ico" href="https://awsmedia.s3.amazonaws.com/favicon.ico")
9
+ %link(rel="shortcut icon" type="image/ico" href="https://awsmedia.s3.amazonaws.com/favicon.ico")
10
+ %body
11
+ %div.container-fluid
12
+ %div.navbar
13
+ %div.navbar-inner
14
+ %a.brand(href="#") SES Proxy WebPanel
15
+ %ul.nav.nav-pills
16
+ %li{:class => ('active' if @menu_item.eql? "mails")}
17
+ %a(href="/") Sent Emails
18
+ %li{:class => ('active' if @menu_item.eql? "bounces")}
19
+ %a(href="/bounces") Bounced Addresses
20
+ = yield
21
+ %script(src="http://code.jquery.com/jquery-latest.js")
22
+ %script(src="bootstrap/js/bootstrap.min.js")
23
+ %script(src="datepicker/js/bootstrap-datepicker.js")
24
+ %script(src="highcharts/highcharts.js")
25
+ %script(src="js/application.js")
@@ -0,0 +1,26 @@
1
+ %h1.page-title(style="text-align: center; margin-bottom:20px;") Sent Emails
2
+ = haml :_search_form
3
+ = haml :_chart
4
+ %table.table.table-striped(style="table-layout:fixed")
5
+ %colgoup
6
+ %col(width="15%")
7
+ %col(width="25%")
8
+ %col(width="30%")
9
+ %col(width="20%")
10
+ %col(width="10%")
11
+ %thead
12
+ %tr
13
+ %th System
14
+ %th Sender
15
+ %th Recipients
16
+ %th Subject
17
+ %th Created At
18
+ %tbody
19
+ - for mail in @mails
20
+ %tr
21
+ %td= mail.system
22
+ %td= mail.sender
23
+ %td= mail.recipients
24
+ %td= mail.subject
25
+ %td= mail.created_at.strftime '%d/%m/%Y'
26
+ = paginate @mails
@@ -0,0 +1,149 @@
1
+ require 'sinatra'
2
+ require 'haml'
3
+ require 'kaminari/sinatra'
4
+ require 'json'
5
+
6
+ helpers do
7
+ def protected!
8
+ unless authorized?
9
+ response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
10
+ throw(:halt, [401, "Not authorized\n"])
11
+ end
12
+ end
13
+
14
+ def authorized?
15
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
16
+ @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [SesProxy::Conf.get[:smtp_auth][:user], SesProxy::Conf.get[:smtp_auth][:password]]
17
+ end
18
+ end
19
+
20
+ helpers Kaminari::Helpers::SinatraHelpers
21
+ module Kaminari::Helpers
22
+ module SinatraHelpers
23
+ class ActionViewTemplateProxy
24
+ def render(*args)
25
+ base = ActionView::Base.new.tap do |a|
26
+ a.view_paths << File.expand_path('../views', __FILE__)
27
+ end
28
+ base.render(*args)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ configure :development do
35
+ Sinatra::Application.reset!
36
+ end
37
+
38
+ get '/' do
39
+ protected!
40
+ @menu_item = "mails"
41
+ @chart_url = "/mails.json"
42
+ @per = params[:per] || 20
43
+ mails = mails_query
44
+ @mails = mails.page(params[:page]).per(@per)
45
+ haml :mails
46
+ end
47
+
48
+ get '/mails.json' do
49
+ protected!
50
+ mails = mails_query
51
+ d_array = (string_to_date(@s)..string_to_date(@e)).to_a
52
+ data = get_data_json "Mails", mails, "created_at", d_array
53
+ content_type :json
54
+ data.to_json
55
+ end
56
+
57
+ get '/bounces' do
58
+ protected!
59
+ @menu_item = "bounces"
60
+ @chart_url = "/bounces.json"
61
+ @per = params[:per] || 20
62
+ bounces = bounces_query
63
+ @bounces = bounces.page(params[:page]).per(@per)
64
+ haml :bounces
65
+ end
66
+
67
+ get '/bounces.json' do
68
+ protected!
69
+ bounces = bounces_query
70
+ d_array = (string_to_date(@s)..string_to_date(@e)).to_a
71
+ data = get_data_json "Bounced", bounces, "created_at", d_array
72
+ content_type :json
73
+ data.to_json
74
+ end
75
+
76
+ private
77
+
78
+ def get_data_json(name, collection, method, d_array)
79
+ data = {}
80
+ grouped_collection = {}
81
+ if d_array.size <= 30
82
+ #days
83
+ key_format = "%d/%m/%Y"
84
+ elsif d_array.size > 30 and d_array.size <= 210
85
+ #weeks
86
+ key_format = "%W/%Y"
87
+ elsif d_array.size > 210
88
+ #months
89
+ key_format = "%B/%Y"
90
+ end
91
+ data[:x] = d_array.map{|d| d.to_date.strftime(key_format)}.uniq
92
+ data[:y] ||= []
93
+ data[:x].each do |d|
94
+ grouped_collection[d] = collection.collect{|b| b if b.send(method).strftime(key_format).eql? d}.compact.size
95
+ end
96
+ data[:y] << {:name=>name,:data=>grouped_collection.values}
97
+ data
98
+ end
99
+
100
+ def mails_query
101
+ @q = params[:q]
102
+ @s = params[:s]||(Date.today-1.month).strftime("%d-%m-%Y")
103
+ @e = params[:e]||Date.today.strftime("%d-%m-%Y")
104
+ if @q.nil? or @q.eql?""
105
+ if valid_date(@s) and valid_date(@e)
106
+ mails = SesProxy::Email.where(:created_at=>{'$gte' => string_to_date(@s),'$lt' => string_to_date(@e)})
107
+ else
108
+ mails = SesProxy::Email.all
109
+ end
110
+ else
111
+ if valid_date(@s) and valid_date(@e)
112
+ mails = SesProxy::Email.where(:created_at=>{'$gte' => string_to_date(@s),'$lt' => string_to_date(@e)}).any_of({:recipients => /.*#{@q}.*/i },{:sender => /.*#{@q}.*/i },{:system => /.*#{@q}.*/i },{:subject => /.*#{@q}.*/i })
113
+ else
114
+ mails = SesProxy::Email.any_of({:recipients => /.*#{@q}.*/i },{:sender => /.*#{@q}.*/i },{:system => /.*#{@q}.*/i },{:subject => /.*#{@q}.*/i }).page(params[:page]).per(20)
115
+ end
116
+ end
117
+ end
118
+
119
+ def bounces_query
120
+ @q = params[:q]
121
+ @s = params[:s]||(Date.today-1.month).strftime("%d-%m-%Y")
122
+ @e = params[:e]||Date.today.strftime("%d-%m-%Y")
123
+ if @q.nil? or @q.eql?""
124
+ if valid_date(@s) and valid_date(@e)
125
+ bounces = SesProxy::Bounce.where(:created_at=>{'$gte' => string_to_date(@s),'$lte' => string_to_date(@e)})
126
+ else
127
+ bounces = SesProxy::Bounce.all
128
+ end
129
+ else
130
+ if valid_date(@s) and valid_date(@e)
131
+ bounces = SesProxy::Bounce.where(:created_at=>{'$gte' => string_to_date(@s),'$lte' => string_to_date(@e)}).any_of({:email => /.*#{@q}.*/i },{ :type => /.*#{@q}.*/i },{ :desc => /.*#{@q}.*/i })
132
+ else
133
+ bounces = SesProxy::Bounce.any_of({:email => /.*#{@q}.*/i },{ :type => /.*#{@q}.*/i },{ :desc => /.*#{@q}.*/i })
134
+ end
135
+ end
136
+ bounces
137
+ end
138
+
139
+ def valid_date(d)
140
+ begin
141
+ d and not d.eql?"" and string_to_date(d)
142
+ rescue Exception=>e
143
+ false
144
+ end
145
+ end
146
+
147
+ def string_to_date(d)
148
+ Date.strptime(d, '%d-%m-%Y')
149
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ require 'rubygems'
5
+
6
+ file_path = Pathname.new(__FILE__).realpath
7
+
8
+ require File.join(File.dirname(File.dirname(file_path)), 'ses_proxy.rb')
9
+ libdir = File.join(File.dirname(File.dirname(file_path)), "lib")
10
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
11
+
12
+ begin
13
+ SesProxy::MainCommand.run
14
+ rescue Exception => e
15
+ puts e.message
16
+ if $DEBUG
17
+ puts $!
18
+ puts $@
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ require 'yaml'
2
+
3
+ module SesProxy
4
+ class Conf
5
+ def self.get
6
+ YAML.load_file(File.join(ENV["HOME"],".ses-proxy","ses-proxy.yml"))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,154 @@
1
+ require 'clamp'
2
+ require 'rack'
3
+ require 'eventmachine'
4
+ require 'thread'
5
+ require 'fileutils'
6
+ require 'mongoid'
7
+ require 'daemons'
8
+ require 'tmpdir'
9
+
10
+ require './app/web_panel'
11
+
12
+ module SesProxy
13
+
14
+ class StartCommand < Clamp::Command
15
+ option ["-s","--smtp-port"], "SMTP_PORT", "SMTP listen port (default: \"1025\")"
16
+ def default_smtp_port
17
+ if SesProxy::Conf.get[:smtp] and SesProxy::Conf.get[:smtp][:port]
18
+ SesProxy::Conf.get[:smtp][:port]
19
+ else
20
+ 1025
21
+ end
22
+ end
23
+
24
+ option ["-o","--smtp-host"], "SMTP_HOST", "SMTP host (default: \"0.0.0.0\")"
25
+ def default_smtp_host
26
+ if SesProxy::Conf.get[:smtp] and SesProxy::Conf.get[:smtp][:host]
27
+ SesProxy::Conf.get[:smtp][:host]
28
+ else
29
+ "0.0.0.0"
30
+ end
31
+ end
32
+
33
+ option ["-p","--http-port"], "HTTP_PORT", "HTTP listen port (default: \"9292\")"
34
+ def default_http_port
35
+ if SesProxy::Conf.get[:http] and SesProxy::Conf.get[:http][:port]
36
+ SesProxy::Conf.get[:http][:port]
37
+ else
38
+ 9292
39
+ end
40
+ end
41
+
42
+ option ["-l","--http-address"], "HTTP_ADDRESS", "HTTP listen address (default: \"0.0.0.0\")"
43
+ def default_http_address
44
+ if SesProxy::Conf.get[:http] and SesProxy::Conf.get[:http][:host]
45
+ SesProxy::Conf.get[:http][:host]
46
+ else
47
+ "0.0.0.0"
48
+ end
49
+ end
50
+
51
+ option ["-e","--environment"], "ENVIRONMENT", "Environment", :default => "development"
52
+
53
+ option ["-c","--config-file"], "CONFIG_FILE", "Configuration file", :default => "#{File.join(Dir.home,'.ses-proxy','ses-proxy.yml')}"
54
+ option ["-m","--mongoid-config-file"], "MONGOID_CONFIG_FILE", "Mongoid configuration file", :default => "#{File.join(Dir.home,'.ses-proxy','mongoid.yml')}"
55
+
56
+ option ["-d","--demonize"], :flag, "Demonize application", :default => false
57
+
58
+ @@env = "development"
59
+
60
+ def execute
61
+ check_for_config_file config_file
62
+ check_for_mongoid_config_file mongoid_config_file
63
+
64
+ @@env = environment
65
+ Mongoid.load! mongoid_config_file, @@env
66
+
67
+ app = Rack::Builder.new do
68
+ use Rack::Reloader, 0 if @@env.eql?"development"
69
+ map "/sns_endpoint" do
70
+ run SesProxy::SnsEndpoint.new
71
+ end
72
+ run Sinatra::Application
73
+ end.to_app
74
+ options = {:app=>app, :environment=>environment, :server=>"thin", :Port=>http_port, :Host=>http_address}
75
+ server = Rack::Server.new options
76
+
77
+ if demonize?
78
+ options = {:app_name => "ses_proxy", :dir_mode=>:normal, :dir=>Dir.tmpdir, :multiple=>true}
79
+ group = Daemons::ApplicationGroup.new('ses_proxy', options)
80
+ options[:mode] = :proc
81
+ options[:proc] = Proc.new { EM.run{ SesProxy::SmtpServer.start smtp_host, smtp_port } }
82
+ pid = Daemons::PidFile.new Dir.tmpdir, "ses_proxy_smtp"
83
+ @smtp = Daemons::Application.new(group, options, pid)
84
+ options[:proc] = Proc.new { server.start }
85
+ pid = Daemons::PidFile.new Dir.tmpdir, "ses_proxy_http"
86
+ @http = Daemons::Application.new(group, options, pid)
87
+ @smtp.start
88
+ @http.start
89
+ else
90
+ @smtp = Thread.new do
91
+ EM.run{ SesProxy::SmtpServer.start smtp_host, smtp_port }
92
+ end
93
+ @http = Thread.new do
94
+ server.start
95
+ end
96
+ @smtp.join
97
+ @http.join
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def check_for_config_file(path)
104
+ if path.eql? File.join(Dir.home,'.ses-proxy','ses-proxy.yml').to_s
105
+ unless File.directory? File.join(Dir.home,'.ses-proxy')
106
+ Dir.mkdir(File.join(Dir.home,'.ses-proxy'))
107
+ end
108
+ unless File.exists? path
109
+ FileUtils.cp File.join(ROOT,"template","ses-proxy.yml").to_s, path
110
+ puts "ATTENTION: Edit '#{path}' file with your data and then restart ses_proxy."
111
+ exit
112
+ end
113
+ else
114
+ unless File.exists? path
115
+ raise ArgumentError, "Configuration file '#{path}' not found!"
116
+ end
117
+ end
118
+ end
119
+
120
+ def check_for_mongoid_config_file(path)
121
+ if path.eql? File.join(Dir.home,'.ses-proxy','mongoid.yml').to_s
122
+ unless File.directory? File.join(Dir.home,'.ses-proxy')
123
+ Dir.mkdir(File.join(Dir.home,'.ses-proxy'))
124
+ end
125
+ unless File.exists? path
126
+ FileUtils.cp File.join(ROOT,"template","mongoid.yml").to_s, path
127
+ puts "ATTENTION: Edit '#{path}' file with your data and then restart ses_proxy."
128
+ exit
129
+ end
130
+ else
131
+ unless File.exists? path
132
+ raise ArgumentError, "Configuration file '#{path}' not found!"
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ class StopCommand < Clamp::Command
139
+ def execute
140
+ options = {:app_name => "ses_proxy", :dir_mode=>:normal, :dir=>Dir.tmpdir, :multiple=>true}
141
+ group = Daemons::ApplicationGroup.new('ses_proxy', options)
142
+ group.setup
143
+ group.applications.each do |application|
144
+ application.stop
145
+ end
146
+ end
147
+ end
148
+
149
+ class MainCommand < Clamp::Command
150
+ subcommand "start", "Start proxy", StartCommand
151
+ subcommand "stop", "Stop proxy", StopCommand
152
+ end
153
+
154
+ end