adobe_doc_api 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +17 -10
- data/lib/adobe_doc_api/client.rb +88 -65
- data/lib/adobe_doc_api/error.rb +25 -2
- data/lib/adobe_doc_api/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d1e60f6687db22347aa8a8d772e066716ff6b9c9d5d9065ab0395543ab8808e
|
4
|
+
data.tar.gz: 40f43095bb7fb02dd9a8724941c823c3870965a40b313f9e580e9d919dc13def
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d47936fe2d22f56b0268c1e60019847a45b63ff35c5dac178e43fe5e597e68ccb9c3be16c293101410ccdc94dc9b4522c891852792d84a42bcd67c3b32a44288
|
7
|
+
data.tar.gz: 2c479317760c7ae45aa6dfd31ac6606cf71e7db98107cd9e1bbd308e19a017816aed99441420492ff400959f24fc4386285b26834c481872cc285f11f3f39b18
|
data/README.md
CHANGED
@@ -18,7 +18,8 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
$ gem install adobe_doc_api
|
20
20
|
|
21
|
-
##
|
21
|
+
## Recommended ENV variables
|
22
|
+
*Client.new will fallback to use these variables if they are not passed in setup
|
22
23
|
```ruby
|
23
24
|
ENV['adobe_org_id']
|
24
25
|
ENV['adobe_tech_account_id']
|
@@ -30,22 +31,28 @@ ENV['adobe_client_secret']
|
|
30
31
|
|
31
32
|
```ruby
|
32
33
|
key_path = "../full_path_to/private.key"
|
33
|
-
|
34
|
-
|
34
|
+
template_path = "../full_path_to/disclosure.docx"
|
35
|
+
output_path = "../full_path_to_output/output.docx"
|
35
36
|
json_data = { 'DocTag': 'Value', 'DocTag2': 'Value2'}
|
36
|
-
client = AdobeDocApi::Client.new(private_key_path: key_path, destination_path: destination)
|
37
|
-
client.submit(json: json_data, disclosure_file_path: doc_path)
|
38
|
-
```
|
39
37
|
|
40
|
-
|
38
|
+
client = AdobeDocApi::Client.new(private_key: key_path)
|
39
|
+
# Without ENV variables set
|
40
|
+
# client = AdobeDocApi::Client.new(private_key: key_path, client_id: ENV['adobe_client_id'], client_secret: ENV['adobe_client_secret']org_id: ENV['adobe_org_id'], tech_account_id: ENV['adobe_tech_account_id'], access_token: nil)
|
41
41
|
|
42
|
-
|
42
|
+
client.submit(json: json_data, template: template_path, output: output_path)
|
43
|
+
```
|
43
44
|
|
44
|
-
|
45
|
+
## Todo
|
46
|
+
- [x] Add multipart parsing to improve saving the file from the response
|
47
|
+
- [ ] Add documentation
|
45
48
|
|
46
49
|
## Contributing
|
47
50
|
|
48
|
-
|
51
|
+
1. Fork it
|
52
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
53
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
54
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
55
|
+
5. Create new Pull Request
|
49
56
|
|
50
57
|
## License
|
51
58
|
|
data/lib/adobe_doc_api/client.rb
CHANGED
@@ -1,116 +1,139 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "faraday"
|
2
|
+
require "faraday_middleware"
|
3
|
+
require "jwt"
|
4
|
+
require "openssl"
|
5
5
|
|
6
6
|
module AdobeDocApi
|
7
7
|
class Client
|
8
|
-
JWT_URL =
|
9
|
-
API_ENDPOINT_URL =
|
10
|
-
attr_reader :access_token, :output_ext, :output_format, :poll_url, :content_request
|
8
|
+
JWT_URL = "https://ims-na1.adobelogin.com/ims/exchange/jwt/".freeze
|
9
|
+
API_ENDPOINT_URL = "https://cpf-ue1.adobe.io".freeze
|
11
10
|
|
12
|
-
|
13
|
-
@destination_path = destination_path
|
14
|
-
@output_ext = File.extname(destination_path)
|
15
|
-
@output_format = @output_ext =~ /docx/ ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'application/pdf'
|
11
|
+
attr_reader :access_token, :location_url, :raw_response, :client_id, :client_secret, :org_id, :tech_account_id
|
16
12
|
|
13
|
+
def initialize(private_key:, client_id: ENV["adobe_client_id"], client_secret: ENV["adobe_client_secret"], org_id: ENV["adobe_org_id"], tech_account_id: ENV["adobe_tech_account_id"], access_token: nil)
|
14
|
+
# TODO Need to validate if any params are missing and return error
|
15
|
+
@client_id = client_id
|
16
|
+
@client_secret = client_secret
|
17
|
+
@org_id = org_id
|
18
|
+
@tech_account_id = tech_account_id
|
19
|
+
@location_url = nil
|
20
|
+
@output_file_path = nil
|
21
|
+
@raw_response = nil
|
22
|
+
@access_token = access_token || get_access_token(private_key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_access_token(private_key)
|
17
26
|
jwt_payload = {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
27
|
+
"iss" => @org_id,
|
28
|
+
"sub" => @tech_account_id,
|
29
|
+
"https://ims-na1.adobelogin.com/s/ent_documentcloud_sdk" => true,
|
30
|
+
"aud" => "https://ims-na1.adobelogin.com/c/#{@client_id}",
|
31
|
+
"exp" => (Time.now.utc + 60).to_i
|
23
32
|
}
|
24
33
|
|
25
|
-
rsa_private = OpenSSL::PKey::RSA.new File.read(
|
26
|
-
|
34
|
+
rsa_private = OpenSSL::PKey::RSA.new File.read(private_key)
|
35
|
+
|
36
|
+
jwt_token = JWT.encode jwt_payload, rsa_private, "RS256"
|
27
37
|
|
28
38
|
connection = Faraday.new do |conn|
|
29
|
-
conn.response :json, content_type:
|
39
|
+
conn.response :json, content_type: "application/json"
|
30
40
|
end
|
31
|
-
|
32
41
|
response = connection.post JWT_URL do |req|
|
33
|
-
req.params[
|
34
|
-
req.params[
|
35
|
-
req.params[
|
42
|
+
req.params["client_id"] = @client_id
|
43
|
+
req.params["client_secret"] = @client_secret
|
44
|
+
req.params["jwt_token"] = jwt_token
|
36
45
|
end
|
37
46
|
|
38
|
-
|
47
|
+
return response.body["access_token"]
|
39
48
|
|
40
49
|
end
|
41
50
|
|
42
|
-
def submit(json:,
|
43
|
-
@
|
51
|
+
def submit(json:, template:, output:)
|
52
|
+
@output = output
|
53
|
+
output_format = /docx/.match?(File.extname(@output)) ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/pdf"
|
54
|
+
|
55
|
+
content_request = {
|
44
56
|
"cpf:engine": {
|
45
|
-
"repo:assetId":
|
57
|
+
"repo:assetId": "urn:aaid:cpf:Service-52d5db6097ed436ebb96f13a4c7bf8fb"
|
46
58
|
},
|
47
59
|
"cpf:inputs": {
|
48
|
-
|
49
|
-
"dc:format":
|
50
|
-
"cpf:location":
|
60
|
+
documentIn: {
|
61
|
+
"dc:format": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
62
|
+
"cpf:location": "InputFile0"
|
51
63
|
},
|
52
|
-
|
64
|
+
params: {
|
53
65
|
"cpf:inline": {
|
54
|
-
|
55
|
-
|
66
|
+
outputFormat: File.extname(@output).delete("."),
|
67
|
+
jsonDataForMerge: json
|
56
68
|
}
|
57
69
|
}
|
58
70
|
},
|
59
71
|
"cpf:outputs": {
|
60
|
-
|
61
|
-
"dc:format":
|
62
|
-
"cpf:location":
|
72
|
+
documentOut: {
|
73
|
+
"dc:format": output_format.to_s,
|
74
|
+
"cpf:location": "multipartLabel"
|
63
75
|
}
|
64
76
|
}
|
65
77
|
}.to_json
|
66
78
|
|
67
79
|
connection = Faraday.new API_ENDPOINT_URL do |conn|
|
68
|
-
conn.request :authorization,
|
69
|
-
conn.headers[
|
80
|
+
conn.request :authorization, "Bearer", @access_token
|
81
|
+
conn.headers["x-api-key"] = @client_id
|
70
82
|
conn.request :multipart
|
71
83
|
conn.request :url_encoded
|
72
|
-
conn.response :json, content_type:
|
84
|
+
conn.response :json, content_type: "application/json"
|
73
85
|
end
|
74
86
|
|
75
|
-
payload = {
|
76
|
-
payload[:InputFile0] = Faraday::FilePart.new(
|
77
|
-
res = connection.post(
|
78
|
-
|
79
|
-
|
87
|
+
payload = {"contentAnalyzerRequests" => content_request}
|
88
|
+
payload[:InputFile0] = Faraday::FilePart.new(template, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
|
89
|
+
res = connection.post("/ops/:create", payload)
|
90
|
+
status_code = res.body["cpf:status"]["status"].to_i
|
91
|
+
@location_url = res.headers["location"]
|
92
|
+
raise Error.new(status_code: status_code, msg: res.body["cpf:status"]) unless status_code == 202
|
93
|
+
poll_for_file(@location_url)
|
80
94
|
end
|
81
95
|
|
96
|
+
private
|
97
|
+
|
82
98
|
def poll_for_file(url)
|
83
|
-
|
84
|
-
conn.request :authorization,
|
85
|
-
conn.headers[
|
99
|
+
connection = Faraday.new do |conn|
|
100
|
+
conn.request :authorization, "Bearer", @access_token
|
101
|
+
conn.headers["x-api-key"] = @client_id
|
86
102
|
end
|
87
103
|
counter = 0
|
88
104
|
loop do
|
89
105
|
sleep(6)
|
90
|
-
|
106
|
+
response = connection.get(url)
|
91
107
|
counter += 1
|
92
|
-
if
|
93
|
-
|
94
|
-
|
108
|
+
if response.body.include?('"cpf:status":{"completed":true,"type":"","status":200}')
|
109
|
+
@raw_response = response
|
110
|
+
return write_to_file(response.body)
|
111
|
+
else
|
112
|
+
status = JSON.parse(response.body)["cpf:status"]
|
113
|
+
raise Error.new(status_code: status["status"], msg: status) if status["status"] != 202
|
95
114
|
end
|
96
115
|
break if counter > 10
|
97
|
-
rescue
|
98
|
-
#
|
116
|
+
rescue => e
|
117
|
+
# Raise other exceptions
|
99
118
|
raise(e)
|
100
119
|
end
|
101
120
|
end
|
102
121
|
|
103
|
-
def write_to_file(
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
122
|
+
def write_to_file(response_body)
|
123
|
+
line_index = []
|
124
|
+
lines = response_body.split("\r\n")
|
125
|
+
lines.each_with_index do |line, i|
|
126
|
+
next if line.include?("--Boundary_") || line.match?(/^Content-(Type|Disposition):/) || line.empty? || JSON.parse(line.force_encoding("UTF-8").to_s)
|
127
|
+
rescue
|
128
|
+
line_index << i
|
129
|
+
end
|
130
|
+
if line_index.length == 1
|
131
|
+
File.open(@output, "wb") { |f| f.write lines.at(line_index[0])}
|
132
|
+
true
|
133
|
+
else
|
134
|
+
false
|
135
|
+
end
|
114
136
|
end
|
137
|
+
|
115
138
|
end
|
116
|
-
end
|
139
|
+
end
|
data/lib/adobe_doc_api/error.rb
CHANGED
@@ -1,3 +1,26 @@
|
|
1
1
|
module AdobeDocApi
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :status_code
|
5
|
+
|
6
|
+
def initialize(status_code:, msg:)
|
7
|
+
super
|
8
|
+
@status_code = status_code
|
9
|
+
@msg = msg
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
case @status_code
|
14
|
+
when 200 then "The operation has failed due to some reason before the wait time provided in the Prefer header specified in the request (if Prefer header was not specified then the wait time is 59s by default), for detailed error refer cpf:status field inside the response body. Expect 200 HTTP status code only when respond-async,wait=0 is NOT specified in the Prefer Header value while creating the request. Refer the response body structure from GET call 200 response."
|
15
|
+
when 201 then "The operation is completed successfully before the wait time provided in the Prefer header specified in the request (if Prefer header was not specified then the wait time is 59s by default). Expect 201 HTTP status code only when respond-async,wait=0 is NOT specified in the Prefer Header value while creating the request. Refer the response body structure from GET call 200 response."
|
16
|
+
when 400 then "#{@msg["title"]} : #{@msg["report"]["error_code"]}"
|
17
|
+
when 408 then "Request Timed Out. Some operation has timed out due to client issue."
|
18
|
+
when 429 then "Caller doesn't have sufficient quota for this operation."
|
19
|
+
when 500 then "Internal Server Error. The server has encountered an error and is unable to process your request at this time."
|
20
|
+
else
|
21
|
+
@msg
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|