ses-proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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