mongo_mailer 0.0.0 → 0.1.3

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.

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