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.
- checksums.yaml +5 -13
- data/.gitignore +3 -6
- data/CHANGELOG +10 -0
- data/README.md +3 -66
- data/circle.yml +10 -0
- data/lib/livefyre.rb +1 -1
- data/lib/livefyre/api/domain.rb +15 -0
- data/lib/livefyre/api/personalized_stream.rb +79 -72
- data/lib/livefyre/core/collection.rb +93 -0
- data/lib/livefyre/core/network.rb +61 -56
- data/lib/livefyre/core/site.rb +34 -82
- data/lib/livefyre/cursor/timeline_cursor.rb +45 -0
- data/lib/livefyre/{entity → dto}/subscription.rb +12 -14
- data/lib/livefyre/{entity → dto}/topic.rb +16 -13
- data/lib/livefyre/exceptions/api_exception.rb +29 -0
- data/lib/livefyre/exceptions/livefyre_exception.rb +9 -0
- data/lib/livefyre/factory/cursor_factory.rb +4 -4
- data/lib/livefyre/model/collection_data.rb +30 -0
- data/lib/livefyre/model/cursor_data.rb +17 -0
- data/lib/livefyre/model/network_data.rb +10 -0
- data/lib/livefyre/model/site_data.rb +10 -0
- data/lib/livefyre/type/collection_type.rb +11 -0
- data/lib/livefyre/type/subscription_type.rb +5 -0
- data/lib/livefyre/utils/livefyre_util.rb +24 -0
- data/lib/livefyre/validator/collection_validator.rb +33 -0
- data/lib/livefyre/validator/cursor_validator.rb +15 -0
- data/lib/livefyre/validator/network_validator.rb +19 -0
- data/lib/livefyre/validator/site_validator.rb +14 -0
- data/lib/livefyre/version.rb +1 -1
- data/livefyre.gemspec +15 -12
- data/spec/livefyre/api/domain_spec.rb +40 -0
- data/spec/livefyre/api/personalized_stream_spec.rb +54 -37
- data/spec/livefyre/core/collection_spec.rb +90 -0
- data/spec/livefyre/core/network_spec.rb +32 -6
- data/spec/livefyre/core/site_spec.rb +24 -62
- data/spec/livefyre/cursor/timeline_cursor_spec.rb +25 -0
- data/spec/livefyre/dto/subscription_spec.rb +27 -0
- data/spec/livefyre/dto/topic_spec.rb +35 -0
- data/spec/livefyre/factory/cursor_factory_spec.rb +29 -0
- data/spec/livefyre/utils/utils_spec.rb +30 -0
- data/spec/spec_helper.rb +34 -0
- metadata +134 -72
- data/.idea/misc.xml +0 -5
- data/.idea/modules.xml +0 -9
- data/lib/livefyre/entity/timeline_cursor.rb +0 -41
- 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
|
-
|
10
|
-
|
11
|
-
|
11
|
+
class Network
|
12
|
+
DEFAULT_USER = 'system'
|
13
|
+
DEFAULT_EXPIRES = 86400
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
59
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
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
|
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
|
71
|
-
"urn:
|
75
|
+
def get_urn_for_user(user)
|
76
|
+
"#{urn}:user=#{user}"
|
72
77
|
end
|
73
78
|
|
74
|
-
def
|
75
|
-
|
79
|
+
def network_name
|
80
|
+
@data.name.split('.')[0]
|
76
81
|
end
|
77
|
-
|
82
|
+
end
|
78
83
|
end
|
data/lib/livefyre/core/site.rb
CHANGED
@@ -1,104 +1,56 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require '
|
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
|
-
|
13
|
-
|
14
|
-
def initialize(network,
|
8
|
+
attr_accessor :network, :data
|
9
|
+
|
10
|
+
def initialize(network, data)
|
15
11
|
@network = network
|
16
|
-
|
17
|
-
@key = key
|
12
|
+
@data = data
|
18
13
|
end
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
20
|
+
def build_comments_collection(title, article_id, url)
|
21
|
+
build_collection(CollectionType::COMMENTS, title, article_id, url)
|
22
|
+
end
|
60
23
|
|
61
|
-
|
62
|
-
|
63
|
-
|
24
|
+
def build_blog_collection(title, article_id, url)
|
25
|
+
build_collection(CollectionType::BLOG, title, article_id, url)
|
26
|
+
end
|
64
27
|
|
65
|
-
|
28
|
+
def build_chat_collection(title, article_id, url)
|
29
|
+
build_collection(CollectionType::CHAT, title, article_id, url)
|
66
30
|
end
|
67
31
|
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
77
|
-
|
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
|
84
|
-
|
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
|
88
|
-
|
44
|
+
def build_sidenotes_collection(title, article_id, url)
|
45
|
+
build_collection(CollectionType::SIDENOTES, title, article_id, url)
|
89
46
|
end
|
90
47
|
|
91
|
-
|
92
|
-
|
93
|
-
|
48
|
+
def build_collection(type, title, article_id, url)
|
49
|
+
Collection::init(self, type, title, article_id, url)
|
50
|
+
end
|
94
51
|
|
95
|
-
def
|
96
|
-
|
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
|
20
|
-
|
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
|
-
|
25
|
+
hash[:createdAt] = @created_at
|
23
26
|
end
|
24
|
-
|
27
|
+
hash
|
25
28
|
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
module SubscriptionType
|
30
|
-
PERSONAL_STREAM = 1
|
31
29
|
end
|
32
30
|
end
|