mongo_mailer 0.0.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mongo_mailer might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +10 -13
  3. data/.travis.yml +6 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +112 -61
  6. data/README.md +112 -0
  7. data/Rakefile +7 -4
  8. data/bin/console +7 -0
  9. data/bin/mongo_mailer +11 -0
  10. data/bin/worker +6 -0
  11. data/config/boot.rb +12 -0
  12. data/config/mongo_mailer.yml.example +38 -0
  13. data/lib/generators/mongo_mailer/mongo_mailer_generator.rb +10 -0
  14. data/lib/generators/mongo_mailer/templates/mongo_mailer.yml +38 -0
  15. data/lib/mongo_mailer.rb +9 -2
  16. data/lib/mongo_mailer/capistrano.rb +33 -0
  17. data/lib/mongo_mailer/capistrano3.rb +49 -0
  18. data/lib/mongo_mailer/common/deliveries_counter.rb +18 -0
  19. data/lib/mongo_mailer/common/has_mongo_collection.rb +36 -0
  20. data/lib/mongo_mailer/common/mail_queue.rb +49 -0
  21. data/lib/mongo_mailer/configuration.rb +127 -0
  22. data/lib/mongo_mailer/core_ext/hash.rb +12 -0
  23. data/lib/mongo_mailer/delivery_methods/mongo_queue.rb +28 -0
  24. data/lib/mongo_mailer/delivery_methods/test.rb +38 -0
  25. data/lib/mongo_mailer/rails.rb +5 -0
  26. data/lib/mongo_mailer/standalone.rb +6 -0
  27. data/lib/mongo_mailer/standalone/final_deliverer.rb +35 -0
  28. data/lib/mongo_mailer/standalone/worker.rb +23 -0
  29. data/lib/mongo_mailer/version.rb +3 -0
  30. data/log/.gitkeep +0 -0
  31. data/mongo_mailer.gemspec +25 -26
  32. data/spec/lib/common/deliveries_counter_spec.rb +22 -0
  33. data/spec/lib/common/mail_queue_spec.rb +68 -0
  34. data/spec/lib/configuration_spec.rb +26 -0
  35. data/spec/lib/standalone/final_deliverer_spec.rb +40 -0
  36. data/spec/rails/delivery_methods/mongo_queue_spec.rb +33 -0
  37. data/spec/spec_helper.rb +29 -0
  38. metadata +97 -44
  39. data/LICENSE +0 -21
@@ -0,0 +1,10 @@
1
+ require 'rails/generators'
2
+
3
+ class MongoMailerGenerator < Rails::Generators::Base
4
+
5
+ self.source_paths << File.join(File.dirname(__FILE__), 'templates')
6
+
7
+ def create_config_files
8
+ template 'mongo_mailer.yml', 'config/mongo_mailer.yml.example'
9
+ end
10
+ end
@@ -0,0 +1,38 @@
1
+ development: &development
2
+ mongodb:
3
+ host: localhost
4
+ port: 27017
5
+ name: mongo_mailer
6
+ base_delivery_method: smtp
7
+ base_delivery_settings:
8
+ user_name: "username"
9
+ password: "apikey"
10
+ domain: "example.com"
11
+ address: "smtp1.example.com"
12
+ port: 587
13
+ authentication: plain
14
+ enable_starttls_auto: true
15
+ emergency_delivery_method: smtp
16
+ emergency_delivery_settings:
17
+ user_name: "username"
18
+ password: "apikey"
19
+ domain: "example.com"
20
+ address: "smtp2.example.com"
21
+ port: 587
22
+ authentication: plain
23
+ enable_starttls_auto: true
24
+ log_level: info
25
+ daemon_options:
26
+ dir_mode: !ruby/symbol normal
27
+ log_dir: log
28
+ dir: log
29
+ log_output: true
30
+ backtrace: true
31
+ multiple: false
32
+ production:
33
+ <<: *development
34
+ test:
35
+ <<: *development
36
+ base_delivery_method: test1
37
+ emergency_delivery_method: test2
38
+ log_level: error
data/lib/mongo_mailer.rb CHANGED
@@ -1,5 +1,12 @@
1
+ require 'active_support/all'
1
2
  require 'mail'
2
3
  require 'mongo'
4
+ require 'daemons'
5
+ require 'singleton'
6
+ require 'mongo_mailer/version'
7
+ require 'mongo_mailer/configuration'
3
8
 
4
- module MongoMailer
5
- end
9
+ require 'mongo_mailer/common/has_mongo_collection'
10
+ Dir[File.expand_path('../mongo_mailer/common/*', __FILE__)].each do |lib|
11
+ require lib
12
+ end
@@ -0,0 +1,33 @@
1
+ Capistrano::Configuration.instance.load do
2
+ after "deploy", "mongo_mailer:consider_restarting"
3
+
4
+ namespace :mongo_mailer do
5
+ def roles
6
+ fetch(:mongo_mailer_server_role, :app)
7
+ end
8
+
9
+ def command_prefix
10
+ "cd #{current_path} && bundle exec mongo_mailer"
11
+ end
12
+
13
+ desc "Stop the mongo_mailer process"
14
+ task :stop, :roles => lambda { roles } do
15
+ run "#{command_prefix} stop #{rails_env}"
16
+ end
17
+
18
+ desc "Start the mongo_mailer process"
19
+ task :start, :roles => lambda { roles } do
20
+ run "#{command_prefix} start #{rails_env}"
21
+ end
22
+
23
+ desc "Restart the mongo_mailer process"
24
+ task :restart, :roles => lambda { roles } do
25
+ run "#{command_prefix} restart #{rails_env}"
26
+ end
27
+
28
+ desc "Restart the mongo_mailer process"
29
+ task :consider_restarting, :roles => lambda { roles } do
30
+ logger.info "Consider restarting mongo_mailer after deploy: `bundle exec cap #{fetch(:stage)} mongo_mailer:restart`"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ after "deploy", "mongo_mailer:consider_restarting"
2
+
3
+ namespace :load do
4
+ task :defaults do
5
+ set :mongo_mailer_server_role, -> { :app }
6
+ # Rbenv, Chruby, and RVM integration
7
+ set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a.concat(%w(mongo_mailer))
8
+ set :rvm_map_bins, fetch(:rvm_map_bins).to_a.concat(%w(mongo_mailer))
9
+ set :chruby_map_bins, fetch(:chruby_map_bins).to_a.concat(%w(mongo_mailer))
10
+ # Bundler integration
11
+ set :bundle_bins, fetch(:bundle_bins).to_a.concat(%w(mongo_mailer))
12
+ end
13
+ end
14
+
15
+ namespace :mongo_mailer do
16
+ desc "Stop the mongo_mailer process"
17
+ task :stop do
18
+ on roles fetch(:mongo_mailer_server_role) do
19
+ within(current_path) do
20
+ execute :mongo_mailer, :stop, fetch(:rails_env)
21
+ end
22
+ end
23
+ end
24
+
25
+ desc "Start the mongo_mailer process"
26
+ task :start do
27
+ on roles fetch(:mongo_mailer_server_role) do
28
+ within(current_path) do
29
+ execute :mongo_mailer, :start, fetch(:rails_env)
30
+ end
31
+ end
32
+ end
33
+
34
+ desc "Restart the mongo_mailer process"
35
+ task :restart do
36
+ on roles fetch(:mongo_mailer_server_role) do
37
+ within(current_path) do
38
+ execute :mongo_mailer, :restart, fetch(:rails_env)
39
+ end
40
+ end
41
+ end
42
+
43
+ desc "Consider restarting the mongo_mailer process"
44
+ task :consider_restarting do
45
+ on roles fetch(:mongo_mailer_server_role) do
46
+ info "Consider restarting mongo_mailer after deploy: `bundle exec cap #{fetch(:stage)} mongo_mailer:restart`"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,18 @@
1
+ module MongoMailer
2
+ class DeliveriesCounter
3
+ include Singleton
4
+ include ::MongoMailer::HasMongoCollection
5
+
6
+ def increment(type)
7
+ collection.update({ type: type }, { '$inc' => { value: 1 } }, {upsert: true})
8
+ end
9
+
10
+ def by_type(type)
11
+ collection.find_one({type: type})
12
+ end
13
+
14
+ def all
15
+ collection.find.to_a
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ module MongoMailer
2
+ module HasMongoCollection
3
+ def self.included(klass)
4
+ klass.send :extend, ClassMethods
5
+ klass.send :include, InstanceMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ def collection
10
+ self.class.collection
11
+ end
12
+
13
+ def configuration
14
+ self.class.configuration
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def collection_name=(name)
20
+ @collection_name = name
21
+ end
22
+
23
+ def collection_name
24
+ @collection_name ||= self.name.demodulize.underscore.pluralize
25
+ end
26
+
27
+ def collection
28
+ @collection ||= Configuration.instance.mongodb.collection(collection_name)
29
+ end
30
+
31
+ def configuration
32
+ Configuration.instance
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ module MongoMailer
2
+ class MailQueue
3
+ include Singleton
4
+ include MongoMailer::HasMongoCollection
5
+
6
+ MAX_RETIRES_COUNT = 5
7
+
8
+ def find_and_deliver!
9
+ item = get_oldest
10
+
11
+ return unless item
12
+
13
+ deliver!(item)
14
+ end
15
+
16
+ def get_oldest
17
+ collection.find_and_modify(
18
+ query: { invalid: nil },
19
+ remove: true,
20
+ sort: { :'$natural' => 1 })
21
+ end
22
+
23
+ def invalid_items
24
+ collection.all(
25
+ { invalid: true },
26
+ sort: { :'$natural' => 1 })
27
+ end
28
+
29
+ def clear_invalid_items!
30
+ collection.remove({ invalid: true })
31
+ end
32
+
33
+ private
34
+
35
+ def deliver!(item)
36
+ begin
37
+ ::MongoMailer::FinalDeliverer.new(item['encoded']).deliver!
38
+ collection.remove(item)
39
+ return true
40
+ rescue => e
41
+ configuration.log_error(e)
42
+ item['retries_count'] = (item['retries_count'] || 0) + 1
43
+ item['invalid'] = true if item['retries_count'] > MAX_RETIRES_COUNT
44
+ collection.insert(item)
45
+ return false
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,127 @@
1
+ require 'singleton'
2
+ require 'mongo_mailer/core_ext/hash'
3
+
4
+ module MongoMailer
5
+ class Configuration
6
+ include Singleton
7
+
8
+ class MissingConfigurationFile < StandardError; end
9
+ class MissingConfigurationData < StandardError; end
10
+ class MissingConfigurationVariables < StandardError; end
11
+
12
+ REQUIRED_KEYS = [:mongodb, :base_delivery_method, :emergency_delivery_method]
13
+
14
+ def env
15
+ @env ||= defined?(Rails) ? Rails.env.to_s : ENV['RAILS_ENV']
16
+ end
17
+
18
+ def root
19
+ return @root if @root
20
+ @root = Rails.root if defined?(Rails)
21
+ end
22
+
23
+ def root=(path)
24
+ @root = Pathname.new(path)
25
+ end
26
+
27
+ def configuration
28
+ @configuration ||= load!
29
+ end
30
+
31
+ def load!
32
+ unless root
33
+ warn "`MongoMailer::Configuration.instance.root` does not seem to be set!"
34
+ return false
35
+ end
36
+
37
+ @configuration = begin
38
+ load_yml('config/mongo_mailer.yml')
39
+ rescue MissingConfigurationFile
40
+ warn "`config/mongo_mailer.yml` seems to be missing, loading `config/mongo_mailer.yml.example` file instead."
41
+ load_yml('config/mongo_mailer.yml.example')
42
+ end
43
+ end
44
+
45
+ def verify!
46
+ missing_keys = REQUIRED_KEYS - configuration.keys
47
+ unless missing_keys.empty?
48
+ raise MissingConfigurationVariables("Following variables are missing in your configuration file: #{missing_keys.join(',')}")
49
+ end
50
+ return true
51
+ end
52
+
53
+ def mongodb
54
+ configuration[:mongodb][:name] ||= 'mongo_mailer'
55
+ @mongodb ||= ::Mongo::MongoClient.new(configuration[:mongodb][:host], configuration[:mongodb][:port]).db("#{configuration[:mongodb][:name]}_#{env}")
56
+ end
57
+
58
+ def lookup_delivery_method(method)
59
+ method = ::Mail::Configuration.instance.lookup_delivery_method(method.to_s)
60
+ return method unless method.is_a?(String)
61
+
62
+ case method.to_sym
63
+ when :test1 then MongoMailer::DeliveryMethods::Test1Mailer
64
+ when :test2 then MongoMailer::DeliveryMethods::Test2Mailer
65
+ else
66
+ raise "Unknown delivery method: #{method}"
67
+ end
68
+ end
69
+
70
+ def base_delivery
71
+ @base_delivery ||= [
72
+ lookup_delivery_method(configuration[:base_delivery_method]),
73
+ configuration[:base_delivery_settings]
74
+ ]
75
+ end
76
+
77
+ def emergency_delivery
78
+ @emergency_delivery ||= [
79
+ lookup_delivery_method(configuration[:emergency_delivery_method]),
80
+ configuration[:emergency_delivery_settings]
81
+ ]
82
+ end
83
+
84
+ def full_daemon_options
85
+ opts = configuration[:daemon_options]
86
+ opts[:log_dir] = root.join(opts[:log_dir]).to_s if opts[:log_dir]
87
+ opts[:dir] = root.join(opts[:dir]).to_s if opts[:dir]
88
+ return opts
89
+ end
90
+
91
+ def logger
92
+ @logger ||= init_logger
93
+ end
94
+
95
+ def log_error(e)
96
+ msg = [e.inspect, e.backtrace].flatten.join("\n")
97
+ logger.error("[ERROR] #{msg}")
98
+ end
99
+
100
+ private
101
+
102
+ def init_logger
103
+ logger = Logger.new(File.join(self.root, 'log/mongo_queue.log'), 'weekly')
104
+ logger.formatter = Logger::Formatter.new
105
+
106
+ log_level = (configuration[:log_level] || :info)
107
+ logger.level = Logger.const_get(log_level.to_s.upcase)
108
+ logger
109
+ end
110
+
111
+ def load_yml(file)
112
+ path = File.join(self.root, file)
113
+
114
+ unless File.exists?(path)
115
+ raise MissingConfigurationFile.new("File #{path} was found")
116
+ end
117
+
118
+ yml = ::YAML.load_file(path)
119
+
120
+ if yml[self.env].is_a?(Hash)
121
+ return yml[self.env].deep_symbolize_keys
122
+ else
123
+ raise MissingConfigurationData.new("Configuration data for #{self.env} was not found in #{path}")
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,12 @@
1
+ class Hash
2
+ # Hash#deep_symbolize_keys
3
+ # based on
4
+ # https://github.com/svenfuchs/i18n/blob/master/lib/i18n/core_ext/hash.rb
5
+ def deep_symbolize_keys
6
+ inject({}) { |result, (key, value)|
7
+ value = value.deep_symbolize_keys if value.is_a?(self.class)
8
+ result[(key.to_sym rescue key) || key] = value
9
+ result
10
+ }
11
+ end unless self.method_defined?(:deep_symbolize_keys)
12
+ end
@@ -0,0 +1,28 @@
1
+ require 'mail/check_delivery_params'
2
+
3
+ module MongoMailer
4
+ module DeliveryMethods
5
+ class MongoQueue
6
+ attr_accessor :settings
7
+
8
+ def initialize(options = {})
9
+ @settings = options.merge(return_response: true)
10
+ end
11
+
12
+ def deliver!(mail)
13
+ Mail::CheckDeliveryParams.check(mail)
14
+ collection.insert({encoded: mail.encoded, uuid: get_uuid})
15
+ end
16
+
17
+ private
18
+
19
+ def get_uuid
20
+ ::SecureRandom.hex(8)
21
+ end
22
+
23
+ def collection
24
+ @collection ||= ::MongoMailer::MailQueue.instance.collection
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ require 'mail/check_delivery_params'
2
+
3
+ module MongoMailer
4
+ module DeliveryMethods
5
+ module CommonTestMailer
6
+ def self.included(klass)
7
+ klass.send :extend, ClassMethods
8
+ klass.send :include, InstanceMethods
9
+ klass.send :attr_accessor, :settings
10
+ end
11
+
12
+ module InstanceMethods
13
+ def initialize(*args)
14
+ @settings = {}
15
+ end
16
+
17
+ def deliver!(mail)
18
+ Mail::CheckDeliveryParams.check(mail)
19
+ self.class.deliveries << mail
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def deliveries
25
+ @deliveries ||= []
26
+ end
27
+ end
28
+ end
29
+
30
+ class Test1Mailer
31
+ include CommonTestMailer
32
+ end
33
+
34
+ class Test2Mailer
35
+ include CommonTestMailer
36
+ end
37
+ end
38
+ end