flapjack 0.7.35 → 0.8.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.
- checksums.yaml +7 -0
- data/.gitignore +1 -1
- data/Gemfile +3 -4
- data/Guardfile +1 -1
- data/README.md +38 -19
- data/Rakefile +1 -3
- data/etc/flapjack_config.yaml.example +11 -1
- data/features/steps/cli_steps.rb +3 -3
- data/features/steps/events_steps.rb +7 -6
- data/features/steps/flapjack-netsaint-parser_steps.rb +8 -8
- data/features/steps/notifications_steps.rb +10 -10
- data/features/steps/packaging-lintian_steps.rb +5 -9
- data/features/steps/time_travel_steps.rb +1 -1
- data/flapjack.gemspec +4 -3
- data/lib/flapjack/data/contact.rb +78 -6
- data/lib/flapjack/data/entity.rb +11 -2
- data/lib/flapjack/data/notification_rule.rb +67 -59
- data/lib/flapjack/data/semaphore.rb +44 -0
- data/lib/flapjack/gateways/api.rb +24 -28
- data/lib/flapjack/gateways/api/contact_methods.rb +1 -2
- data/lib/flapjack/gateways/api/entity_methods.rb +3 -3
- data/lib/flapjack/gateways/jsonapi.rb +249 -0
- data/lib/flapjack/gateways/jsonapi/contact_methods.rb +544 -0
- data/lib/flapjack/gateways/jsonapi/entity_check_presenter.rb +217 -0
- data/lib/flapjack/gateways/jsonapi/entity_methods.rb +350 -0
- data/lib/flapjack/gateways/jsonapi/entity_presenter.rb +75 -0
- data/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +32 -0
- data/lib/flapjack/gateways/web.rb +78 -12
- data/lib/flapjack/gateways/web/public/css/bootstrap-theme.css +397 -0
- data/lib/flapjack/gateways/web/public/css/bootstrap-theme.min.css +7 -0
- data/lib/flapjack/gateways/web/public/css/bootstrap.css +7118 -0
- data/lib/flapjack/gateways/web/public/css/bootstrap.min.css +6 -8
- data/lib/flapjack/gateways/web/public/css/font-awesome.css +1338 -0
- data/lib/flapjack/gateways/web/public/css/font-awesome.min.css +4 -0
- data/lib/flapjack/gateways/web/public/css/screen.css +80 -0
- data/lib/flapjack/gateways/web/public/css/select2-bootstrap.css +87 -0
- data/lib/flapjack/gateways/web/public/css/select2.css +615 -0
- data/lib/flapjack/gateways/web/public/fonts/FontAwesome.otf +0 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.eot +0 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.svg +414 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.ttf +0 -0
- data/lib/flapjack/gateways/web/public/fonts/fontawesome-webfont.woff +0 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/flapjack/gateways/web/public/img/flapjack-2013-notext-transparent-300-300.png +0 -0
- data/lib/flapjack/gateways/web/public/img/select2.png +0 -0
- data/lib/flapjack/gateways/web/public/img/select2x2.png +0 -0
- data/lib/flapjack/gateways/web/public/js/backbone-min.js +2 -0
- data/lib/flapjack/gateways/web/public/js/backbone.js +1581 -0
- data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +75 -0
- data/lib/flapjack/gateways/web/public/js/bootstrap.js +2276 -0
- data/lib/flapjack/gateways/web/public/js/contacts.js +225 -0
- data/lib/flapjack/gateways/web/public/js/jquery-1.10.2.js +9789 -0
- data/lib/flapjack/gateways/web/public/js/jquery-1.10.2.min.js +6 -0
- data/lib/flapjack/gateways/web/public/js/select2.js +3255 -0
- data/lib/flapjack/gateways/web/public/js/select2.min.js +22 -0
- data/lib/flapjack/gateways/web/public/js/underscore-min.js +6 -0
- data/lib/flapjack/gateways/web/public/js/underscore.js +1276 -0
- data/lib/flapjack/gateways/web/views/check.html.erb +423 -193
- data/lib/flapjack/gateways/web/views/checks.html.erb +51 -71
- data/lib/flapjack/gateways/web/views/contact.html.erb +142 -164
- data/lib/flapjack/gateways/web/views/contacts.html.erb +20 -40
- data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +83 -0
- data/lib/flapjack/gateways/web/views/entities.html.erb +18 -37
- data/lib/flapjack/gateways/web/views/entity.html.erb +46 -65
- data/lib/flapjack/gateways/web/views/index.html.erb +6 -27
- data/lib/flapjack/gateways/web/views/layout.erb +95 -0
- data/lib/flapjack/gateways/web/views/self_stats.html.erb +100 -114
- data/lib/flapjack/pikelet.rb +4 -2
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/coordinator_spec.rb +120 -120
- data/spec/lib/flapjack/data/contact_spec.rb +66 -58
- data/spec/lib/flapjack/data/entity_check_spec.rb +179 -179
- data/spec/lib/flapjack/data/entity_spec.rb +71 -71
- data/spec/lib/flapjack/data/event_spec.rb +34 -30
- data/spec/lib/flapjack/data/message_spec.rb +6 -6
- data/spec/lib/flapjack/data/notification_rule_spec.rb +24 -24
- data/spec/lib/flapjack/data/notification_spec.rb +19 -19
- data/spec/lib/flapjack/data/semaphore_spec.rb +24 -0
- data/spec/lib/flapjack/data/tag_spec.rb +11 -10
- data/spec/lib/flapjack/gateways/api/contact_methods_spec.rb +201 -201
- data/spec/lib/flapjack/gateways/api/entity_check_presenter_spec.rb +55 -55
- data/spec/lib/flapjack/gateways/api/entity_methods_spec.rb +257 -257
- data/spec/lib/flapjack/gateways/api/entity_presenter_spec.rb +26 -26
- data/spec/lib/flapjack/gateways/api_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/email_spec.rb +4 -4
- data/spec/lib/flapjack/gateways/jabber_spec.rb +77 -77
- data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +830 -0
- data/spec/lib/flapjack/gateways/jsonapi/entity_check_presenter_spec.rb +211 -0
- data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +863 -0
- data/spec/lib/flapjack/gateways/jsonapi/entity_presenter_spec.rb +108 -0
- data/spec/lib/flapjack/gateways/jsonapi_spec.rb +8 -0
- data/spec/lib/flapjack/gateways/oobetet_spec.rb +35 -35
- data/spec/lib/flapjack/gateways/pagerduty_spec.rb +40 -40
- data/spec/lib/flapjack/gateways/sms_messagenet_spec.rb +3 -3
- data/spec/lib/flapjack/gateways/web/views/check.html.erb_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/web/views/contact.html.erb_spec.rb +5 -5
- data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +1 -1
- data/spec/lib/flapjack/gateways/web_spec.rb +73 -74
- data/spec/lib/flapjack/logger_spec.rb +13 -13
- data/spec/lib/flapjack/pikelet_spec.rb +33 -33
- data/spec/lib/flapjack/processor_spec.rb +22 -22
- data/spec/lib/flapjack/redis_pool_spec.rb +1 -1
- data/spec/lib/flapjack/utility_spec.rb +12 -12
- data/spec/spec_helper.rb +9 -9
- data/spec/support/erb_view_helper.rb +4 -0
- metadata +107 -96
- data/lib/flapjack/gateways/web/public/css/flapjack.css +0 -49
- data/lib/flapjack/gateways/web/views/_css.html.erb +0 -42
- data/lib/flapjack/gateways/web/views/_foot.html.erb +0 -3
- data/lib/flapjack/gateways/web/views/_head.html.erb +0 -5
- data/lib/flapjack/gateways/web/views/_nav.html.erb +0 -10
|
@@ -118,8 +118,7 @@ module Flapjack
|
|
|
118
118
|
app.get '/contacts/:contact_id/notification_rules' do
|
|
119
119
|
content_type :json
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
contact.notification_rules.to_json
|
|
121
|
+
"[" + find_contact(params[:contact_id]).notification_rules.map {|r| r.to_json }.join(',') + "]"
|
|
123
122
|
end
|
|
124
123
|
|
|
125
124
|
# Get the specified notification rule for this user
|
|
@@ -135,7 +135,7 @@ module Flapjack
|
|
|
135
135
|
|
|
136
136
|
app.get '/entities' do
|
|
137
137
|
content_type :json
|
|
138
|
-
ret = Flapjack::Data::Entity.all(:redis => redis).
|
|
138
|
+
ret = Flapjack::Data::Entity.all(:redis => redis).collect {|e|
|
|
139
139
|
presenter = Flapjack::Gateways::API::EntityPresenter.new(e, :redis => redis)
|
|
140
140
|
{'id' => e.id, 'name' => e.name, 'checks' => presenter.status }
|
|
141
141
|
}
|
|
@@ -164,10 +164,10 @@ module Flapjack
|
|
|
164
164
|
if entity_name
|
|
165
165
|
# compatible with previous data format
|
|
166
166
|
results = results.collect {|status_h| status_h[:status]}
|
|
167
|
-
|
|
167
|
+
check ? results.first.to_json : "[" + results.map {|r| r.to_json }.join(',') + "]"
|
|
168
168
|
else
|
|
169
169
|
# new and improved data format which reflects the request param structure
|
|
170
|
-
results.to_json
|
|
170
|
+
"[" + results.map {|r| r.to_json }.join(',') + "]"
|
|
171
171
|
end
|
|
172
172
|
end
|
|
173
173
|
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# A HTTP-based API server, which provides queries to determine the status of
|
|
4
|
+
# entities and the checks that are reported against them.
|
|
5
|
+
#
|
|
6
|
+
# There's a matching flapjack-diner gem at https://github.com/flpjck/flapjack-diner
|
|
7
|
+
# which consumes data from this API.
|
|
8
|
+
|
|
9
|
+
require 'time'
|
|
10
|
+
|
|
11
|
+
require 'rack/fiber_pool'
|
|
12
|
+
require 'sinatra/base'
|
|
13
|
+
|
|
14
|
+
require 'flapjack/rack_logger'
|
|
15
|
+
require 'flapjack/redis_pool'
|
|
16
|
+
|
|
17
|
+
require 'flapjack/gateways/jsonapi/rack/json_params_parser'
|
|
18
|
+
|
|
19
|
+
require 'flapjack/gateways/jsonapi/contact_methods'
|
|
20
|
+
require 'flapjack/gateways/jsonapi/entity_methods'
|
|
21
|
+
|
|
22
|
+
module Flapjack
|
|
23
|
+
|
|
24
|
+
module Gateways
|
|
25
|
+
|
|
26
|
+
class JSONAPI < Sinatra::Base
|
|
27
|
+
|
|
28
|
+
include Flapjack::Utility
|
|
29
|
+
|
|
30
|
+
JSON_REQUEST_MIME_TYPES = ['application/vnd.api+json', 'application/json']
|
|
31
|
+
|
|
32
|
+
class ContactNotFound < RuntimeError
|
|
33
|
+
attr_reader :contact_id
|
|
34
|
+
def initialize(contact_id)
|
|
35
|
+
@contact_id = contact_id
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class NotificationRuleNotFound < RuntimeError
|
|
40
|
+
attr_reader :rule_id
|
|
41
|
+
def initialize(rule_id)
|
|
42
|
+
@rule_id = rule_id
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class EntityNotFound < RuntimeError
|
|
47
|
+
attr_reader :entity
|
|
48
|
+
def initialize(entity)
|
|
49
|
+
@entity = entity
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class EntityCheckNotFound < RuntimeError
|
|
54
|
+
attr_reader :entity, :check
|
|
55
|
+
def initialize(entity, check)
|
|
56
|
+
@entity = entity
|
|
57
|
+
@check = check
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class ResourceLocked < RuntimeError
|
|
62
|
+
attr_reader :resource
|
|
63
|
+
def initialize(resource)
|
|
64
|
+
@resource = resource
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
set :dump_errors, false
|
|
69
|
+
|
|
70
|
+
rescue_error = Proc.new {|status, exception, request_info, *msg|
|
|
71
|
+
if !msg || msg.empty?
|
|
72
|
+
trace = exception.backtrace.join("\n")
|
|
73
|
+
msg = "#{exception.class} - #{exception.message}"
|
|
74
|
+
msg_str = "#{msg}\n#{trace}"
|
|
75
|
+
else
|
|
76
|
+
msg_str = msg.join(", ")
|
|
77
|
+
end
|
|
78
|
+
case
|
|
79
|
+
when status < 500
|
|
80
|
+
@logger.warn "Error: #{msg_str}"
|
|
81
|
+
else
|
|
82
|
+
@logger.error "Error: #{msg_str}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
response_body = {:errors => msg}.to_json
|
|
86
|
+
|
|
87
|
+
query_string = (request_info[:query_string].respond_to?(:length) &&
|
|
88
|
+
request_info[:query_string].length > 0) ? "?#{request_info[:query_string]}" : ""
|
|
89
|
+
if @logger.debug?
|
|
90
|
+
@logger.debug("Returning #{status} for #{request_info[:request_method]} " +
|
|
91
|
+
"#{request_info[:path_info]}#{query_string}, body: #{response_body}")
|
|
92
|
+
elsif @logger.info?
|
|
93
|
+
@logger.info("Returning #{status} for #{request_info[:request_method]} " +
|
|
94
|
+
"#{request_info[:path_info]}#{query_string}")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
[status, {}, response_body]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
rescue_exception = Proc.new {|env, e|
|
|
101
|
+
request_info = {
|
|
102
|
+
:path_info => env['REQUEST_PATH'],
|
|
103
|
+
:request_method => env['REQUEST_METHOD'],
|
|
104
|
+
:query_string => env['QUERY_STRING']
|
|
105
|
+
}
|
|
106
|
+
case e
|
|
107
|
+
when Flapjack::Gateways::JSONAPI::ContactNotFound
|
|
108
|
+
rescue_error.call(404, e, request_info, "could not find contact '#{e.contact_id}'")
|
|
109
|
+
when Flapjack::Gateways::JSONAPI::NotificationRuleNotFound
|
|
110
|
+
rescue_error.call(404, e, request_info,"could not find notification rule '#{e.rule_id}'")
|
|
111
|
+
when Flapjack::Gateways::JSONAPI::EntityNotFound
|
|
112
|
+
rescue_error.call(404, e, request_info, "could not find entity '#{e.entity}'")
|
|
113
|
+
when Flapjack::Gateways::JSONAPI::EntityCheckNotFound
|
|
114
|
+
rescue_error.call(404, e, request_info, "could not find entity check '#{e.check}'")
|
|
115
|
+
when Flapjack::Gateways::JSONAPI::ResourceLocked
|
|
116
|
+
rescue_error.call(423, e, request_info, "unable to obtain lock for resource '#{e.resource}'")
|
|
117
|
+
else
|
|
118
|
+
rescue_error.call(500, e, request_info)
|
|
119
|
+
end
|
|
120
|
+
}
|
|
121
|
+
use ::Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
|
|
122
|
+
|
|
123
|
+
use ::Rack::MethodOverride
|
|
124
|
+
use Flapjack::Gateways::JSONAPI::Rack::JsonParamsParser
|
|
125
|
+
|
|
126
|
+
class << self
|
|
127
|
+
def start
|
|
128
|
+
@redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2)
|
|
129
|
+
|
|
130
|
+
@logger.info "starting jsonapi - class"
|
|
131
|
+
|
|
132
|
+
if @config && @config['access_log']
|
|
133
|
+
access_logger = Flapjack::AsyncLogger.new(@config['access_log'])
|
|
134
|
+
use Flapjack::CommonLogger, access_logger
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@base_url = @config['base_url']
|
|
138
|
+
dummy_url = "http://api.example.com"
|
|
139
|
+
if @base_url
|
|
140
|
+
@base_url = $1 if @base_url.match(/^(.+)\/$/)
|
|
141
|
+
else
|
|
142
|
+
@logger.error "base_url must be a valid http or https URI (not configured), setting to dummy value (#{dummy_url})"
|
|
143
|
+
# FIXME: at this point I'd like to stop this pikelet without bringing down the whole
|
|
144
|
+
@base_url = dummy_url
|
|
145
|
+
end
|
|
146
|
+
if (@base_url =~ /^#{URI::regexp(%w(http https))}$/).nil?
|
|
147
|
+
@logger.error "base_url must be a valid http or https URI (#{@base_url}), setting to dummy value (#{dummy_url})"
|
|
148
|
+
# FIXME: at this point I'd like to stop this pikelet without bringing down the whole
|
|
149
|
+
# flapjack process
|
|
150
|
+
# For now, set a dummy value
|
|
151
|
+
@base_url = dummy_url
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def redis
|
|
157
|
+
self.class.instance_variable_get('@redis')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def logger
|
|
161
|
+
self.class.instance_variable_get('@logger')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def base_url
|
|
165
|
+
self.class.instance_variable_get('@base_url')
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
before do
|
|
169
|
+
input = nil
|
|
170
|
+
query_string = (request.query_string.respond_to?(:length) &&
|
|
171
|
+
request.query_string.length > 0) ? "?#{request.query_string}" : ""
|
|
172
|
+
if logger.debug?
|
|
173
|
+
input = env['rack.input'].read
|
|
174
|
+
logger.debug("#{request.request_method} #{request.path_info}#{query_string} #{input}")
|
|
175
|
+
elsif logger.info?
|
|
176
|
+
input = env['rack.input'].read
|
|
177
|
+
input_short = input.gsub(/\n/, '').gsub(/\s+/, ' ')
|
|
178
|
+
logger.info("#{request.request_method} #{request.path_info}#{query_string} #{input_short[0..80]}")
|
|
179
|
+
end
|
|
180
|
+
env['rack.input'].rewind unless input.nil?
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
after do
|
|
184
|
+
return if response.status == 500
|
|
185
|
+
|
|
186
|
+
query_string = (request.query_string.respond_to?(:length) &&
|
|
187
|
+
request.query_string.length > 0) ? "?#{request.query_string}" : ""
|
|
188
|
+
if logger.debug?
|
|
189
|
+
logger.debug("Returning #{response.status} for #{request.request_method} " +
|
|
190
|
+
"#{request.path_info}#{query_string}, body: #{response.body.join(', ')}")
|
|
191
|
+
elsif logger.info?
|
|
192
|
+
logger.info("Returning #{response.status} for #{request.request_method} " +
|
|
193
|
+
"#{request.path_info}#{query_string}")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
register Flapjack::Gateways::JSONAPI::EntityMethods
|
|
198
|
+
|
|
199
|
+
register Flapjack::Gateways::JSONAPI::ContactMethods
|
|
200
|
+
|
|
201
|
+
# the following should add the cors headers to every request, but is no work
|
|
202
|
+
#register Sinatra::CrossOrigin
|
|
203
|
+
#
|
|
204
|
+
#configure do
|
|
205
|
+
# enable :cross_origin
|
|
206
|
+
#end
|
|
207
|
+
#set :allow_origin, :any
|
|
208
|
+
#set :allow_methods, [:get, :post, :put, :patch, :delete, :options]
|
|
209
|
+
|
|
210
|
+
options '*' do
|
|
211
|
+
cors_headers
|
|
212
|
+
204
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
not_found do
|
|
216
|
+
err(404, "not routable")
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def cors_headers
|
|
220
|
+
allow_headers = %w(* Content-Type Accept AUTHORIZATION Cache-Control)
|
|
221
|
+
allow_methods = %w(GET POST PUT PATCH DELETE OPTIONS)
|
|
222
|
+
expose_headers = %w(Cache-Control Content-Language Content-Type Expires Last-Modified Pragma)
|
|
223
|
+
cors_headers = {
|
|
224
|
+
'Access-Control-Allow-Origin' => '*',
|
|
225
|
+
'Access-Control-Allow-Methods' => allow_methods.join(', '),
|
|
226
|
+
'Access-Control-Allow-Headers' => allow_headers.join(', '),
|
|
227
|
+
'Access-Control-Expose-Headers' => expose_headers.join(', '),
|
|
228
|
+
'Access-Control-Max-Age' => '1728000'
|
|
229
|
+
}
|
|
230
|
+
headers(cors_headers)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def location(ids)
|
|
234
|
+
location = "#{base_url}#{request.path_info}#{ids.length == 1 ? '/' + ids.first : '?ids=' + ids.join(',')}"
|
|
235
|
+
headers({'Location' => location})
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private
|
|
239
|
+
|
|
240
|
+
def err(status, *msg)
|
|
241
|
+
msg_str = msg.join(", ")
|
|
242
|
+
logger.info "Error: #{msg_str}"
|
|
243
|
+
[status, {}, {:errors => msg}.to_json]
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
end
|
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'sinatra/base'
|
|
4
|
+
|
|
5
|
+
require 'flapjack/data/contact'
|
|
6
|
+
require 'flapjack/data/notification_rule'
|
|
7
|
+
require 'flapjack/data/semaphore'
|
|
8
|
+
|
|
9
|
+
module Flapjack
|
|
10
|
+
|
|
11
|
+
module Gateways
|
|
12
|
+
|
|
13
|
+
class JSONAPI < Sinatra::Base
|
|
14
|
+
|
|
15
|
+
module ContactMethods
|
|
16
|
+
|
|
17
|
+
SEMAPHORE_CONTACT_MASS_UPDATE = 'contact_mass_update'
|
|
18
|
+
|
|
19
|
+
module Helpers
|
|
20
|
+
|
|
21
|
+
def find_contact(contact_id)
|
|
22
|
+
contact = Flapjack::Data::Contact.find_by_id(contact_id, :logger => logger, :redis => redis)
|
|
23
|
+
raise Flapjack::Gateways::JSONAPI::ContactNotFound.new(contact_id) if contact.nil?
|
|
24
|
+
contact
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_rule(rule_id)
|
|
28
|
+
rule = Flapjack::Data::NotificationRule.find_by_id(rule_id, :logger => logger, :redis => redis)
|
|
29
|
+
raise Flapjack::Gateways::JSONAPI::NotificationRuleNotFound.new(rule_id) if rule.nil?
|
|
30
|
+
rule
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def find_tags(tags)
|
|
34
|
+
halt err(400, "no tags given") if tags.nil? || tags.empty?
|
|
35
|
+
tags
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def obtain_semaphore(resource)
|
|
39
|
+
semaphore = nil
|
|
40
|
+
strikes = 0
|
|
41
|
+
begin
|
|
42
|
+
semaphore = Flapjack::Data::Semaphore.new(resource, {:redis => redis, :expiry => 30})
|
|
43
|
+
rescue Flapjack::Data::Semaphore::ResourceLocked
|
|
44
|
+
strikes += 1
|
|
45
|
+
raise Flapjack::Gateways::JSONAPI::ResourceLocked.new(resource) unless strikes < 3
|
|
46
|
+
sleep 1
|
|
47
|
+
retry
|
|
48
|
+
end
|
|
49
|
+
raise Flapjack::Gateways::JSONAPI::ResourceLocked.new(resource) unless semaphore
|
|
50
|
+
semaphore
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.registered(app)
|
|
55
|
+
|
|
56
|
+
app.helpers Flapjack::Gateways::JSONAPI::ContactMethods::Helpers
|
|
57
|
+
|
|
58
|
+
app.post '/contacts' do
|
|
59
|
+
pass unless Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(request.content_type)
|
|
60
|
+
content_type :json
|
|
61
|
+
cors_headers
|
|
62
|
+
|
|
63
|
+
contacts_data = params[:contacts]
|
|
64
|
+
|
|
65
|
+
if contacts_data.nil? || !contacts_data.is_a?(Enumerable)
|
|
66
|
+
halt err(422, "No valid contacts were submitted")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
contacts_ids = contacts_data.reject {|c| c['id'].nil? }.
|
|
70
|
+
map {|co| co['id'].to_s }
|
|
71
|
+
|
|
72
|
+
semaphore = obtain_semaphore(SEMAPHORE_CONTACT_MASS_UPDATE)
|
|
73
|
+
|
|
74
|
+
conflicted_ids = contacts_ids.find_all {|id|
|
|
75
|
+
Flapjack::Data::Contact.exists_with_id?(id, :redis => redis)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
unless conflicted_ids.empty?
|
|
79
|
+
semaphore.release
|
|
80
|
+
halt err(409, "Contacts already exist with the following IDs: " +
|
|
81
|
+
conflicted_ids.join(', '))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
contacts_data.each do |contact_data|
|
|
85
|
+
unless contact_data['id']
|
|
86
|
+
contact_data['id'] = SecureRandom.uuid
|
|
87
|
+
end
|
|
88
|
+
Flapjack::Data::Contact.add(contact_data, :redis => redis)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
semaphore.release
|
|
92
|
+
|
|
93
|
+
ids = contacts_data.map {|c| c['id']}
|
|
94
|
+
location(ids)
|
|
95
|
+
|
|
96
|
+
contacts_data.map {|cd| cd['id']}.to_json
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
app.post '/contacts_atomic' do
|
|
100
|
+
pass unless Flapjack::Gateways::JSONAPI::JSON_REQUEST_MIME_TYPES.include?(request.content_type)
|
|
101
|
+
content_type :json
|
|
102
|
+
|
|
103
|
+
contacts_data = params[:contacts]
|
|
104
|
+
if contacts_data.nil? || !contacts_data.is_a?(Enumerable)
|
|
105
|
+
halt err(422, "No valid contacts were submitted")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# stringifying as integer string params are automatically integered,
|
|
109
|
+
# but our redis ids are strings
|
|
110
|
+
contacts_data_ids = contacts_data.reject {|c| c['id'].nil? }.
|
|
111
|
+
map {|co| co['id'].to_s }
|
|
112
|
+
|
|
113
|
+
if contacts_data_ids.empty?
|
|
114
|
+
halt err(422, "No contacts with IDs were submitted")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
semaphore = obtain_semaphore(SEMAPHORE_CONTACT_MASS_UPDATE)
|
|
118
|
+
|
|
119
|
+
contacts = Flapjack::Data::Contact.all(:redis => redis)
|
|
120
|
+
contacts_h = hashify(*contacts) {|c| [c.id, c] }
|
|
121
|
+
contacts_ids = contacts_h.keys
|
|
122
|
+
|
|
123
|
+
# delete contacts not found in the bulk list
|
|
124
|
+
(contacts_ids - contacts_data_ids).each do |contact_to_delete_id|
|
|
125
|
+
contact_to_delete = contacts.detect {|c| c.id == contact_to_delete_id }
|
|
126
|
+
contact_to_delete.delete!
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# add or update contacts found in the bulk list
|
|
130
|
+
contacts_data.reject {|cd| cd['id'].nil? }.each do |contact_data|
|
|
131
|
+
if contacts_ids.include?(contact_data['id'].to_s)
|
|
132
|
+
contacts_h[contact_data['id'].to_s].update(contact_data)
|
|
133
|
+
else
|
|
134
|
+
Flapjack::Data::Contact.add(contact_data, :redis => redis)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
semaphore.release
|
|
139
|
+
204
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Returns all the contacts
|
|
143
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts
|
|
144
|
+
app.get '/contacts' do
|
|
145
|
+
content_type :json
|
|
146
|
+
cors_headers
|
|
147
|
+
|
|
148
|
+
contacts = if params[:ids]
|
|
149
|
+
Flapjack::Data::Contact.find_by_ids(params[:ids].split(',').uniq, :redis => redis)
|
|
150
|
+
else
|
|
151
|
+
Flapjack::Data::Contact.all(:redis => redis)
|
|
152
|
+
end
|
|
153
|
+
contacts.compact!
|
|
154
|
+
|
|
155
|
+
linked_entity_data, linked_entity_ids = if contacts.empty?
|
|
156
|
+
[[], []]
|
|
157
|
+
else
|
|
158
|
+
Flapjack::Data::Contact.entities_jsonapi(contacts.map(&:id), :redis => redis)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
contacts_json = contacts.collect {|contact|
|
|
162
|
+
contact.linked_entity_ids = linked_entity_ids[contact.id]
|
|
163
|
+
contact.to_json
|
|
164
|
+
}.join(", ")
|
|
165
|
+
|
|
166
|
+
'{"contacts":[' + contacts_json + ']' +
|
|
167
|
+
( linked_entity_data.empty? ? '}' :
|
|
168
|
+
', "linked": {"entities":' + linked_entity_data.to_json + '}}')
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Returns the core information about the specified contact
|
|
172
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id
|
|
173
|
+
app.get '/contacts/:contact_id' do
|
|
174
|
+
content_type :json
|
|
175
|
+
cors_headers
|
|
176
|
+
contact = find_contact(params[:contact_id])
|
|
177
|
+
|
|
178
|
+
entities = contact.entities.map {|e| e[:entity] }
|
|
179
|
+
|
|
180
|
+
'{"contacts":[' + contact.to_json + ']' +
|
|
181
|
+
( entities.empty? ? '}' :
|
|
182
|
+
', "linked": {"entities":' + entities.values.to_json + '}}')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Updates a contact
|
|
186
|
+
app.put '/contacts/:contact_id' do
|
|
187
|
+
content_type :json
|
|
188
|
+
cors_headers
|
|
189
|
+
|
|
190
|
+
contacts_data = params[:contacts]
|
|
191
|
+
|
|
192
|
+
if contacts_data.nil? || !contacts_data.is_a?(Enumerable)
|
|
193
|
+
halt err(422, "No valid contacts were submitted")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
unless contacts_data.length == 1
|
|
197
|
+
halt err(422, "Exactly one contact hash must be supplied.")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
contact_data = contacts_data.first
|
|
201
|
+
|
|
202
|
+
if contact_data['id'] && contact_data['id'].to_s != params[:contact_id]
|
|
203
|
+
halt err(422, "ID, if supplied, must match URL")
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
contact = find_contact(params[:contact_id])
|
|
207
|
+
#contact_data = hashify('first_name', 'last_name', 'email', 'media', 'tags') {|k| [k, params[k]]}
|
|
208
|
+
logger.debug("contact_data: #{contact_data}")
|
|
209
|
+
contact.update(contact_data)
|
|
210
|
+
|
|
211
|
+
contact.to_json
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Deletes a contact
|
|
215
|
+
app.delete '/contacts/:contact_id' do
|
|
216
|
+
cors_headers
|
|
217
|
+
semaphore = obtain_semaphore(SEMAPHORE_CONTACT_MASS_UPDATE)
|
|
218
|
+
contact = find_contact(params[:contact_id])
|
|
219
|
+
contact.delete!
|
|
220
|
+
semaphore.release
|
|
221
|
+
status 204
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Lists this contact's notification rules
|
|
225
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_notification_rules
|
|
226
|
+
app.get '/contacts/:contact_id/notification_rules' do
|
|
227
|
+
content_type :json
|
|
228
|
+
cors_headers
|
|
229
|
+
|
|
230
|
+
"[" + find_contact(params[:contact_id]).notification_rules.map {|r| r.to_json }.join(',') + "]"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Get the specified notification rule for this user
|
|
234
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_notification_rules_id
|
|
235
|
+
app.get '/notification_rules/:id' do
|
|
236
|
+
content_type :json
|
|
237
|
+
cors_headers
|
|
238
|
+
|
|
239
|
+
'{"notification_rules":[' +
|
|
240
|
+
find_rule(params[:id]).to_json +
|
|
241
|
+
']}'
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Creates a notification rule or rules for a contact
|
|
245
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-post_contacts_id_notification_rules
|
|
246
|
+
app.post '/notification_rules' do
|
|
247
|
+
content_type :json
|
|
248
|
+
cors_headers
|
|
249
|
+
|
|
250
|
+
rules_data = params[:notification_rules]
|
|
251
|
+
|
|
252
|
+
if rules_data.nil? || !rules_data.is_a?(Enumerable)
|
|
253
|
+
halt err(422, "No valid notification rules were submitted")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
if rules_data.any? {|rule| rule['id']}
|
|
257
|
+
halt err(422, "ID fields may not be generated by you. Remove IDs and POST again")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
errors = []
|
|
261
|
+
rules_data.each do |rule_data|
|
|
262
|
+
errors << Flapjack::Data::NotificationRule.prevalidate_data(symbolize(rule_data), {:logger => logger})
|
|
263
|
+
end
|
|
264
|
+
errors.compact!
|
|
265
|
+
|
|
266
|
+
unless errors.nil? || errors.empty?
|
|
267
|
+
halt err(422, *errors)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
rules = []
|
|
271
|
+
errors = []
|
|
272
|
+
rules_data.each do |rule_data|
|
|
273
|
+
rule_data = symbolize(rule_data)
|
|
274
|
+
contact = find_contact(rule_data.delete(:contact_id))
|
|
275
|
+
rule_or_errors = contact.add_notification_rule(rule_data, :logger => logger)
|
|
276
|
+
if rule_or_errors.respond_to?(:critical_media)
|
|
277
|
+
rules << rule_or_errors
|
|
278
|
+
else
|
|
279
|
+
errors << rule_or_errors
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
if rules.empty?
|
|
284
|
+
halt err(422, *errors)
|
|
285
|
+
else
|
|
286
|
+
if errors.empty?
|
|
287
|
+
status 201
|
|
288
|
+
else
|
|
289
|
+
logger.warn("Errors during bulk notification rules creation: " + errors.join(', '))
|
|
290
|
+
status 200
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
ids = rules.map {|r| r.id}
|
|
294
|
+
location(ids)
|
|
295
|
+
'{"notification_rules":[' +
|
|
296
|
+
rules.map {|r| r.to_json}.join(',') +
|
|
297
|
+
']}'
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Updates a notification rule
|
|
301
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_notification_rules_id
|
|
302
|
+
app.put('/notification_rules/:id') do
|
|
303
|
+
content_type :json
|
|
304
|
+
cors_headers
|
|
305
|
+
|
|
306
|
+
rules_data = params[:notification_rules]
|
|
307
|
+
|
|
308
|
+
if rules_data.nil? || !rules_data.is_a?(Enumerable)
|
|
309
|
+
halt err(422, "No valid notification rules were submitted")
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
unless rules_data.length == 1
|
|
313
|
+
halt err(422, "Exactly one notification rules hash must be supplied.")
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
rule_data = rules_data.first
|
|
317
|
+
|
|
318
|
+
if rule_data['id'] && rule_data['id'].to_s != params[:id]
|
|
319
|
+
halt err(422, "ID, if supplied, must match URL")
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
rule = find_rule(params[:id])
|
|
323
|
+
contact = find_contact(rule.contact_id)
|
|
324
|
+
|
|
325
|
+
supplied_contact = rule_data.delete('contact_id')
|
|
326
|
+
if supplied_contact && supplied_contact != contact.id
|
|
327
|
+
halt err(422, "contact_id cannot be modified")
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
errors = rule.update(symbolize(rule_data), :logger => logger)
|
|
331
|
+
|
|
332
|
+
unless errors.nil? || errors.empty?
|
|
333
|
+
halt err(422, *errors)
|
|
334
|
+
end
|
|
335
|
+
'{"notification_rules":[' +
|
|
336
|
+
rule.to_json +
|
|
337
|
+
']}'
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Deletes a notification rule
|
|
341
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_notification_rules_id
|
|
342
|
+
app.delete('/notification_rules/:id') do
|
|
343
|
+
cors_headers
|
|
344
|
+
rule = find_rule(params[:id])
|
|
345
|
+
logger.debug("rule to delete: #{rule.inspect}, contact_id: #{rule.contact_id}")
|
|
346
|
+
contact = find_contact(rule.contact_id)
|
|
347
|
+
contact.delete_notification_rule(rule)
|
|
348
|
+
status 204
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Returns the media of a contact
|
|
352
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_media
|
|
353
|
+
app.get '/contacts/:contact_id/media' do
|
|
354
|
+
content_type :json
|
|
355
|
+
cors_headers
|
|
356
|
+
|
|
357
|
+
contact = find_contact(params[:contact_id])
|
|
358
|
+
|
|
359
|
+
media = contact.media
|
|
360
|
+
media_intervals = contact.media_intervals
|
|
361
|
+
media_rollup_thresholds = contact.media_rollup_thresholds
|
|
362
|
+
media_addr_int = hashify(*media.keys) {|k|
|
|
363
|
+
[k, {'address' => media[k],
|
|
364
|
+
'interval' => media_intervals[k],
|
|
365
|
+
'rollup_threshold' => media_rollup_thresholds[k] }]
|
|
366
|
+
}
|
|
367
|
+
media_addr_int.to_json
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Returns the specified media of a contact
|
|
371
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_media_media
|
|
372
|
+
app.get('/contacts/:contact_id/media/:id') do
|
|
373
|
+
content_type :json
|
|
374
|
+
cors_headers
|
|
375
|
+
|
|
376
|
+
contact = find_contact(params[:contact_id])
|
|
377
|
+
media = contact.media[params[:id]]
|
|
378
|
+
if media.nil?
|
|
379
|
+
halt err(404, "no #{params[:id]} for contact '#{params[:contact_id]}'")
|
|
380
|
+
end
|
|
381
|
+
interval = contact.media_intervals[params[:id]]
|
|
382
|
+
# FIXME: does erroring when no interval found make sense?
|
|
383
|
+
if interval.nil?
|
|
384
|
+
halt err(403, "no #{params[:id]} interval for contact '#{params[:contact_id]}'")
|
|
385
|
+
end
|
|
386
|
+
rollup_threshold = contact.media_rollup_thresholds[params[:id]]
|
|
387
|
+
{'address' => media,
|
|
388
|
+
'interval' => interval,
|
|
389
|
+
'rollup_threshold' => rollup_threshold }.to_json
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Creates or updates a media of a contact
|
|
393
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_media_media
|
|
394
|
+
app.put('/contacts/:contact_id/media/:id') do
|
|
395
|
+
content_type :json
|
|
396
|
+
cors_headers
|
|
397
|
+
|
|
398
|
+
contact = find_contact(params[:contact_id])
|
|
399
|
+
errors = []
|
|
400
|
+
|
|
401
|
+
if 'pagerduty'.eql?(params[:id])
|
|
402
|
+
errors = [:service_key, :subdomain, :username, :password].inject([]) do |memo, pdp|
|
|
403
|
+
memo << "no #{pdp.to_s} for 'pagerduty' media" if params[pdp].nil?
|
|
404
|
+
memo
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
halt err(422, *errors) unless errors.empty?
|
|
408
|
+
|
|
409
|
+
contact.set_pagerduty_credentials('service_key' => params[:service_key],
|
|
410
|
+
'subdomain' => params[:subdomain],
|
|
411
|
+
'username' => params[:username],
|
|
412
|
+
'password' => params[:password])
|
|
413
|
+
|
|
414
|
+
contact.pagerduty_credentials.to_json
|
|
415
|
+
else
|
|
416
|
+
if params[:address].nil?
|
|
417
|
+
errors << "no address for '#{params[:id]}' media"
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
halt err(422, *errors) unless errors.empty?
|
|
421
|
+
|
|
422
|
+
contact.set_address_for_media(params[:id], params[:address])
|
|
423
|
+
contact.set_interval_for_media(params[:id], params[:interval])
|
|
424
|
+
contact.set_rollup_threshold_for_media(params[:id], params[:rollup_threshold])
|
|
425
|
+
|
|
426
|
+
{'address' => contact.media[params[:id]],
|
|
427
|
+
'interval' => contact.media_intervals[params[:id]],
|
|
428
|
+
'rollup_threshold' => contact.media_rollup_thresholds[params[:id]]}.to_json
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# delete a media of a contact
|
|
433
|
+
app.delete('/contacts/:contact_id/media/:id') do
|
|
434
|
+
cors_headers
|
|
435
|
+
contact = find_contact(params[:contact_id])
|
|
436
|
+
contact.remove_media(params[:id])
|
|
437
|
+
status 204
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Returns the timezone of a contact
|
|
441
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-get_contacts_id_timezone
|
|
442
|
+
app.get('/contacts/:contact_id/timezone') do
|
|
443
|
+
content_type :json
|
|
444
|
+
cors_headers
|
|
445
|
+
|
|
446
|
+
contact = find_contact(params[:contact_id])
|
|
447
|
+
contact.timezone.name.to_json
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Sets the timezone of a contact
|
|
451
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_timezone
|
|
452
|
+
app.put('/contacts/:contact_id/timezone') do
|
|
453
|
+
content_type :json
|
|
454
|
+
cors_headers
|
|
455
|
+
|
|
456
|
+
contact = find_contact(params[:contact_id])
|
|
457
|
+
contact.timezone = params[:timezone]
|
|
458
|
+
contact.timezone.name.to_json
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# Removes the timezone of a contact
|
|
462
|
+
# https://github.com/flpjck/flapjack/wiki/API#wiki-put_contacts_id_timezone
|
|
463
|
+
app.delete('/contacts/:contact_id/timezone') do
|
|
464
|
+
cors_headers
|
|
465
|
+
contact = find_contact(params[:contact_id])
|
|
466
|
+
contact.timezone = nil
|
|
467
|
+
status 204
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
app.post '/contacts/:contact_id/tags' do
|
|
471
|
+
content_type :json
|
|
472
|
+
cors_headers
|
|
473
|
+
|
|
474
|
+
tags = find_tags(params[:tags])
|
|
475
|
+
contact = find_contact(params[:contact_id])
|
|
476
|
+
contact.add_tags(*tags)
|
|
477
|
+
'{"tags":' +
|
|
478
|
+
contact.tags.to_json +
|
|
479
|
+
'}'
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
app.post '/contacts/:contact_id/entity_tags' do
|
|
483
|
+
content_type :json
|
|
484
|
+
cors_headers
|
|
485
|
+
contact = find_contact(params[:contact_id])
|
|
486
|
+
contact.entities.map {|e| e[:entity]}.each do |entity|
|
|
487
|
+
next unless tags = params[:entity][entity.name]
|
|
488
|
+
entity.add_tags(*tags)
|
|
489
|
+
end
|
|
490
|
+
contact_ent_tag = hashify(*contact.entities(:tags => true)) {|et|
|
|
491
|
+
[et[:entity].name, et[:tags]]
|
|
492
|
+
}
|
|
493
|
+
contact_ent_tag.to_json
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
app.delete '/contacts/:contact_id/tags' do
|
|
497
|
+
cors_headers
|
|
498
|
+
tags = find_tags(params[:tags])
|
|
499
|
+
contact = find_contact(params[:contact_id])
|
|
500
|
+
contact.delete_tags(*tags)
|
|
501
|
+
status 204
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
app.delete '/contacts/:contact_id/entity_tags' do
|
|
505
|
+
cors_headers
|
|
506
|
+
contact = find_contact(params[:contact_id])
|
|
507
|
+
contact.entities.map {|e| e[:entity]}.each do |entity|
|
|
508
|
+
next unless tags = params[:entity][entity.name]
|
|
509
|
+
entity.delete_tags(*tags)
|
|
510
|
+
end
|
|
511
|
+
status 204
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
app.get '/contacts/:contact_id/tags' do
|
|
515
|
+
content_type :json
|
|
516
|
+
cors_headers
|
|
517
|
+
|
|
518
|
+
contact = find_contact(params[:contact_id])
|
|
519
|
+
'{"tags":' +
|
|
520
|
+
contact.tags.to_json +
|
|
521
|
+
'}'
|
|
522
|
+
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
app.get '/contacts/:contact_id/entity_tags' do
|
|
526
|
+
content_type :json
|
|
527
|
+
cors_headers
|
|
528
|
+
|
|
529
|
+
contact = find_contact(params[:contact_id])
|
|
530
|
+
contact_ent_tag = hashify(*contact.entities(:tags => true)) {|et|
|
|
531
|
+
[et[:entity].name, et[:tags]]
|
|
532
|
+
}
|
|
533
|
+
contact_ent_tag.to_json
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
end
|