contentful-scheduler-custom-build-john 1.0

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