rev-api 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +20 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +42 -0
  7. data/LICENSE +191 -0
  8. data/README.md +124 -0
  9. data/Rakefile +14 -0
  10. data/examples/cli.rb +200 -0
  11. data/lib/rev-api.rb +26 -0
  12. data/lib/rev-api/api.rb +311 -0
  13. data/lib/rev-api/api_serializable.rb +30 -0
  14. data/lib/rev-api/exceptions.rb +108 -0
  15. data/lib/rev-api/http_client.rb +97 -0
  16. data/lib/rev-api/models/order.rb +113 -0
  17. data/lib/rev-api/models/order_request.rb +183 -0
  18. data/lib/rev-api/version.rb +3 -0
  19. data/rev-api.gemspec +33 -0
  20. data/spec/fixtures/api_cassettes/cancel_order.yml +38 -0
  21. data/spec/fixtures/api_cassettes/cancel_order_not_allowed.yml +40 -0
  22. data/spec/fixtures/api_cassettes/get_attachment_content.yml +399 -0
  23. data/spec/fixtures/api_cassettes/get_attachment_content_as_pdf.yml +399 -0
  24. data/spec/fixtures/api_cassettes/get_attachment_content_as_text.yml +65 -0
  25. data/spec/fixtures/api_cassettes/get_attachment_content_as_youtube_transcript.yml +66 -0
  26. data/spec/fixtures/api_cassettes/get_attachment_content_unacceptable_representation.yml +42 -0
  27. data/spec/fixtures/api_cassettes/get_attachment_content_with_invalid_id.yml +42 -0
  28. data/spec/fixtures/api_cassettes/get_attachment_metadata.yml +42 -0
  29. data/spec/fixtures/api_cassettes/get_attachment_with_invalid_id.yml +40 -0
  30. data/spec/fixtures/api_cassettes/get_orders.yml +122 -0
  31. data/spec/fixtures/api_cassettes/get_tc_order.yml +44 -0
  32. data/spec/fixtures/api_cassettes/get_third_page_of_orders.yml +58 -0
  33. data/spec/fixtures/api_cassettes/get_tr_order.yml +44 -0
  34. data/spec/fixtures/api_cassettes/link_input.yml +44 -0
  35. data/spec/fixtures/api_cassettes/link_input_with_all_attributes.yml +44 -0
  36. data/spec/fixtures/api_cassettes/not_found_order.yml +42 -0
  37. data/spec/fixtures/api_cassettes/submit_tc_order_with_account_balance.yml +45 -0
  38. data/spec/fixtures/api_cassettes/submit_tc_order_with_cc_and_all_attributes.yml +46 -0
  39. data/spec/fixtures/api_cassettes/submit_tc_order_with_invalid_request.yml +45 -0
  40. data/spec/fixtures/api_cassettes/submit_tc_order_with_saved_cc.yml +45 -0
  41. data/spec/fixtures/api_cassettes/submit_tr_order.yml +44 -0
  42. data/spec/fixtures/api_cassettes/unauthorized.yml +42 -0
  43. data/spec/fixtures/api_cassettes/upload_input.yml +130 -0
  44. data/spec/fixtures/api_cassettes/upload_input_with_invalid_content_type.yml +131 -0
  45. data/spec/fixtures/sourcedocument.png +0 -0
  46. data/spec/lib/rev/api_spec.rb +24 -0
  47. data/spec/lib/rev/cancel_order_spec.rb +25 -0
  48. data/spec/lib/rev/get_attachment_content_spec.rb +79 -0
  49. data/spec/lib/rev/get_attachment_metadata_spec.rb +33 -0
  50. data/spec/lib/rev/get_order_spec.rb +68 -0
  51. data/spec/lib/rev/get_orders_spec.rb +39 -0
  52. data/spec/lib/rev/http_client_spec.rb +32 -0
  53. data/spec/lib/rev/post_inputs_spec.rb +75 -0
  54. data/spec/lib/rev/post_order_spec.rb +207 -0
  55. data/spec/spec_helper.rb +31 -0
  56. metadata +248 -0
@@ -0,0 +1,97 @@
1
+ module Rev
2
+
3
+ # HTTP client handling authentication and HTTP requests at the low level for the Api class.
4
+ # Not indended to be used directly - clients should be using the Api class instead.
5
+ class HttpClient
6
+
7
+ include HTTParty
8
+
9
+ USER_AGENT = "RevOfficialRubySDK/#{VERSION}"
10
+
11
+ # Create a new HttpClient, connecting to given host, and using the given Client and User API Keys.
12
+ #
13
+ # @param client_api_key [String] the client API key to use for authenticating
14
+ # @param user_api_key [String] the user API key to use for authenticating
15
+ # @param host [String] the host to send requests to. Should be one of Rev::Api::PRODCUTION_HOST or Rev::Api::SANDBOX_HOST
16
+ def initialize(client_api_key, user_api_key, host)
17
+ endpoint_uri = "https://#{host}/api/v1"
18
+ self.class.base_uri(endpoint_uri)
19
+
20
+ auth_string = "Rev #{client_api_key}:#{user_api_key}"
21
+ @default_headers = {
22
+ 'Authorization' => auth_string,
23
+ 'User-Agent' => USER_AGENT # to track usage of SDK
24
+ }
25
+ end
26
+
27
+ # Performs HTTP GET of JSON data.
28
+ #
29
+ # @param operation [String] URL suffix describing specific operation, like '/orders'
30
+ # @param headers [Hash] hash of headers to use for the request
31
+ # @return [HTTParty::Response] response
32
+ def get(operation, headers = {})
33
+ headers = @default_headers.merge(headers)
34
+ self.class.get(operation, :headers => headers)
35
+ end
36
+
37
+ # Performs HTTP GET of binary data. Note, unlike post, this returns a
38
+ # Net::HTTP::Response, not HTTParty::Response.
39
+ #
40
+ # If this method is passed a block, will pass response to that block. in that case the response is not yet
41
+ # read into memory, so the block can read it progressively. otherwise, returns the response.
42
+ #
43
+ # @param operation [String] URL suffix describing specific operation, like '/orders'
44
+ # @param headers [Hash] hash of headers to use for the request
45
+ # @yieldparam resp [Net::HTTP::Response] the response, ready to be read
46
+ # @return [Net::HTTP::Response] response
47
+ def get_binary(operation, headers = {}, &block)
48
+ uri = URI.parse("#{self.class.base_uri}#{operation}")
49
+ headers = @default_headers.merge(headers)
50
+
51
+ http = Net::HTTP.new(uri.host, uri.port)
52
+ http.use_ssl = true
53
+
54
+ get = Net::HTTP::Get.new(uri.request_uri, headers)
55
+ if block_given?
56
+ http.request(get) do |resp|
57
+ yield resp
58
+ end
59
+ else
60
+ http.request(get)
61
+ end
62
+ end
63
+
64
+ # Performs HTTP POST of JSON data.
65
+ #
66
+ # @param operation[String] URL suffix describing specific operation
67
+ # @param data [Hash] hash of keys/values to post in request body
68
+ # @param headers [Hash] hash of headers to use for the request
69
+ # @return [HTTParty::Response] response
70
+ def post(operation, data = {}, headers = {})
71
+ headers = @default_headers.merge(headers)
72
+ self.class.post(operation, :headers => headers, :body => data)
73
+ end
74
+
75
+
76
+ # Performs HTTP POST of binary data. Note, unlike post, this returns a
77
+ # Net::HTTP::Response, not HTTParty::Response.
78
+ #
79
+ # @param operation[String] URL suffix describing specific operation
80
+ # @param file [File] file-like object containing the data to post
81
+ # @param headers [Hash] hash of headers to use for the request
82
+ # @return [Net::HTTP::Response] response
83
+ def post_binary(operation, file, headers = {})
84
+ uri = URI.parse("#{self.class.base_uri}#{operation}")
85
+ headers = @default_headers.merge(headers)
86
+
87
+ http = Net::HTTP.new(uri.host, uri.port)
88
+ http.use_ssl = true
89
+
90
+ post = Net::HTTP::Post.new(uri.request_uri, headers)
91
+ post["Content-Length"] = file.stat.size.to_s
92
+ post.body_stream = file
93
+
94
+ response = http.request(post)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,113 @@
1
+ require 'rev-api/api_serializable'
2
+
3
+ module Rev
4
+ # Represents Translation or Transcription order.
5
+ # Should have TranslationInfo or TranscriptionInfo, list
6
+ # of comments and attachments. Attributes names reflect
7
+ # API exposed names, but occasional hyphens are replaced
8
+ # with underscores
9
+ class Order < ApiSerializable
10
+ attr_reader :order_number, :price, :status, :attachments, :comments,
11
+ :translation, :transcription, :client_ref
12
+
13
+ # @param fields [Hash] hash of order fields parsed from JSON API response
14
+ def initialize(fields)
15
+ super fields
16
+ @attachments = fields['attachments'].map { |attachment_fields| Attachment.new(attachment_fields) }
17
+ @comments = fields['comments'].map { |comment_fields| Comment.new(comment_fields) }
18
+ @translation = TranslationInfo.new(fields['translation']) if fields['translation']
19
+ @transcription = TranscriptionInfo.new(fields['transcription']) if fields['transcription']
20
+ end
21
+
22
+ # @return [Array of Attachment] with the kind of "transcript"
23
+ def transcripts
24
+ @attachments.select { |a| a.kind == Attachment::KINDS[:transcript]}
25
+ end
26
+
27
+ # @return [Array of Attachment] with the kind of "translation"
28
+ def translations
29
+ @attachments.select { |a| a.kind == Attachment::KINDS[:translation]}
30
+ end
31
+
32
+ # @return [Array of Attachment] with the kind of "sources"
33
+ def sources
34
+ @attachments.select { |a| a.kind == Attachment::KINDS[:media]}
35
+ end
36
+ end
37
+
38
+ # Order comment, containing author, creation timestamp and text
39
+ class Comment < ApiSerializable
40
+ require 'date'
41
+
42
+ attr_reader :by, :timestamp, :text
43
+
44
+ # @param fields [Hash] hash of comment fields parsed from JSON API response
45
+ def initialize(fields)
46
+ super fields
47
+ @timestamp = Date.iso8601(fields['timestamp'])
48
+ @text = fields['text'] ? fields['text'] : String.new # right now API gives no 'text' field if text is empty
49
+ end
50
+ end
51
+
52
+ # Additional information specific to translation orders,
53
+ # such as word count, languages
54
+ class TranslationInfo < ApiSerializable
55
+ attr_reader :total_word_count, :source_language_code,
56
+ :destination_language_code
57
+ end
58
+
59
+ # Additional information specific to transcription orders,
60
+ # such as total length in minutes, verbatim and timestamps flags
61
+ class TranscriptionInfo < ApiSerializable
62
+ attr_reader :total_length, :verbatim, :timestamps
63
+ end
64
+
65
+ # Represents order attachment - logical document associated with order
66
+ class Attachment < ApiSerializable
67
+ attr_reader :kind, :name, :id, :audio_length, :word_count, :links
68
+
69
+ KINDS = {
70
+ :transcript => 'transcript',
71
+ :translation => 'translation',
72
+ :media => 'media'
73
+ }
74
+
75
+ # List of supported mime-types used to request attachment's content
76
+ # within 'Accept' header
77
+ REPRESENTATIONS = {
78
+ :docx => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
79
+ :doc => 'application/msword',
80
+ :pdf => 'application/pdf',
81
+ :txt => 'text/plain',
82
+ :youtube => 'text/plain; format=youtube-transcript'
83
+ }
84
+
85
+ # @param fields [Hash] fields of attachment fields parsed from JSON API response
86
+ def initialize(fields)
87
+ super fields
88
+ @links = fields['links'].map { |link_fields| Link.new(link_fields) }
89
+ end
90
+
91
+ # @param ext [Symbol] extension
92
+ # @return [String] mime-type for requested extension
93
+ def self.representation_mime(ext)
94
+ REPRESENTATIONS[ext]
95
+ end
96
+ end
97
+
98
+ # Link to actual file represented by attachment
99
+ class Link < ApiSerializable
100
+ attr_reader :rel, :href, :content_type
101
+ end
102
+
103
+ # Represents a paginated list of orders, including padination info.
104
+ class OrdersListPage < ApiSerializable
105
+ attr_reader :total_count, :results_per_page, :page, :orders
106
+
107
+ # @param fields [Hash] hash of OrdersListPage fields parsed from JSON API response
108
+ def initialize(fields)
109
+ super fields
110
+ @orders = fields['orders'].map { |order_fields| Order.new(order_fields) }
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,183 @@
1
+ require 'rev-api/api_serializable'
2
+
3
+ module Rev
4
+ # OrderRequest is used for constructing order 'spec' in consumer code and passing it into.
5
+ # It consists of three main elements: :payment, :transcription_options and :notification.
6
+ # You can also supply reference number and customer comment
7
+ #
8
+ # @note http://www.rev.com/api/ordersposttranscription, http://www.rev.com/api/ordersposttranslation
9
+ class OrderRequest < ApiSerializable
10
+ # see {Rev::Payment}
11
+ attr_reader :payment
12
+
13
+ # see {Rev::TranscriptionOptions}
14
+ attr_reader :transcription_options
15
+
16
+ # see {Rev::TranslationOptions}
17
+ attr_reader :translation_options
18
+
19
+ # see {Rev::Notification}
20
+ attr_reader :notification
21
+
22
+ # a reference number for the order meaningful for the client (optional)
23
+ attr_reader :client_ref
24
+
25
+ # a comment with any special messages about the order (optional)
26
+ attr_reader :comment
27
+
28
+ # @param payment [Payment] payment info
29
+ # @param fields [Hash] of fields to initialize instance. See instance attributes for available fields.
30
+ def initialize(payment, fields = {})
31
+ super fields
32
+ @payment = payment
33
+ end
34
+ end
35
+
36
+ # Payment Info. Payment can be done either by charging a credit card or by debiting the user's
37
+ # account balance. If using a credit card, then either the user's saved credit card can be used
38
+ # or credit card details provided.
39
+ #
40
+ # For credit card payments, if specifying the credit card details in the request, the required
41
+ # elements are the card number, cardholder name, expiration month and year, and billing zipcode.
42
+ # If using the user's saved card, you must currently specify the value "1" for the saved card id,
43
+ # as we currently only allow a single card to be saved for a user.
44
+ class Payment < ApiSerializable
45
+ attr_accessor :type, :credit_card
46
+
47
+ # use to correctly set payment type
48
+ TYPES = {
49
+ :credit_card => 'CreditCard',
50
+ :balance => 'AccountBalance'
51
+ }
52
+
53
+ CC_ON_FILE_ID = 1
54
+
55
+ # @param type [String] payment method
56
+ # @param credit_card [CreditCard] cc obj, if type is 'CreditCard'
57
+ def initialize(type, credit_card = nil)
58
+ @type = type
59
+ @credit_card = credit_card unless credit_card.nil?
60
+ end
61
+
62
+ class << self
63
+ def with_credit_card_on_file()
64
+ Payment::new(TYPES[:credit_card], CreditCard.new(:saved_id => CC_ON_FILE_ID))
65
+ end
66
+
67
+ def with_saved_credit_card(credit_card)
68
+ Payment::new(TYPES[:credit_card], credit_card)
69
+ end
70
+
71
+ def with_account_balance()
72
+ Payment::new(TYPES[:account_balance])
73
+ end
74
+ end
75
+ end
76
+
77
+ # Billing address
78
+ class BillingAddress < ApiSerializable
79
+ attr_reader :street, :street2, :city, :state, :zip, :country_alpha2
80
+ end
81
+
82
+ # Credit Card
83
+ class CreditCard < ApiSerializable
84
+ attr_reader :number, :expiration_month, :expiration_year, :cardholder, :billing_address, :saved_id
85
+ end
86
+
87
+ # Transcription options. This section contains the input media that must be transferred to our servers
88
+ # using a POST to /inputs, and are referenced using the URIs returned by that call. We also support external links.
89
+ # Following points explain usage of inputs:
90
+ # - For each input, you must provide either uri or external_link, but not both. If both or neither is provided,
91
+ # error is returned.
92
+ # - You should only provide an external_link if it links to page where the media can be found, rather than directly to
93
+ # the media file, and that we will not attempt to do anything with the link when the API call is made.
94
+ # This is in contrast to when you post to /inputs with a link to a media file - in that case we do download the file.
95
+ # So the external_link should only be used when you can't link to the media file directly.
96
+ # - The external_link can contain anything you want, but if it's a YouTube link, we will attempt to determine the
97
+ # duration of the video on that page.
98
+ # We also allow users of the api to specify if translation should be done using our Verbatim option (:verbatim => true)
99
+ # and to specify if Time stamps should be included (:timestamps => true).
100
+ class TranscriptionOptions < ApiSerializable
101
+ # Mandatory, contains list of media to transcribe. Must have at least one element.
102
+ attr_reader :inputs
103
+
104
+ # Optional, should we transcribe the provided files verbatim? If true,
105
+ # all filler words (i.e. umm, huh) will be included.
106
+ attr_reader :verbatim
107
+
108
+ # Optional, should we include timestamps?
109
+ attr_reader :timestamps
110
+
111
+ # @param inputs [Array] list of inputs
112
+ # @param info [Hash] of fields to initialize instance. May contain:
113
+ # - :verbatim => true/false
114
+ # - :timestams => true/false
115
+ def initialize(inputs, info = {})
116
+ super info
117
+ @inputs = inputs
118
+ end
119
+ end
120
+
121
+ # Translation options. This section contains the input media that must be transferred to our
122
+ # servers using a POST to /inputs, and are referenced using the URIs returned by that call.
123
+ # For each media, word count must be specified. The language code for the source and desitination
124
+ # languages must also be specified.
125
+ class TranslationOptions < ApiSerializable
126
+ # Mandatory, contains list of media to transcribe. Must have at least one element.
127
+ attr_reader :inputs
128
+
129
+ # Mandatory, source language code
130
+ attr_reader :source_language_code
131
+
132
+ # Mandatory, destination language code
133
+ attr_reader :destination_language_code
134
+
135
+ # @param inputs [Array] list of inputs
136
+ # @param info [Hash] of fields to initialize instance. May contain:
137
+ # - :source_language_code
138
+ # - :destination_language_code
139
+ # @note For language codes refer to http://www.loc.gov/standards/iso639-2/php/code_list.php
140
+ def initialize(inputs, info = {})
141
+ super(info)
142
+ @inputs = inputs
143
+ end
144
+ end
145
+
146
+ # Input for order (aka source file)
147
+ class Input < ApiSerializable
148
+ # Mandatory when used with {Rev::OrderRequest::TranslationInfo}, length of document, in words
149
+ attr_reader :word_length
150
+
151
+ # Length of audio, in minutes (mandatory in case of inability to determine it automatically).
152
+ # Used within {Rev::OrderRequest::TranscriptionInfo}
153
+ attr_reader :audio_length
154
+
155
+ # Mandatory, URI of the media, as returned from the call to POST /inputs.
156
+ # :external_link might substitute :uri for Transcription.
157
+ attr_reader :uri
158
+
159
+ # External URL, if sources wasn't POSTed as input (YouTube, Vimeo, Dropbox, etc)
160
+ attr_reader :external_link
161
+ end
162
+
163
+ # Notification Info. Optionally you may request that an HTTP post be made to a url of your choice when the order enters
164
+ # a new status (eg being transcribed or reviewed) and when it is complete.
165
+ class Notification < ApiSerializable
166
+ attr_reader :url, :level
167
+
168
+ # Notification levels
169
+ LEVELS = {
170
+ :detailed => 'Detailed',
171
+ :final_only => 'FinalOnly'
172
+ }
173
+
174
+ # @param url [String] The url for notifications. Mandatory if the notifications element is used. Updates will be posted to this URL
175
+ # @param level [String] Optional, specifies which notifications are sent:
176
+ # - :detailed - a notification is sent whenever the order is in a new status or has a new comment
177
+ # - :final_only - (the default), notification is sent only when the order is complete
178
+ def initialize(url, level = nil)
179
+ @url = url
180
+ @level = level ? level : LEVEL[:final_only]
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,3 @@
1
+ module Rev
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'date'
2
+ require File.dirname(__FILE__) + '/lib/rev-api/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'rev-api'
6
+ s.version = Rev::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.required_ruby_version = '>= 1.9.3'
9
+ s.date = Date.today.to_s
10
+ s.summary = "Ruby wrapper for Rev.com API"
11
+ s.description = "Communicate with Rev.com API using plain Ruby objects without bothering about HTTP"
12
+ s.authors = ["Rev.com, Inc"]
13
+ s.email = 'api@rev.com'
14
+ s.homepage = 'http://www.rev.com/api'
15
+ s.license = 'Apache License 2.0'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = [ "lib", "spec" ]
21
+
22
+ s.add_runtime_dependency('httparty', '>= 0.11.0')
23
+
24
+ s.add_development_dependency('webmock', '~> 1.11.0')
25
+ s.add_development_dependency('vcr', '~> 2.5.0')
26
+ s.add_development_dependency('turn', '~> 0.9.6')
27
+ s.add_development_dependency('rake', '>= 10.1.0')
28
+ s.add_development_dependency('yard')
29
+ s.add_development_dependency('redcarpet')
30
+ s.add_development_dependency('rubygems-tasks')
31
+
32
+ s.has_rdoc = 'yard'
33
+ end
@@ -0,0 +1,38 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.revtrunk.com/api/v1/orders/TC0166192942/cancel
6
+ body:
7
+ encoding: US-ASCII
8
+ string: order_num=TC0166192942
9
+ headers:
10
+ Authorization:
11
+ - Rev welcome:AAAAAu/YjZ3phXU5FsF35yIcgiA=
12
+ User-Agent:
13
+ - RevOfficialRubySDK/1.0.0
14
+ response:
15
+ status:
16
+ code: 204
17
+ message: No Content
18
+ headers:
19
+ Cache-Control:
20
+ - no-cache
21
+ Pragma:
22
+ - no-cache
23
+ Expires:
24
+ - '-1'
25
+ Server:
26
+ - Microsoft-IIS/7.5
27
+ X-Miniprofiler-Ids:
28
+ - ! '["6f3dca3a-b095-4332-889f-d1212487407c"]'
29
+ X-Powered-By:
30
+ - ASP.NET
31
+ Date:
32
+ - Thu, 12 Sep 2013 20:14:44 GMT
33
+ body:
34
+ encoding: US-ASCII
35
+ string: ''
36
+ http_version:
37
+ recorded_at: Thu, 12 Sep 2013 20:14:44 GMT
38
+ recorded_with: VCR 2.5.0