wcc-blogs-client 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2fe6e7e2f78e21fcfc1152bfafd4d3803092be05
4
- data.tar.gz: 04e1df2e485fb58dfa0617650906c615dc986aaa
3
+ metadata.gz: d0ee40f5dbfa5ed338bfcfc4ef0a28cf1fac1554
4
+ data.tar.gz: cd4b83add94f175077a9a31062caf2a7a9a0dd64
5
5
  SHA512:
6
- metadata.gz: b67d47ffa6a706f454c4c3bba183c5597a2b3cb538fa18fe23c36a7a89286cd88875b1cfebc75161f838ca5e8bcb3a7314a1de3500702b58648fdb4862f18826
7
- data.tar.gz: 2ec2a66bac4f62eeefee9e24abe1ca113b92ba23014c0d74b44b2fd2b82ebbfc694a6c561ca3b87c833af3f16bf09202c7dce5991a466f57befc53c5d38bc025
6
+ metadata.gz: 1b3cb2d385fe25b0c312e2e742d2245f7f4e6eb1a58bc7a4b140b210bb612cab8fcb396c16afbbedfb36555f9c6ed5e73387854b5c477b8b7af3c28f99d2b9b1
7
+ data.tar.gz: 2efbf1c351083e6460b1e09db3a383f8e6c6c48ac9ad25720e6a3982823545e11d5080ab77d3772da86e2150e91b29ef3ba91e7dcb2ec302d0d1339b0edcd3d7
data/.rubocop.yml CHANGED
@@ -14,6 +14,9 @@ Style/ClassAndModuleChildren:
14
14
  Style/Documentation:
15
15
  Enabled: false
16
16
 
17
+ Style/DoubleNegation:
18
+ Enabled: false
19
+
17
20
  Style/MultilineBlockChain:
18
21
  Exclude:
19
22
  - 'spec/**/*'
data/Guardfile ADDED
@@ -0,0 +1,32 @@
1
+ directories %w[lib spec]
2
+
3
+ group :red_green_refactor, halt_on_fail: true do
4
+ guard :rspec, cmd: 'bundle exec rspec --order rand', all_on_start: false do
5
+ require 'guard/rspec/dsl'
6
+ dsl = Guard::RSpec::Dsl.new(self)
7
+
8
+ # RSpec files
9
+ rspec = dsl.rspec
10
+ watch(rspec.spec_helper) { rspec.spec_dir }
11
+ watch(rspec.spec_support) { rspec.spec_dir }
12
+ watch(rspec.spec_files)
13
+
14
+ # Ruby files
15
+ ruby = dsl.ruby
16
+ dsl.watch_spec_files_for(ruby.lib_files)
17
+ end
18
+
19
+ guard :rubocop, cli: ['--display-cop-names'] do
20
+ watch(/.+\.rb$/)
21
+ watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
22
+ end
23
+ end
24
+
25
+ group :autofix do
26
+ guard :rubocop, all_on_start: false, cli: ['--auto-correct', '--display-cop-names'] do
27
+ watch(/.+\.rb$/)
28
+ watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
29
+ end
30
+ end
31
+
32
+ scope group: :red_green_refactor
data/lib/wcc/blogs.rb CHANGED
@@ -9,50 +9,14 @@ require 'wcc/blogs/errors'
9
9
 
10
10
  require 'wcc/blogs/metadata'
11
11
  require 'wcc/blogs/post'
12
+ require 'wcc/blogs/post_summary'
12
13
 
13
14
  module WCC::Blogs
14
15
  class << self
15
- def configure
16
- yield config
17
-
18
- @client = WCC::Blogs::Client.new(config: config)
19
-
20
- config
21
- end
16
+ attr_writer :client
22
17
 
23
18
  def client
24
19
  @client || raise(WCC::Blogs::NotConfiguredException, 'Not configured')
25
20
  end
26
-
27
- def config
28
- Config.instance
29
- end
30
- end
31
-
32
- class Config
33
- include Singleton
34
-
35
- attr_accessor :publishing_target
36
- attr_writer :base_url, :cache, :query_url
37
-
38
- def base_url
39
- @base_url || 'https://wcc-papyrus.herokuapp.com'
40
- end
41
-
42
- def cache
43
- @cache || NilCache.new
44
- end
45
-
46
- def query_url
47
- @query_url || 'http://papyrus.wcc/graphql'
48
- end
49
-
50
- class NilCache
51
- def clear; end
52
-
53
- def fetch(*_args)
54
- yield
55
- end
56
- end
57
21
  end
58
22
  end
@@ -1,42 +1,87 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'faraday'
4
+ require 'faraday-http-cache'
5
+ require_relative 'client/response'
6
+
3
7
  module WCC::Blogs
4
8
  class Client
5
- def initialize(config:)
6
- @config = config
9
+ attr_reader :base_url, :publishing_target
10
+
11
+ def initialize(publishing_target:, **options)
12
+ @publishing_target = publishing_target
13
+ @base_url = options[:base_url] || 'https://di0v2frwtdqnv.cloudfront.net'
14
+ @connection = options[:connection] || default_connection
15
+ @query_defaults = options[:query_defaults] || {}
16
+ end
17
+
18
+ # performs an HTTP GET request to the specified path within the configured
19
+ # space and environment. Query parameters are merged with the defaults and
20
+ # appended to the request.
21
+ def get(path, query = {})
22
+ url = URI.join(@base_url, path)
23
+
24
+ Response.new(self,
25
+ { url: url, query: query },
26
+ get_http(url, query))
27
+ end
28
+
29
+ def blog_post(slug, digest: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
30
+ path = '/blog/' + slug.sub(%r{^/}, '')
31
+ path += '/' + digest if digest
32
+ path += '.json'
33
+
34
+ get(path).tap do |resp|
35
+ resp.assert_ok!
36
+ next if resp.raw['publishingTargets'].any? { |t| t['key'] == publishing_target }
37
+
38
+ raise WCC::Blogs::NotFoundException,
39
+ "Blog post '#{slug}' is not published to #{publishing_target}"
40
+ end
41
+ rescue WCC::Blogs::Client::NotFoundError => e
42
+ raise WCC::Blogs::NotFoundException, e.message
7
43
  end
8
44
 
9
- REDIRECT_HANDLER =
10
- ->(resp) {
11
- location = resp.headers['Location']
12
- digest = File.basename(location).gsub(/\.(fragment|json)$/, '')
45
+ def list
46
+ path = '/targets/' + publishing_target + '.json'
47
+ get(path).assert_ok!
48
+ end
49
+
50
+ private
51
+
52
+ def get_http(url, query, headers = {})
53
+ q = @query_defaults.merge(query || {})
13
54
 
14
- @config.cache.fetch(['BlogPost', digest, File.extname(location)]) do
15
- get(location, resp.effective_url)
55
+ loop do
56
+ resp = @connection.get(url, q, headers)
57
+
58
+ if [301, 302, 307].include?(resp.status)
59
+ url = URI.join(@base_url, resp.headers['Location'])
60
+ next
16
61
  end
17
- }
18
-
19
- RESPONSE_HANDLERS = {
20
- 404 => ->(resp) {
21
- raise WCC::Blogs::NotFoundException, "Blog post '#{resp.effective_url}' does not exist"
22
- },
23
- 200 => ->(resp) {
24
- resp.body
25
- },
26
- 301 => REDIRECT_HANDLER,
27
- 302 => REDIRECT_HANDLER,
28
- }.freeze
29
-
30
- def get(path, base = @config.base_url)
31
- url = URI.join(base, path)
32
-
33
- resp = Typhoeus.get(url, followlocation: false)
34
-
35
- if handler = RESPONSE_HANDLERS[resp.code]
36
- return instance_exec(resp, &handler)
62
+
63
+ return resp
37
64
  end
65
+ end
38
66
 
39
- raise WCC::Blogs::ApiException, "#{resp.return_code}: #{resp.effective_url}"
67
+ def default_connection
68
+ ::Faraday.new do |faraday|
69
+ faraday.use :http_cache,
70
+ shared_cache: false,
71
+ logger: (Rails.logger if defined?(Rails)),
72
+ store: default_cache_store,
73
+ serializer: Marshal
74
+ faraday.response :logger, (Rails.logger if defined?(Rails)), headers: false, bodies: false
75
+ faraday.adapter :typhoeus
76
+ end
77
+ end
78
+
79
+ def default_cache_store
80
+ if defined?(Rails)
81
+ Rails.cache
82
+ elsif defined?(ActiveSupport::Cache)
83
+ ActiveSupport::Cache.lookup_store(:memory, size: 64.megabytes)
84
+ end
40
85
  end
41
86
  end
42
87
  end
@@ -0,0 +1,106 @@
1
+ require 'forwardable'
2
+
3
+ module WCC::Blogs
4
+ class Client
5
+ Response = Struct.new(:client, :request, :raw_response) do # rubocop:disable Metrics/BlockLength
6
+ extend Forwardable
7
+
8
+ def_delegators :raw_response, :status, :headers
9
+ alias_method :code, :status
10
+
11
+ def body
12
+ @body ||= raw_response.body.to_s
13
+ end
14
+
15
+ def raw
16
+ @raw ||= JSON.parse(body)
17
+ end
18
+ alias_method :to_json, :raw
19
+
20
+ def next_page_url
21
+ raw.dig('_links', 'previous')
22
+ end
23
+
24
+ def next_page?
25
+ !!next_page_url
26
+ end
27
+
28
+ def next_page
29
+ return unless next_page_url
30
+ return @next_page if @next_page
31
+
32
+ np = client.get(next_page_url, request[:query])
33
+ @next_page = np.assert_ok!
34
+ end
35
+
36
+ def assert_ok!
37
+ return self if status >= 200 && status < 300
38
+
39
+ raise ApiError[status], self
40
+ end
41
+
42
+ def each_page(&block) # rubocop:disable Metrics/MethodLength
43
+ raise ArgumentError, 'Not a collection response' unless page_items
44
+
45
+ ret =
46
+ Enumerator.new do |y|
47
+ y << self
48
+
49
+ if next_page?
50
+ next_page.each_page.each do |page|
51
+ y << page
52
+ end
53
+ end
54
+ end
55
+
56
+ if block_given?
57
+ ret.map(&block)
58
+ else
59
+ ret.lazy
60
+ end
61
+ end
62
+
63
+ def items
64
+ each_page.flat_map(&:page_items)
65
+ end
66
+
67
+ def page_items
68
+ raw['items']
69
+ end
70
+
71
+ def count
72
+ total
73
+ end
74
+
75
+ def first
76
+ raise ArgumentError, 'Not a collection response' unless page_items
77
+
78
+ page_items.first
79
+ end
80
+ end
81
+
82
+ class ApiError < StandardError
83
+ attr_reader :response
84
+
85
+ def self.[](code)
86
+ case code
87
+ when 404
88
+ NotFoundException
89
+ else
90
+ ApiError
91
+ end
92
+ end
93
+
94
+ def initialize(response, message = nil)
95
+ @response = response
96
+ super(message || "An unexpected error occurred: #{response.status}")
97
+ end
98
+ end
99
+
100
+ class NotFoundError < ApiError
101
+ def initialize(response)
102
+ super(response, "Blog post '#{response.effective_url}' does not exist")
103
+ end
104
+ end
105
+ end
106
+ end
@@ -6,16 +6,18 @@ module WCC::Blogs
6
6
  require 'time'
7
7
 
8
8
  def self.find(slug)
9
- path = '/blog/' + slug.sub(%r{^/}, '') + '.json'
10
- new(JSON.parse(WCC::Blogs.client.get(path))).tap do |blog|
11
- raise WCC::Blogs::NotFoundException, "Blog post '#{slug}' is not published" unless blog.published?
12
- end
9
+ new(WCC::Blogs.client.blog_post(slug).raw, client: WCC::Blogs.client)
10
+ end
11
+
12
+ def self.find_all
13
+ PostSummary.find_all.map(&:full_post)
13
14
  end
14
15
 
15
16
  attr_reader :raw
16
17
 
17
- def initialize(raw)
18
+ def initialize(raw, client: WCC::Blogs.client)
18
19
  @raw = raw
20
+ @client = client
19
21
  end
20
22
 
21
23
  def author_full_name
@@ -25,12 +27,9 @@ module WCC::Blogs
25
27
  end
26
28
 
27
29
  def html
28
- @html ||=
29
- WCC::Blogs.config.cache.fetch(['BlogPost', fragment_path]) do
30
- WCC::Blogs.client
31
- .get(fragment_path, host || WCC::Blogs.config.base_url)
32
- .force_encoding('UTF-8')
33
- end
30
+ @html ||= @client.get(URI.join(host, fragment_path).to_s)
31
+ .body
32
+ .force_encoding('UTF-8')
34
33
  end
35
34
 
36
35
  def metadata
@@ -38,9 +37,7 @@ module WCC::Blogs
38
37
  end
39
38
 
40
39
  def published?
41
- return false if publishing_targets.empty?
42
-
43
- publishing_targets.any? { |t| t['key'] == WCC::Blogs.config.publishing_target }
40
+ true
44
41
  end
45
42
 
46
43
  def time_to_read
@@ -0,0 +1,57 @@
1
+ module WCC::Blogs
2
+ class PostSummary
3
+ extend WCC::Blogs::Utils
4
+
5
+ def self.find_all
6
+ WCC::Blogs.client.list.items.map do |summary|
7
+ new(summary, client: WCC::Blogs.client)
8
+ end
9
+ end
10
+
11
+ attr_reader :raw
12
+
13
+ def initialize(raw, client: WCC::Blogs.client)
14
+ @raw = raw
15
+ @client = client
16
+ end
17
+
18
+ def full_post
19
+ Post.new(@client.blog_post(slug, digest: digest).raw)
20
+ end
21
+
22
+ define_camelcase_alias(
23
+ 'id',
24
+ 'title',
25
+ 'subtitle',
26
+ 'slug',
27
+ 'path',
28
+ 'digest',
29
+ 'fragment_path'
30
+ ) do |camelcase|
31
+ raw[camelcase]
32
+ end
33
+
34
+ define_camelcase_alias(
35
+ 'date',
36
+ 'updated_at'
37
+ ) do |camelcase|
38
+ value = raw[camelcase]
39
+
40
+ Time.parse(value) if value && value.length
41
+ end
42
+
43
+ define_camelcase_alias(
44
+ 'thumbnail_image'
45
+ ) do |camelcase|
46
+ OpenStruct.new(raw[camelcase]) if raw[camelcase]
47
+ end
48
+
49
+ define_camelcase_alias('publishing_targets') do |camelcase|
50
+ targets = raw[camelcase] || []
51
+
52
+ targets.map { |val| OpenStruct.new(val) if val }
53
+ end
54
+
55
+ alias cache_key digest
56
+ end
57
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module WCC
4
4
  module Blogs
5
- VERSION = '0.3.4'.freeze
5
+ VERSION = '0.4.0'.freeze
6
6
  end
7
7
  end
@@ -24,12 +24,17 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.require_paths = ['lib']
26
26
 
27
- spec.add_runtime_dependency 'typhoeus', '~> 1.3'
27
+ spec.add_runtime_dependency 'faraday', '~> 0.15.4'
28
+ spec.add_runtime_dependency 'faraday-http-cache', '~> 1.3'
28
29
  spec.add_runtime_dependency 'wcc-base'
29
30
 
31
+ spec.add_development_dependency 'guard', '~> 2.15'
32
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
33
+ spec.add_development_dependency 'guard-rubocop', '~> 1.3'
30
34
  spec.add_development_dependency 'rake', '~> 12.3'
31
35
  spec.add_development_dependency 'rspec', '~> 3.0'
32
36
  spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3.0'
33
37
  spec.add_development_dependency 'rubocop', '~> 0.60.0'
38
+ spec.add_development_dependency 'typhoeus'
34
39
  spec.add_development_dependency 'webmock', '~> 3.0'
35
40
  end
metadata CHANGED
@@ -1,17 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wcc-blogs-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Watermark Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-04 00:00:00.000000000 Z
11
+ date: 2019-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: typhoeus
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.15.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.15.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-http-cache
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
@@ -38,6 +52,48 @@ dependencies:
38
52
  - - ">="
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.15'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.15'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
41
97
  - !ruby/object:Gem::Dependency
42
98
  name: rake
43
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +150,20 @@ dependencies:
94
150
  - - "~>"
95
151
  - !ruby/object:Gem::Version
96
152
  version: 0.60.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: typhoeus
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
97
167
  - !ruby/object:Gem::Dependency
98
168
  name: webmock
99
169
  requirement: !ruby/object:Gem::Requirement
@@ -119,13 +189,16 @@ files:
119
189
  - ".rubocop.yml"
120
190
  - ".rubocop_todo.yml"
121
191
  - Gemfile
192
+ - Guardfile
122
193
  - README.md
123
194
  - Rakefile
124
195
  - lib/wcc/blogs.rb
125
196
  - lib/wcc/blogs/client.rb
197
+ - lib/wcc/blogs/client/response.rb
126
198
  - lib/wcc/blogs/errors.rb
127
199
  - lib/wcc/blogs/metadata.rb
128
200
  - lib/wcc/blogs/post.rb
201
+ - lib/wcc/blogs/post_summary.rb
129
202
  - lib/wcc/blogs/utils.rb
130
203
  - lib/wcc/blogs/version.rb
131
204
  - wcc-blogs-client.gemspec
@@ -149,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
222
  version: '0'
150
223
  requirements: []
151
224
  rubyforge_project:
152
- rubygems_version: 2.5.2
225
+ rubygems_version: 2.6.11
153
226
  signing_key:
154
227
  specification_version: 4
155
228
  summary: ''