vhx-ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +2 -0
  5. data/README.md +59 -0
  6. data/Rakefile +1 -0
  7. data/lib/vhx.rb +66 -0
  8. data/lib/vhx/client.rb +86 -0
  9. data/lib/vhx/error.rb +24 -0
  10. data/lib/vhx/middleware/error_response.rb +32 -0
  11. data/lib/vhx/middleware/oauth2.rb +22 -0
  12. data/lib/vhx/oauth_token.rb +11 -0
  13. data/lib/vhx/objects/authorization.rb +5 -0
  14. data/lib/vhx/objects/collection.rb +8 -0
  15. data/lib/vhx/objects/collection_item.rb +4 -0
  16. data/lib/vhx/objects/customer.rb +8 -0
  17. data/lib/vhx/objects/product.rb +6 -0
  18. data/lib/vhx/objects/site.rb +4 -0
  19. data/lib/vhx/objects/user.rb +10 -0
  20. data/lib/vhx/objects/video.rb +7 -0
  21. data/lib/vhx/objects/video_file.rb +4 -0
  22. data/lib/vhx/utilities/api_operations/create.rb +22 -0
  23. data/lib/vhx/utilities/api_operations/delete.rb +16 -0
  24. data/lib/vhx/utilities/api_operations/list.rb +21 -0
  25. data/lib/vhx/utilities/api_operations/request.rb +17 -0
  26. data/lib/vhx/utilities/api_operations/update.rb +15 -0
  27. data/lib/vhx/utilities/vhx_helper.rb +20 -0
  28. data/lib/vhx/utilities/vhx_list_object.rb +44 -0
  29. data/lib/vhx/utilities/vhx_object.rb +106 -0
  30. data/lib/vhx/version.rb +3 -0
  31. data/spec/client_spec.rb +69 -0
  32. data/spec/fixtures/sample_array_list.json +15 -0
  33. data/spec/fixtures/sample_file_response.json +18 -0
  34. data/spec/fixtures/sample_hash_list.json +23 -0
  35. data/spec/fixtures/sample_package_response.json +185 -0
  36. data/spec/fixtures/sample_site_response.json +26 -0
  37. data/spec/fixtures/sample_user_response.json +21 -0
  38. data/spec/fixtures/sample_video_response.json +51 -0
  39. data/spec/middleware/error_response_spec.rb +11 -0
  40. data/spec/middleware/oauth2_spec.rb +14 -0
  41. data/spec/objects/file_spec.rb +29 -0
  42. data/spec/objects/package_spec.rb +74 -0
  43. data/spec/objects/site_spec.rb +44 -0
  44. data/spec/objects/user_spec.rb +51 -0
  45. data/spec/objects/video_spec.rb +36 -0
  46. data/spec/spec_helper.rb +18 -0
  47. data/spec/test_data.rb +55 -0
  48. data/spec/utilities/vhx_collection_spec.rb +27 -0
  49. data/spec/utilities/vhx_helper_spec.rb +47 -0
  50. data/spec/utilities/vhx_object_spec.rb +53 -0
  51. data/spec/vcr/Vhx_Client/application_user/_refresh_access_token/access_token_changed.yml +57 -0
  52. data/spec/vcr/Vhx_Client/application_user/_refresh_access_token/oauth_token_refreshed.yml +57 -0
  53. data/spec/vcr/Vhx_File/associations/are_present.yml +409 -0
  54. data/spec/vcr/Vhx_Middleware_ErrorResponse/unauthorized_user_credentials.yml +122 -0
  55. data/spec/vcr/Vhx_Middleware_OAuth2/access_token_refresh.yml +797 -0
  56. data/spec/vcr/Vhx_Package/_add_video/with_hypermedia/returns_package_object.yml +51 -0
  57. data/spec/vcr/Vhx_Package/_add_video/with_id/returns_package_object.yml +51 -0
  58. data/spec/vcr/Vhx_Package/_create/returns_package_object.yml +140 -0
  59. data/spec/vcr/Vhx_Package/_find/with_hypermedia.yml +243 -0
  60. data/spec/vcr/Vhx_Package/_find/with_id.yml +243 -0
  61. data/spec/vcr/Vhx_Package/_remove_video/with_hypermedia/returns_success.yml +51 -0
  62. data/spec/vcr/Vhx_Package/_remove_video/with_id/returns_success.yml +51 -0
  63. data/spec/vcr/Vhx_Package/associations/are_present.yml +195 -0
  64. data/spec/vcr/Vhx_Site/_create/returns_site_object.yml +91 -0
  65. data/spec/vcr/Vhx_Site/_find/with_hypermedia.yml +195 -0
  66. data/spec/vcr/Vhx_Site/_find/with_id.yml +195 -0
  67. data/spec/vcr/Vhx_User/_find/with_hypermedia.yml +81 -0
  68. data/spec/vcr/Vhx_User/_find/with_id.yml +81 -0
  69. data/spec/vcr/Vhx_User/_me/returns_user_object.yml +720 -0
  70. data/spec/vcr/Vhx_User/_update/returns_user_object.yml +212 -0
  71. data/spec/vcr/Vhx_VhxObject/associations/cache/retreive_if_available.yml +195 -0
  72. data/spec/vcr/Vhx_VhxObject/associations/falls_back_to_links.yml +310 -0
  73. data/spec/vcr/Vhx_Video/_create/returns_video_object.yml +125 -0
  74. data/spec/vcr/Vhx_Video/associations/are_present.yml +294 -0
  75. data/vhx.gemspec +27 -0
  76. metadata +204 -0
@@ -0,0 +1,21 @@
1
+ require_relative '../vhx_list_object'
2
+
3
+ module Vhx
4
+ module ApiOperations
5
+ module List
6
+ module ClassMethods
7
+ def all(payload = {})
8
+ response = Vhx.connection.get do |req|
9
+ req.url '/' + get_klass.downcase + 's'
10
+ req.body = payload
11
+ end
12
+ VhxListObject.new(response.body, get_klass.downcase + 's')
13
+ end
14
+ end
15
+
16
+ def self.included(klass)
17
+ klass.extend(ClassMethods)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module Vhx
2
+ module ApiOperations
3
+ module Request
4
+ module ClassMethods
5
+ def find(identifier)
6
+ response = Vhx.connection.get(get_hypermedia(identifier))
7
+ self.new(response.body)
8
+ end
9
+ end
10
+
11
+ def self.included(klass)
12
+ klass.extend(Vhx::HelperMethods)
13
+ klass.extend(ClassMethods)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Vhx
2
+ module ApiOperations
3
+ module Update
4
+ module InstanceMethods
5
+ def update(payload)
6
+ Vhx.connection.put(self.href, payload)
7
+ end
8
+ end
9
+
10
+ def self.included(klass)
11
+ klass.include(InstanceMethods)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module Vhx
2
+ module HelperMethods
3
+ def get_klass
4
+ if self.is_a?(Class)
5
+ self.to_s.split("::").last
6
+ else
7
+ self.class.to_s.split("::").last
8
+ end
9
+ end
10
+
11
+ def get_hypermedia(identifier, klass = nil)
12
+ if identifier.class.to_s.match(/Integer|Fixnum/)
13
+ klass ||= get_klass
14
+ return Vhx.client.api_base_url + '/' + klass.downcase + 's' + '/' + identifier.to_s #This url is based purely on VHX's API convention (not nested).
15
+ end
16
+
17
+ identifier
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ module Vhx
2
+ class VhxListObject < Array
3
+
4
+ def initialize(obj, list_type)
5
+ @obj = obj
6
+
7
+ if @obj.is_a?(Array)
8
+ ar = @obj.map{|association_hash| Object.const_get("Vhx::#{vhx_object_type(list_type)}").new(association_hash)}
9
+ elsif @obj.is_a?(Hash)
10
+ @previous, @next = @obj['_links']['prev']['href'], @obj['_links']['next']['href']
11
+ @total = @obj['total']
12
+ ar = @obj['_embedded'][list_type].map{|association_hash| Object.const_get("Vhx::#{vhx_object_type(list_type)}").new(association_hash)}
13
+ end
14
+
15
+ super(ar)
16
+ end
17
+
18
+ def previous
19
+ @previous # TODO
20
+ end
21
+
22
+ def next
23
+ @next # TODO
24
+ end
25
+
26
+ def total
27
+ @total
28
+ end
29
+
30
+ protected
31
+
32
+ def vhx_object_type(list_type)
33
+ case list_type
34
+ when 'items'
35
+ 'Collection::Item'
36
+ when 'files'
37
+ 'Video::File'
38
+ else
39
+ list_type.gsub(/s\z/, '').capitalize
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,106 @@
1
+ module Vhx
2
+ class VhxObject
3
+ include HelperMethods
4
+ ASSOCIATION_WHITELIST = ['products', 'sites', 'site', 'videos', 'video', 'subscription', 'files', 'items', 'customer']
5
+
6
+ def initialize(obj_hash)
7
+ @obj_hash = obj_hash
8
+
9
+ validate_class(obj_hash)
10
+ create_readers(obj_hash)
11
+ create_associations(obj_hash)
12
+ end
13
+
14
+ def to_json
15
+ @obj_hash.to_json
16
+ end
17
+
18
+ def to_hash
19
+ @obj_hash
20
+ end
21
+
22
+ def href
23
+ @obj_hash['_links']['self']['href']
24
+ end
25
+
26
+ def links
27
+ data = {}
28
+ @obj_hash['_links'].each do |k, v|
29
+ data[k] = v['href']
30
+ end
31
+ return OpenStruct.new(data)
32
+ end
33
+
34
+ protected
35
+ def validate_class(obj_hash)
36
+ return nil unless obj_hash['_links'].fetch('self', nil)
37
+
38
+ href = obj_hash['_links']['self']['href']
39
+ klass = self.class.to_s.split("::")
40
+ klass.shift #strip out Vhx
41
+ klass = klass.join("_").downcase
42
+
43
+ is_valid_match = case klass
44
+ when 'collection_item'
45
+ href.match('video|file|collection')
46
+ when 'video_file'
47
+ href.match('file')
48
+ else
49
+ href.match(klass)
50
+ end
51
+
52
+ unless is_valid_match
53
+ raise InvalidResourceError.new 'The resource returned from the API does not match the resource requested'
54
+ end
55
+ end
56
+
57
+ def create_readers(obj_hash)
58
+ obj_hash.keys.each do |key|
59
+ next if key.match(/embedded|links/)
60
+ self.class.send(:define_method, key) do
61
+ return obj_hash[key]
62
+ end
63
+ end
64
+ end
65
+
66
+ def create_associations(obj_hash)
67
+ associations = (obj_hash.fetch('_links', {}).keys | obj_hash.fetch('_embedded', {}).keys).select{|k| ASSOCIATION_WHITELIST.include?(k)}
68
+ associations.each do |association_method|
69
+ self.class.send(:define_method, association_method) do |payload = {}|
70
+ if payload.empty? && obj_hash['_embedded'] && obj_hash['_embedded'].fetch(association_method, []).length > 0
71
+ return fetch_embedded_association(obj_hash, association_method)
72
+ end
73
+
74
+ if obj_hash['_links'] && obj_hash['_links'].fetch(association_method, []).length > 0
75
+ return fetch_linked_association(obj_hash, association_method, payload)
76
+ end
77
+
78
+ raise InvalidResourceError.new 'Association does not exist'
79
+ end
80
+ end
81
+ end
82
+
83
+ def fetch_embedded_association(obj_hash, association_method)
84
+ association_obj = obj_hash['_embedded'][association_method]
85
+ build_association(association_obj, association_method)
86
+ end
87
+
88
+ def fetch_linked_association(obj_hash, association_method, payload = {})
89
+ response = Vhx.connection.get do |req|
90
+ req.url(obj_hash['_links'][association_method]['href'].gsub(Vhx.client.api_base_url, ""))
91
+ req.body = payload
92
+ end
93
+
94
+ build_association(response.body, association_method)
95
+ end
96
+
97
+ def build_association(association_obj, association_method)
98
+ # Support for legacy arrays, and new list objects starting with Video resource
99
+ if association_obj.is_a?(Array) || association_obj.has_key?('total')
100
+ return VhxListObject.new(association_obj, association_method)
101
+ end
102
+
103
+ Object.const_get("Vhx::#{association_method.gsub(/s\z/, '').capitalize}").new(association_obj)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ module Vhx
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vhx::Client, :vcr do
4
+ describe 'faraday configuration' do
5
+ subject(:vhx_client){ Vhx::Client.new(application_only_credentials)}
6
+
7
+ it 'uses custom error response middleware' do
8
+ expect(vhx_client.connection.builder.handlers).to include(Vhx::Middleware::ErrorResponse)
9
+ end
10
+
11
+ it 'uses api host' do
12
+ expect(vhx_client.connection.url_prefix.host).to eq 'api.crystal.dev'
13
+ end
14
+ end
15
+
16
+ context 'application_only' do
17
+ subject(:vhx_client){ Vhx::Client.new(application_only_credentials)}
18
+
19
+ describe 'connection' do
20
+ it 'initializes faraday connection' do
21
+ expect(vhx_client.connection).to be_an_instance_of(Faraday::Connection)
22
+ end
23
+
24
+ it 'authorization header presence' do
25
+ expect(vhx_client.connection.headers['Authorization']).to_not be_nil
26
+ end
27
+
28
+ it 'basic auth presence' do
29
+ expect(vhx_client.connection.headers['Authorization']).to match /Basic/
30
+ end
31
+ end
32
+ end
33
+
34
+ context 'application_user' do
35
+ subject(:vhx_client){ Vhx::Client.new(application_user_credentials)}
36
+
37
+ it 'oauth_token presence' do
38
+ expect(vhx_client.oauth_token).to be_an_instance_of(OAuthToken)
39
+ end
40
+
41
+ describe 'connection' do
42
+ it 'initializes faraday connection' do
43
+ expect(vhx_client.connection).to be_an_instance_of(Faraday::Connection)
44
+ end
45
+
46
+ it 'authorization header presence' do
47
+ expect(vhx_client.connection.headers['Authorization']).to_not be_nil
48
+ end
49
+
50
+ it 'Bearer auth presence' do
51
+ expect(vhx_client.connection.headers['Authorization']).to match /Bearer/
52
+ end
53
+ end
54
+
55
+ describe '#refresh_access_token!' do
56
+ subject(:oauth_token){ vhx_client.oauth_token }
57
+ let!(:original_access_token){ oauth_token.access_token }
58
+ before { vhx_client.refresh_access_token! }
59
+
60
+ it 'oauth_token refreshed' do
61
+ expect(vhx_client.oauth_token.refreshed).to eq(true)
62
+ end
63
+
64
+ it 'access_token changed' do
65
+ expect(vhx_client.oauth_token.access_token).to_not eq(original_access_token)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,15 @@
1
+ [{
2
+ "_links": {
3
+ "self": { "href": "https://api.vhx.tv/videos/1/files?quality=adaptive&format=m3u8" },
4
+ "video": { "href": "https://api.vhx.tv/videos/1" },
5
+ "site": { "href": "https://api.vhx.tv/sites/1" }
6
+ },
7
+ "quality": "adaptive",
8
+ "format": "m3u8",
9
+ "method": "hls",
10
+ "size": {
11
+ "bytes": 1073741824,
12
+ "formatted": "1 GB"
13
+ },
14
+ "mime_type": "application/x-mpegURL"
15
+ }]
@@ -0,0 +1,18 @@
1
+ {
2
+ "_links": {
3
+ "self": { "href": "http://api.crystal.dev/videos/3008/files?quality=adaptive&format=m3u8" },
4
+ "video": { "href": "http://api.crystal.dev/videos/3008" },
5
+ "site": { "href": "http://api.crystal.dev/sites/1900" },
6
+ "source": { "href": "http://video.crystal.dev/mymovie/adaptive.m3u8?token=f4v3v3i4c2o209_3" }
7
+ },
8
+ "quality": "adaptive",
9
+ "format": "m3u8",
10
+ "method": "hls",
11
+ "size": {
12
+ "bytes": 1073741824,
13
+ "formatted": "1 GB"
14
+ },
15
+ "mime_type": "application/x-mpegURL",
16
+ "created_at": "2014-02-25T20:19:30Z",
17
+ "updated_at": "2014-02-25T20:19:30Z"
18
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "_links": {
3
+ "prev": {"href": "pending"},
4
+ "next": {"href": "pending"}
5
+ },
6
+ "_embedded": {
7
+ "files": [{
8
+ "_links": {
9
+ "self": { "href": "https://api.vhx.tv/videos/1/files?quality=adaptive&format=m3u8" },
10
+ "video": { "href": "https://api.vhx.tv/videos/1" },
11
+ "site": { "href": "https://api.vhx.tv/sites/1" }
12
+ },
13
+ "quality": "adaptive",
14
+ "format": "m3u8",
15
+ "method": "hls",
16
+ "size": {
17
+ "bytes": 1073741824,
18
+ "formatted": "1 GB"
19
+ },
20
+ "mime_type": "application/x-mpegURL"
21
+ }]
22
+ }
23
+ }
@@ -0,0 +1,185 @@
1
+ {
2
+ "_links": {
3
+ "self": {
4
+ "href": "http://api.crystal.dev/packages/1025"
5
+ },
6
+ "site": {
7
+ "href": "http://api.crystal.dev/sites/1900"
8
+ },
9
+ "videos": {
10
+ "href": "http://api.crystal.dev/packages/1025/videos"
11
+ },
12
+ "buy_page": {
13
+ "href": "http://sagartestmovie.crystal.dev/buy/testing123"
14
+ },
15
+ "home_page": {
16
+ "href": "http://sagartestmovie.crystal.dev/packages/testing123"
17
+ }
18
+ },
19
+ "_embedded": {
20
+ "site": {
21
+ "_links": {
22
+ "self": {
23
+ "href": "http://api.crystal.dev/sites/1900"
24
+ },
25
+ "home_page": {
26
+ "href": "http://sagartestmovie.crystal.dev"
27
+ },
28
+ "followers": {
29
+ "href": "http://api.crystal.dev/sites/1900/followers"
30
+ }
31
+ },
32
+ "id": 1900,
33
+ "title": "SagarTestMovie",
34
+ "description": "This is my test movie. ",
35
+ "domain": "sagartestmovie.crystal.dev",
36
+ "subdomain": "sagartestmovie",
37
+ "key": "sagartestmovie",
38
+ "color": "#28DBF7",
39
+ "facebook_url": null,
40
+ "twitter_name": null,
41
+ "google_analytics_id": "",
42
+ "packages_count": 2,
43
+ "videos_count": 4,
44
+ "followers_count": 31,
45
+ "created_at": "2013-12-27T00:20:28Z",
46
+ "updated_at": "2015-05-04T20:46:01Z"
47
+ },
48
+ "videos": [
49
+ {
50
+ "_links": {
51
+ "self": {
52
+ "href": "http://api.crystal.dev/videos/3008"
53
+ },
54
+ "site": {
55
+ "href": "http://api.crystal.dev/sites/1900"
56
+ },
57
+ "files": {
58
+ "href": "http://api.crystal.dev/videos/3008/files"
59
+ }
60
+ },
61
+ "_embedded": {
62
+ "subtitles": [
63
+ {
64
+ "_links": {
65
+ "self": {
66
+ "href": "http://api.crystal.dev/videos/7726"
67
+ },
68
+ "video": {
69
+ "href": "http://api.crystal.dev/videos/3008"
70
+ },
71
+ "site": {
72
+ "href": "http://api.crystal.dev/sites/1900"
73
+ }
74
+ },
75
+ "language": "Bengali",
76
+ "type": "video"
77
+ },
78
+ {
79
+ "_links": {
80
+ "self": {
81
+ "href": "http://api.crystal.dev/videos/5558"
82
+ },
83
+ "video": {
84
+ "href": "http://api.crystal.dev/videos/3008"
85
+ },
86
+ "site": {
87
+ "href": "http://api.crystal.dev/sites/1900"
88
+ }
89
+ },
90
+ "language": "English",
91
+ "type": "video"
92
+ }
93
+ ]
94
+ },
95
+ "id": 3008,
96
+ "title": "Test Movie",
97
+ "description": "Testing",
98
+ "status": "uploaded",
99
+ "duration": {
100
+ "seconds": 19,
101
+ "formatted": "00:00:19"
102
+ },
103
+ "thumbnail": {
104
+ "small": "https://dv6n47kh00ig2.cloudfront.net/sagartestmovie/assets/images/video-3008-1403716255-240x135.jpg",
105
+ "medium": "https://dv6n47kh00ig2.cloudfront.net/sagartestmovie/assets/images/video-3008-1403716255-640x360.jpg",
106
+ "large": "https://dv6n47kh00ig2.cloudfront.net/sagartestmovie/assets/images/video-3008-1403716255-1280x720.jpg"
107
+ },
108
+ "is_drm_enabled": false,
109
+ "subtitles_count": 2,
110
+ "files_count": 5,
111
+ "created_at": "2014-02-06T05:30:18Z",
112
+ "updated_at": "2015-02-18T16:21:24Z",
113
+ "position": 0
114
+ },
115
+ {
116
+ "_links": {
117
+ "self": {
118
+ "href": "http://api.crystal.dev/videos/7830"
119
+ },
120
+ "site": {
121
+ "href": "http://api.crystal.dev/sites/1900"
122
+ },
123
+ "files": {
124
+ "href": "http://api.crystal.dev/videos/7830/files"
125
+ }
126
+ },
127
+ "_embedded": {
128
+ "subtitles": [
129
+
130
+ ]
131
+ },
132
+ "id": 7830,
133
+ "title": "test movie",
134
+ "description": null,
135
+ "status": null,
136
+ "duration": {
137
+ "seconds": 0,
138
+ "formatted": "00:00:00"
139
+ },
140
+ "thumbnail": {
141
+ "small": "http://cdn.crystal.dev/assets/thumbnails/default-small.png",
142
+ "medium": "http://cdn.crystal.dev/assets/thumbnails/default-medium.png",
143
+ "large": "http://cdn.crystal.dev/assets/thumbnails/default-large.png"
144
+ },
145
+ "is_drm_enabled": false,
146
+ "subtitles_count": 0,
147
+ "files_count": 0,
148
+ "created_at": "2014-07-14T15:19:03Z",
149
+ "updated_at": "2015-05-04T19:36:50Z",
150
+ "position": 1
151
+ }
152
+ ],
153
+ "trailer": null
154
+ },
155
+ "id": 1025,
156
+ "title": "testing123",
157
+ "description": "test",
158
+ "sku": "testing123",
159
+ "price": {
160
+ "cents": 200,
161
+ "currency": "USD",
162
+ "formatted": "$2",
163
+ "US": {
164
+ "cents": 200,
165
+ "currency": "USD",
166
+ "formatted": "$2"
167
+ }
168
+ },
169
+ "rental": null,
170
+ "trailer_url": null,
171
+ "trailer_embed_code": "<iframe src=\"http://embed.crystal.dev/packages/1025\" width=\"640\" height=\"360\" frameborder=\"0\" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>",
172
+ "thumbnail": {
173
+ "small": "http://cdn.crystal.dev/assets/thumbnails/default-small.png",
174
+ "medium": "http://cdn.crystal.dev/assets/thumbnails/default-medium.png",
175
+ "large": "http://cdn.crystal.dev/assets/thumbnails/default-large.png",
176
+ "blurred": null
177
+ },
178
+ "is_active": false,
179
+ "is_preorder": false,
180
+ "release_date": null,
181
+ "videos_count": 2,
182
+ "extras_count": 0,
183
+ "created_at": "2014-02-06T18:00:39Z",
184
+ "updated_at": "2015-02-18T16:21:15Z"
185
+ }