sp-job 0.2.3 → 0.3.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/configure +40 -0
- data/lib/sp-job.rb +21 -2
- data/lib/sp/job/back_burner.rb +350 -68
- data/lib/sp/job/broker.rb +18 -16
- data/lib/sp/job/broker_http_client.rb +80 -20
- data/lib/sp/job/broker_oauth2_client.rb +12 -4
- data/lib/sp/job/common.rb +876 -62
- data/lib/sp/job/configure/configure.rb +640 -0
- data/lib/sp/job/curl_http_client.rb +100 -0
- data/lib/sp/job/easy_http_client.rb +94 -0
- data/lib/sp/job/http_client.rb +51 -0
- data/lib/sp/job/job_db_adapter.rb +38 -36
- data/lib/sp/job/jsonapi_error.rb +31 -74
- data/lib/sp/job/jwt.rb +55 -5
- data/lib/sp/job/mail_queue.rb +9 -2
- data/lib/sp/job/manticore_http_client.rb +94 -0
- data/lib/sp/job/pg_connection.rb +90 -10
- data/lib/sp/job/query_params.rb +45 -0
- data/lib/sp/job/rfc822.rb +13 -0
- data/lib/sp/job/session.rb +239 -0
- data/lib/sp/job/unique_file.rb +37 -1
- data/lib/sp/job/uploaded_image_converter.rb +27 -19
- data/lib/sp/job/worker.rb +51 -1
- data/lib/sp/job/worker_thread.rb +22 -7
- data/lib/sp/jsonapi.rb +24 -0
- data/lib/sp/jsonapi/adapters/base.rb +177 -0
- data/lib/sp/jsonapi/adapters/db.rb +26 -0
- data/lib/sp/jsonapi/adapters/raw_db.rb +96 -0
- data/lib/sp/jsonapi/exceptions.rb +54 -0
- data/lib/sp/jsonapi/model/base.rb +31 -0
- data/lib/sp/jsonapi/model/concerns/attributes.rb +91 -0
- data/lib/sp/jsonapi/model/concerns/model.rb +39 -0
- data/lib/sp/jsonapi/model/concerns/persistence.rb +212 -0
- data/lib/sp/jsonapi/model/concerns/serialization.rb +57 -0
- data/lib/sp/jsonapi/parameters.rb +54 -0
- data/lib/sp/jsonapi/service.rb +96 -0
- data/lib/tasks/configure.rake +2 -496
- data/sp-job.gemspec +3 -2
- metadata +24 -2
@@ -0,0 +1,100 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
# encoding: utf-8
|
20
|
+
#
|
21
|
+
|
22
|
+
#
|
23
|
+
# A helper class to do HTTP request without session management.
|
24
|
+
#
|
25
|
+
|
26
|
+
require 'curb'
|
27
|
+
require_relative 'easy_http_client'
|
28
|
+
require_relative 'jsonapi_error'
|
29
|
+
|
30
|
+
module SP
|
31
|
+
module Job
|
32
|
+
class CurlHTTPClient < EasyHttpClient
|
33
|
+
def self.post(url:, headers:, body:, expect:, conn_options: {})
|
34
|
+
# since we're not auto-renew tokens, we can use a simple CURL request
|
35
|
+
conn_options[:connection_timeout] ||= 10
|
36
|
+
conn_options[:request_timeout] ||= 60
|
37
|
+
r = Curl::Easy.http_post(url, body) do |handle|
|
38
|
+
handle.connect_timeout = conn_options[:connection_timeout]
|
39
|
+
handle.timeout = conn_options[:request_timeout]
|
40
|
+
headers.each do |k,v|
|
41
|
+
handle.headers[k] = v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
nr = self.normalize_response(curb_r: r)
|
45
|
+
# compare status code
|
46
|
+
if nr[:code] != expect[:code]
|
47
|
+
if 401 == nr[:code]
|
48
|
+
raise ::SP::Job::JSONAPI::Error.new(status: nr[:code], code: 'A01', detail: nil)
|
49
|
+
else
|
50
|
+
raise ::SP::Job::JSONAPI::Error.new(status: nr[:code], code: 'B01', detail: nil)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# compare content-type
|
54
|
+
if nr[:content][:type] != expect[:content][:type]
|
55
|
+
raise ::SP::Job::JSONAPI::Error.new(status: 500, code: 'I01', detail: "Unexpected 'Content-Type': #{nr[:content][:type]}, expected #{expect[:content][:type]}!")
|
56
|
+
end
|
57
|
+
# done
|
58
|
+
nr
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.get(url:)
|
62
|
+
response = Curl::Easy.http_get(url)
|
63
|
+
self.normalize_response(curb_r: response)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.delete(url:, headers:)
|
67
|
+
response = Curl::Easy.http_delete(url, headers)
|
68
|
+
self.normalize_response(curb_r: response)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def self.normalize_response(curb_r:)
|
74
|
+
http_response, *http_headers = curb_r.header_str.split(/[\r\n]+/).map(&:strip)
|
75
|
+
o = {
|
76
|
+
code: curb_r.response_code,
|
77
|
+
body: curb_r.body,
|
78
|
+
description: http_reason(code: curb_r.response_code),
|
79
|
+
content: {
|
80
|
+
type: nil,
|
81
|
+
length: 0
|
82
|
+
}
|
83
|
+
}
|
84
|
+
http_headers.each do |header|
|
85
|
+
m = header.match("(^Content-Type){1}:\s(.*){1}")
|
86
|
+
if nil != m && 3 == m.length
|
87
|
+
o[:content][:type] = m[2]
|
88
|
+
end
|
89
|
+
m = header.match("(^Content-Length){1}:\s\([0-9]+){1}")
|
90
|
+
if nil != m && 3 == m.length
|
91
|
+
o[:content][:length] = m[2]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
o
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
# encoding: utf-8
|
20
|
+
#
|
21
|
+
|
22
|
+
module SP
|
23
|
+
module Job
|
24
|
+
class EasyHttpClient
|
25
|
+
@@REASONS = {
|
26
|
+
100 => 'Continue',
|
27
|
+
101 => 'Switching Protocols',
|
28
|
+
102 => 'Processing',
|
29
|
+
200 => 'OK',
|
30
|
+
201 => 'Created',
|
31
|
+
202 => 'Accepted',
|
32
|
+
203 => 'Non-Authoritative Information',
|
33
|
+
204 => 'No Content',
|
34
|
+
205 => 'Reset Content',
|
35
|
+
206 => 'Partial Content',
|
36
|
+
207 => 'Multi-Status',
|
37
|
+
226 => 'IM Used',
|
38
|
+
300 => 'Multiple Choices',
|
39
|
+
301 => 'Moved Permanently',
|
40
|
+
302 => 'Found',
|
41
|
+
303 => 'See Other',
|
42
|
+
304 => 'Not Modified',
|
43
|
+
305 => 'Use Proxy',
|
44
|
+
306 => 'Reserved',
|
45
|
+
307 => 'Temporary Redirect',
|
46
|
+
308 => 'Permanent Redirect',
|
47
|
+
400 => 'Bad Request',
|
48
|
+
401 => 'Unauthorized',
|
49
|
+
402 => 'Payment Required',
|
50
|
+
403 => 'Forbidden',
|
51
|
+
404 => 'Not Found',
|
52
|
+
405 => 'Method Not Allowed',
|
53
|
+
406 => 'Not Acceptable',
|
54
|
+
407 => 'Proxy Authentication Required',
|
55
|
+
408 => 'Request Timeout',
|
56
|
+
409 => 'Conflict',
|
57
|
+
410 => 'Gone',
|
58
|
+
411 => 'Length Required',
|
59
|
+
412 => 'Precondition Failed',
|
60
|
+
413 => 'Request Entity Too Large',
|
61
|
+
414 => 'Request-URI Too Long',
|
62
|
+
415 => 'Unsupported Media Type',
|
63
|
+
416 => 'Requested Range Not Satisfiable',
|
64
|
+
417 => 'Expectation Failed',
|
65
|
+
418 => "I'm a Teapot",
|
66
|
+
422 => 'Unprocessable Entity',
|
67
|
+
423 => 'Locked',
|
68
|
+
424 => 'Failed Dependency',
|
69
|
+
426 => 'Upgrade Required',
|
70
|
+
500 => 'Internal Server Error',
|
71
|
+
501 => 'Not Implemented',
|
72
|
+
502 => 'Bad Gateway',
|
73
|
+
503 => 'Service Unavailable',
|
74
|
+
504 => 'Gateway Timeout',
|
75
|
+
505 => 'HTTP Version Not Supported',
|
76
|
+
506 => 'Variant Also Negotiates',
|
77
|
+
507 => 'Insufficient Storage',
|
78
|
+
510 => 'Not Extended'
|
79
|
+
}
|
80
|
+
|
81
|
+
def self.http_reason(code:)
|
82
|
+
return @@REASONS[code]
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.post(url:, headers:, body:, expect:)
|
86
|
+
raise NotImplementedError
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.get(url:)
|
90
|
+
raise NotImplementedError
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
# encoding: utf-8
|
20
|
+
#
|
21
|
+
|
22
|
+
#
|
23
|
+
# A helper class to do HTTP request without session management.
|
24
|
+
#
|
25
|
+
|
26
|
+
require_relative 'curl_http_client' unless RUBY_ENGINE == 'jruby'
|
27
|
+
require_relative 'manticore_http_client' if RUBY_ENGINE == 'jruby'
|
28
|
+
|
29
|
+
module SP
|
30
|
+
module Job
|
31
|
+
class HttpClient < EasyHttpClient
|
32
|
+
|
33
|
+
def self.get_klass
|
34
|
+
RUBY_ENGINE == 'jruby' ? ManticoreHTTPClient : CurlHTTPClient
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.post(url:, headers:, body:, expect:, conn_options: {})
|
38
|
+
get_klass.post(url: url, headers: headers, body: body, expect: expect, conn_options: conn_options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.get(url:)
|
42
|
+
get_klass.get(url: url)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.delete(url:, headers:)
|
46
|
+
get_klass.delete(url: url, headers: headers)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,60 +1,62 @@
|
|
1
1
|
module SP
|
2
2
|
module Job
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
class JobDbAdapter < ::SP::Duh::JSONAPI::Adapters::Db
|
4
|
+
if RUBY_ENGINE == 'jruby' # TODO suck in the base class from SP-DUH
|
5
|
+
class JobDbAdapter < ::SP::JSONAPI::Adapters::Db
|
7
6
|
|
8
7
|
private
|
9
8
|
|
10
9
|
# Implement the JSONAPI request by direct querying of the JSONAPI function in the database
|
11
10
|
def do_request_on_the_db(method, path, params)
|
12
|
-
jsonapi_query = %Q[ SELECT * FROM public.jsonapi(
|
11
|
+
jsonapi_query = %Q[ SELECT * FROM public.jsonapi('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ]
|
13
12
|
|
14
|
-
response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id,
|
13
|
+
response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id, entity_id, entity_schema, sharded_schema, subentity_schema, subentity_prefix
|
15
14
|
response.first if response.first
|
16
15
|
end
|
17
16
|
|
18
|
-
def explicit_do_request_on_the_db(
|
19
|
-
jsonapi_query = %Q[ SELECT * FROM public.jsonapi(
|
17
|
+
def explicit_do_request_on_the_db(exp_subentity_schema, exp_subentity_prefix, method, path, params)
|
18
|
+
jsonapi_query = %Q[ SELECT * FROM public.jsonapi('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ]
|
20
19
|
|
21
|
-
response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id,
|
20
|
+
response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id, entity_id, entity_schema, sharded_schema, exp_subentity_schema, exp_subentity_prefix
|
22
21
|
response.first if response.first
|
23
22
|
end
|
24
23
|
|
25
|
-
def user_id
|
26
|
-
def
|
27
|
-
def
|
28
|
-
def sharded_schema
|
29
|
-
def
|
30
|
-
def
|
24
|
+
def user_id ; service.parameters.user_id ; end
|
25
|
+
def entity_id ; service.parameters.entity_id ; end
|
26
|
+
def entity_schema ; service.parameters.entity_schema.nil? ? nil : service.parameters.entity_schema ; end
|
27
|
+
def sharded_schema ; service.parameters.sharded_schema.nil? ? nil : service.parameters.sharded_schema ; end
|
28
|
+
def subentity_schema ; service.parameters.subentity_schema.nil? ? nil : service.parameters.subentity_schema ; end
|
29
|
+
def subentity_prefix ; service.parameters.subentity_prefix.nil? ? nil : service.parameters.subentity_prefix ; end
|
30
|
+
|
31
|
+
end
|
32
|
+
else
|
33
|
+
|
34
|
+
class JobDbAdapter < ::SP::Duh::JSONAPI::Adapters::Db
|
35
|
+
|
36
|
+
private
|
31
37
|
|
32
|
-
|
33
|
-
|
38
|
+
# Implement the JSONAPI request by direct querying of the JSONAPI function in the database
|
39
|
+
def do_request_on_the_db(method, path, params)
|
40
|
+
jsonapi_query = %Q[ SELECT * FROM public.jsonapi('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ]
|
41
|
+
|
42
|
+
response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id, entity_id, entity_schema, sharded_schema, subentity_schema, subentity_prefix
|
43
|
+
response.first if response.first
|
34
44
|
end
|
35
45
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
# query = params.join('&')
|
42
|
-
query = params.map{ |v| URI.encode(URI.encode(v), "&") }.join('&')
|
43
|
-
when params.is_a?(Hash)
|
44
|
-
query = params.map do |k,v|
|
45
|
-
if v.is_a?(String)
|
46
|
-
"#{k}=\"#{URI.encode(URI.encode(v), "&")}\""
|
47
|
-
else
|
48
|
-
"#{k}=#{v}"
|
49
|
-
end
|
50
|
-
end.join('&')
|
51
|
-
else
|
52
|
-
query = params.to_s
|
53
|
-
end
|
54
|
-
end
|
55
|
-
query
|
46
|
+
def explicit_do_request_on_the_db(exp_subentity_schema, exp_subentity_prefix, method, path, params)
|
47
|
+
jsonapi_query = %Q[ SELECT * FROM public.jsonapi('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ]
|
48
|
+
|
49
|
+
response = service.connection.exec jsonapi_query, method, (method == 'GET' ? url_with_params_for_query(path, params) : url(path)), (method == 'GET' ? '' : params_for_body(params)), user_id, entity_id, entity_schema, sharded_schema, exp_subentity_schema, exp_subentity_prefix
|
50
|
+
response.first if response.first
|
56
51
|
end
|
57
52
|
|
53
|
+
def user_id ; service.parameters.user_id ; end
|
54
|
+
def entity_id ; service.parameters.entity_id ; end
|
55
|
+
def entity_schema ; service.parameters.entity_schema.nil? ? nil : service.parameters.entity_schema ; end
|
56
|
+
def sharded_schema ; service.parameters.sharded_schema.nil? ? nil : service.parameters.sharded_schema ; end
|
57
|
+
def subentity_schema ; service.parameters.subentity_schema.nil? ? nil : service.parameters.subentity_schema ; end
|
58
|
+
def subentity_prefix ; service.parameters.subentity_prefix.nil? ? nil : service.parameters.subentity_prefix ; end
|
59
|
+
|
58
60
|
end
|
59
61
|
|
60
62
|
end
|
data/lib/sp/job/jsonapi_error.rb
CHANGED
@@ -7,88 +7,45 @@
|
|
7
7
|
# Helper to obtain tokens to access toconline API's.
|
8
8
|
#
|
9
9
|
|
10
|
+
require 'sp/job/common'
|
11
|
+
|
10
12
|
module SP
|
11
13
|
module Job
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# "meta": {
|
25
|
-
# "internal-error": nullptr
|
26
|
-
# }
|
27
|
-
# }
|
28
|
-
# ]
|
29
|
-
# }
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
@error
|
34
|
-
|
35
|
-
public
|
36
|
-
|
37
|
-
def initialize (code: 500, detail: nil, internal: nil)
|
38
|
-
@errors = [
|
39
|
-
{
|
40
|
-
:code => code,
|
41
|
-
:detail => detail
|
14
|
+
module JSONAPI
|
15
|
+
|
16
|
+
class Error < ::SP::Job::Common::Exception
|
17
|
+
def initialize(status:, code:, detail:, internal:nil)
|
18
|
+
body = {
|
19
|
+
errors: [
|
20
|
+
{
|
21
|
+
code: "#{code}",
|
22
|
+
detail: "#{detail}",
|
23
|
+
status: "#{status} - #{::SP::Job::HttpClient.http_reason(code: status)}"
|
24
|
+
}
|
25
|
+
]
|
42
26
|
}
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
@errors[0][:status] = "Unauthorized"
|
50
|
-
when 403
|
51
|
-
@errors[0][:status] = "Forbidden"
|
52
|
-
when 404
|
53
|
-
@errors[0][:status] = "Not Found"
|
54
|
-
when 405
|
55
|
-
@errors[0][:status] = "Method Not Allowed"
|
56
|
-
when 406
|
57
|
-
@errors[0][:status] = "Not Acceptable"
|
58
|
-
when 408
|
59
|
-
@errors[0][:status] = "Request Timeout"
|
60
|
-
# 5xx
|
61
|
-
when 501
|
62
|
-
@errors[0][:status] = "Not Implemented"
|
63
|
-
else
|
64
|
-
# other
|
65
|
-
@errors[0][:status] = "Internal Server Error"
|
66
|
-
end
|
67
|
-
@errors[0][:status] = "#{code} #{@errors[0][:status]}"
|
68
|
-
if nil != internal
|
69
|
-
@errors[0][:meta] = { :'internal-error' => internal }
|
27
|
+
if nil != internal
|
28
|
+
body[:meta] = {
|
29
|
+
'internal-error' => internal
|
30
|
+
}
|
31
|
+
end
|
32
|
+
super(status_code: status, content_type: 'application/vnd.api+json;charset=utf-8', body: body)
|
70
33
|
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def code ()
|
74
|
-
return @errors[0][:code]
|
75
|
-
end
|
76
34
|
|
77
|
-
|
78
|
-
|
79
|
-
|
35
|
+
def code
|
36
|
+
@body[:errors][0][:code]
|
37
|
+
end
|
80
38
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
}
|
85
|
-
end
|
39
|
+
def message
|
40
|
+
@body[:errors][0][:detail]
|
41
|
+
end
|
86
42
|
|
87
|
-
|
88
|
-
|
89
|
-
|
43
|
+
def content_type_and_body
|
44
|
+
[ 'application/vnd.api+json', message ]
|
45
|
+
end
|
90
46
|
|
91
|
-
|
47
|
+
end # class Error
|
92
48
|
|
49
|
+
end # JSONAPI module
|
93
50
|
end # Job module
|
94
51
|
end # SP module
|