mail2cb 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 33aab03227466886d6226040f67f9fae5e3e6257
4
+ data.tar.gz: fc394d4cf8701deb082584652ef5000f9f76a5eb
5
+ SHA512:
6
+ metadata.gz: 8aa7c084a6643350703598e925ab60763f8d495b80770eb49ce4bd975f15c79ab584e0b8fc4f2c5f024ec22e49b524faf9861aa11c312dfb52366f27afa07daa
7
+ data.tar.gz: e5d63b2a62b9c42d5e13832e64ab0ad5c5b5cf141ae99497e07dbe35efab2cdf656ef3a94a289166e09ea7973265cadec62d89f23a7f4e131629a2f44df603a9
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ *.swp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ mail2cb
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.2.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mail2cb.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 EmergeAdapt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Ijonas Kisselbach
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # mail2cb
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'mail2cb'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install mail2cb
18
+
19
+ ## Design
20
+
21
+ ###Gems
22
+
23
+ * mail_room - retrieves mail from multiple mailboxes at once and sends them through sidekiq or an http postback
24
+ * mail - mail parsing and sending gem
25
+ * talon - python code to extract the relevant body of the message
26
+
27
+
28
+ ### Notes
29
+
30
+ * config for office365 smtp needs authentication type 'login' gmail is 'plain'
31
+ * gmail apps authentication is by ip address only, suggest a feature of being able to host the send daemon on another box
32
+
33
+ ## Usage
34
+
35
+ Ensure the following environment variables are set:
36
+
37
+ REDIS_URL=redis://localhost:6379
38
+ CB_API_ENDPOINT=https://login.caseblocks.com
39
+ CB_API_TOKEN=...
40
+
41
+ And run the daemon from the CLI
42
+
43
+ mail2cb
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it ( https://github.com/[my-github-username]/mail2cb/fork )
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/mail2cb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby -Ilib
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ # Prepares the $LOAD_PATH by adding to it lib directories of the gem and
5
+ # its dependencies:
6
+ require 'mail2cb'
7
+
8
+ ENV["REDIS_URL"] = "redis://localhost:6379" unless ENV["REDIS_URL"]
9
+ ENV["MYSQL_HOST"] = "localhost" unless ENV["MYSQL_HOST"]
10
+ ENV["MYSQL_PORT"] = "3306"
11
+ ENV["MYSQL_DATABASE"] = "caseblocks_dev"
12
+
13
+ ENV["MYSQL_USERNAME"] = "root"
14
+ ENV["MYSQL_PASSWORD"] = ""
15
+
16
+ oh = Mail2cb::Handler.new
data/config.yml ADDED
@@ -0,0 +1,27 @@
1
+ ---
2
+ :mailboxes:
3
+ -
4
+ :email: "stewart@activeinformationdesign.com"
5
+ :password: "junction1"
6
+ :name: "inbox"
7
+ :search_command: 'UNSEEN'
8
+ :delivery_method: sidekiq
9
+ :delivery_options:
10
+ :worker: EmailHandlerWorker
11
+ -
12
+ :email: "stewart@emergeadapt.com"
13
+ :password: "junction1"
14
+ :name: "inbox"
15
+ :search_command: 'UNSEEN'
16
+ :delivery_method: sidekiq
17
+ :delivery_options:
18
+ :worker: EmailHandlerWorker
19
+ -
20
+ :email: "stewartmckee@emergeadapttest.onmicrosoft.com"
21
+ :host: "outlook.office365.com"
22
+ :password: "Katr1n33"
23
+ :name: "inbox"
24
+ :search_command: 'UNSEEN'
25
+ :delivery_method: sidekiq
26
+ :delivery_options:
27
+ :worker: EmailHandlerWorker
@@ -0,0 +1,28 @@
1
+ require 'open3'
2
+
3
+ module Mail2cb
4
+ class EmailBodyParser
5
+ def self.parse(text, type="text/plain")
6
+
7
+ data_hash = {:body => text, :type => type}
8
+
9
+ json_hash = data_hash.to_json
10
+
11
+ stdin, stdout, stderr, wait_thr = Open3.popen3('python', '/Users/stewartmckee/code/talon/test.py')
12
+ stdin.write(json_hash)
13
+ stdin.close
14
+ result = stdout.read
15
+ stdout.close
16
+ error = stderr.read
17
+ stderr.close
18
+ exit_code = wait_thr.value
19
+ if exit_code == 0
20
+ result
21
+ else
22
+ puts result
23
+ raise error
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,113 @@
1
+ require 'fileutils'
2
+ require 'mail'
3
+ require_relative "email_body_parser"
4
+ require 'nokogiri'
5
+ require 'sanitize'
6
+
7
+ module Mail2cb
8
+ class EmailHandler
9
+ def initialize(options)
10
+ @options = options
11
+ @email = Mail.read_from_string(options[:inbound_message])
12
+
13
+ puts "Handling inbound email from #{@email.from}"
14
+
15
+ if @email.multipart?
16
+ @email.parts.each do |part|
17
+ content_type = part.content_type.split(";")[0]
18
+ if content_type[0..3] == "text"
19
+ if content_type == "text/html"
20
+ @html_body = part.body.decoded.force_encoding("ASCII-8BIT").encode('UTF-8', undef: :replace, replace: '')
21
+ elsif content_type == "text/plain"
22
+ @text_body = part.body.decoded.force_encoding("ASCII-8BIT").encode('UTF-8', undef: :replace, replace: '')
23
+ end
24
+ end
25
+ end
26
+ else
27
+ @text_body = @email.body.decoded.force_encoding("ASCII-8BIT").encode('UTF-8', undef: :replace, replace: '')
28
+ end
29
+
30
+ @body = @html_body || @text_body
31
+ end
32
+
33
+ def generate_message_hash
34
+
35
+ message_hash = {
36
+ "from" => @email.from,
37
+ "to" => @email.to,
38
+ "subject" => @email.subject,
39
+ "date" => @email.date,
40
+ "body" => @body,
41
+ "attachements" => prepare_attachments,
42
+ "metadata" => fetch_metadata
43
+ }
44
+ message_hash.delete(:inbound_message)
45
+ message_hash
46
+ end
47
+
48
+ def fetch_metadata
49
+ fetch_fingerprints.merge({
50
+ "has_bounced" => @email.bounced?,
51
+ "in_reply_to" => @email.in_reply_to,
52
+ "message_id" => @email.message_id,
53
+ "references" => references
54
+ })
55
+ end
56
+
57
+ def references
58
+ @email.header["References"] ? @email.header["References"].field.element.message_ids : []
59
+ end
60
+
61
+ def fetch_fingerprints
62
+ previous_id = references.last
63
+ match = /(.*?)\.(.*?)@emergeadapt.com/.match(previous_id)
64
+ if match
65
+ if match[2] == "case"
66
+ {"case_id" => match[1]}
67
+ else
68
+ {"message_id" => match[1]}
69
+ end
70
+ else
71
+ {}
72
+ end
73
+ end
74
+
75
+ def prepare_attachments
76
+ attachments = []
77
+ @email.attachments.each do | attachment |
78
+ filename = attachment.filename
79
+ random_tag = Random.rand(100000000000)
80
+ attachment_folder = File.join(["/", "tmp", "caseblocks", @options[:mailbox][:account_code], "attachments"])
81
+ FileUtils::mkdir_p(attachment_folder)
82
+ file_path = File.join([attachment_folder, "#{random_tag}.#{filename}"])
83
+ File.open(file_path, "w+b", 0644) {|f| f.write attachment.body.decoded}
84
+ attachments << {
85
+ "filename" => attachment.filename,
86
+ "filepath" => file_path
87
+ }
88
+ end
89
+ attachments
90
+ end
91
+
92
+
93
+ def content_type
94
+ @html_body.nil? ? "text/plain" : "text/html"
95
+ end
96
+
97
+ def sanitize!
98
+ @body = Sanitize.fragment(@body, Sanitize::Config::RELAXED)
99
+ end
100
+
101
+ def clean_replies!
102
+ @body = Mail2cb::EmailBodyParser.parse(@body, content_type)
103
+ end
104
+
105
+ def queue!
106
+ $redis
107
+ end
108
+
109
+ def body
110
+ @body
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,126 @@
1
+ require 'redis'
2
+ require 'ap'
3
+ require 'mysql2'
4
+
5
+ module Mail2cb
6
+ class EmailWatcher
7
+
8
+ def initialize
9
+
10
+ raise "REDIS_URL environment variable is required (eg redis://localhost:6739)" unless ENV["REDIS_URL"]
11
+ raise "MYSQL_HOST environment variable is required" unless ENV["MYSQL_HOST"]
12
+ raise "MYSQL_DATABASE environment variable is required" unless ENV["MYSQL_DATABASE"]
13
+ raise "MYSQL_USERNAME environment variable is required" unless ENV["MYSQL_USERNAME"]
14
+ raise "MYSQL_PASSWORD environment variable is required" unless ENV["MYSQL_PASSWORD"]
15
+
16
+ ENV["MYSQL_PASSWORD"] = "" unless ENV["MYSQL_PASSWORD"]
17
+ ENV["MYSQL_PORT"] = "3306"
18
+
19
+ redis_url = URI.parse(ENV["REDIS_URL"])
20
+
21
+ $redis = Redis.new(:host => redis_url.host, :port => redis_url.port)
22
+
23
+ Signal.trap("INT") {
24
+ Thread.new {self.stop}.join
25
+ }
26
+ # Trap `Kill `
27
+ Signal.trap("TERM") {
28
+ Thread.new {self.stop}.join
29
+ }
30
+
31
+ @daemon = MailDaemon::Handler.new(:connections => configuration)
32
+ end
33
+
34
+ def start
35
+ Thread.new do
36
+ @daemon.start do |message, type|
37
+ if type == "status_update"
38
+ update_status(message)
39
+ else
40
+ handle_incoming_message(message)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def configuration
47
+ mailboxes = []
48
+ mysql_client do |mysql|
49
+ statement = mysql.prepare("SELECT case_blocks_email_accounts.id, case_blocks_accounts.nickname, case_blocks_email_accounts.imap_username, case_blocks_email_accounts.imap_encrypted_password, case_blocks_email_accounts.imap_host, case_blocks_email_accounts.imap_port, case_blocks_email_accounts.imap_ssl, case_blocks_email_accounts.imap_start_tls, case_blocks_email_accounts.imap_folder_name, case_blocks_email_accounts.imap_search_command, case_blocks_email_accounts.imap_messages_processed, case_blocks_email_accounts.imap_last_processed_at FROM case_blocks_email_accounts JOIN case_blocks_accounts ON case_blocks_email_accounts.account_id = case_blocks_accounts.id where imap_enabled=1")
50
+ result = statement.execute()
51
+ result.each do |row|
52
+ ssl_options = row["imap_ssl"]==1 ? {:verify_mode => OpenSSL::SSL::VERIFY_NONE} : false
53
+ decrypted_password = Encryption.new.decrypt(row["imap_encrypted_password"])
54
+ mailboxes << {:id => row["id"], :account_code => row["nickname"], :username => row["imap_username"], :host => row["imap_host"], :port => row["imap_port"], :password => decrypted_password, :ssl_options => ssl_options, :start_tls => row["imap_start_tls"]==1, :name => row["imap_folder_nam"], :search_command => row["imap_search_command"], :message_count => row["imap_messages_processed"], :last_delivered_at => row["imap_last_processed_at"]}
55
+ end
56
+ end
57
+ mailboxes
58
+ end
59
+
60
+ def restart
61
+ @daemon.reload(:connections => configuration)
62
+ end
63
+
64
+ def handle_incoming_message(options)
65
+ @handler = Mail2cb::EmailHandler.new(options)
66
+ @handler.sanitize!
67
+ # @handler.clean_replies!
68
+
69
+ hash = @handler.generate_message_hash
70
+
71
+ $redis.lpush(redis_key(options), hash)
72
+ puts "Queued."
73
+ end
74
+
75
+ def redis_key(options)
76
+ "cb:comms:#{options[:mailbox][:account_code]}:inbox"
77
+ end
78
+
79
+ def update_status(options)
80
+ puts "Status Update: #{options[:status]} for #{options[:mailbox][:username]}"
81
+ mysql_client do |mysql|
82
+ status_message = options[:status]
83
+ statement = mysql.prepare("UPDATE case_blocks_email_accounts SET imap_status='#{options[:status]}', imap_status_message='#{status_message}' where id=?")
84
+ statement.execute(options[:mailbox][:id])
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def mysql_client(&block)
91
+ client = Mysql2::Client.new(:host => ENV["MYSQL_HOST"], :port => ENV["MYSQL_PORT"], :username => ENV["MYSQL_USERNAME"], :password => ENV["MYSQL_PASSWORD"], :database => ENV["MYSQL_DATABASE"])
92
+ yield client
93
+ client.close
94
+ end
95
+
96
+ def mailbox_status_message(watcher)
97
+ if watcher.logging_in?
98
+ "Connecting and Logging into server"
99
+ elsif watcher.logged_in?
100
+ "Connected"
101
+ else
102
+ "Disconnected"
103
+ end
104
+ end
105
+ def mailbox_status(watcher)
106
+ if watcher.logging_in?
107
+ :logging_in
108
+ elsif watcher.logged_in?
109
+ :connected
110
+ else
111
+ :disconnected
112
+ end
113
+ end
114
+ def mailbox_status_colour(watcher)
115
+ if watcher.logging_in?
116
+ "orange"
117
+ elsif watcher.logged_in?
118
+ "green"
119
+ else
120
+ "red"
121
+ end
122
+ end
123
+
124
+
125
+ end
126
+ end
@@ -0,0 +1,42 @@
1
+ require 'base64'
2
+
3
+ module Mail2cb
4
+ class Encryption
5
+
6
+ def initialize()
7
+ @key = ENV["CASEBLOCKS_ENCRYPTION_KEY"] || "YLX0IBT+OXaO4mP2bVYqzMPbrrss8eUcX1XtgLxlVH8="
8
+ @iv = ENV["CASEBLOCKS_ENCRYPTION_IV"] || "vvSVfoWvZQ3T/DfjsjO/9w=="
9
+ end
10
+
11
+ def encrypt(data)
12
+ unless data.nil?
13
+ cipher = OpenSSL::Cipher::AES.new(128, :CBC)
14
+ cipher.encrypt
15
+ cipher.key = Base64.decode64(@key)
16
+ cipher.iv = Base64.decode64(@iv)
17
+ encrypted = cipher.update(data) + cipher.final
18
+
19
+ # [0..-2] strip off trailing carriage return
20
+ Base64.encode64(encrypted)[0..-2]
21
+ else
22
+ data
23
+ end
24
+ end
25
+
26
+ def decrypt(value)
27
+ unless value.nil?
28
+ decipher = OpenSSL::Cipher::AES.new(128, :CBC)
29
+ decipher.decrypt
30
+ decipher.key = Base64.decode64(@key)
31
+ decipher.iv = Base64.decode64(@iv)
32
+
33
+ encrypted = Base64.decode64(value)
34
+ decipher.update(encrypted) + decipher.final
35
+ else
36
+ value
37
+ end
38
+ end
39
+
40
+
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ module Mail2cb
2
+ module Helpers
3
+ def setup_options(options)
4
+ @options = options.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
5
+ end
6
+ def default_option(name, value)
7
+ @options[name.to_s.to_sym] = value unless @options.has_key?(name.to_s.to_sym)
8
+ end
9
+ def required_option(names)
10
+ names_array = Array(names)
11
+ names_array.each do |name|
12
+ raise "#{name} is a required option, please supply this in the call to new" unless @options.has_key?(name.to_s.to_sym)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Mail2cb
2
+ VERSION = "0.0.6"
3
+ end
data/lib/mail2cb.rb ADDED
@@ -0,0 +1,33 @@
1
+ require_relative 'mail2cb/version'
2
+ require_relative 'mail2cb/helpers'
3
+ require_relative 'mail2cb/email_body_parser'
4
+ require_relative 'mail2cb/email_watcher'
5
+ require_relative 'mail2cb/email_handler'
6
+ require_relative 'mail2cb/encryption'
7
+ require 'sinatra/base'
8
+ require 'json'
9
+ require 'open3'
10
+ require 'mail'
11
+ require 'mail_daemon'
12
+
13
+ module Mail2cb
14
+ class Handler
15
+ def initialize
16
+ @@watcher = EmailWatcher.new
17
+
18
+ @@watcher.start
19
+
20
+ my_app = Sinatra.new do
21
+ set :logging, false
22
+ set :run, false
23
+
24
+ get('/reload') do
25
+ @@watcher.restart
26
+ "OK"
27
+ end
28
+
29
+ end
30
+ my_app.run!
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ .status-circle {
2
+ width: 10px;
3
+ height: 10px;
4
+ background-color: #ccc;
5
+ border-radius: 50%;
6
+ margin-top: 6px;
7
+ }
8
+
9
+ .status-circle.red {
10
+ background-color: red;
11
+ }
12
+
13
+ .status-circle.green {
14
+ background-color: green;
15
+ }
16
+
17
+ .status-circle.orange {
18
+ background-color: orange;
19
+ }
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Base structure
3
+ */
4
+
5
+ /* Move down content because we have a fixed navbar that is 50px tall */
6
+ body {
7
+ padding-top: 50px;
8
+ }
9
+
10
+
11
+ /*
12
+ * Global add-ons
13
+ */
14
+
15
+ .sub-header {
16
+ padding-bottom: 10px;
17
+ border-bottom: 1px solid #eee;
18
+ }
19
+
20
+ /*
21
+ * Top navigation
22
+ * Hide default border to remove 1px line.
23
+ */
24
+ .navbar-fixed-top {
25
+ border: 0;
26
+ }
27
+
28
+ /*
29
+ * Sidebar
30
+ */
31
+
32
+ /* Hide for mobile, show later */
33
+ .sidebar {
34
+ display: none;
35
+ }
36
+ @media (min-width: 768px) {
37
+ .sidebar {
38
+ position: fixed;
39
+ top: 51px;
40
+ bottom: 0;
41
+ left: 0;
42
+ z-index: 1000;
43
+ display: block;
44
+ padding: 20px;
45
+ overflow-x: hidden;
46
+ overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
47
+ background-color: #f5f5f5;
48
+ border-right: 1px solid #eee;
49
+ }
50
+ }
51
+
52
+ /* Sidebar navigation */
53
+ .nav-sidebar {
54
+ margin-right: -21px; /* 20px padding + 1px border */
55
+ margin-bottom: 20px;
56
+ margin-left: -20px;
57
+ }
58
+ .nav-sidebar > li > a {
59
+ padding-right: 20px;
60
+ padding-left: 20px;
61
+ }
62
+ .nav-sidebar > .active > a,
63
+ .nav-sidebar > .active > a:hover,
64
+ .nav-sidebar > .active > a:focus {
65
+ color: #fff;
66
+ background-color: #428bca;
67
+ }
68
+
69
+
70
+ /*
71
+ * Main content
72
+ */
73
+
74
+ .main {
75
+ padding: 20px;
76
+ }
77
+ @media (min-width: 768px) {
78
+ .main {
79
+ padding-right: 40px;
80
+ padding-left: 40px;
81
+ }
82
+ }
83
+ .main .page-header {
84
+ margin-top: 0;
85
+ }
86
+
87
+
88
+ /*
89
+ * Placeholder dashboard ideas
90
+ */
91
+
92
+ .placeholders {
93
+ margin-bottom: 30px;
94
+ text-align: center;
95
+ }
96
+ .placeholders h4 {
97
+ margin-bottom: 0;
98
+ }
99
+ .placeholder {
100
+ margin-bottom: 20px;
101
+ }
102
+ .placeholder img {
103
+ display: inline-block;
104
+ border-radius: 50%;
105
+ }
data/lib/server.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'sinatra'
2
+
3
+ get '/reload' do
4
+ Mail2cb::EmailWatcher.reload
5
+ Mail2cb::EmailWatcher.restart
6
+ end
@@ -0,0 +1,113 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
8
+ <title>CaseBlocks Email Watcher</title>
9
+
10
+ <!-- Bootstrap -->
11
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
12
+ <link href="dashboard.css" rel="stylesheet">
13
+
14
+
15
+ <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
16
+ <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
17
+ <!--[if lt IE 9]>
18
+ <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
19
+ <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
20
+ <![endif]-->
21
+ </head>
22
+ <body>
23
+
24
+ <nav class="navbar navbar-inverse navbar-fixed-top">
25
+ <div class="container-fluid">
26
+ <div class="navbar-header">
27
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
28
+ <span class="sr-only">Toggle navigation</span>
29
+ <span class="icon-bar"></span>
30
+ <span class="icon-bar"></span>
31
+ <span class="icon-bar"></span>
32
+ </button>
33
+ <a class="navbar-brand" href="#">CaseBlocks Email Watcher</a>
34
+ </div>
35
+ <div id="navbar" class="navbar-collapse collapse">
36
+ <ul class="nav navbar-nav navbar-right">
37
+ <li><a href="/">Dashboard</a></li>
38
+ <li><a href="/settings">Settings</a></li>
39
+ </ul>
40
+ <form class="navbar-form navbar-right">
41
+ <input type="text" class="form-control" placeholder="Search...">
42
+ </form>
43
+ </div>
44
+ </div>
45
+ </nav>
46
+
47
+ <div class="container-fluid">
48
+ <div class="row">
49
+
50
+ <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
51
+ <h1 class="page-header">Dashboard</h1>
52
+
53
+ <div class="row placeholders">
54
+ <div class="col-xs-6 col-sm-3 placeholder">
55
+ <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
56
+ <h4>Label</h4>
57
+ <span class="text-muted">Something else</span>
58
+ </div>
59
+ <div class="col-xs-6 col-sm-3 placeholder">
60
+ <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
61
+ <h4>Label</h4>
62
+ <span class="text-muted">Something else</span>
63
+ </div>
64
+ <div class="col-xs-6 col-sm-3 placeholder">
65
+ <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
66
+ <h4>Label</h4>
67
+ <span class="text-muted">Something else</span>
68
+ </div>
69
+ <div class="col-xs-6 col-sm-3 placeholder">
70
+ <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
71
+ <h4>Label</h4>
72
+ <span class="text-muted">Something else</span>
73
+ </div>
74
+ </div>
75
+
76
+ <h2 class="sub-header">Mailboxes</h2>
77
+ <div class="table-responsive">
78
+ <table class="table table-striped">
79
+ <thead>
80
+ <tr>
81
+ <th>Email Address</th>
82
+ <th>Host</th>
83
+ <th>Delivery Method</th>
84
+ </tr>
85
+ </thead>
86
+ <tbody>
87
+ <% @mailboxes.each do |mailbox| %>
88
+ <tr>
89
+ <td><%= mailbox[:email] %></td>
90
+ <td><%= mailbox[:host] %></td>
91
+ <td><%= mailbox[:delivery_method] %></td>
92
+ </tr>
93
+ <% end %>
94
+ </tbody>
95
+ </table>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Bootstrap core JavaScript
102
+ ================================================== -->
103
+ <!-- Placed at the end of the document so the pages load faster -->
104
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
105
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
106
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
107
+
108
+
109
+ <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
110
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
111
+
112
+ </body>
113
+ </html>
@@ -0,0 +1,100 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
8
+ <title>CaseBlocks Email Watcher</title>
9
+
10
+ <!-- Bootstrap -->
11
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
12
+ <link href="dashboard.css" rel="stylesheet">
13
+ <link href="custom.css" rel="stylesheet">
14
+
15
+
16
+ <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
17
+ <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
18
+ <!--[if lt IE 9]>
19
+ <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
20
+ <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
21
+ <![endif]-->
22
+ </head>
23
+ <body>
24
+
25
+ <nav class="navbar navbar-inverse navbar-fixed-top">
26
+ <div class="container-fluid">
27
+ <div class="navbar-header">
28
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
29
+ <span class="sr-only">Toggle navigation</span>
30
+ <span class="icon-bar"></span>
31
+ <span class="icon-bar"></span>
32
+ <span class="icon-bar"></span>
33
+ </button>
34
+ <a class="navbar-brand" href="#">CaseBlocks Email Watcher</a>
35
+ </div>
36
+ <div id="navbar" class="navbar-collapse collapse">
37
+ <ul class="nav navbar-nav navbar-right">
38
+ <li><a href="/">Dashboard</a></li>
39
+ <li><a href="/reload">Reload Configuration</a></li>
40
+ </ul>
41
+ <form class="navbar-form navbar-right">
42
+ <input type="text" class="form-control" placeholder="Search...">
43
+ </form>
44
+ </div>
45
+ </div>
46
+ </nav>
47
+
48
+ <div class="container-fluid">
49
+ <div class="row">
50
+ <div class="col-sm-9 col-md-10 main" style="width: 100%;">
51
+ <!-- <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> -->
52
+
53
+ <h2 class="sub-header">Mailboxes</h2>
54
+ <div class="table-responsive">
55
+ <table class="table table-striped">
56
+ <thead>
57
+ <tr>
58
+ <th></th>
59
+ <th>Email Address</th>
60
+ <th>Host</th>
61
+ <th>Port</th>
62
+ <th>SSL</th>
63
+ <th>Start TLS</th>
64
+ <th>Messages Processed</th>
65
+ <th>Last Processed At</th>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ <% @mailboxes.each do |mailbox| %>
70
+ <tr>
71
+ <td><div class="status-circle <%=mailbox[:status_colour]%>"></td>
72
+ <td><%= mailbox[:email] %></td>
73
+ <td><%= mailbox[:host] %></td>
74
+ <td><%= mailbox[:port] %></td>
75
+ <td><%= !!mailbox[:ssl] ? "Yes" : "No" %></td>
76
+ <td><%= mailbox[:start_tls] ? "Yes" : "No" %></td>
77
+ <td><%= mailbox[:message_count] %></td>
78
+ <td><%= mailbox[:last_delivered_at] %></td>
79
+ </tr>
80
+ <% end %>
81
+ </tbody>
82
+ </table>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Bootstrap core JavaScript
89
+ ================================================== -->
90
+ <!-- Placed at the end of the document so the pages load faster -->
91
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
92
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
93
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
94
+
95
+
96
+ <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
97
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
98
+
99
+ </body>
100
+ </html>
data/mail2cb.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mail2cb/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mail2cb"
8
+ spec.version = Mail2cb::VERSION
9
+ spec.authors = ["EmergeAdapt"]
10
+ spec.email = ["development@emergeadapt.com"]
11
+ spec.summary = %q{Grabs orders from Tyreshopper and posts them to SQS.}
12
+ spec.description = %q{Basic WIP.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "awesome_print"
24
+
25
+ spec.add_dependency 'mysql2'
26
+ spec.add_dependency 'sidekiq'
27
+ spec.add_dependency 'mail'
28
+ spec.add_dependency 'mail_daemon', "~> 0.0.6"
29
+ spec.add_dependency 'charlock_holmes' # required for sidekiq delivery
30
+ spec.add_dependency 'nokogiri'
31
+ spec.add_dependency 'sinatra'
32
+ spec.add_dependency 'sanitize'
33
+
34
+
35
+ end
@@ -0,0 +1,20 @@
1
+ require "minitest/autorun"
2
+
3
+ class TestOrder < Minitest::Test
4
+ def setup
5
+ @sample = {}
6
+ @order = Order.new(@sample)
7
+ end
8
+
9
+ def test_that_kitty_can_eat
10
+ assert_equal "OHAI!", @meme.i_can_has_cheezburger?
11
+ end
12
+
13
+ def test_that_it_will_not_blend
14
+ refute_match /^no/i, @meme.will_it_blend?
15
+ end
16
+
17
+ def test_that_will_be_skipped
18
+ skip "test this later"
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class VersionTest < MiniTest::Unit::TestCase
4
+ def test_version
5
+ assert_equal '0.0.1', mail2cb::VERSION
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ require_relative '../test_helper'
2
+
3
+ class MessageHandlerTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ ENV['CB_API_ENDPOINT'] = 'https://cb.local/'
6
+ ENV['CB_API_TOKEN'] = '1234'
7
+ end
8
+ def test_invalid_construction
9
+ ENV['AWS_ACCESS_KEY'] = nil
10
+ ENV['AWS_SECRET_ACCESS_KEY'] = nil
11
+ ENV['CB_API_ENDPOINT'] = nil
12
+ ENV['CB_API_TOKEN'] = nil
13
+ assert_raises(RuntimeError) { mail2cb::OrderHandler.new }
14
+ end
15
+ def test_valid_construction
16
+ ENV['AWS_ACCESS_KEY'] = 'access'
17
+ ENV['AWS_SECRET_ACCESS_KEY'] = 'secret'
18
+ refute_nil mail2cb::OrderHandler.new
19
+ end
20
+ def test_basic_sqs_connectivity
21
+ oh = mail2cb::OrderHandler.new
22
+ result = oh.send_orders
23
+
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+ require File.expand_path('../../lib/mail2cb.rb', __FILE__)
metadata ADDED
@@ -0,0 +1,230 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail2cb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ platform: ruby
6
+ authors:
7
+ - EmergeAdapt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
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: awesome_print
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
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: mysql2
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
+ - !ruby/object:Gem::Dependency
70
+ name: sidekiq
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mail
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mail_daemon
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.0.6
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.0.6
111
+ - !ruby/object:Gem::Dependency
112
+ name: charlock_holmes
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: nokogiri
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sinatra
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: sanitize
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: Basic WIP.
168
+ email:
169
+ - development@emergeadapt.com
170
+ executables:
171
+ - mail2cb
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".gitignore"
176
+ - ".ruby-gemset"
177
+ - ".ruby-version"
178
+ - Gemfile
179
+ - LICENSE
180
+ - LICENSE.txt
181
+ - README.md
182
+ - Rakefile
183
+ - bin/mail2cb
184
+ - config.yml
185
+ - lib/mail2cb.rb
186
+ - lib/mail2cb/email_body_parser.rb
187
+ - lib/mail2cb/email_handler.rb
188
+ - lib/mail2cb/email_watcher.rb
189
+ - lib/mail2cb/encryption.rb
190
+ - lib/mail2cb/helpers.rb
191
+ - lib/mail2cb/version.rb
192
+ - lib/public/custom.css
193
+ - lib/public/dashboard.css
194
+ - lib/server.rb
195
+ - lib/views/settings.erb
196
+ - lib/views/stats.erb
197
+ - mail2cb.gemspec
198
+ - test/lib/order2cb/order_test.rb
199
+ - test/lib/order2cb/version_test.rb
200
+ - test/lib/order2cb_test.rb
201
+ - test/test_helper.rb
202
+ homepage: ''
203
+ licenses:
204
+ - MIT
205
+ metadata: {}
206
+ post_install_message:
207
+ rdoc_options: []
208
+ require_paths:
209
+ - lib
210
+ required_ruby_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ required_rubygems_version: !ruby/object:Gem::Requirement
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: '0'
220
+ requirements: []
221
+ rubyforge_project:
222
+ rubygems_version: 2.4.5.1
223
+ signing_key:
224
+ specification_version: 4
225
+ summary: Grabs orders from Tyreshopper and posts them to SQS.
226
+ test_files:
227
+ - test/lib/order2cb/order_test.rb
228
+ - test/lib/order2cb/version_test.rb
229
+ - test/lib/order2cb_test.rb
230
+ - test/test_helper.rb