#!/usr/bin/env ruby require 'sinatra/base' require 'flapjack/data/contact' module Flapjack module Gateways class JSONAPI < Sinatra::Base module MediumMethods SEMAPHORE_CONTACT_MASS_UPDATE = 'contact_mass_update' module Helpers def obtain_semaphore(resource) semaphore = nil strikes = 0 begin semaphore = Flapjack::Data::Semaphore.new(resource, :redis => redis, :expiry => 30) rescue Flapjack::Data::Semaphore::ResourceLocked strikes += 1 raise Flapjack::Gateways::JSONAPI::ResourceLocked.new(resource) unless strikes < 3 sleep 1 retry end raise Flapjack::Gateways::JSONAPI::ResourceLocked.new(resource) unless semaphore semaphore end # TODO validate that media type exists in redis def split_media_ids(media_ids) contact_cache = {} known_media_identifiers = Flapjack::Data::Contact::ALL_MEDIA.reject{|m| m == 'pagerduty'}.join('|') media_ids.split(',').uniq.collect do |m_id| m_id =~ /\A(.+)_(#{known_media_identifiers})\z/ contact_id = $1 media_type = $2 halt err(422, "Could not get contact_id from media_id '#{m_id}'") if contact_id.nil? halt err(422, "Could not get media type from media_id '#{m_id}'") if media_type.nil? contact_cache[contact_id] ||= find_contact(contact_id) {:contact => contact_cache[contact_id], :type => media_type} end end end def self.registered(app) app.helpers Flapjack::Gateways::JSONAPI::Helpers app.helpers Flapjack::Gateways::JSONAPI::MediumMethods::Helpers # Creates media records for a contact app.post '/contacts/:contact_id/media' do media_data = params[:media] if media_data.nil? || !media_data.is_a?(Enumerable) halt err(422, "No valid media were submitted") end media_id_re = /^#{params[:contact_id]}_(?:#{Flapjack::Data::Contact::ALL_MEDIA.join('|')}$)/ unless media_data.all? {|m| m['id'].nil? || media_id_re === m['id'] } halt err(422, "Media creation cannot include non-conformant IDs") end semaphore = obtain_semaphore(SEMAPHORE_CONTACT_MASS_UPDATE) contact = Flapjack::Data::Contact.find_by_id(params[:contact_id], :redis => redis) if contact.nil? semaphore.release halt err(422, "Contact id: '#{params[:contact_id]}' could not be loaded") end media_data.each do |medium_data| type = medium_data['type'] contact.set_address_for_media(type, medium_data['address']) contact.set_interval_for_media(type, medium_data['interval']) contact.set_rollup_threshold_for_media(type, medium_data['rollup_threshold']) medium_data['id'] = "#{contact.id}_#{type}" end semaphore.release media_ids = media_data.collect {|md| md['id']} status 201 response.headers['Location'] = "#{base_url}/media/#{media_ids.join(',')}" Flapjack.dump_json(media_ids) end # get one or more media records; media ids are, for Flapjack # v1, composed of "#{contact.id}_#{media_type}" app.get %r{^/media(?:/)?([^/]+)?$} do media_list_cache = {} contact_media = if params[:captures] && params[:captures][0] split_media_ids(params[:captures][0]) else Flapjack::Data::Contact.all(:redis => redis).collect do |contact| media_list_cache[contact.id] ||= contact.media_list media_list_cache[contact.id].collect do |media_type| {:contact => contact, :type => media_type} end end.flatten(1) end media_data = contact_media.inject([]) do |memo, contact_media_type| contact = contact_media_type[:contact] media_type = contact_media_type[:type] media_list_cache[contact.id] ||= contact.media_list if media_list_cache[contact.id].include?(media_type) medium_id = "#{contact.id}_#{media_type}" int = contact.media_intervals[media_type] rut = contact.media_rollup_thresholds[media_type] memo << {:id => medium_id, :type => media_type, :address => contact.media[media_type], :interval => int.nil? ? nil : int.to_i, :rollup_threshold => rut.nil? ? nil : rut.to_i, :links => {:contacts => [contact.id]}} end memo end '{"media":' + Flapjack.dump_json(media_data) + '}' end # update one or more media records; media ids are, for Flapjack # v1, composed of "#{contact.id}_#{media_type}" app.patch '/media/:id' do media_list_cache = {} split_media_ids(params[:id]).each do |contact_media_type| contact = contact_media_type[:contact] media_type = contact_media_type[:type] media_list_cache[contact.id] ||= contact.media_list next unless media_list_cache[contact.id].include?(media_type) apply_json_patch('media') do |op, property, linked, value| if 'replace'.eql?(op) case property when 'address' contact.set_address_for_media(media_type, value) when 'interval' contact.set_interval_for_media(media_type, value) when 'rollup_threshold' contact.set_rollup_threshold_for_media(media_type, value) end end end end status 204 end # delete one or more media records; media ids are, for Flapjack # v1, composed of "#{contact.id}_#{media_type}" app.delete '/media/:id' do media_list_cache = {} split_media_ids(params[:id]).each do |contact_media_type| contact = contact_media_type[:contact] media_type = contact_media_type[:type] media_list_cache[contact.id] ||= contact.media_list next unless media_list_cache[contact.id].include?(media_type) contact.remove_media(media_type) end status 204 end end end end end end