quintype 0.0.2 → 0.0.3
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 +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
|