rev-api 1.0.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.
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