moogle 0.1.0

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