contentful-scheduler-custom-build-john 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.
@@ -0,0 +1,30 @@
1
+ require 'contentful/webhook/listener'
2
+ require_relative 'queue'
3
+
4
+ module Contentful
5
+ module Scheduler
6
+ class Controller < ::Contentful::Webhook::Listener::Controllers::WebhookAware
7
+ def create
8
+ return unless webhook.entry?
9
+
10
+ logger.info "Queueing - Space: #{webhook.space_id} - Entry: #{webhook.id}"
11
+
12
+ Queue.instance(logger).update_or_create(webhook)
13
+ end
14
+ alias_method :save, :create
15
+ alias_method :auto_save, :create
16
+ alias_method :unarchive, :create
17
+
18
+ def delete
19
+ return unless webhook.entry?
20
+
21
+ logger.info "Unqueueing - Space: #{webhook.space_id} - Entry: #{webhook.id}"
22
+
23
+ Queue.instance(logger).remove(webhook)
24
+ end
25
+ alias_method :unpublish, :delete
26
+ alias_method :archive, :delete
27
+ alias_method :publish, :delete
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,102 @@
1
+ require_relative "tasks"
2
+ require 'chronic'
3
+ require 'contentful/webhook/listener'
4
+
5
+ module Contentful
6
+ module Scheduler
7
+ class Queue
8
+ @@instance = nil
9
+
10
+ attr_reader :config, :logger
11
+
12
+ def self.instance(logger = ::Contentful::Webhook::Listener::Support::NullLogger.new)
13
+ @@instance ||= new(logger)
14
+ end
15
+
16
+ def update_or_create(webhook)
17
+ return unless publishable?(webhook)
18
+ remove(webhook) if in_queue?(webhook)
19
+ return if already_published?(webhook)
20
+
21
+ success = Resque.enqueue_at(
22
+ publish_date(webhook),
23
+ ::Contentful::Scheduler::Tasks::Publish,
24
+ webhook.space_id,
25
+ webhook.id,
26
+ ::Contentful::Scheduler.config[:spaces][webhook.space_id][:management_token]
27
+ )
28
+
29
+ if success
30
+ logger.info "Webhook {id: #{webhook.id}, space_id: #{webhook.space_id}} successfully added to queue"
31
+ else
32
+ logger.warn "Webhook {id: #{webhook.id}, space_id: #{webhook.space_id}} couldn't be added to queue"
33
+ end
34
+ end
35
+
36
+ def remove(webhook)
37
+ return unless publishable?(webhook)
38
+ return unless in_queue?(webhook)
39
+
40
+ success = Resque.remove_delayed(
41
+ ::Contentful::Scheduler::Tasks::Publish,
42
+ webhook.space_id,
43
+ webhook.id,
44
+ ::Contentful::Scheduler.config[:management_token]
45
+ )
46
+
47
+ if success
48
+ logger.info "Webhook {id: #{webhook.id}, space_id: #{webhook.space_id}} successfully removed from queue"
49
+ else
50
+ logger.warn "Webhook {id: #{webhook.id}, space_id: #{webhook.space_id}} couldn't be removed from queue"
51
+ end
52
+ end
53
+
54
+ def publishable?(webhook)
55
+ return false unless spaces.key?(webhook.space_id)
56
+
57
+ if webhook_publish_field?(webhook)
58
+ return !webhook_publish_field(webhook).nil?
59
+ end
60
+
61
+ false
62
+ end
63
+
64
+ def already_published?(webhook)
65
+ return true if publish_date(webhook) < Time.now.utc
66
+
67
+ false
68
+ end
69
+
70
+ def in_queue?(webhook)
71
+ Resque.peek(::Contentful::Scheduler::Tasks::Publish, 0, -1).any? do |job|
72
+ job['args'][0] == webhook.space_id && job['args'][1] == webhook.id
73
+ end
74
+ end
75
+
76
+ def publish_date(webhook)
77
+ date_field = webhook_publish_field(webhook)
78
+ date_field = date_field[date_field.keys[0]] if date_field.is_a? Hash
79
+ Chronic.parse(date_field).utc
80
+ end
81
+
82
+ def spaces
83
+ config[:spaces]
84
+ end
85
+
86
+ def webhook_publish_field?(webhook)
87
+ webhook.fields.key?(spaces.fetch(webhook.space_id, {})[:publish_field])
88
+ end
89
+
90
+ def webhook_publish_field(webhook)
91
+ webhook.fields[spaces[webhook.space_id][:publish_field]]
92
+ end
93
+
94
+ private
95
+
96
+ def initialize(logger)
97
+ @config = ::Contentful::Scheduler.config
98
+ @logger = logger
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1 @@
1
+ require_relative 'tasks/publish'
@@ -0,0 +1,21 @@
1
+ require 'contentful/management'
2
+
3
+ module Contentful
4
+ module Scheduler
5
+ module Tasks
6
+ class Publish
7
+ @queue = :publish
8
+
9
+ def self.perform(space_id, entry_id, token)
10
+ client = ::Contentful::Management::Client.new(
11
+ token,
12
+ raise_errors: true,
13
+ application_name: 'contentful-scheduler',
14
+ application_version: Contentful::Scheduler::VERSION
15
+ )
16
+ client.entries.find(space_id, entry_id).publish
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module Contentful
2
+ module Scheduler
3
+ VERSION = "1.0"
4
+ end
5
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Contentful::Scheduler::Controller do
4
+ let(:server) { MockServer.new }
5
+ let(:logger) { Contentful::Webhook::Listener::Support::NullLogger.new }
6
+ let(:timeout) { 10 }
7
+ let(:headers) { {'X-Contentful-Topic' => '', 'X-Contentful-Webhook-Name' => 'SomeName'} }
8
+ let(:body) { {sys: { id: 'foo', space: { sys: { id: 'space_foo' } } }, fields: {} } }
9
+ let(:queue) { ::Contentful::Scheduler::Queue.instance }
10
+ subject { described_class.new server, logger, timeout }
11
+
12
+ describe 'events' do
13
+ [:create, :save, :auto_save, :unarchive].each do |event|
14
+ it "creates or updates webhook metadata in publish queue on #{event}" do
15
+ expect(queue).to receive(:update_or_create)
16
+
17
+ headers['X-Contentful-Topic'] = "ContentfulManagement.Entry.#{event}"
18
+ request = RequestDummy.new(headers, body)
19
+ subject.respond(request, MockResponse.new).join
20
+ end
21
+ end
22
+
23
+ [:delete, :unpublish, :archive, :publish].each do |event|
24
+ it "deletes webhook metadata in publish queue on #{event}" do
25
+ expect(queue).to receive(:remove)
26
+
27
+ headers['X-Contentful-Topic'] = "ContentfulManagement.Entry.#{event}"
28
+ request = RequestDummy.new(headers, body)
29
+ subject.respond(request, MockResponse.new).join
30
+ end
31
+ end
32
+
33
+ [:create, :save, :unarchive, :delete, :unpublish, :archive, :publish].each do |event|
34
+ ['Asset', 'ContentType'].each do |kind|
35
+ it "ignores #{kind} on #{event}" do
36
+ expect(queue).not_to receive(:remove)
37
+ expect(queue).not_to receive(:update_or_create)
38
+
39
+ headers['X-Contentful-Topic'] = "ContentfulManagement.#{kind}.#{event}"
40
+ request = RequestDummy.new(headers, body)
41
+ subject.respond(request, MockResponse.new).join
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,257 @@
1
+ require 'spec_helper'
2
+
3
+ class WebhookDouble
4
+ attr_reader :id, :space_id, :sys, :fields
5
+ def initialize(id, space_id, sys = {}, fields = {})
6
+ @id = id
7
+ @space_id = space_id
8
+ @sys = sys
9
+ @fields = fields
10
+ end
11
+ end
12
+
13
+ describe Contentful::Scheduler::Queue do
14
+ let(:config) {
15
+ {
16
+ logger: ::Contentful::Scheduler::DEFAULT_LOGGER,
17
+ endpoint: ::Contentful::Scheduler::DEFAULT_ENDPOINT,
18
+ port: ::Contentful::Scheduler::DEFAULT_PORT,
19
+ redis: {
20
+ host: 'localhost',
21
+ port: 12341,
22
+ password: 'foobar'
23
+ },
24
+ spaces: {
25
+ 'foo' => {
26
+ publish_field: 'my_field',
27
+ management_token: 'foo'
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ subject { described_class.instance }
34
+
35
+ before :each do
36
+ allow(Resque).to receive(:redis=)
37
+ described_class.class_variable_set(:@@instance, nil)
38
+ ::Contentful::Scheduler.config = config
39
+ end
40
+
41
+ describe 'singleton' do
42
+ it 'creates an instance if not initialized' do
43
+ queue = described_class.instance
44
+ expect(queue).to be_a described_class
45
+ end
46
+
47
+ it 'reuses same instance' do
48
+ queue = described_class.instance
49
+
50
+ expect(queue).to eq described_class.instance
51
+ end
52
+ end
53
+
54
+ describe 'attributes' do
55
+ it '.config' do
56
+ expect(subject.config).to eq config
57
+ end
58
+ end
59
+
60
+ describe 'instance methods' do
61
+ it '#spaces' do
62
+ expect(subject.spaces).to eq config[:spaces]
63
+ end
64
+
65
+ it '#webhook_publish_field?' do
66
+ expect(subject.webhook_publish_field?(
67
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => 'something'})
68
+ )).to be_truthy
69
+
70
+ expect(subject.webhook_publish_field?(
71
+ WebhookDouble.new('bar', 'foo', {}, {'not_my_field' => 'something'})
72
+ )).to be_falsey
73
+
74
+ expect(subject.webhook_publish_field?(
75
+ WebhookDouble.new('bar', 'not_foo', {}, {'not_my_field' => 'something'})
76
+ )).to be_falsey
77
+ end
78
+
79
+ it '#webhook_publish_field' do
80
+ expect(subject.webhook_publish_field(
81
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => 'something'})
82
+ )).to eq 'something'
83
+ end
84
+
85
+ describe '#publish_date' do
86
+ it 'works if date field not localized' do
87
+ expect(subject.publish_date(
88
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2011-04-04T22:00:00+00:00'})
89
+ )).to eq DateTime.new(2011, 4, 4, 22, 0, 0).to_time.utc
90
+ end
91
+
92
+ it 'works if date field localized by grabbing first available locale' do
93
+ expect(subject.publish_date(
94
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => {'en-US': '2011-04-04T22:00:00+00:00'}})
95
+ )).to eq DateTime.new(2011, 4, 4, 22, 0, 0).to_time.utc
96
+
97
+ expect(subject.publish_date(
98
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => {'en-CA': '2011-04-04T23:00:00Z'}})
99
+ )).to eq DateTime.new(2011, 4, 4, 23, 0, 0).to_time.utc
100
+ end
101
+ end
102
+
103
+ describe '#already_published?' do
104
+ it 'true if webhook publish_date is in past' do
105
+ expect(subject.already_published?(
106
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2011-04-04T22:00:00+00:00'})
107
+ )).to be_truthy
108
+ end
109
+
110
+ it 'true if sys.publishedAt is in past' do
111
+ expect(subject.already_published?(
112
+ WebhookDouble.new('bar', 'foo', {'publishedAt' => '2011-04-04T22:00:00+00:00'}, {'my_field' => '2099-04-04T22:00:00+00:00'})
113
+ )).to be_truthy
114
+ end
115
+
116
+ it 'false if sys.publishedAt is not present' do
117
+ expect(subject.already_published?(
118
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2099-04-04T22:00:00+00:00'})
119
+ )).to be_falsey
120
+ end
121
+
122
+ it 'false if sys.publishedAt is present but nil' do
123
+ expect(subject.already_published?(
124
+ WebhookDouble.new('bar', 'foo', {'publishedAt' => nil}, {'my_field' => '2099-04-04T22:00:00+00:00'})
125
+ )).to be_falsey
126
+ end
127
+ end
128
+
129
+ describe '#publishable?' do
130
+ it 'false if webhook space not present in config' do
131
+ expect(subject.publishable?(
132
+ WebhookDouble.new('bar', 'not_foo')
133
+ )).to be_falsey
134
+ end
135
+
136
+ it 'false if publish_field is not found' do
137
+ expect(subject.publishable?(
138
+ WebhookDouble.new('bar', 'foo')
139
+ )).to be_falsey
140
+ end
141
+
142
+ it 'false if publish_field is nil' do
143
+ expect(subject.publishable?(
144
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => nil})
145
+ )).to be_falsey
146
+ end
147
+
148
+ it 'true if publish_field is populated' do
149
+ expect(subject.publishable?(
150
+ WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2011-04-04T22:00:00+00:00'})
151
+ )).to be_truthy
152
+ end
153
+ end
154
+
155
+ describe '#in_queue?' do
156
+ it 'false if not in queue' do
157
+ allow(Resque).to receive(:peek) { [] }
158
+ expect(subject.in_queue?(
159
+ WebhookDouble.new('bar', 'foo')
160
+ )).to be_falsey
161
+ end
162
+
163
+ it 'true if in queue' do
164
+ allow(Resque).to receive(:peek) { [{'args' => ['foo', 'bar']}] }
165
+ expect(subject.in_queue?(
166
+ WebhookDouble.new('bar', 'foo')
167
+ )).to be_truthy
168
+ end
169
+ end
170
+
171
+ describe '#update_or_create' do
172
+ it 'does nothing if webhook is unpublishable' do
173
+ expect(Resque).not_to receive(:enqueue_at)
174
+
175
+ subject.update_or_create(WebhookDouble.new('bar', 'not_foo'))
176
+ end
177
+
178
+ describe 'webhook is new' do
179
+ it 'queues' do
180
+ mock_redis = Object.new
181
+ allow(mock_redis).to receive(:client) { mock_redis }
182
+ allow(mock_redis).to receive(:id) { 'foo' }
183
+ allow(Resque).to receive(:peek) { [] }
184
+ allow(Resque).to receive(:redis) { mock_redis }
185
+
186
+ expect(Resque).to receive(:enqueue_at).with(
187
+ DateTime.strptime('2099-04-04T22:00:00+00:00').to_time.utc,
188
+ ::Contentful::Scheduler::Tasks::Publish,
189
+ 'foo',
190
+ 'bar',
191
+ 'foo'
192
+ ) { true }
193
+
194
+ subject.update_or_create(WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2099-04-04T22:00:00+00:00'}))
195
+ end
196
+
197
+ it 'does nothing if already published' do
198
+ allow(Resque).to receive(:peek) { [] }
199
+ expect(Resque).not_to receive(:enqueue_at)
200
+
201
+ subject.update_or_create(WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2011-04-04T22:00:00+00:00'}))
202
+ end
203
+ end
204
+
205
+ describe 'webhook already in queue' do
206
+ it 'calls remove then queues again' do
207
+ mock_redis = Object.new
208
+ allow(mock_redis).to receive(:client) { mock_redis }
209
+ allow(mock_redis).to receive(:id) { 'foo' }
210
+ allow(Resque).to receive(:redis) { mock_redis }
211
+
212
+ allow(Resque).to receive(:peek) { [{'args' => ['foo', 'bar']}] }
213
+ expect(Resque).to receive(:enqueue_at).with(
214
+ DateTime.strptime('2099-04-04T22:00:00+00:00').to_time.utc,
215
+ ::Contentful::Scheduler::Tasks::Publish,
216
+ 'foo',
217
+ 'bar',
218
+ 'foo'
219
+ ) { true }
220
+ expect(subject).to receive(:remove)
221
+
222
+ subject.update_or_create(WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2099-04-04T22:00:00+00:00'}))
223
+ end
224
+
225
+ it 'removes old call if already published' do
226
+ allow(Resque).to receive(:peek) { [{'args' => ['foo', 'bar']}] }
227
+ expect(Resque).not_to receive(:enqueue_at)
228
+ expect(subject).to receive(:remove)
229
+
230
+ subject.update_or_create(WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2011-04-04T22:00:00+00:00'}))
231
+ end
232
+ end
233
+ end
234
+
235
+ describe '#remove' do
236
+ it 'does nothing if webhook is unpublishable' do
237
+ expect(Resque).not_to receive(:remove_delayed)
238
+
239
+ subject.remove(WebhookDouble.new('bar', 'foo'))
240
+ end
241
+
242
+ it 'does nothing if webhook not in queue' do
243
+ allow(Resque).to receive(:peek) { [] }
244
+ expect(Resque).not_to receive(:remove_delayed)
245
+
246
+ subject.remove(WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2011-04-04T22:00:00+00:00'}))
247
+ end
248
+
249
+ it 'removes if in queue' do
250
+ allow(Resque).to receive(:peek) { [{'args' => ['foo', 'bar']}] }
251
+ expect(Resque).to receive(:remove_delayed)
252
+
253
+ subject.remove(WebhookDouble.new('bar', 'foo', {}, {'my_field' => '2011-04-04T22:00:00+00:00'}))
254
+ end
255
+ end
256
+ end
257
+ end