livefyre 1.3.2 → 2.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 (46) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +3 -6
  3. data/CHANGELOG +10 -0
  4. data/README.md +3 -66
  5. data/circle.yml +10 -0
  6. data/lib/livefyre.rb +1 -1
  7. data/lib/livefyre/api/domain.rb +15 -0
  8. data/lib/livefyre/api/personalized_stream.rb +79 -72
  9. data/lib/livefyre/core/collection.rb +93 -0
  10. data/lib/livefyre/core/network.rb +61 -56
  11. data/lib/livefyre/core/site.rb +34 -82
  12. data/lib/livefyre/cursor/timeline_cursor.rb +45 -0
  13. data/lib/livefyre/{entity → dto}/subscription.rb +12 -14
  14. data/lib/livefyre/{entity → dto}/topic.rb +16 -13
  15. data/lib/livefyre/exceptions/api_exception.rb +29 -0
  16. data/lib/livefyre/exceptions/livefyre_exception.rb +9 -0
  17. data/lib/livefyre/factory/cursor_factory.rb +4 -4
  18. data/lib/livefyre/model/collection_data.rb +30 -0
  19. data/lib/livefyre/model/cursor_data.rb +17 -0
  20. data/lib/livefyre/model/network_data.rb +10 -0
  21. data/lib/livefyre/model/site_data.rb +10 -0
  22. data/lib/livefyre/type/collection_type.rb +11 -0
  23. data/lib/livefyre/type/subscription_type.rb +5 -0
  24. data/lib/livefyre/utils/livefyre_util.rb +24 -0
  25. data/lib/livefyre/validator/collection_validator.rb +33 -0
  26. data/lib/livefyre/validator/cursor_validator.rb +15 -0
  27. data/lib/livefyre/validator/network_validator.rb +19 -0
  28. data/lib/livefyre/validator/site_validator.rb +14 -0
  29. data/lib/livefyre/version.rb +1 -1
  30. data/livefyre.gemspec +15 -12
  31. data/spec/livefyre/api/domain_spec.rb +40 -0
  32. data/spec/livefyre/api/personalized_stream_spec.rb +54 -37
  33. data/spec/livefyre/core/collection_spec.rb +90 -0
  34. data/spec/livefyre/core/network_spec.rb +32 -6
  35. data/spec/livefyre/core/site_spec.rb +24 -62
  36. data/spec/livefyre/cursor/timeline_cursor_spec.rb +25 -0
  37. data/spec/livefyre/dto/subscription_spec.rb +27 -0
  38. data/spec/livefyre/dto/topic_spec.rb +35 -0
  39. data/spec/livefyre/factory/cursor_factory_spec.rb +29 -0
  40. data/spec/livefyre/utils/utils_spec.rb +30 -0
  41. data/spec/spec_helper.rb +34 -0
  42. metadata +134 -72
  43. data/.idea/misc.xml +0 -5
  44. data/.idea/modules.xml +0 -9
  45. data/lib/livefyre/entity/timeline_cursor.rb +0 -41
  46. data/spec/livefyre/test_spec.rb +0 -7
@@ -0,0 +1,93 @@
1
+ require 'base64'
2
+ require 'digest'
3
+ require 'json'
4
+ require 'jwt'
5
+ require 'rest-client'
6
+
7
+ require 'livefyre/api/domain'
8
+ require 'livefyre/exceptions/api_exception'
9
+ require 'livefyre/model/collection_data'
10
+ require 'livefyre/utils/livefyre_util'
11
+ require 'livefyre/validator/collection_validator'
12
+
13
+ module Livefyre
14
+ class Collection
15
+ attr_accessor :site, :data
16
+
17
+ def initialize(site, data)
18
+ @site = site
19
+ @data = data
20
+ end
21
+
22
+ def self.init(site, type, title, article_id, url)
23
+ data = CollectionData.new(type, title, article_id, url)
24
+ Collection.new(site, CollectionValidator::validate(data))
25
+ end
26
+
27
+ def create_or_update
28
+ response = invoke_collection_api('create')
29
+ if response.code == 200
30
+ @data.id = JSON.parse(response)['data']['collectionId']
31
+ return self
32
+ elsif response.code == 409
33
+ response = invoke_collection_api('update')
34
+ if response.code == 200
35
+ return self
36
+ end
37
+ end
38
+ raise ApiException.new(self, response.code)
39
+ end
40
+
41
+ def build_collection_meta_token
42
+ attr = @data.as_hash
43
+ attr[:iss] = is_network_issued ? @site.network.urn : @site.urn
44
+ JWT.encode(attr, is_network_issued ? @site.network.data.key : @site.data.key)
45
+ end
46
+
47
+ def build_checksum
48
+ attr_json = '{' + @data.as_hash.sort.map{|k,v| "#{k.inspect}:#{v.inspect}"}.join(',') + '}'
49
+ Digest::MD5.new.update(attr_json).hexdigest
50
+ end
51
+
52
+ def get_collection_content
53
+ response = RestClient.get(
54
+ "#{Domain::bootstrap(self)}/bs3/#{@site.network.data.name}/#{@site.data.id}/#{Base64.encode64(@data.article_id.to_s).chomp}/init",
55
+ :accepts => :json
56
+ ){|response, request, result| response }
57
+ raise ApiException.new(self, response.code) if response.code >= 400
58
+ JSON.parse(response)
59
+ end
60
+
61
+ def urn
62
+ "#{@site.urn}:collection=#{@data.id}"
63
+ end
64
+
65
+ def is_network_issued
66
+ network_urn = @site.network.urn
67
+
68
+ if @data.topics
69
+ @data.topics.each do |topic|
70
+ topic_id = topic.id
71
+ if topic_id.start_with?(network_urn) && !topic_id.sub(network_urn, '').start_with?(':site=')
72
+ return true
73
+ end
74
+ end
75
+ end
76
+
77
+ false
78
+ end
79
+
80
+ private
81
+
82
+ def invoke_collection_api(method)
83
+ uri = "#{Domain::quill(self)}/api/v3.0/site/#{@site.data.id}/collection/#{method}/?sync=1"
84
+ data = {
85
+ :articleId => @data.article_id,
86
+ :collectionMeta => build_collection_meta_token,
87
+ :checksum => build_checksum
88
+ }
89
+ headers = { :accepts => :json, :content_type => :json }
90
+ RestClient.post(uri, data.to_json, headers){|response, request, result| response }
91
+ end
92
+ end
93
+ end
@@ -1,78 +1,83 @@
1
- require 'addressable/uri'
2
1
  require 'jwt'
3
2
  require 'rest-client'
4
3
 
5
- require 'livefyre/core/site'
6
4
  require 'livefyre/api/domain'
5
+ require 'livefyre/core/site'
6
+ require 'livefyre/exceptions/api_exception'
7
+ require 'livefyre/model/network_data'
8
+ require 'livefyre/validator/network_validator'
7
9
 
8
10
  module Livefyre
9
- class Network
10
- DEFAULT_USER = 'system'
11
- DEFAULT_EXPIRES = 86400
11
+ class Network
12
+ DEFAULT_USER = 'system'
13
+ DEFAULT_EXPIRES = 86400
12
14
 
13
- def initialize(name, key)
14
- @name = name
15
- @key = key
15
+ attr_accessor :data, :ssl
16
+
17
+ def initialize(data)
18
+ @data = data
16
19
  @ssl = true
17
- @network_name = name.split('.')[0]
18
20
  end
19
21
 
20
- attr_reader :name
21
- attr_reader :key
22
- attr_accessor :ssl
23
- attr_reader :network_name
24
-
25
- def set_user_sync_url(url_template)
26
- raise ArgumentError, 'url_template should contain {id}' if !url_template.include?('{id}')
27
-
28
- response = RestClient.post(
29
- "#{Domain::quill(self)}/",
30
- { actor_token: build_livefyre_token, pull_profile_url: url_template }
31
- )
32
- response.code == 204
33
- end
34
-
35
- def sync_user(user_id)
36
- response = RestClient.post(
37
- "#{Domain::quill(self)}/api/v3_0/user/#{user_id}/refresh",
38
- { lftoken: build_livefyre_token }
39
- )
40
- response.code == 200
41
- end
42
-
43
- def build_livefyre_token
44
- build_user_auth_token(DEFAULT_USER, DEFAULT_USER, DEFAULT_EXPIRES)
45
- end
46
-
47
- def build_user_auth_token(user_id, display_name, expires)
48
- raise ArgumentError, 'user_id must be alphanumeric' if !(user_id =~ /\A\p{Alnum}+\z/)
49
-
50
- JWT.encode({
51
- domain: @name,
52
- user_id: user_id,
53
- display_name: display_name,
54
- expires: Time.new.to_i + expires},
55
- @key)
22
+ def self.init(name, key)
23
+ data = NetworkData.new(name, key)
24
+ Network.new(NetworkValidator::validate(data))
56
25
  end
57
26
 
58
- def validate_livefyre_token(lf_token)
59
- token_attributes = JWT.decode(lf_token, @key)
27
+ def set_user_sync_url(url_template)
28
+ raise ArgumentError, 'url_template should contain {id}' if !url_template.include?('{id}')
29
+
30
+ response = RestClient.post(
31
+ "#{Domain::quill(self)}/",
32
+ { :actor_token => build_livefyre_token, :pull_profile_url => url_template }
33
+ ){|response, request, result| response }
34
+ raise ApiException.new(self, response.code) if response.code >= 400
35
+ end
36
+
37
+ def sync_user(user_id)
38
+ response = RestClient.post(
39
+ "#{Domain::quill(self)}/api/v3_0/user/#{user_id}/refresh",
40
+ { :lftoken => build_livefyre_token }
41
+ ){|response, request, result| response }
42
+ raise ApiException.new(self, response.code) if response.code >= 400
43
+ self
44
+ end
60
45
 
61
- token_attributes['domain'] == @name \
62
- && token_attributes['user_id'] == DEFAULT_USER \
63
- && token_attributes['expires'] >= Time.new.to_i
46
+ def build_livefyre_token
47
+ build_user_auth_token(DEFAULT_USER, DEFAULT_USER, DEFAULT_EXPIRES)
48
+ end
49
+
50
+ def build_user_auth_token(user_id, display_name, expires)
51
+ raise ArgumentError, 'user_id must be alphanumeric' if !(user_id =~ /^[a-zZA-Z0-9_\.-]+$/)
52
+ raise ArgumentError, 'expires must be a number' if !expires.is_a? Numeric
53
+
54
+ JWT.encode({
55
+ :domain => @data.name,
56
+ :user_id => user_id,
57
+ :display_name => display_name,
58
+ :expires => Time.new.to_i + expires},
59
+ @data.key)
60
+ end
61
+
62
+ def validate_livefyre_token(lf_token)
63
+ token_attributes = JWT.decode(lf_token, @data.key)
64
+ token_attributes['domain'] == @data.name && token_attributes['user_id'] == DEFAULT_USER && token_attributes['expires'] >= Time.new.to_i
64
65
  end
65
66
 
66
67
  def get_site(site_id, site_key)
67
- Site.new(self, site_id, site_key)
68
+ Site::init(self, site_id, site_key)
69
+ end
70
+
71
+ def urn
72
+ "urn:livefyre:#{@data.name}"
68
73
  end
69
74
 
70
- def get_urn
71
- "urn:livefyre:#{@name}"
75
+ def get_urn_for_user(user)
76
+ "#{urn}:user=#{user}"
72
77
  end
73
78
 
74
- def get_user_urn(user)
75
- "#{get_urn}:user=#{user}"
79
+ def network_name
80
+ @data.name.split('.')[0]
76
81
  end
77
- end
82
+ end
78
83
  end
@@ -1,104 +1,56 @@
1
- require 'base64'
2
- require 'digest'
3
- require 'json'
4
- require 'jwt'
5
- require 'rest-client'
6
- require 'addressable/uri'
7
-
8
- require 'livefyre/api/domain'
1
+ require 'livefyre/core/collection'
2
+ require 'livefyre/model/site_data'
3
+ require 'livefyre/type/collection_type'
4
+ require 'livefyre/validator/site_validator'
9
5
 
10
6
  module Livefyre
11
7
  class Site
12
- TYPE = %w(reviews sidenotes ratings counting liveblog livechat livecomments)
13
-
14
- def initialize(network, id, key)
8
+ attr_accessor :network, :data
9
+
10
+ def initialize(network, data)
15
11
  @network = network
16
- @id = id
17
- @key = key
12
+ @data = data
18
13
  end
19
14
 
20
- attr_reader :network
21
- attr_reader :id
22
- attr_reader :key
23
-
24
- def build_collection_meta_token(title, article_id, url, options={})
25
- raise ArgumentError, 'provided url is not a valid url' if !uri?(url)
26
- raise ArgumentError, 'title length should be under 255 char' if title.length > 255
27
-
28
- collection_meta = {
29
- url: url,
30
- title: title,
31
- articleId: article_id
32
- }
33
-
34
- if options.has_key?(:type) && !TYPE.include?(options[:type])
35
- raise ArgumentError, 'type is not a recognized type. should be liveblog, livechat, livecomments, reviews, sidenotes, or an empty string'
36
- end
37
-
38
- JWT.encode(collection_meta.merge(options), @key)
39
- end
40
-
41
- def build_checksum(title, url, tags='')
42
- raise ArgumentError, 'provided url is not a valid url' if !uri?(url)
43
- raise ArgumentError, 'title length should be under 255 char' if title.length > 255
44
-
45
- collection_meta = { tags: tags, title: title, url: url }
46
- Digest::MD5.new.update(collection_meta.to_json).hexdigest
47
- end
48
-
49
-
50
- def create_collection(title, article_id, url, options={})
51
- uri = "#{Domain::quill(self)}/api/v3.0/site/#{@id}/collection/create/?sync=1"
52
- data = {
53
- articleId: article_id,
54
- collectionMeta: build_collection_meta_token(title, article_id, url, options),
55
- checksum: build_checksum(title, url, options.has_key?(:tags) ? options[:tags] : '')
56
- }
57
- headers = {:accepts => :json, :content_type => :json}
15
+ def self.init(network, id, key)
16
+ data = SiteData.new(id, key)
17
+ Site.new(network, SiteValidator::validate(data))
18
+ end
58
19
 
59
- response = RestClient.post(uri, data.to_json, headers)
20
+ def build_comments_collection(title, article_id, url)
21
+ build_collection(CollectionType::COMMENTS, title, article_id, url)
22
+ end
60
23
 
61
- if response.code == 200
62
- return JSON.parse(response)['data']['collectionId']
63
- end
24
+ def build_blog_collection(title, article_id, url)
25
+ build_collection(CollectionType::BLOG, title, article_id, url)
26
+ end
64
27
 
65
- nil
28
+ def build_chat_collection(title, article_id, url)
29
+ build_collection(CollectionType::CHAT, title, article_id, url)
66
30
  end
67
31
 
68
- def get_collection_content(article_id)
69
- response = RestClient.get(
70
- "#{Domain::bootstrap(self)}/bs3/#{@network.name}/#{@id}/#{Base64.encode64(article_id.to_s).chomp}/init",
71
- :accepts => :json
72
- )
73
- response.code == 200 ? JSON.parse(response) : nil
74
- end
32
+ def build_counting_collection(title, article_id, url)
33
+ build_collection(CollectionType::COUNTING, title, article_id, url)
34
+ end
75
35
 
76
- def get_collection_id(article_id)
77
- content = get_collection_content(article_id)
78
- if content
79
- content['collectionSettings']['collectionId']
80
- end
36
+ def build_ratings_collection(title, article_id, url)
37
+ build_collection(CollectionType::RATINGS, title, article_id, url)
81
38
  end
82
39
 
83
- def network_name
84
- @network.network_name
40
+ def build_reviews_collection(title, article_id, url)
41
+ build_collection(CollectionType::REVIEWS, title, article_id, url)
85
42
  end
86
43
 
87
- def build_livefyre_token
88
- @network.build_livefyre_token
44
+ def build_sidenotes_collection(title, article_id, url)
45
+ build_collection(CollectionType::SIDENOTES, title, article_id, url)
89
46
  end
90
47
 
91
- def get_urn
92
- "#{@network.get_urn}:site=#{@id}"
93
- end
48
+ def build_collection(type, title, article_id, url)
49
+ Collection::init(self, type, title, article_id, url)
50
+ end
94
51
 
95
- def uri?(string)
96
- uri = Addressable::URI.parse(string)
97
- %w( ftp ftps http https ).include?(uri.scheme)
98
- rescue Addressable::URI::BadURIError
99
- false
100
- rescue Addressable::URI::InvalidURIError
101
- false
52
+ def urn
53
+ "#{@network.urn}:site=#{@data.id}"
102
54
  end
103
55
  end
104
56
  end
@@ -0,0 +1,45 @@
1
+ require 'livefyre/api/personalized_stream'
2
+ require 'livefyre/model/cursor_data'
3
+ require 'livefyre/validator/cursor_validator'
4
+
5
+ module Livefyre
6
+ class TimelineCursor
7
+ attr_accessor :core, :data
8
+
9
+ def initialize(core, data)
10
+ @core = core
11
+ @data = data
12
+ end
13
+
14
+ def self.init(core, resource, limit, date)
15
+ data = CursorData.new(resource, limit, date)
16
+ TimelineCursor.new(core, CursorValidator::validate(data))
17
+ end
18
+
19
+ def next
20
+ data = PersonalizedStream::get_timeline_stream(self, true)
21
+ cursor = data['meta']['cursor']
22
+
23
+ @data.next = cursor['hasNext']
24
+ @data.previous = cursor['next'] != nil
25
+ if @data.previous
26
+ @data.cursor_time = cursor['next']
27
+ end
28
+
29
+ data
30
+ end
31
+
32
+ def previous
33
+ data = PersonalizedStream::get_timeline_stream(self, false)
34
+ cursor = data['meta']['cursor']
35
+
36
+ @data.previous = cursor['hasPrev']
37
+ @data.next = cursor['prev'] != nil
38
+ if @data.next
39
+ @data.cursor_time = cursor['prev']
40
+ end
41
+
42
+ data
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,9 @@
1
+ require 'json'
2
+
1
3
  module Livefyre
2
4
  class Subscription
5
+ attr_accessor :to, :by, :type, :created_at
6
+
3
7
  def initialize(to, by, type, created_at=nil)
4
8
  @to = to
5
9
  @by = by
@@ -7,26 +11,20 @@ module Livefyre
7
11
  @created_at = created_at
8
12
  end
9
13
 
10
- attr_reader :to
11
- attr_reader :by
12
- attr_reader :type
13
- attr_reader :created_at
14
-
15
14
  def self.serialize_from_json(json)
16
15
  new(json['to'], json['by'], json['type'], json['createdAt'])
17
16
  end
18
17
 
19
- def to_dict
20
- dict = { :to => @to, :by => @by, :type => @type }
18
+ def to_json(options = {})
19
+ to_hash.to_json(options)
20
+ end
21
+
22
+ def to_hash
23
+ hash = { :to => @to, :by => @by, :type => @type }
21
24
  if @created_at != nil
22
- dict[:createdAt] = @created_at
25
+ hash[:createdAt] = @created_at
23
26
  end
24
- dict
27
+ hash
25
28
  end
26
-
27
- end
28
-
29
- module SubscriptionType
30
- PERSONAL_STREAM = 1
31
29
  end
32
30
  end