quintype 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +68 -0
- data/lib/quintype/api.rb +224 -0
- data/lib/quintype/api/author.rb +42 -0
- data/lib/quintype/api/stack.rb +18 -0
- data/lib/quintype/api/story.rb +119 -0
- data/lib/quintype/api/story/reading_time.rb +94 -0
- data/lib/quintype/api/url.rb +46 -0
- data/lib/quintype/engine.rb +5 -0
- data/quintype.gemspec +17 -0
- data/spec/api/stack_spec.rb +29 -0
- data/spec/api/story_spec.rb +102 -0
- data/spec/fixtures/vcr_cassettes/api_stack_all.yml +222 -0
- data/spec/fixtures/vcr_cassettes/api_stack_stories_with_params.yml +7334 -0
- data/spec/fixtures/vcr_cassettes/api_stack_with_stories.yml +6189 -0
- data/spec/fixtures/vcr_cassettes/api_stories_find_by_slug.yml +371 -0
- data/spec/fixtures/vcr_cassettes/api_stories_video.yml +67 -0
- data/spec/fixtures/vcr_cassettes/api_story_all.yml +1177 -0
- data/spec/fixtures/vcr_cassettes/api_story_find.yml +187 -0
- data/spec/fixtures/vcr_cassettes/api_story_find_by_stacks.yml +5970 -0
- data/spec/fixtures/vcr_cassettes/api_story_find_by_stacks_and_sections.yml +7115 -0
- data/spec/fixtures/vcr_cassettes/api_story_find_config.yml +406 -0
- data/spec/fixtures/vcr_cassettes/api_story_find_in_bulk.yml +186 -0
- data/spec/spec_helper.rb +110 -0
- data/spec/unit/api_spec.rb +0 -0
- metadata +28 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fefb62987388a3665a201c7b5516bb4b16207319
|
4
|
+
data.tar.gz: f2f0d1d2a058b4f884b9fd9848bc836d31022bdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c1d743109ee6717b5d3ba473c93601387d0ff9f3b91085cdef31c81b0f9e8a5eda044b3defd55450b1d278a726c56656c2bf62c358927a51b7eb76e6c70b590
|
7
|
+
data.tar.gz: 676ec05fe4c62ca5a8ac93e1a54de6c9cd721ee1496b514f1c4cb8dd7056d53c843f864a1dcd8853d0eee0a03afa27e2dab138f66ef32f6b14cc3c13dd7f6151
|
data/.gitignore
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore all logfiles and tempfiles.
|
11
|
+
/log/*
|
12
|
+
!/log/.keep
|
13
|
+
/tmp
|
14
|
+
.DS_Store
|
15
|
+
.sass-cache/
|
16
|
+
.byebug_history
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
quintype (0.0.0)
|
5
|
+
activesupport (~> 4.2)
|
6
|
+
faraday (~> 0.9)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (4.2.5.1)
|
12
|
+
i18n (~> 0.7)
|
13
|
+
json (~> 1.7, >= 1.7.7)
|
14
|
+
minitest (~> 5.1)
|
15
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
16
|
+
tzinfo (~> 1.1)
|
17
|
+
addressable (2.4.0)
|
18
|
+
byebug (8.2.2)
|
19
|
+
coderay (1.1.1)
|
20
|
+
crack (0.4.3)
|
21
|
+
safe_yaml (~> 1.0.0)
|
22
|
+
diff-lcs (1.2.5)
|
23
|
+
faraday (0.9.2)
|
24
|
+
multipart-post (>= 1.2, < 3)
|
25
|
+
hashdiff (0.3.0)
|
26
|
+
i18n (0.7.0)
|
27
|
+
json (1.8.3)
|
28
|
+
method_source (0.8.2)
|
29
|
+
minitest (5.8.4)
|
30
|
+
multipart-post (2.0.0)
|
31
|
+
pry (0.10.3)
|
32
|
+
coderay (~> 1.1.0)
|
33
|
+
method_source (~> 0.8.1)
|
34
|
+
slop (~> 3.4)
|
35
|
+
rspec (3.4.0)
|
36
|
+
rspec-core (~> 3.4.0)
|
37
|
+
rspec-expectations (~> 3.4.0)
|
38
|
+
rspec-mocks (~> 3.4.0)
|
39
|
+
rspec-core (3.4.3)
|
40
|
+
rspec-support (~> 3.4.0)
|
41
|
+
rspec-expectations (3.4.0)
|
42
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
43
|
+
rspec-support (~> 3.4.0)
|
44
|
+
rspec-mocks (3.4.1)
|
45
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
46
|
+
rspec-support (~> 3.4.0)
|
47
|
+
rspec-support (3.4.1)
|
48
|
+
safe_yaml (1.0.4)
|
49
|
+
slop (3.6.0)
|
50
|
+
thread_safe (0.3.5)
|
51
|
+
tzinfo (1.2.2)
|
52
|
+
thread_safe (~> 0.1)
|
53
|
+
vcr (3.0.1)
|
54
|
+
webmock (1.24.2)
|
55
|
+
addressable (>= 2.3.6)
|
56
|
+
crack (>= 0.3.2)
|
57
|
+
hashdiff
|
58
|
+
|
59
|
+
PLATFORMS
|
60
|
+
ruby
|
61
|
+
|
62
|
+
DEPENDENCIES
|
63
|
+
byebug
|
64
|
+
pry
|
65
|
+
quintype!
|
66
|
+
rspec (~> 3.4)
|
67
|
+
vcr
|
68
|
+
webmock
|
data/lib/quintype/api.rb
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
# API models
|
6
|
+
require_relative './api/story'
|
7
|
+
require_relative './api/stack'
|
8
|
+
require_relative './api/url'
|
9
|
+
|
10
|
+
class API
|
11
|
+
class << self
|
12
|
+
def establish_connection(host, conn = Faraday.new(url: host))
|
13
|
+
@@host = host
|
14
|
+
@@api_base = host + '/api/'
|
15
|
+
@@conn = conn
|
16
|
+
end
|
17
|
+
|
18
|
+
def conn
|
19
|
+
@@conn
|
20
|
+
end
|
21
|
+
|
22
|
+
def bulk_post(params)
|
23
|
+
_post("bulk", params)
|
24
|
+
end
|
25
|
+
|
26
|
+
def config
|
27
|
+
_get("config")
|
28
|
+
end
|
29
|
+
|
30
|
+
def story(story_id)
|
31
|
+
_get("stories/#{story_id}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def story_by_slug(slug, params = {})
|
35
|
+
_get("stories-by-slug", params.merge({ slug: slug }))
|
36
|
+
end
|
37
|
+
|
38
|
+
def related_stories(story_id, section, fields = [])
|
39
|
+
_get("related-stories?", {
|
40
|
+
"story-id" => story_id,
|
41
|
+
section: section,
|
42
|
+
fields: make_fields(fields)
|
43
|
+
})
|
44
|
+
end
|
45
|
+
|
46
|
+
def stories(params, options = {})
|
47
|
+
url = options[:facets] ? "stories-with-facets" : "stories"
|
48
|
+
_get(url, params)
|
49
|
+
end
|
50
|
+
|
51
|
+
def comments_and_likes(story_ids)
|
52
|
+
if story_ids.present?
|
53
|
+
_get("comments-and-votes/" + story_ids.join('|'))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def videos
|
58
|
+
_get("stories-by-template", {
|
59
|
+
template: "video",
|
60
|
+
limit: 12,
|
61
|
+
fields: "hero-image-s3-key,hero-image-metadata,hero-image-caption,headline,slug"
|
62
|
+
})
|
63
|
+
end
|
64
|
+
|
65
|
+
def search_story_collection(name, options)
|
66
|
+
_get("story-collection", {
|
67
|
+
name: name,
|
68
|
+
type: "search",
|
69
|
+
fields: "author-name,hero-image-s3-key,hero-image-metadata,hero-image-caption,headline,slug,sections,metadata"
|
70
|
+
}.merge(options))
|
71
|
+
end
|
72
|
+
|
73
|
+
def story_collection(options)
|
74
|
+
_get("story-collection", options)
|
75
|
+
end
|
76
|
+
|
77
|
+
def story_collection_by_tag(options)
|
78
|
+
_get("story-collection/find-by-tag", options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def post_comment(story_content_id, text, parent_comment_id=nil, session_cookie)
|
82
|
+
hash = {
|
83
|
+
"story-content-id" => story_content_id,
|
84
|
+
"text" => text
|
85
|
+
}
|
86
|
+
hash.merge!("parent-comment-id" => parent_comment_id.to_i) if parent_comment_id
|
87
|
+
_post("comment", hash, session_cookie)
|
88
|
+
end
|
89
|
+
|
90
|
+
def invite_users(emails, from_email, from_name)
|
91
|
+
params = { emails: emails }
|
92
|
+
params['from-email'] = from_email if from_email.present?
|
93
|
+
params['from-name'] = from_name if from_name.present?
|
94
|
+
|
95
|
+
_post("emails/invite", params)
|
96
|
+
end
|
97
|
+
|
98
|
+
def contact_publisher(params)
|
99
|
+
_post("emails/contact", params)
|
100
|
+
end
|
101
|
+
|
102
|
+
def unsubscribe_publisher(params)
|
103
|
+
_post("emails/unsubscribe", params)
|
104
|
+
end
|
105
|
+
|
106
|
+
def authors(params)
|
107
|
+
_get("authors", params)
|
108
|
+
end
|
109
|
+
|
110
|
+
def author_profile(author_id)
|
111
|
+
_get("author/#{author_id}")
|
112
|
+
end
|
113
|
+
|
114
|
+
def search(options)
|
115
|
+
_get("search", options)
|
116
|
+
end
|
117
|
+
|
118
|
+
def subscribe(member, profile, payment)
|
119
|
+
_post("subscribe", {
|
120
|
+
member: member,
|
121
|
+
profile: profile,
|
122
|
+
payment: payment
|
123
|
+
})
|
124
|
+
end
|
125
|
+
|
126
|
+
def unsubscribe(options)
|
127
|
+
_post("unsubscribe", { options: options })
|
128
|
+
end
|
129
|
+
|
130
|
+
def save_member_metadata(metadata)
|
131
|
+
_post("member/metadata", { metadata: metadata })
|
132
|
+
end
|
133
|
+
|
134
|
+
def check_email(email)
|
135
|
+
_get("member/check", { email: email })
|
136
|
+
end
|
137
|
+
|
138
|
+
def signup_member(member)
|
139
|
+
_post("member", member)
|
140
|
+
end
|
141
|
+
|
142
|
+
def login_member(auth)
|
143
|
+
_post("member/login", auth)
|
144
|
+
end
|
145
|
+
|
146
|
+
def login(provider, data)
|
147
|
+
user, headers = _post_returning_headers("login/#{provider}", data)
|
148
|
+
user['auth_token'] = headers['X-QT-AUTH']
|
149
|
+
user['member'].merge(user.except('member'))
|
150
|
+
end
|
151
|
+
|
152
|
+
def logout
|
153
|
+
_get("logout")
|
154
|
+
end
|
155
|
+
|
156
|
+
def forgot_password(member)
|
157
|
+
_post("member/forgot-password", member)
|
158
|
+
end
|
159
|
+
|
160
|
+
def reset_password(params)
|
161
|
+
_post("member/password", params)
|
162
|
+
end
|
163
|
+
|
164
|
+
def vote_on_story (data)
|
165
|
+
_post("stories/#{data[:story_id]}/votes", data)
|
166
|
+
end
|
167
|
+
|
168
|
+
def votes_on_story (options = {})
|
169
|
+
_get("stories/#{story_id}/votes", options)
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def _post(url_path, body, session_cookie=nil)
|
175
|
+
body, headers = _post_returning_headers(url_path, body, session_cookie)
|
176
|
+
|
177
|
+
body
|
178
|
+
end
|
179
|
+
|
180
|
+
def _post_returning_headers(url_path, body, session_cookie=nil)
|
181
|
+
response = @@conn.post(@@api_base + url_path) do |request|
|
182
|
+
request.headers['Content-Type'] = 'application/json'
|
183
|
+
request.headers['X-QT-AUTH'] = session_cookie if session_cookie
|
184
|
+
|
185
|
+
request.body = body.to_json
|
186
|
+
end
|
187
|
+
|
188
|
+
if response.body.present?
|
189
|
+
body = case body = JSON.parse(response.body)
|
190
|
+
when Array
|
191
|
+
body.map { |i| keywordize(i) }
|
192
|
+
when Object
|
193
|
+
keywordize body
|
194
|
+
end
|
195
|
+
|
196
|
+
[body, response.headers]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def _get(url_path, *args)
|
201
|
+
response = @@conn.get(@@api_base + url_path, *args)
|
202
|
+
return nil if response.status >= 400
|
203
|
+
|
204
|
+
if response.body.present?
|
205
|
+
body = JSON.parse(response.body)
|
206
|
+
|
207
|
+
case body
|
208
|
+
when Array
|
209
|
+
body.map { |i| keywordize(i) }
|
210
|
+
when Object
|
211
|
+
keywordize body
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def keywordize(obj)
|
217
|
+
obj.deep_transform_keys { |k| k.gsub('-', '_') }
|
218
|
+
end
|
219
|
+
|
220
|
+
def make_fields(arr)
|
221
|
+
arr.join(',') if arr.present?
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class API
|
2
|
+
class Author
|
3
|
+
attr_reader :author
|
4
|
+
class << self
|
5
|
+
def wrap_all(authors)
|
6
|
+
authors ||= []
|
7
|
+
authors.is_a?(Array) ?
|
8
|
+
authors.map { |a| wrap(a) } :
|
9
|
+
wrap(authors)
|
10
|
+
end
|
11
|
+
|
12
|
+
def wrap(author)
|
13
|
+
new(author) if author
|
14
|
+
end
|
15
|
+
|
16
|
+
def where(params)
|
17
|
+
if params['ids'].kind_of? Array
|
18
|
+
params['ids'] = params['ids'].join ","
|
19
|
+
end
|
20
|
+
authors = API.authors(params)
|
21
|
+
wrap_all(authors)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(params)
|
25
|
+
if authors = API.authors({ids: params}).presence
|
26
|
+
author = authors.first
|
27
|
+
wrap(author)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(author)
|
33
|
+
@author = author
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h(config={})
|
37
|
+
author
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class API
|
2
|
+
class Stack
|
3
|
+
class << self
|
4
|
+
def all
|
5
|
+
API.config['layout']['stacks']
|
6
|
+
end
|
7
|
+
#TODO filter by stacks
|
8
|
+
def with_stories(params={}, config={})
|
9
|
+
stories_with_stacks = API::Story.find_by_stacks(all, params)
|
10
|
+
stacks = all.map do |stack|
|
11
|
+
stories = stories_with_stacks[stack['story_group'].gsub('-', '_')]
|
12
|
+
stack['stories'] = stories
|
13
|
+
stack
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative './story/reading_time'
|
2
|
+
|
3
|
+
class API
|
4
|
+
class Story
|
5
|
+
attr_reader :story
|
6
|
+
include ReadingTime
|
7
|
+
class << self
|
8
|
+
def wrap_all(stories)
|
9
|
+
stories ||= []
|
10
|
+
stories.is_a?(Array) ?
|
11
|
+
stories.map { |s| wrap(s) } :
|
12
|
+
wrap(stories)
|
13
|
+
end
|
14
|
+
|
15
|
+
def wrap(story)
|
16
|
+
new(story) if story
|
17
|
+
end
|
18
|
+
|
19
|
+
def where(params, opts={})
|
20
|
+
stories = API.stories(params, opts)
|
21
|
+
wrap_all(stories)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(params, opts={})
|
25
|
+
if stories = API.stories(params, opts).presence
|
26
|
+
story = stories.first
|
27
|
+
wrap(story)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_by_stacks(stacks, options={})
|
32
|
+
if stacks.present?
|
33
|
+
requests = stacks.inject({}) do |hash, stack|
|
34
|
+
options.reject! {|k,v| k == 'section' }
|
35
|
+
hash[stack['story_group']] = { 'story_group' => stack['story_group'] }.merge(options)
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
|
39
|
+
stories = find_in_bulk(requests)
|
40
|
+
stories
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_in_bulk(params)
|
45
|
+
if params.present?
|
46
|
+
params = params.inject({}) do |hash, param|
|
47
|
+
hash[param.first] = param.last.merge(_type: 'stories')
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
response = API.bulk_post(requests: params)
|
51
|
+
response['results']
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_by_slug(slug, params = {})
|
58
|
+
if story = API.story_by_slug(slug, params).presence
|
59
|
+
wrap(story['story'])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def all_video_stories
|
64
|
+
stories = API.videos
|
65
|
+
wrap_all(stories['stories'])
|
66
|
+
end
|
67
|
+
|
68
|
+
def all
|
69
|
+
stories = API.stories({})
|
70
|
+
wrap_all(stories)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(story)
|
75
|
+
@story = story
|
76
|
+
end
|
77
|
+
|
78
|
+
def cards
|
79
|
+
@cards = story['cards'] || []
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_h(config={})
|
83
|
+
hash = story.merge({
|
84
|
+
'url' => URL.story(story),
|
85
|
+
'time_in_minutes' => time_in_minutes,
|
86
|
+
'tags' => add_urls_to_tags
|
87
|
+
})
|
88
|
+
if config.present?
|
89
|
+
hash.merge!({ 'sections' => add_display_names_to_sections(config),
|
90
|
+
'canonical_url' => URL.story_canonical(config['root_url'], story)
|
91
|
+
})
|
92
|
+
end
|
93
|
+
hash
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def add_display_names_to_sections(config)
|
98
|
+
return story unless story['sections'].present?
|
99
|
+
|
100
|
+
sections = story['sections'].map do |section|
|
101
|
+
display_section = config['sections'].find { |s| s['id'] == section['id'] } || {}
|
102
|
+
|
103
|
+
display_name = display_section['display_name'] || display_section['name'] || section['name']
|
104
|
+
|
105
|
+
section.merge({ 'display_name' => display_name })
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def add_urls_to_tags
|
110
|
+
if story['tags'].present?
|
111
|
+
tags = story['tags'].map do |tag|
|
112
|
+
tag.merge('url' => URL.topic(tag['name']))
|
113
|
+
end
|
114
|
+
else
|
115
|
+
story['tags']
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|