moogle 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 (68) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +35 -0
  4. data/Gemfile.lock +148 -0
  5. data/LICENSE.txt +202 -0
  6. data/NOTICE.txt +4 -0
  7. data/README.md +33 -0
  8. data/Rakefile +44 -0
  9. data/fixtures/vcr_cassettes/push_blog_entry.yml +48 -0
  10. data/fixtures/vcr_cassettes/push_webhook_ping.yml +41 -0
  11. data/lib/moogle/commands/create_link.rb +45 -0
  12. data/lib/moogle/commands/create_target.rb +46 -0
  13. data/lib/moogle/commands/destroy_link.rb +37 -0
  14. data/lib/moogle/commands/destroy_target.rb +37 -0
  15. data/lib/moogle/commands/find_targets.rb +31 -0
  16. data/lib/moogle/commands/push_blog_entry.rb +54 -0
  17. data/lib/moogle/commands/push_email.rb +60 -0
  18. data/lib/moogle/commands/push_webhook_ping.rb +58 -0
  19. data/lib/moogle/commands/update_target.rb +44 -0
  20. data/lib/moogle/commands.rb +9 -0
  21. data/lib/moogle/error.rb +6 -0
  22. data/lib/moogle/events/blog_entry_pushed.rb +34 -0
  23. data/lib/moogle/events/email_pushed.rb +36 -0
  24. data/lib/moogle/events/error.rb +24 -0
  25. data/lib/moogle/events/link_created.rb +21 -0
  26. data/lib/moogle/events/link_destroyed.rb +21 -0
  27. data/lib/moogle/events/target_created.rb +21 -0
  28. data/lib/moogle/events/target_destroyed.rb +21 -0
  29. data/lib/moogle/events/target_updated.rb +21 -0
  30. data/lib/moogle/events/webhook_ping_pushed.rb +34 -0
  31. data/lib/moogle/handlers/accept_notification.rb +96 -0
  32. data/lib/moogle/messages/notification.rb +34 -0
  33. data/lib/moogle/models/blog_target.rb +29 -0
  34. data/lib/moogle/models/email_target.rb +16 -0
  35. data/lib/moogle/models/facebook_target.rb +14 -0
  36. data/lib/moogle/models/link.rb +20 -0
  37. data/lib/moogle/models/post_log.rb +21 -0
  38. data/lib/moogle/models/target.rb +22 -0
  39. data/lib/moogle/models/twitter_target.rb +14 -0
  40. data/lib/moogle/models/webhook_target.rb +18 -0
  41. data/lib/moogle/models.rb +9 -0
  42. data/lib/moogle/representers/link_representer.rb +23 -0
  43. data/lib/moogle/representers/target_representer.rb +34 -0
  44. data/lib/moogle/requests/create_link.rb +24 -0
  45. data/lib/moogle/requests/create_target.rb +40 -0
  46. data/lib/moogle/requests/destroy_link.rb +22 -0
  47. data/lib/moogle/requests/destroy_target.rb +22 -0
  48. data/lib/moogle/requests/find_targets.rb +21 -0
  49. data/lib/moogle/requests/push_blog_entry.rb +67 -0
  50. data/lib/moogle/requests/push_email.rb +46 -0
  51. data/lib/moogle/requests/push_webhook_ping.rb +49 -0
  52. data/lib/moogle/requests/update_target.rb +22 -0
  53. data/lib/moogle/requests.rb +6 -0
  54. data/lib/moogle/version.rb +11 -0
  55. data/lib/moogle.rb +5 -0
  56. data/moogle.gemspec +160 -0
  57. data/spec/commands/find_targets_spec.rb +32 -0
  58. data/spec/commands/links_spec.rb +92 -0
  59. data/spec/commands/push_blog_entry_spec.rb +33 -0
  60. data/spec/commands/push_email_spec.rb +44 -0
  61. data/spec/commands/push_webhook_ping_spec.rb +30 -0
  62. data/spec/commands/targets_spec.rb +165 -0
  63. data/spec/handlers/accept_notification_spec.rb +55 -0
  64. data/spec/moogle_spec.rb +1 -0
  65. data/spec/spec_helper.rb +12 -0
  66. data/spec/support/dependencies.rb +10 -0
  67. data/spec/support/vcr.rb +6 -0
  68. metadata +326 -0
@@ -0,0 +1,37 @@
1
+ require 'serf/command'
2
+
3
+ require 'moogle/error'
4
+ require 'moogle/events/link_destroyed'
5
+ require 'moogle/requests/destroy_link'
6
+ require 'moogle/models'
7
+
8
+ module Moogle
9
+ module Commands
10
+
11
+ class DestroyLink
12
+ include Serf::Command
13
+
14
+ self.request_factory = Moogle::Requests::DestroyLink
15
+
16
+ def call
17
+ link_model = opts :link_model, Moogle::Link
18
+ event_class = opts :event_class, Moogle::Events::LinkDestroyed
19
+
20
+ link_id = request.link_id
21
+ link = link_model.get link_id
22
+
23
+ # We only attempt to destroy the link if it exists.
24
+ # We raise an error if we are unable to destroy an existing link.
25
+ raise 'Unable to destroy link' unless link.destroy if link
26
+
27
+ return event_class.new(
28
+ request.create_child_uuids.merge(link_id: link_id))
29
+ rescue => e
30
+ e.extend Moogle::Error
31
+ raise e
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'serf/command'
2
+
3
+ require 'moogle/error'
4
+ require 'moogle/events/target_destroyed'
5
+ require 'moogle/requests/destroy_target'
6
+ require 'moogle/models'
7
+
8
+ module Moogle
9
+ module Commands
10
+
11
+ class DestroyTarget
12
+ include Serf::Command
13
+
14
+ self.request_factory = Moogle::Requests::DestroyTarget
15
+
16
+ def call
17
+ target_model = opts :target_model, Moogle::Target
18
+ event_class = opts :event_class, Moogle::Events::TargetDestroyed
19
+
20
+ target_id = request.target_id
21
+ target = target_model.get target_id
22
+
23
+ # We only attempt to destroy the link if it exists.
24
+ # We raise an error if we are unable to destroy an existing link.
25
+ raise 'Unable to destroy target' unless target.destroy if target
26
+
27
+ return event_class.new(
28
+ request.create_child_uuids.merge(target_id: target_id))
29
+ rescue => e
30
+ e.extend Moogle::Error
31
+ raise e
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ require 'serf/command'
2
+
3
+ require 'moogle/error'
4
+ require 'moogle/representers/target_representer'
5
+ require 'moogle/requests/find_targets'
6
+ require 'moogle/models'
7
+
8
+ module Moogle
9
+ module Commands
10
+
11
+ class FindTargets
12
+ include Serf::Command
13
+
14
+ self.request_factory = Moogle::Requests::FindTargets
15
+
16
+ def call
17
+ target_model = opts :target_model, Moogle::Target
18
+ representer = opts :representer, Moogle::TargetRepresenter
19
+
20
+ targets = target_model.all owner_ref: request.owner_ref
21
+
22
+ return targets.map{ |t| t.extend representer }
23
+ rescue => e
24
+ e.extend Moogle::Error
25
+ raise e
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ require 'serf/command'
2
+ require 'xmlrpc/client'
3
+
4
+ require 'moogle/error'
5
+ require 'moogle/events/blog_entry_pushed'
6
+ require 'moogle/requests/push_blog_entry'
7
+
8
+ module Moogle
9
+ module Commands
10
+
11
+ ##
12
+ # Posts an entry to a target blog.
13
+ #
14
+ class PushBlogEntry
15
+ include Serf::Command
16
+
17
+ self.request_factory = Moogle::Requests::PushBlogEntry
18
+
19
+ def call
20
+ # Xml-Rpc server
21
+ server = XMLRPC::Client.new request.host, request.path, request.port
22
+
23
+ # Makes the metaWeblog new post API call
24
+ post_ref = server.call(
25
+ 'metaWeblog.newPost',
26
+ request.blog_id,
27
+ request.username,
28
+ request.password,
29
+ {
30
+ 'title' => request.subject,
31
+ 'link' => request.blog_uri,
32
+ 'description' => request.html_body,
33
+ 'categories' => request.categories
34
+ },
35
+ request.publish_immediately)
36
+
37
+ # Return an event representing this action.
38
+ event_class = opts :event_class, Moogle::Events::BlogEntryPushed
39
+ return event_class.new(
40
+ request.create_child_uuids.merge(
41
+ message_origin: request.message_origin,
42
+ target_id: request.target_id,
43
+ post_ref: post_ref))
44
+ rescue XMLRPC::FaultException => e
45
+ e.extend Moogle::Error
46
+ raise e
47
+ rescue => e
48
+ e.extend Moogle::Error
49
+ raise e
50
+ end
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,60 @@
1
+ require 'mail'
2
+ require 'serf/command'
3
+
4
+ require 'moogle/events/email_pushed'
5
+ require 'moogle/requests/push_email'
6
+
7
+ module Moogle
8
+ module Commands
9
+
10
+ ##
11
+ # Sends an email
12
+ #
13
+ class PushEmail
14
+ include Serf::Command
15
+
16
+ self.request_factory = Moogle::Requests::PushEmail
17
+
18
+ def call
19
+ mail_class = opts :mail_class, Mail
20
+
21
+ # Create the mail object to send, get a local var 'req' so it is
22
+ # visible inside the proc.
23
+ req = request
24
+ mail = mail_class.new do
25
+ to req.to
26
+ from req.from
27
+ subject req.subject
28
+
29
+ text_part do
30
+ body req.text_body
31
+ end
32
+
33
+ if req.html_body
34
+ html_part do
35
+ content_type req.html_content_type
36
+ body req.html_body
37
+ end
38
+ end
39
+ end
40
+
41
+ # Tags for postmark
42
+ if mail.respond_to?(:tag=) && !request.categories.blank?
43
+ mail.tag = request.categories.sort.join ', '
44
+ end
45
+
46
+ # Deliver the mail using the default mailer delivery settings
47
+ mail.deliver!
48
+
49
+ # Return an event representing this action.
50
+ event_class = opts :event_class, Moogle::Events::EmailPushed
51
+ return event_class.new(
52
+ request.create_child_uuids.merge(
53
+ message_origin: request.message_origin,
54
+ target_id: request.target_id,
55
+ request: request))
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,58 @@
1
+ require 'addressable/uri'
2
+ require 'faraday'
3
+ require 'rafaday/body_signing_middleware'
4
+ require 'serf/command'
5
+
6
+ require 'moogle/events/webhook_ping_pushed'
7
+ require 'moogle/requests/push_webhook_ping'
8
+
9
+ module Moogle
10
+ module Commands
11
+
12
+ ##
13
+ # Posts a webhook callback.
14
+ #
15
+ class PushWebhookPing
16
+ include Serf::Command
17
+
18
+ self.request_factory = Moogle::Requests::PushWebhookPing
19
+
20
+ def call
21
+ # Options pulled from the delegated object
22
+ ssl_opt = opts :ssl_opt
23
+
24
+ webhook_uri = Addressable::URI.parse request.webhook_uri
25
+ secret = request.secret
26
+
27
+ # Build request and make it.
28
+ conn = Faraday.new url: webhook_uri.origin, ssl: ssl_opt do |b|
29
+ # Signs the post body, adds 'sig' to query parameters.
30
+ b.use Rafaday::BodySigningMiddleware, secret: secret if secret
31
+
32
+ b.response :raise_error
33
+ b.adapter :net_http
34
+ end
35
+ results = conn.post webhook_uri.path, request.data
36
+
37
+ # Checks for errors that didn't get caught in the response :raise_error.
38
+ case results.status
39
+ when 200..299
40
+ else
41
+ raise "PushWebhookPing (#{request.uuid}) failed: #{results.status}"
42
+ end
43
+
44
+ event_class = opts :event_class, Moogle::Events::WebhookPingPushed
45
+ return event_class.new(
46
+ request.create_child_uuids.merge(
47
+ message_origin: request.message_origin,
48
+ target_id: request.target_id,
49
+ webhook_uri: request.webhook_uri))
50
+ rescue => e
51
+ e.extend Moogle::Error
52
+ raise e
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ require 'serf/command'
2
+
3
+ require 'moogle/error'
4
+ require 'moogle/events/target_updated'
5
+ require 'moogle/requests/update_target'
6
+ require 'moogle/models'
7
+
8
+ module Moogle
9
+ module Commands
10
+
11
+ class UpdateTarget
12
+ include Serf::Command
13
+
14
+ self.request_factory = Moogle::Requests::UpdateTarget
15
+
16
+ def call
17
+ target_model = opts :target_model, Moogle::Target
18
+ event_class = opts :event_class, Moogle::Events::TargetUpdated
19
+ representer = opts :representer, Moogle::TargetRepresenter
20
+
21
+ target = target_model.get request.target_id
22
+ raise '404 Not found' unless target
23
+
24
+ result = target.update update_params
25
+ raise target.errors.full_messages.join('; ') unless target.saved?
26
+
27
+ target_rep = target.dup.extend representer
28
+
29
+ return event_class.new request.create_child_uuids.merge(target: target)
30
+ rescue => e
31
+ e.extend Moogle::Error
32
+ raise e
33
+ end
34
+
35
+ protected
36
+
37
+ def update_params
38
+ { options: request.options }
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ require 'moogle/commands/create_link'
2
+ require 'moogle/commands/create_target'
3
+ require 'moogle/commands/destroy_link'
4
+ require 'moogle/commands/destroy_target'
5
+ require 'moogle/commands/find_targets'
6
+ require 'moogle/commands/push_blog_entry'
7
+ require 'moogle/commands/push_email'
8
+ require 'moogle/commands/push_webhook_ping'
9
+ require 'moogle/commands/update_target'
@@ -0,0 +1,6 @@
1
+ module Moogle
2
+
3
+ module Error
4
+ end
5
+
6
+ end
@@ -0,0 +1,34 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ ##
10
+ # Signals that a blog entry was posted for a target_id, with the
11
+ # a reference to a message's origin.
12
+ #
13
+ class BlogEntryPushed
14
+ include Virtus
15
+ include Aequitas
16
+ include Serf::Message
17
+ include Serf::More::UuidFields
18
+
19
+ ##
20
+ # The target_id of the blog where we posted.
21
+ attribute :target_id, Integer
22
+
23
+ ##
24
+ # A descriptive origin that describes how we can find more about
25
+ # what was posted. And why.
26
+ attribute :message_origin, String
27
+
28
+ ##
29
+ # The returned post reference id of the blog entry created.
30
+ attribute :post_ref, String
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ require 'moogle/requests/push_email'
7
+
8
+ module Moogle
9
+ module Events
10
+
11
+ ##
12
+ # Signals that a blog entry was posted for a target_id, with the
13
+ # a reference to a message's origin.
14
+ #
15
+ class EmailPushed
16
+ include Virtus
17
+ include Aequitas
18
+ include Serf::Message
19
+ include Serf::More::UuidFields
20
+
21
+ ##
22
+ # The target_id of the email recipient
23
+ attribute :target_id, Integer
24
+
25
+ ##
26
+ # A descriptive origin that describes how we can find more about
27
+ # what was posted. And why.
28
+ attribute :message_origin, String
29
+
30
+ ##
31
+ # The returned post reference id of the blog entry created.
32
+ attribute :request, Moogle::Requests::PushEmail
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ class Error
10
+ include Virtus
11
+ include Aequitas
12
+ include Serf::Message
13
+ include Serf::More::UuidFields
14
+
15
+ attribute :error, String
16
+ attribute :message, String
17
+ attribute :backtrace, String
18
+ attribute :context, Object
19
+
20
+ validates_presence_of :error, :message, :context
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ class LinkCreated
10
+ include Virtus
11
+ include Aequitas
12
+ include Serf::Message
13
+ include Serf::More::UuidFields
14
+
15
+ attribute :link, Object
16
+
17
+ validates_presence_of :link
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ class LinkDestroyed
10
+ include Virtus
11
+ include Aequitas
12
+ include Serf::Message
13
+ include Serf::More::UuidFields
14
+
15
+ attribute :link_id, Integer
16
+
17
+ validates_presence_of :link_id
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ class TargetCreated
10
+ include Virtus
11
+ include Aequitas
12
+ include Serf::Message
13
+ include Serf::More::UuidFields
14
+
15
+ attribute :target, Object
16
+
17
+ validates_presence_of :target
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ class TargetDestroyed
10
+ include Virtus
11
+ include Aequitas
12
+ include Serf::Message
13
+ include Serf::More::UuidFields
14
+
15
+ attribute :target_id, Integer
16
+
17
+ validates_presence_of :target_id
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ class TargetUpdated
10
+ include Virtus
11
+ include Aequitas
12
+ include Serf::Message
13
+ include Serf::More::UuidFields
14
+
15
+ attribute :target, Object
16
+
17
+ validates_presence_of :target
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ require 'aequitas'
2
+ require 'serf/message'
3
+ require 'serf/more/uuid_fields'
4
+ require 'virtus'
5
+
6
+ module Moogle
7
+ module Events
8
+
9
+ ##
10
+ # Signals that a we made a webhook callback for a target_id, with the
11
+ # a reference to a message's origin.
12
+ #
13
+ class WebhookPingPushed
14
+ include Virtus
15
+ include Aequitas
16
+ include Serf::Message
17
+ include Serf::More::UuidFields
18
+
19
+ ##
20
+ # The target_id of the webhook recipient
21
+ attribute :target_id, Integer
22
+
23
+ ##
24
+ # A descriptive origin that describes how we can find more about
25
+ # what was posted. And why.
26
+ attribute :message_origin, String
27
+
28
+ ##
29
+ # The uri where we posted the callback
30
+ attribute :webhook_uri, String
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,96 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'serf/command'
3
+ require 'serf/util/error_handling'
4
+
5
+ require 'moogle/events/error'
6
+ require 'moogle/messages/notification'
7
+ require 'moogle/models'
8
+ require 'moogle/requests/push_blog_entry'
9
+ require 'moogle/requests/push_email'
10
+ require 'moogle/requests/push_facebook_action'
11
+ require 'moogle/requests/push_tweet'
12
+ require 'moogle/requests/push_webhook_ping'
13
+
14
+ module Moogle
15
+ module Handlers
16
+
17
+ ##
18
+ # Handler that accepts Moogle::Messges::Notifications and creates
19
+ # actual push requests to relay the notification to each found target.
20
+ #
21
+ class AcceptNotification
22
+ include Serf::Command
23
+ include Serf::Util::ErrorHandling
24
+
25
+ self.request_factory = Moogle::Messages::Notification
26
+
27
+ attr_reader :pusher_queue
28
+ attr_reader :default_options
29
+
30
+ DEFAULT_PUSH_OPTIONS = {
31
+ 'from' => 'no-reply@example.com'
32
+ }.freeze
33
+
34
+ def initialize
35
+ @pusher_queue = opts! :pusher_queue
36
+ @default_options = opts :default_push_options, DEFAULT_PUSH_OPTIONS
37
+ end
38
+
39
+ def call
40
+ return nil if request.receiver_refs.blank? || request.message_kind.blank?
41
+
42
+ # Get all targets whose links' kinds + receivers match.
43
+ model = opts :model, Moogle::Target
44
+ targets = model.all(
45
+ model.links.message_kind => request.message_kind,
46
+ model.links.receiver_ref => request.receiver_refs)
47
+
48
+ # Create a new push request for each target found.
49
+ # Push each request to the pusher queue, and errors are caught
50
+ # and pushed to the error channel.
51
+ targets.each do |target|
52
+ with_error_handling(
53
+ kind: 'moogle/handlers/accept_notification',
54
+ request: request.to_hash,
55
+ target_id: target.id) do
56
+ request_factory = request_factory_for target.type
57
+ push_data = [
58
+ request.attributes,
59
+ default_options,
60
+ target.options,
61
+ {
62
+ target_id: target.id,
63
+ message_origin: "#{request.message_kind}:#{request.uuid}:"
64
+ }
65
+ ].reduce(&:merge)
66
+ push_request = request_factory.build push_data
67
+ pusher_queue.push push_request.to_hash
68
+ end
69
+ end
70
+
71
+ return nil
72
+ rescue => e
73
+ e.extend Moogle::Error
74
+ raise e
75
+ end
76
+
77
+ def request_factory_for(target_type)
78
+ case target_type.to_s
79
+ when 'Moogle::BlogTarget'
80
+ Moogle::Requests::PushBlogEntry
81
+ when 'Moogle::EmailTarget'
82
+ Moogle::Requests::PushEmail
83
+ when 'Moogle::FacebookTarget'
84
+ Moogle::Requests::PushFacebookAction
85
+ when 'Moogle::TwitterTarget'
86
+ Moogle::Requests::PushTweet
87
+ when 'Moogle::WebhookTarget'
88
+ Moogle::Requests::PushWebhookPing
89
+ else
90
+ raise ArgumentError, "Unsupported Target #{target_type}"
91
+ end
92
+ end
93
+ end
94
+
95
+ end
96
+ end