hootenanny 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -7
- data/Gemfile.lock +46 -28
- data/app/controllers/hootenanny/notifications_controller.rb +31 -0
- data/app/controllers/hootenanny/parameters.rb +16 -0
- data/app/controllers/hootenanny/subscriptions_controller.rb +24 -3
- data/app/models/hootenanny/publish_notification.rb +90 -0
- data/app/models/hootenanny/subscription.rb +72 -23
- data/config/routes.rb +2 -1
- data/db/migrate/20130607182642_add_started_at_and_lease_duration_to_subscriptions.rb +15 -0
- data/db/migrate/20130608225621_add_hmac_secret_to_subscription.rb +5 -0
- data/db/migrate/20130611235218_add_publish_notifications.rb +9 -0
- data/db/migrate/20130612153138_add_timestamps_to_publish_notification.rb +5 -0
- data/db/migrate/20130705200729_add_processed_flag_to_publish_notifications.rb +6 -0
- data/db/migrate/20130711061329_switch_publish_notification_process_state_from_boolean_to_string.rb +11 -0
- data/db/migrate/20130711061558_add_index_to_notifications_state.rb +5 -0
- data/hootenanny.gemspec +6 -2
- data/lib/hootenanny/configuration.rb +43 -0
- data/lib/hootenanny/correspondent.rb +44 -0
- data/lib/hootenanny/errors.rb +42 -1
- data/lib/hootenanny/feed.rb +75 -0
- data/lib/hootenanny/feed/atom_feed.rb +18 -0
- data/lib/hootenanny/feed/atom_feed_item.rb +8 -0
- data/lib/hootenanny/feed/digest_feed.rb +14 -0
- data/lib/hootenanny/feed/digest_feed_item.rb +11 -0
- data/lib/hootenanny/feed/feed_item.rb +30 -0
- data/lib/hootenanny/feed/file.rb +66 -0
- data/lib/hootenanny/feed/json_feed.rb +48 -0
- data/lib/hootenanny/feed/json_feed_item.rb +8 -0
- data/lib/hootenanny/feed/null_feed.rb +27 -0
- data/lib/hootenanny/feed/rss_feed.rb +52 -0
- data/lib/hootenanny/feed/rss_feed_item.rb +8 -0
- data/lib/hootenanny/feed_store.rb +30 -0
- data/lib/hootenanny/feed_store/file_feed_store.rb +55 -0
- data/lib/hootenanny/feed_store/web_feed_store.rb +42 -0
- data/lib/hootenanny/hub.rb +116 -22
- data/lib/hootenanny/publish_notification_expiration_policy.rb +17 -0
- data/lib/hootenanny/request.rb +40 -0
- data/lib/hootenanny/request/publish_notification.rb +94 -0
- data/lib/hootenanny/request/subscription.rb +153 -0
- data/lib/hootenanny/subscription_delivery.rb +47 -0
- data/lib/hootenanny/topic.rb +38 -0
- data/lib/hootenanny/topic_synchronizer.rb +71 -0
- data/lib/hootenanny/uri.rb +53 -0
- data/lib/hootenanny/verification.rb +108 -0
- data/lib/hootenanny/version.rb +1 -1
- data/spec/dummy/config/database.yml +12 -6
- data/spec/dummy/db/migrate/20130607183149_add_started_at_and_lease_duration_to_subscriptions.hootenanny.rb +16 -0
- data/spec/dummy/db/migrate/20130608231253_add_hmac_secret_to_subscription.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130611235546_add_publish_notifications.hootenanny.rb +10 -0
- data/spec/dummy/db/migrate/20130612153353_add_timestamps_to_publish_notification.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130705200832_add_processed_flag_to_publish_notifications.hootenanny.rb +7 -0
- data/spec/dummy/db/migrate/20130711061518_switch_publish_notification_process_state_from_boolean_to_string.hootenanny.rb +12 -0
- data/spec/dummy/db/migrate/20130711061629_add_index_to_notifications_state.hootenanny.rb +6 -0
- data/spec/factories/publish_notification.rb +13 -0
- data/spec/factories/requests/publish_notification.rb +11 -0
- data/spec/factories/requests/subscription.rb +46 -0
- data/spec/factories/subscription.rb +14 -2
- data/spec/factories/verification.rb +15 -0
- data/spec/features/publishers/can_notify_the_hub_of_content_updates_spec.rb +58 -0
- data/spec/features/subscribers/are_protected_from_unwarranted_subscriptions_spec.rb +59 -0
- data/spec/features/subscribers/can_receive_distributions_of_topic_content_spec.rb +175 -0
- data/spec/features/subscribers/can_subscribe_to_a_topic_spec.rb +76 -7
- data/spec/fixtures/feeds/atom/97d79220f68b4bf27.atom +0 -0
- data/spec/fixtures/feeds/atom/sample_feed.atom +51 -0
- data/spec/fixtures/feeds/digest/97d79220f68b4bf27.digest +1 -0
- data/spec/fixtures/feeds/digest/complete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +6 -0
- data/spec/fixtures/feeds/digest/incomplete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +5 -0
- data/spec/fixtures/feeds/digest/sample_feed.digest +6 -0
- data/spec/fixtures/feeds/json/97d79220f68b4bf27.json +1 -0
- data/spec/fixtures/feeds/json/feed_with_one_item.json +21 -0
- data/spec/fixtures/feeds/json/sample_feed.json +30 -0
- data/spec/fixtures/feeds/rss/5b187098da59f077f/97d79220f68b4bf27.rss +21 -0
- data/spec/fixtures/feeds/rss/97d79220f68b4bf27.rss +0 -0
- data/spec/fixtures/feeds/rss/feed_with_one_item.rss +15 -0
- data/spec/fixtures/feeds/rss/minimal_feed.rss +12 -0
- data/spec/fixtures/feeds/rss/sample_feed.rss +22 -0
- data/spec/fixtures/feeds/rss/sample_feed_2.rss +22 -0
- data/spec/lib/hootenanny/configuration_spec.rb +7 -0
- data/spec/lib/hootenanny/correspondent_spec.rb +94 -0
- data/spec/lib/hootenanny/errors_spec.rb +21 -0
- data/spec/lib/hootenanny/feed/atom_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/atom_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/digest_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/digest_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/feed_item_spec.rb +49 -0
- data/spec/lib/hootenanny/feed/file_spec.rb +66 -0
- data/spec/lib/hootenanny/feed/json_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/json_feed_spec.rb +128 -0
- data/spec/lib/hootenanny/feed/null_feed_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_spec.rb +143 -0
- data/spec/lib/hootenanny/feed_spec.rb +159 -0
- data/spec/lib/hootenanny/feed_store/file_feed_store_spec.rb +58 -0
- data/spec/lib/hootenanny/feed_store/web_feed_store_spec.rb +47 -0
- data/spec/lib/hootenanny/feed_store_spec.rb +27 -0
- data/spec/lib/hootenanny/hub_spec.rb +73 -0
- data/spec/lib/hootenanny/publish_notification_expiration_policy_spec.rb +35 -0
- data/spec/lib/hootenanny/request/publish_notification_spec.rb +43 -0
- data/spec/lib/hootenanny/request/subscription_spec.rb +89 -0
- data/spec/lib/hootenanny/request_spec.rb +21 -0
- data/spec/lib/hootenanny/subscription_delivery_spec.rb +54 -0
- data/spec/lib/hootenanny/topic_spec.rb +15 -0
- data/spec/lib/hootenanny/topic_synchronizer_spec.rb +98 -0
- data/spec/lib/hootenanny/uri_spec.rb +32 -0
- data/spec/lib/hootenanny/verification_spec.rb +92 -0
- data/spec/models/hootenanny/publish_notification_spec.rb +55 -0
- data/spec/models/hootenanny/subscription_spec.rb +58 -19
- data/spec/support/verification.rb +63 -0
- metadata +231 -14
- data/spec/controllers/hootenanny/hub_spec.rb +0 -15
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'hootenanny/feed'
|
3
|
+
require 'hootenanny/feed/json_feed_item'
|
4
|
+
|
5
|
+
module Hootenanny
|
6
|
+
class Feed
|
7
|
+
class JSONFeed < ::Hootenanny::Feed
|
8
|
+
|
9
|
+
def items
|
10
|
+
@items ||= content.fetch('items', []).map { |i| Hootenanny::Feed::JSONFeedItem.new i }
|
11
|
+
end
|
12
|
+
|
13
|
+
def content_type
|
14
|
+
'application/json'
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@to_s ||= -> do
|
19
|
+
content['items'] = items.map(&:content)
|
20
|
+
|
21
|
+
JSON.dump(content)
|
22
|
+
end.call
|
23
|
+
end
|
24
|
+
|
25
|
+
def content
|
26
|
+
@content
|
27
|
+
end
|
28
|
+
|
29
|
+
def content=(content)
|
30
|
+
@content = coerce_content(content)
|
31
|
+
rescue JSON::ParserError => e
|
32
|
+
raise Hootenanny::Feed::ParseError.wrap(e)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def coerce_content(content)
|
38
|
+
if content.is_a? String
|
39
|
+
JSON.parse(content)
|
40
|
+
elsif content.is_a? Hash
|
41
|
+
content.inject({}) { |hash, pair| hash[pair[0].to_s] = pair[1]; hash }
|
42
|
+
else
|
43
|
+
raise JSON::ParserError.new("Do not know how to parse '#{content}'")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Hootenanny
|
2
|
+
class Feed
|
3
|
+
class NullFeed < ::Hootenanny::Feed
|
4
|
+
|
5
|
+
def items
|
6
|
+
[]
|
7
|
+
end
|
8
|
+
|
9
|
+
def content_type
|
10
|
+
''
|
11
|
+
end
|
12
|
+
|
13
|
+
def -(other)
|
14
|
+
other
|
15
|
+
end
|
16
|
+
|
17
|
+
def +(other)
|
18
|
+
other
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def content=(content)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rss'
|
2
|
+
require 'hootenanny/feed'
|
3
|
+
require 'hootenanny/feed/rss_feed_item'
|
4
|
+
|
5
|
+
module Hootenanny
|
6
|
+
class Feed
|
7
|
+
class RSSFeed < ::Hootenanny::Feed
|
8
|
+
|
9
|
+
def items
|
10
|
+
@items ||= content.items.map { |i| Hootenanny::Feed::RSSFeedItem.new i }
|
11
|
+
end
|
12
|
+
|
13
|
+
def content_type
|
14
|
+
'application/rss+xml'
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@to_s ||= -> do
|
19
|
+
item_string = items.map { |item| item.content.to_s }.join('')
|
20
|
+
|
21
|
+
feed_string = content.to_s
|
22
|
+
feed_string.gsub!(%r{<item>.*</item>}m, item_string)
|
23
|
+
|
24
|
+
feed_string
|
25
|
+
end.call
|
26
|
+
end
|
27
|
+
|
28
|
+
def content
|
29
|
+
@content
|
30
|
+
end
|
31
|
+
|
32
|
+
def content=(content)
|
33
|
+
@content = coerce_content(content)
|
34
|
+
rescue RSS::Error => e
|
35
|
+
raise Hootenanny::Feed::ParseError.wrap(e)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def coerce_content(content)
|
41
|
+
if content.is_a? String
|
42
|
+
RSS::Parser.parse(content) ||
|
43
|
+
RSS::Rss.new('2.0')
|
44
|
+
elsif content.is_a? RSS::Rss
|
45
|
+
content
|
46
|
+
else
|
47
|
+
raise RSS::Error.new("Do not know how to parse '#{content}'")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'hootenanny/feed_store/file_feed_store'
|
3
|
+
require 'hootenanny/feed_store/web_feed_store'
|
4
|
+
|
5
|
+
module Hootenanny
|
6
|
+
class FeedStore
|
7
|
+
|
8
|
+
def self.fetch(url, options = {})
|
9
|
+
storage_type = options.fetch(:location, :web)
|
10
|
+
feed_store = infer(type: storage_type)
|
11
|
+
|
12
|
+
feed_store.fetch(url)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.infer(options = {})
|
16
|
+
storage_type = options.fetch(:type)
|
17
|
+
storage_class = from_type(storage_type)
|
18
|
+
|
19
|
+
storage_class.new(options)
|
20
|
+
rescue KeyError => e
|
21
|
+
raise Hootenanny::FeedStore::InferenceError.wrap(e)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_type(type)
|
25
|
+
"Hootenanny::FeedStore::#{type.to_s.camelize}FeedStore".constantize
|
26
|
+
rescue NameError
|
27
|
+
raise Hootenanny::FeedStore::UnknownStorageTypeError
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'hootenanny/uri'
|
3
|
+
require 'hootenanny/feed'
|
4
|
+
require 'hootenanny/feed/file'
|
5
|
+
|
6
|
+
module Hootenanny
|
7
|
+
class FeedStore
|
8
|
+
class FileFeedStore
|
9
|
+
def initialize(options = {})
|
10
|
+
self.feed_file = options.fetch(:location)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch(options = {})
|
14
|
+
self.url = options.fetch(:url)
|
15
|
+
feed_file = self.feed_file << options[:path] << url_file_glob
|
16
|
+
|
17
|
+
Hootenanny::Feed.infer(feed_file.read,
|
18
|
+
type: feed_file.type)
|
19
|
+
end
|
20
|
+
|
21
|
+
def store(options = {})
|
22
|
+
self.url = options.fetch(:url)
|
23
|
+
feed = options.fetch(:feed)
|
24
|
+
feed_type = feed.type
|
25
|
+
|
26
|
+
feed_file = self.feed_file << options[:path] << "#{url}.#{feed_type}"
|
27
|
+
|
28
|
+
feed_file.write(feed)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
attr_reader :feed_file,
|
34
|
+
:url
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def url_file_glob
|
39
|
+
if @url.nil? || @url.to_s == ''
|
40
|
+
'*.*'
|
41
|
+
else
|
42
|
+
"#{@url}.*"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def url=(other_url)
|
47
|
+
@url = Hootenanny::URI.parse(other_url.to_s).to_digest
|
48
|
+
end
|
49
|
+
|
50
|
+
def feed_file=(location)
|
51
|
+
@feed_file = Hootenanny::Feed::File.new(location)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'hootenanny/feed'
|
3
|
+
|
4
|
+
module Hootenanny
|
5
|
+
class FeedStore
|
6
|
+
class WebFeedStore
|
7
|
+
CONTENT_TYPE_MAPPINGS = {
|
8
|
+
'application/rss+xml' => 'RSS',
|
9
|
+
'application/atom+xml' => 'Atom',
|
10
|
+
'application/json' => 'JSON',
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(options = {})
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch(options = {})
|
17
|
+
self.url = options.fetch(:url)
|
18
|
+
|
19
|
+
Hootenanny::Feed.infer(content,
|
20
|
+
type: feed_serialization_type)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_accessor :url
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def content
|
30
|
+
response.body
|
31
|
+
end
|
32
|
+
|
33
|
+
def feed_serialization_type
|
34
|
+
CONTENT_TYPE_MAPPINGS.fetch(response.env[:response_headers][:content_type])
|
35
|
+
end
|
36
|
+
|
37
|
+
def response
|
38
|
+
@response ||= Faraday.get(url.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/hootenanny/hub.rb
CHANGED
@@ -1,41 +1,135 @@
|
|
1
|
-
require 'hootenanny/
|
1
|
+
require 'hootenanny/request'
|
2
|
+
require 'hootenanny/correspondent'
|
3
|
+
require 'hootenanny/publish_notification'
|
4
|
+
require 'hootenanny/publish_notification_expiration_policy'
|
2
5
|
|
6
|
+
###
|
7
|
+
# Public: The class that provides the interface for all of the PubSubHubbub
|
8
|
+
# server functionaliity.
|
9
|
+
#
|
10
|
+
# ## Subscribing to a Feed ##
|
11
|
+
#
|
12
|
+
# In order to subscribe to a topic URI, the subscription request must be passed
|
13
|
+
# in. This object contains all of the information necessary to actually apply
|
14
|
+
# the subscription between the subscriber URI and the topic URI.
|
15
|
+
#
|
16
|
+
# ```ruby
|
17
|
+
# request = Hootenanny::Hub.request(request_attribute_hash)
|
18
|
+
#
|
19
|
+
# Hootenanny::Hub.subscribe(request)
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# ## Generating a Request ##
|
23
|
+
#
|
24
|
+
# Before an action may be taken, the proper request must be generated from all
|
25
|
+
# of the disparate information. Once generated, it can be passed to other
|
26
|
+
# methods on the class to perform that action.
|
27
|
+
#
|
28
|
+
# `:type` and `:action` are required options for all requests, other requests
|
29
|
+
# may have additional required options, depending on the `:type` passed in. For
|
30
|
+
# example, a subscription request requires a `:callback` and an `:action`.
|
31
|
+
#
|
32
|
+
# ```ruby
|
33
|
+
# Hootenanny::Hub.request(type: :subscription,
|
34
|
+
# action: :create,
|
35
|
+
# callback: 'http://example.com/callback',
|
36
|
+
# topic: 'http://example.org/topic')
|
37
|
+
# ```
|
38
|
+
#
|
39
|
+
# ## Notifying of a Content Update
|
40
|
+
#
|
41
|
+
# In order to remove the need for the hub to have to poll each of its
|
42
|
+
# publishers, each publisher can create a notification on the hub so that the
|
43
|
+
# hub can handle disseminating that information to its various subscribers.
|
44
|
+
#
|
45
|
+
# ```ruby
|
46
|
+
# Hootenanny::Hub.notify_of_publication(url: 'http://mytopic')
|
47
|
+
# ```
|
48
|
+
#
|
3
49
|
module Hootenanny
|
4
50
|
class Hub
|
5
51
|
|
6
52
|
###
|
7
|
-
# Public: Subscribes
|
8
|
-
#
|
9
|
-
# URL.
|
53
|
+
# Public: Subscribes a subscriber to a topic based on the information defined
|
54
|
+
# in a subscription request.
|
10
55
|
#
|
11
56
|
# This method is idempotent. If a subscription already exists, it will be
|
12
57
|
# reused and sent back without notifying the user of that fact.
|
13
58
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# to this callback URI.
|
18
|
-
# options - A Hash of options
|
59
|
+
# If the subscription does not already exist, it will be verified against the
|
60
|
+
# callback URL. If all is ok, a subscription will be created, if not, an
|
61
|
+
# error with details will be returned.
|
19
62
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# activities for a given topic/item/resource.
|
63
|
+
# request - Any object which responds to #apply. Typically this should be
|
64
|
+
# a Hootenanny::Request::Subscription.
|
23
65
|
#
|
24
|
-
#
|
25
|
-
#
|
66
|
+
# Returns an indeterminate object (based on the request passed in) but is
|
67
|
+
# typically a Hootenanny::Subscription.
|
68
|
+
# Raises Hootenanny::SubscriptionAssignmentError with a message describing the
|
69
|
+
# problem
|
70
|
+
#
|
71
|
+
def self.subscribe(request)
|
72
|
+
request.apply
|
73
|
+
end
|
74
|
+
|
75
|
+
###
|
76
|
+
# Public: Notifies the hub that topic has had its content updated since the
|
77
|
+
# last time the content was pulled.
|
26
78
|
#
|
27
|
-
#
|
79
|
+
# This method is idempotent. If a notification already exists, it will be
|
80
|
+
# reused and sent back without notifying the user of that fact.
|
28
81
|
#
|
29
|
-
#
|
30
|
-
#
|
82
|
+
# If all is ok, a notification will be created, if not, an error with details
|
83
|
+
# will be returned.
|
84
|
+
#
|
85
|
+
# request - Any object which responds to #apply. Typically this should be
|
86
|
+
# a Hootenanny::Request::PublishNotification.
|
87
|
+
#
|
88
|
+
# Returns an indeterminate object (based on the request passed in) but is
|
89
|
+
# typically a Hootenanny::PublishNotification.
|
90
|
+
# Raises Hootenanny::PublishNotificationError with a message describing the
|
31
91
|
# problem
|
32
92
|
#
|
33
|
-
def self.
|
34
|
-
|
35
|
-
|
93
|
+
def self.notify_of_publication(request)
|
94
|
+
request.apply
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.broadcast
|
98
|
+
Hootenanny::PublishNotification.each_unprocessed do |notification|
|
99
|
+
if Hootenanny::Correspondent.broadcast(notification.topic)
|
100
|
+
notification.process
|
101
|
+
else
|
102
|
+
Hootenanny::PublishNotificationExpirationPolicy.apply notification
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
36
106
|
|
37
|
-
|
38
|
-
|
107
|
+
###
|
108
|
+
# Public: Generates a request of varying types which can be used to perform
|
109
|
+
# actions such as subscribing to a topic.
|
110
|
+
#
|
111
|
+
# This method has zero side-effects and is only utilized to prepare a request.
|
112
|
+
#
|
113
|
+
# options - A Hash of options which can vary based on the type of request that
|
114
|
+
# is being... requested.
|
115
|
+
#
|
116
|
+
# :type - The type of request (eg: :subscription)
|
117
|
+
# :action - The action to be performed when the request is
|
118
|
+
# applied. This can vary based on the type of request
|
119
|
+
# being created.
|
120
|
+
# :callback - Used for :subscription requests. Is a URI which
|
121
|
+
# should be notified when the topic that it is
|
122
|
+
# interested in, is updated.
|
123
|
+
# :topic - Used for :subscription requests. Is a URI which
|
124
|
+
# represents all of the items which the subscriber is
|
125
|
+
# interested in.
|
126
|
+
#
|
127
|
+
# Returns an indeterminate object (based on the type of request that is
|
128
|
+
# generated, but all of them will inherit from Hootenanny::Request
|
129
|
+
# Raises a Hootenanny::Request::BuildError if any problems are encountered
|
130
|
+
#
|
131
|
+
def self.request(options = {})
|
132
|
+
Hootenanny::Request.build(options)
|
39
133
|
end
|
40
134
|
end
|
41
135
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'hootenanny/configuration'
|
2
|
+
|
3
|
+
module Hootenanny
|
4
|
+
class PublishNotificationExpirationPolicy
|
5
|
+
def self.apply(notification)
|
6
|
+
if notification.updated_at < expiration_threshold
|
7
|
+
notification.expire
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def self.expiration_threshold
|
14
|
+
Hootenanny.config.default_publish_notification_expiration_threshold
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|