flapjack 0.8.10 → 0.8.11

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile +1 -1
  4. data/bin/flapjack +10 -1
  5. data/bin/flapjack-nagios-receiver +1 -2
  6. data/bin/simulate-failed-check +12 -4
  7. data/etc/flapjack_config.yaml.example +2 -1
  8. data/flapjack.gemspec +1 -0
  9. data/lib/flapjack/data/contact.rb +46 -26
  10. data/lib/flapjack/data/entity.rb +28 -0
  11. data/lib/flapjack/data/entity_check.rb +52 -11
  12. data/lib/flapjack/data/event.rb +9 -3
  13. data/lib/flapjack/data/notification_rule.rb +8 -0
  14. data/lib/flapjack/gateways/api.rb +0 -1
  15. data/lib/flapjack/gateways/api/entity_check_presenter.rb +2 -1
  16. data/lib/flapjack/gateways/email.rb +1 -2
  17. data/lib/flapjack/gateways/jabber.rb +3 -3
  18. data/lib/flapjack/gateways/jsonapi.rb +186 -38
  19. data/lib/flapjack/gateways/jsonapi/check_methods.rb +120 -0
  20. data/lib/flapjack/gateways/jsonapi/{entity_check_presenter.rb → check_presenter.rb} +7 -6
  21. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +61 -352
  22. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +117 -248
  23. data/lib/flapjack/gateways/jsonapi/medium_methods.rb +179 -0
  24. data/lib/flapjack/gateways/jsonapi/notification_rule_methods.rb +124 -0
  25. data/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods.rb +128 -0
  26. data/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +4 -5
  27. data/lib/flapjack/gateways/jsonapi/report_methods.rb +143 -0
  28. data/lib/flapjack/gateways/web.rb +1 -0
  29. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +165 -101
  30. data/lib/flapjack/gateways/web/public/js/contacts.js +34 -46
  31. data/lib/flapjack/gateways/web/public/js/select2.js +232 -90
  32. data/lib/flapjack/gateways/web/public/js/select2.min.js +4 -4
  33. data/lib/flapjack/gateways/web/views/check.html.erb +11 -2
  34. data/lib/flapjack/processor.rb +6 -6
  35. data/lib/flapjack/version.rb +1 -1
  36. data/spec/lib/flapjack/data/entity_check_spec.rb +1 -1
  37. data/spec/lib/flapjack/data/event_spec.rb +10 -9
  38. data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +25 -25
  39. data/spec/lib/flapjack/gateways/api_spec.rb +23 -1
  40. data/spec/lib/flapjack/gateways/email_spec.rb +40 -2
  41. data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -1
  42. data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +134 -0
  43. data/spec/lib/flapjack/gateways/jsonapi/{entity_check_presenter_spec.rb → check_presenter_spec.rb} +17 -17
  44. data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +27 -232
  45. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +217 -687
  46. data/spec/lib/flapjack/gateways/jsonapi/medium_methods_spec.rb +232 -0
  47. data/spec/lib/flapjack/gateways/jsonapi/notification_rule_methods_spec.rb +131 -0
  48. data/spec/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods_spec.rb +113 -0
  49. data/spec/lib/flapjack/gateways/jsonapi/report_methods_spec.rb +546 -0
  50. data/spec/lib/flapjack/gateways/jsonapi_spec.rb +10 -1
  51. data/spec/lib/flapjack/gateways/web_spec.rb +1 -0
  52. data/spec/support/jsonapi_helper.rb +62 -0
  53. metadata +36 -8
  54. data/lib/flapjack/gateways/jsonapi/entity_presenter.rb +0 -75
  55. data/spec/lib/flapjack/gateways/jsonapi/entity_presenter_spec.rb +0 -108
@@ -5,9 +5,6 @@ require 'sinatra/base'
5
5
  require 'flapjack/data/entity'
6
6
  require 'flapjack/data/entity_check'
7
7
 
8
- require 'flapjack/gateways/jsonapi/entity_presenter'
9
- require 'flapjack/gateways/jsonapi/entity_check_presenter'
10
-
11
8
  module Flapjack
12
9
 
13
10
  module Gateways
@@ -18,297 +15,169 @@ module Flapjack
18
15
 
19
16
  module Helpers
20
17
 
21
- def find_entity(entity_name)
22
- entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis)
23
- raise Flapjack::Gateways::JSONAPI::EntityNotFound.new(entity_name) if entity.nil?
24
- entity
25
- end
26
-
27
- def find_entity_check(entity, check)
28
- entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
29
- check, :redis => redis)
30
- raise Flapjack::Gateways::JSONAPI::EntityCheckNotFound.new(entity, check) if entity_check.nil?
31
- entity_check
32
- end
33
-
34
- def find_tags(tags)
35
- halt err(403, "no tags") if tags.nil? || tags.empty?
36
- tags
37
- end
38
-
39
- def entities_and_checks(entity_name, check)
40
- if entity_name
41
- # backwards-compatible, single entity or entity&check from route
42
- entities = check ? nil : [entity_name]
43
- checks = check ? {entity_name => check} : nil
44
- else
45
- # new and improved bulk API queries
46
- entities = params[:entity]
47
- checks = params[:check]
48
- entities = [entities] unless entities.nil? || entities.is_a?(Array)
49
- # TODO err if checks isn't a Hash (similar rules as in flapjack-diner)
50
- end
51
- [entities, checks]
52
- end
53
-
54
- def bulk_api_check_action(entities, entity_checks, action, params = {})
55
- unless entities.nil? || entities.empty?
56
- entities.each do |entity_name|
57
- entity = find_entity(entity_name)
58
- checks = entity.check_list.sort
59
- checks.each do |check|
60
- action.call( find_entity_check(entity, check) )
61
- end
62
- end
63
- end
64
-
65
- unless entity_checks.nil? || entity_checks.empty?
66
- entity_checks.each_pair do |entity_name, checks|
67
- entity = find_entity(entity_name)
68
- checks = [checks] unless checks.is_a?(Array)
69
- checks.each do |check|
70
- action.call( find_entity_check(entity, check) )
71
- end
18
+ def checks_for_entity_ids(entity_ids)
19
+ return if entity_ids.nil?
20
+ entity_ids.inject([]) do |memo, entity_id|
21
+ entity = find_entity_by_id(entity_id)
22
+ check_names = entity.check_list.sort
23
+ check_names.each do |check_name|
24
+ memo << find_entity_check(entity, check_name)
72
25
  end
26
+ memo
73
27
  end
74
28
  end
29
+ end
75
30
 
76
- def present_api_results(entities, entity_checks, result_type, &block)
77
- result = []
31
+ def self.registered(app)
32
+ app.helpers Flapjack::Gateways::JSONAPI::Helpers
33
+ app.helpers Flapjack::Gateways::JSONAPI::EntityMethods::Helpers
78
34
 
79
- unless entities.nil? || entities.empty?
80
- result += entities.collect {|entity_name|
81
- entity = find_entity(entity_name)
82
- yield(Flapjack::Gateways::JSONAPI::EntityPresenter.new(entity, :redis => redis))
83
- }.flatten(1)
35
+ # Returns all (/entities) or some (/entities/A,B,C) or one (/entities/A) contact(s)
36
+ # NB: only works with good data -- i.e. entity must have an id
37
+ app.get %r{^/entities(?:/)?([^/]+)?$} do
38
+ requested_entities = if params[:captures] && params[:captures][0]
39
+ params[:captures][0].split(',').uniq
40
+ else
41
+ nil
84
42
  end
85
43
 
86
- unless entity_checks.nil? || entity_checks.empty?
87
- result += entity_checks.inject([]) {|memo, (entity_name, checks)|
88
- checks = [checks] unless checks.is_a?(Array)
89
- entity = find_entity(entity_name)
90
- memo += checks.collect {|check|
91
- entity_check = find_entity_check(entity, check)
92
- {:entity => entity_name,
93
- :check => check,
94
- result_type.to_sym => yield(Flapjack::Gateways::JSONAPI::EntityCheckPresenter.new(entity_check))
95
- }
96
- }
97
- }.flatten(1)
44
+ entities = if requested_entities
45
+ Flapjack::Data::Entity.find_by_ids(requested_entities, :logger => logger, :redis => redis)
46
+ else
47
+ Flapjack::Data::Entity.all(:redis => redis)
98
48
  end
49
+ entities.compact!
99
50
 
100
- result
101
- end
102
-
103
- # NB: casts to UTC before converting to a timestamp
104
- def validate_and_parsetime(value)
105
- return unless value
106
- Time.iso8601(value).getutc.to_i
107
- rescue ArgumentError => e
108
- logger.error "Couldn't parse time from '#{value}'"
109
- nil
110
- end
111
-
112
- end
113
-
114
- # used for backwards-compatible route matching below
115
- ENTITY_CHECK_FRAGMENT = '(?:/([a-zA-Z0-9][a-zA-Z0-9\.\-]*[a-zA-Z0-9])(?:/(.+))?)?'
116
-
117
- def self.registered(app)
118
-
119
- app.helpers Flapjack::Gateways::JSONAPI::EntityMethods::Helpers
51
+ if requested_entities && requested_entities.empty?
52
+ raise Flapjack::Gateways::JSONAPI::EntitiesNotFound.new(requested_entities)
53
+ end
120
54
 
121
- app.get '/entities' do
122
- content_type :json
123
- cors_headers
55
+ linked_contact_ids = Flapjack::Data::Entity.contact_ids_for(entities.map(&:id), :redis => redis)
124
56
 
125
- entities_json = Flapjack::Data::Entity.all(:redis => redis).collect {|e|
126
- presenter = Flapjack::Gateways::JSONAPI::EntityPresenter.new(e, :redis => redis)
127
- id = (e.id.respond_to?(:length) && e.id.length > 0) ? e.id : e.name
128
- {'id' => id, 'name' => e.name, 'checks' => presenter.status }.to_json
129
- }.join(',')
57
+ entities_json = entities.collect {|entity|
58
+ entity.to_jsonapi(:contact_ids => linked_contact_ids[entity.id])
59
+ }.join(",")
130
60
 
131
61
  '{"entities":[' + entities_json + ']}'
132
62
  end
133
63
 
64
+ app.post '/entities' do
65
+ entities = wrapped_params('entities')
66
+ return err(403, "Entity with a nil id detected") if entities.any? {|e| e['id'].nil?}
134
67
 
135
- app.get %r{/status#{ENTITY_CHECK_FRAGMENT}} do
136
- content_type :json
137
-
138
- captures = params[:captures] || []
139
- entity_name = captures[0]
140
- check = captures[1]
141
-
142
- entities, checks = entities_and_checks(entity_name, check)
143
-
144
- results = present_api_results(entities, checks, 'status') {|presenter|
145
- presenter.status
68
+ entity_ids = entities.collect{|entity_data|
69
+ Flapjack::Data::Entity.add(entity_data, :redis => redis)
70
+ entity_data['id']
146
71
  }
147
72
 
148
- if entity_name
149
- # compatible with previous data format
150
- results = results.collect {|status_h| status_h[:status]}
151
- check ? results.first.to_json : "[" + results.map {|r| r.to_json }.join(',') + "]"
152
- else
153
- # new and improved data format which reflects the request param structure
154
- "[" + results.map {|r| r.to_json }.join(',') + "]"
155
- end
73
+ response.headers['Location'] = "#{request.base_url}/entities/#{entity_ids.join(',')}"
74
+ status 201
75
+ entity_ids.to_json
156
76
  end
157
77
 
158
- app.get %r{/((?:outages|(?:un)?scheduled_maintenances|downtime))#{ENTITY_CHECK_FRAGMENT}} do
159
- action = params[:captures][0].to_sym
160
- entity_name = params[:captures][1]
161
- check = params[:captures][2]
162
-
163
- entities, checks = entities_and_checks(entity_name, check)
164
-
165
- start_time = validate_and_parsetime(params[:start_time])
166
- end_time = validate_and_parsetime(params[:end_time])
167
-
168
- results = present_api_results(entities, checks, action) {|presenter|
169
- presenter.send(action, start_time, end_time)
170
- }
171
-
172
- if check
173
- # compatible with previous data format
174
- results.first[action].to_json
175
- elsif entity_name
176
- # compatible with previous data format
177
- rename = {:unscheduled_maintenances => :unscheduled_maintenance,
178
- :scheduled_maintenances => :scheduled_maintenance}
179
- drop = [:entity]
180
- results.collect{|r|
181
- r.inject({}) {|memo, (k, v)|
182
- if new_k = rename[k]
183
- memo[new_k] = v
184
- elsif !drop.include?(k)
185
- memo[k] = v
78
+ app.patch '/entities/:id' do
79
+ params[:id].split(',').collect {|entity_id|
80
+ find_entity_by_id(entity_id)
81
+ }.each do |entity|
82
+ apply_json_patch('entities') do |op, property, linked, value|
83
+ case op
84
+ when 'replace'
85
+ if ['name'].include?(property)
86
+ # # Name change not supported in Flapjack v1.x, too many changes required
87
+ # entity.update(property => value)
186
88
  end
187
- memo
188
- }
189
- }.to_json
190
- else
191
- # new and improved data format which reflects the request param structure
192
- results.to_json
89
+ when 'add'
90
+ if 'contacts'.eql?(linked)
91
+ contact = Flapjack::Data::Contact.find_by_id(value, :redis => redis)
92
+ contact.add_entity(entity) unless contact.nil?
93
+ end
94
+ when 'remove'
95
+ if 'contacts'.eql?(linked)
96
+ contact = Flapjack::Data::Contact.find_by_id(value, :redis => redis)
97
+ contact.remove_entity(entity) unless contact.nil?
98
+ end
99
+ end
100
+ end
193
101
  end
102
+
103
+ status 204
194
104
  end
195
105
 
196
106
  # create a scheduled maintenance period for a check on an entity
197
- app.post %r{/scheduled_maintenances#{ENTITY_CHECK_FRAGMENT}} do
198
-
199
- captures = params[:captures] || []
200
- entity_name = captures[0]
201
- check = captures[1]
202
-
203
- entities, checks = entities_and_checks(entity_name, check)
204
-
205
- start_time = validate_and_parsetime(params[:start_time])
206
- halt( err(403, "start time must be provided") ) unless start_time
207
-
208
- act_proc = proc {|entity_check|
209
- entity_check.create_scheduled_maintenance(start_time,
210
- params[:duration].to_i, :summary => params[:summary])
211
- }
107
+ app.post %r{^/scheduled_maintenances/entities/([^/]+)$} do
108
+ scheduled_maintenances = wrapped_params('scheduled_maintenances')
109
+ checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
110
+ scheduled_maintenances.each do |wp|
111
+ start_time = validate_and_parsetime(wp['start_time'])
112
+ halt( err(403, "start time must be provided") ) unless start_time
113
+ check.create_scheduled_maintenance(start_time,
114
+ wp['duration'].to_i, :summary => wp['summary'])
115
+ end
116
+ end
212
117
 
213
- bulk_api_check_action(entities, checks, act_proc)
214
118
  status 204
215
119
  end
216
120
 
217
121
  # create an acknowledgement for a service on an entity
218
122
  # NB currently, this does not acknowledge a specific failure event, just
219
123
  # the entity-check as a whole
220
- app.post %r{/acknowledgements#{ENTITY_CHECK_FRAGMENT}} do
221
- captures = params[:captures] || []
222
- entity_name = captures[0]
223
- check = captures[1]
224
-
225
- entities, checks = entities_and_checks(entity_name, check)
226
-
227
- dur = params[:duration] ? params[:duration].to_i : nil
228
- duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
229
- summary = params[:summary]
230
-
231
- opts = {'duration' => duration}
232
- opts['summary'] = summary if summary
233
-
234
- act_proc = proc {|entity_check|
235
- Flapjack::Data::Event.create_acknowledgement(
236
- entity_check.entity_name, entity_check.check,
237
- :summary => params[:summary],
238
- :duration => duration,
239
- :redis => redis)
240
- }
124
+ app.post %r{^/unscheduled_maintenances/entities/([^/]+)$} do
125
+ unscheduled_maintenances = wrapped_params('unscheduled_maintenances', false)
126
+ checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
127
+ unscheduled_maintenances.each do |wp|
128
+ dur = wp['duration'] ? wp['duration'].to_i : nil
129
+ duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
130
+ summary = wp['summary']
131
+
132
+ opts = {:duration => duration}
133
+ opts[:summary] = summary if summary
134
+
135
+ Flapjack::Data::Event.create_acknowledgement(
136
+ check.entity_name, check.check, {:redis => redis}.merge(opts))
137
+ end
138
+ end
241
139
 
242
- bulk_api_check_action(entities, checks, act_proc)
243
140
  status 204
244
141
  end
245
142
 
246
- app.delete %r{/((?:un)?scheduled_maintenances)} do
247
- action = params[:captures][0]
248
-
249
- # no backwards-compatible mode here, it's a new method
250
- entities, checks = entities_and_checks(nil, nil)
251
-
252
- act_proc = case action
253
- when 'scheduled_maintenances'
254
- start_time = validate_and_parsetime(params[:start_time])
255
- halt( err(403, "start time must be provided") ) unless start_time
256
- opts = {}
257
- proc {|entity_check| entity_check.end_scheduled_maintenance(start_time.to_i) }
258
- when 'unscheduled_maintenances'
259
- end_time = validate_and_parsetime(params[:end_time]) || Time.now
260
- proc {|entity_check| entity_check.end_unscheduled_maintenance(end_time.to_i) }
143
+ app.patch %r{^/unscheduled_maintenances/entities/([^/]+)$} do
144
+ checks_for_entity_ids( params[:captures][0].split(',') ).each do |check|
145
+ apply_json_patch('unscheduled_maintenances') do |op, property, linked, value|
146
+ case op
147
+ when 'replace'
148
+ if ['end_time'].include?(property)
149
+ end_time = validate_and_parsetime(value)
150
+ check.end_unscheduled_maintenance(end_time.to_i)
151
+ end
152
+ end
153
+ end
261
154
  end
262
-
263
- bulk_api_check_action(entities, checks, act_proc)
264
155
  status 204
265
156
  end
266
157
 
267
- app.post %r{/test_notifications#{ENTITY_CHECK_FRAGMENT}} do
268
- captures = params[:captures] || []
269
- entity_name = captures[0]
270
- check = captures[1]
271
-
272
- entities, checks = entities_and_checks(entity_name, check)
273
-
274
- act_proc = proc {|entity_check|
275
- summary = params[:summary] ||
276
- "Testing notifications to all contacts interested in entity #{entity_check.entity.name}"
277
- Flapjack::Data::Event.test_notifications(
278
- entity_check.entity_name, entity_check.check,
279
- :summary => summary,
280
- :redis => redis)
281
- }
158
+ app.delete %r{^/scheduled_maintenances/entities/([^/]+)$} do
159
+ start_time = validate_and_parsetime(params[:start_time])
160
+ halt( err(403, "start time must be provided") ) unless start_time
282
161
 
283
- bulk_api_check_action(entities, checks, act_proc)
162
+ checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
163
+ check.end_scheduled_maintenance(start_time.to_i)
164
+ end
284
165
  status 204
285
166
  end
286
167
 
287
- app.post '/entities' do
288
- pass unless Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(request.content_type)
289
-
290
- errors = []
291
- ret = nil
292
-
293
- # FIXME should scan for invalid records before making any changes, fail early
294
-
295
- entities = params[:entities]
296
- unless entities
297
- logger.debug("no entities object found in the following supplied JSON:")
298
- logger.debug(request.body)
299
- return err(403, "No entities object received")
300
- end
301
- return err(403, "The received entities object is not an Enumerable") unless entities.is_a?(Enumerable)
302
- return err(403, "Entity with a nil id detected") unless entities.any? {|e| !e['id'].nil?}
303
-
304
- entities.each do |entity|
305
- unless entity['id']
306
- errors << "Entity not imported as it has no id: #{entity.inspect}"
307
- next
168
+ app.post %r{^/test_notifications/entities/([^/]+)$} do
169
+ test_notifications = wrapped_params('test_notifications', false)
170
+ checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
171
+ test_notifications.each do |wp|
172
+ summary = wp['summary'] ||
173
+ "Testing notifications to all contacts interested in entity #{check.entity.name}"
174
+ Flapjack::Data::Event.test_notifications(
175
+ check.entity_name, check.check,
176
+ :summary => summary,
177
+ :redis => redis)
308
178
  end
309
- Flapjack::Data::Entity.add(entity, :redis => redis)
310
179
  end
311
- errors.empty? ? 204 : err(403, *errors)
180
+ status 204
312
181
  end
313
182
 
314
183
  end
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sinatra/base'
4
+
5
+ require 'flapjack/data/contact'
6
+
7
+ module Flapjack
8
+
9
+ module Gateways
10
+
11
+ class JSONAPI < Sinatra::Base
12
+
13
+ module MediumMethods
14
+
15
+ SEMAPHORE_CONTACT_MASS_UPDATE = 'contact_mass_update'
16
+
17
+ module Helpers
18
+
19
+ def obtain_semaphore(resource)
20
+ semaphore = nil
21
+ strikes = 0
22
+ begin
23
+ semaphore = Flapjack::Data::Semaphore.new(resource, :redis => redis, :expiry => 30)
24
+ rescue Flapjack::Data::Semaphore::ResourceLocked
25
+ strikes += 1
26
+ raise Flapjack::Gateways::JSONAPI::ResourceLocked.new(resource) unless strikes < 3
27
+ sleep 1
28
+ retry
29
+ end
30
+ raise Flapjack::Gateways::JSONAPI::ResourceLocked.new(resource) unless semaphore
31
+ semaphore
32
+ end
33
+
34
+ # TODO validate that media type exists in redis
35
+ def split_media_ids(media_ids)
36
+
37
+ contact_cache = {}
38
+
39
+ media_ids.split(',').uniq.collect do |m_id|
40
+ m_id =~ /\A(.+)_(email|sms|jabber)\z/
41
+
42
+ contact_id = $1
43
+ media_type = $2
44
+ halt err(422, "Could not get contact_id from media_id") if contact_id.nil?
45
+ halt err(422, "Could not get media type from media_id") if media_type.nil?
46
+
47
+ contact_cache[contact_id] ||= find_contact(contact_id)
48
+
49
+ {:contact => contact_cache[contact_id], :type => media_type}
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ def self.registered(app)
56
+ app.helpers Flapjack::Gateways::JSONAPI::Helpers
57
+ app.helpers Flapjack::Gateways::JSONAPI::MediumMethods::Helpers
58
+
59
+ # Creates media records for a contact
60
+ app.post '/contacts/:contact_id/media' do
61
+ media_data = params[:media]
62
+
63
+ if media_data.nil? || !media_data.is_a?(Enumerable)
64
+ halt err(422, "No valid media were submitted")
65
+ end
66
+
67
+ unless media_data.all? {|m| m['id'].nil? }
68
+ halt err(422, "Media creation cannot include IDs")
69
+ end
70
+
71
+ semaphore = obtain_semaphore(SEMAPHORE_CONTACT_MASS_UPDATE)
72
+ contact = Flapjack::Data::Contact.find_by_id(params[:contact_id], :redis => redis)
73
+ if contact.nil?
74
+ semaphore.release
75
+ halt err(422, "Contact id:'#{params[:contact_id]}' could not be loaded")
76
+ end
77
+
78
+ media_data.each do |medium_data|
79
+ type = medium_data['type']
80
+ contact.set_address_for_media(type, medium_data['address'])
81
+ contact.set_interval_for_media(type, medium_data['interval'])
82
+ contact.set_rollup_threshold_for_media(type, medium_data['rollup_threshold'])
83
+ medium_data[:id] = "#{contact.id}_#{type}"
84
+ medium_data[:links] = {:contacts => [contact.id]}
85
+ end
86
+
87
+ semaphore.release
88
+
89
+ status 201
90
+
91
+ '{"media":' + media_data.to_json + '}'
92
+ end
93
+
94
+ # get one or more media records; media ids are, for Flapjack
95
+ # v1, composed of "#{contact.id}_#{media_type}"
96
+ app.get %r{^/media(?:/)?([^/]+)?$} do
97
+ media_list_cache = {}
98
+ contact_media = if params[:captures] && params[:captures][0]
99
+ split_media_ids(params[:captures][0])
100
+ else
101
+ Flapjack::Data::Contact.all(:redis => redis).collect do |contact|
102
+ media_list_cache[contact.id] ||= contact.media_list
103
+ media_list_cache[contact.id].collect do |media_type|
104
+ {:contact => contact, :type => media_type}
105
+ end
106
+ end.flatten(1)
107
+ end
108
+
109
+ media_data = contact_media.inject([]) do |memo, contact_media_type|
110
+ contact = contact_media_type[:contact]
111
+ media_type = contact_media_type[:type]
112
+
113
+ media_list_cache[contact.id] ||= contact.media_list
114
+ if media_list_cache[contact.id].include?(media_type)
115
+ medium_id = "#{contact.id}_#{media_type}"
116
+ memo <<
117
+ {:id => medium_id,
118
+ :type => media_type,
119
+ :address => contact.media[media_type],
120
+ :interval => contact.media_intervals[media_type],
121
+ :rollup_threshold => contact.media_rollup_thresholds[media_type],
122
+ :links => {:contacts => [contact.id]}}
123
+ end
124
+
125
+ memo
126
+ end
127
+
128
+ '{"media":' + media_data.to_json + '}'
129
+ end
130
+
131
+ # update one or more media records; media ids are, for Flapjack
132
+ # v1, composed of "#{contact.id}_#{media_type}"
133
+ app.patch '/media/:id' do
134
+ media_list_cache = {}
135
+ split_media_ids(params[:id]).each do |contact_media_type|
136
+ contact = contact_media_type[:contact]
137
+ media_type = contact_media_type[:type]
138
+ media_list_cache[contact.id] ||= contact.media_list
139
+ next unless media_list_cache[contact.id].include?(media_type)
140
+ apply_json_patch('media') do |op, property, linked, value|
141
+ if 'replace'.eql?(op)
142
+ case property
143
+ when 'address'
144
+ contact.set_address_for_media(media_type, value)
145
+ when 'interval'
146
+ contact.set_interval_for_media(media_type, value)
147
+ when 'rollup_threshold'
148
+ contact.set_rollup_threshold_for_media(media_type, value)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ status 204
155
+ end
156
+
157
+ # delete one or more media records; media ids are, for Flapjack
158
+ # v1, composed of "#{contact.id}_#{media_type}"
159
+ app.delete '/media/:id' do
160
+ media_list_cache = {}
161
+ split_media_ids(params[:id]).each do |contact_media_type|
162
+ contact = contact_media_type[:contact]
163
+ media_type = contact_media_type[:type]
164
+ media_list_cache[contact.id] ||= contact.media_list
165
+ next unless media_list_cache[contact.id].include?(media_type)
166
+ contact.remove_media(media_type)
167
+ end
168
+ status 204
169
+ end
170
+
171
+ end
172
+
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+
179
+ end