noun-project-api 0.2.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +5 -5
  2. data/Rakefile +9 -7
  3. data/lib/noun_project_api.rb +38 -0
  4. data/lib/noun_project_api/base_item.rb +30 -0
  5. data/lib/noun_project_api/collection.rb +36 -0
  6. data/lib/noun_project_api/collection_retriever.rb +14 -0
  7. data/lib/{noun-project-api → noun_project_api}/connection.rb +3 -1
  8. data/lib/noun_project_api/errors.rb +14 -0
  9. data/lib/noun_project_api/icon.rb +41 -0
  10. data/lib/noun_project_api/icon_retriever.rb +14 -0
  11. data/lib/noun_project_api/icons_retriever.rb +66 -0
  12. data/lib/noun_project_api/reporter.rb +22 -0
  13. data/lib/noun_project_api/retriever.rb +18 -0
  14. data/spec/lib/noun_project_api/base_item_spec.rb +13 -0
  15. data/spec/lib/noun_project_api/collection_retriever_spec.rb +87 -0
  16. data/spec/lib/noun_project_api/collection_spec.rb +66 -0
  17. data/spec/lib/{noun-project-api → noun_project_api}/icon_retriever_spec.rb +20 -18
  18. data/spec/lib/{noun-project-api → noun_project_api}/icon_spec.rb +21 -19
  19. data/spec/lib/{noun-project-api → noun_project_api}/icons_retriever_spec.rb +27 -25
  20. data/spec/lib/{noun-project-api → noun_project_api}/reporter_spec.rb +24 -24
  21. data/spec/lib/noun_project_api/retriever_spec.rb +22 -0
  22. data/spec/lib/{nount_project_api_spec.rb → noun_project_api_spec.rb} +5 -3
  23. data/spec/spec_helper.rb +3 -4
  24. data/spec/support/fakes.rb +93 -10
  25. metadata +90 -45
  26. data/lib/noun-project-api.rb +0 -30
  27. data/lib/noun-project-api/icon.rb +0 -52
  28. data/lib/noun-project-api/icon_retriever.rb +0 -20
  29. data/lib/noun-project-api/icons_retriever.rb +0 -51
  30. data/lib/noun-project-api/reporter.rb +0 -20
  31. data/lib/noun-project-api/retriever.rb +0 -6
  32. data/spec/lib/noun-project-api/retriever_spec.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 11f21fc8fa8332fb270247aba04d4ac778c5cff9
4
- data.tar.gz: 9cf63e9099b5f9b1b777aa0191cf80fd516b3b90
2
+ SHA256:
3
+ metadata.gz: 7599a88ab282cc57200c3907a08b057d466d91af307c992c42fa59ca15bdbe55
4
+ data.tar.gz: cee01ba5d89c09fb74b62c7ef2b5d630437632ce6c43406b5d65b2fd417c544b
5
5
  SHA512:
6
- metadata.gz: 1d1e4725b6dd873d4f4f66cc9d0636123196cbd02a4b090d3fdf548df54d2ecc40b16eecac4050c2e6bbb6e9b190f84f4706cfbf4d04c5391b85bab302d715de
7
- data.tar.gz: 5ade878aa4c75eb4cd0e0875be509fd381381ce090789784307f8e5f6e74631a23b9097f05a837300200b4547aeeabef5ce9148e7f43952c6e2dd6098c9e0ea0
6
+ metadata.gz: bf81da78826aee4244949577e0b103b1dc16435e813115576b81701d72a31a4569e78cc85ec5cd7e921666e12392f18aff9fd02dd9f3ec1d2a006e21cb7c341b
7
+ data.tar.gz: de4ab0a45285f4979c697beff40d1f02ff497a9d4037487172000cede3ca46f20a47c2a51b8d635ddc9a478d3b0a6cdba07dba8b3417311b6cbc75df73491140
data/Rakefile CHANGED
@@ -1,14 +1,16 @@
1
- require 'bundler'
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
2
4
  Bundler::GemHelper.install_tasks
3
5
 
4
- $:.unshift 'lib'
6
+ $LOAD_PATH.unshift "lib"
5
7
 
6
- desc 'Default: run unit tests.'
7
- task :default => [:spec]
8
+ desc "Default: run unit tests."
9
+ task default: [:spec]
8
10
 
9
- require 'rspec/core/rake_task'
11
+ require "rspec/core/rake_task"
10
12
 
11
- desc 'Run specs'
13
+ desc "Run specs"
12
14
  RSpec::Core::RakeTask.new do |t|
13
- t.pattern = './spec/**/*_spec.rb'
15
+ t.pattern = "./spec/**/*_spec.rb"
14
16
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "oauth"
4
+ require "active_support"
5
+ require "json"
6
+ require "noun_project_api/errors"
7
+ require "noun_project_api/connection"
8
+ require "noun_project_api/icon_retriever"
9
+ require "noun_project_api/reporter"
10
+ require "noun_project_api/icons_retriever"
11
+ require "noun_project_api/icon"
12
+ require "noun_project_api/collection_retriever"
13
+ require "noun_project_api/collection"
14
+
15
+ # Top level name space for the entire Gem.
16
+ module NounProjectApi
17
+ API_BASE = "http://api.thenounproject.com"
18
+
19
+ def self.configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def self.configure
24
+ self.configuration ||= Configuration.new
25
+ yield(configuration) if block_given?
26
+ end
27
+
28
+ # Main configuration class.
29
+ class Configuration
30
+ attr_accessor :public_domain, :cache, :cache_ttl
31
+
32
+ def initialize
33
+ @public_domain = false
34
+ @cache = ActiveSupport::Cache::NullStore.new
35
+ @cache_ttl = 604800 # Week in seconds
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ # A basis to Items returned by the noun project.
5
+ class BaseItem
6
+ attr_accessor :original_hash
7
+ ITEM_NAME = nil
8
+
9
+ def initialize(origin)
10
+ raise NotImplementedError, "Must use a subclass" if self.class::ITEM_NAME.nil?
11
+
12
+ origin = JSON.parse(origin, symbolize_names: true) if origin.is_a? String
13
+ if origin.key? self.class::ITEM_NAME
14
+ origin = origin.delete(
15
+ self.class::ITEM_NAME
16
+ )
17
+ end
18
+
19
+ @original_hash = origin
20
+ end
21
+
22
+ def id
23
+ original_hash[:id].to_i
24
+ end
25
+
26
+ def to_json(*_args)
27
+ JSON.dump(to_hash)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/base_item"
4
+
5
+ module NounProjectApi
6
+ # A single Collection as an abstracted ruby object.
7
+ class Collection < BaseItem
8
+ ITEM_NAME = :collection
9
+
10
+ def author_id
11
+ original_hash[:author_id].to_i
12
+ end
13
+
14
+ def author_name
15
+ original_hash[:author][:name]
16
+ end
17
+
18
+ def icon_count
19
+ original_hash[:icon_count].to_i
20
+ end
21
+
22
+ def published?
23
+ original_hash[:is_published].to_i == 1
24
+ end
25
+
26
+ def to_hash
27
+ {
28
+ id: id,
29
+ author_id: author_id,
30
+ author_name: author_name,
31
+ icon_count: icon_count,
32
+ published: published?
33
+ }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/collection"
4
+ require "noun_project_api/retriever"
5
+
6
+ module NounProjectApi
7
+ # Retrieve a collection.
8
+ class CollectionRetriever < Retriever
9
+ API_PATH = "/collection/"
10
+ ITEM_CLASS = Collection
11
+
12
+ alias find_by_slug find
13
+ end
14
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NounProjectApi
2
4
  # Basic connection methods and setup.
3
5
  module Connection
@@ -6,7 +8,7 @@ module NounProjectApi
6
8
  def initialize(token, secret)
7
9
  @token = token
8
10
  @secret = secret
9
- fail(ArgumentError, 'Missing token or secret') unless @token && @secret
11
+ raise ArgumentError, "Missing token or secret" unless @token && @secret
10
12
 
11
13
  @access_token = OAuth::AccessToken.new(OAuth::Consumer.new(token, secret))
12
14
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ class ServiceError < StandardError
5
+ attr_reader :status, :body
6
+
7
+ def initialize(status, body)
8
+ @status = status
9
+ @body = body
10
+
11
+ super("Noun Project API Error")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/base_item"
4
+
5
+ module NounProjectApi
6
+ # A single Icon as an abstracted ruby object.
7
+ class Icon < BaseItem
8
+ PREVIEW_SIZE_200 = 200
9
+ PREVIEW_SIZE_42 = 42
10
+ PREVIEW_SIZE_84 = 84
11
+
12
+ PUBLIC_DOMAIN_LICENSE = "public-domain"
13
+
14
+ ITEM_NAME = :icon
15
+
16
+ def public_domain?
17
+ original_hash[:license_description] == PUBLIC_DOMAIN_LICENSE
18
+ end
19
+
20
+ def svg_url
21
+ original_hash[:icon_url]
22
+ end
23
+
24
+ def preview_url(size = PREVIEW_SIZE_200)
25
+ if size == PREVIEW_SIZE_200
26
+ original_hash[:preview_url]
27
+ else
28
+ original_hash[:"preview_url_#{size}"]
29
+ end
30
+ end
31
+
32
+ def to_hash
33
+ {
34
+ id: id,
35
+ preview_url_200: preview_url(PREVIEW_SIZE_200),
36
+ preview_url_84: preview_url(PREVIEW_SIZE_84),
37
+ preview_url_42: preview_url(PREVIEW_SIZE_42)
38
+ }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/icon"
4
+ require "noun_project_api/retriever"
5
+
6
+ module NounProjectApi
7
+ # Retrieve an icon.
8
+ class IconRetriever < Retriever
9
+ API_PATH = "/icon/"
10
+ ITEM_CLASS = Icon
11
+
12
+ alias find_by_slug find
13
+ end
14
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "noun_project_api/retriever"
4
+
5
+ module NounProjectApi
6
+ # Retrieve icons.
7
+ class IconsRetriever < Retriever
8
+ API_PATH = "/icons/"
9
+
10
+ # Finds multiple icons based on the term
11
+ # * term - search term
12
+ # * limit - limit the amount of results
13
+ # * offset - offset the results
14
+ # * page - page number
15
+ def find(term, limit = nil, offset = nil, page = nil)
16
+ cache_key = Digest::MD5.hexdigest("#{term}+#{limit}+#{offset}+#{page}")
17
+ cache_ttl = NounProjectApi.configuration.cache_ttl
18
+
19
+ NounProjectApi.configuration.cache.fetch(cache_key, expires_in: cache_ttl) do
20
+ raise ArgumentError, "Missing search term" unless term
21
+
22
+ search = OAuth::Helper.escape(term)
23
+ search += "?limit_to_public_domain=#{NounProjectApi.configuration.public_domain ? 1 : 0}"
24
+
25
+ args = {
26
+ "limit" => limit,
27
+ "offset" => offset,
28
+ "page" => page
29
+ }.reject { |_, v| v.nil? }
30
+ args.each { |k, v| search += "&#{k}=#{v}" } unless args.empty?
31
+
32
+ result = access_token.get("#{API_BASE}#{API_PATH}#{search}")
33
+ raise ServiceError.new(result.code, result.body) unless ["200", "404"].include? result.code
34
+
35
+ if result.code == "200"
36
+ JSON.parse(result.body, symbolize_names: true)[:icons].map { |icon| Icon.new(icon) }
37
+ else
38
+ []
39
+ end
40
+ end
41
+ end
42
+
43
+ # List recent uploads
44
+ # * limit - limit the amount of results
45
+ # * offset - offset the results
46
+ # * page - page number
47
+ def recent_uploads(limit = nil, offset = nil, page = nil)
48
+ args = {
49
+ "limit" => limit,
50
+ "offset" => offset,
51
+ "page" => page
52
+ }.reject { |_, v| v.nil? }
53
+ if !args.empty?
54
+ search = "?"
55
+ args.each { |k, v| search += "#{k}=#{v}&" }
56
+ else
57
+ search = ""
58
+ end
59
+
60
+ result = access_token.get("#{API_BASE}#{API_PATH}recent_uploads#{search}")
61
+ raise ServiceError.new(result.code, result.body) unless result.code == "200"
62
+
63
+ JSON.parse(result.body, symbolize_names: true)[:recent_uploads].map { |icon| Icon.new(icon) }
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ # Main class to hold reporting actions back to the Noun Project.
5
+ class Reporter
6
+ include Connection
7
+
8
+ API_PATH = "/notify/publish"
9
+
10
+ def report_used(ids)
11
+ ids = [ids] if ids.is_a?(String) || ids.is_a?(Integer)
12
+ raise ArgumentError, "Missing ids" if ids.nil? || ids.empty?
13
+
14
+ result = access_token.post(
15
+ "#{API_BASE}#{API_PATH}",
16
+ { icons: ids.join(",") }.to_json,
17
+ "Accept" => "application/json", "Content-Type" => "application/json"
18
+ )
19
+ result.code == "200"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NounProjectApi
4
+ # A base class for different retriever classes.
5
+ class Retriever
6
+ include Connection
7
+
8
+ # Find an item based on it's id.
9
+ def find(id)
10
+ raise ArgumentError, "Missing id/slug" unless id
11
+
12
+ result = access_token.get("#{API_BASE}#{self.class::API_PATH}#{id}")
13
+ raise ServiceError.new(result.code, result.body) unless result.code == "200"
14
+
15
+ self.class::ITEM_CLASS.new(result.body)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "ostruct"
5
+
6
+ RSpec.describe NounProjectApi::BaseItem do
7
+ it "raises an error on direct initialization" do
8
+ data = JSON.parse(Fakes::Results::ICON_VALID)
9
+ expect {
10
+ NounProjectApi::BaseItem.new(JSON.dump(data["icon"]))
11
+ }.to raise_error(NotImplementedError)
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "ostruct"
5
+
6
+ RSpec.describe NounProjectApi::CollectionRetriever do
7
+ before :each do
8
+ @collection = NounProjectApi::CollectionRetriever.new(Faker::Internet.password(min_length: 16), Faker::Internet.password(min_length: 16))
9
+ @valid_hash = JSON.parse(Fakes::Results::COLLECTION_VALID, symbolize_names: true)
10
+ @valid_response = OpenStruct.new(
11
+ body: Fakes::Results::COLLECTION_VALID,
12
+ code: "200"
13
+ )
14
+
15
+ @missing_response = OpenStruct.new(
16
+ code: "404"
17
+ )
18
+ end
19
+
20
+ context "id" do
21
+ it "raises an error when no id is provided" do
22
+ expect { @collection.find(nil) }.to raise_error(ArgumentError)
23
+ end
24
+
25
+ it "returns a proper result with a correct id" do
26
+ id = 1
27
+ expect(@collection.access_token).to receive(
28
+ :get
29
+ ).with(
30
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{id}"
31
+ ).and_return(
32
+ @valid_response
33
+ )
34
+
35
+ result = @collection.find(id)
36
+ expect(result).to be_a(NounProjectApi::Collection)
37
+ expect(result.original_hash).to eq(@valid_hash[:collection])
38
+ end
39
+
40
+ it "raises an error with a missing id" do
41
+ id = 1
42
+ expect(@collection.access_token).to receive(
43
+ :get
44
+ ).with(
45
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{id}"
46
+ ).and_return(
47
+ @missing_response
48
+ )
49
+
50
+ expect { @collection.find(id) }.to raise_error(NounProjectApi::ServiceError)
51
+ end
52
+ end
53
+
54
+ context "slug" do
55
+ it "raises an error when no slug is provided" do
56
+ expect { @collection.find_by_slug(nil) }.to raise_error(ArgumentError)
57
+ end
58
+
59
+ it "returns a proper result with a correct slug" do
60
+ slug = "existing_slug"
61
+ expect(@collection.access_token).to receive(
62
+ :get
63
+ ).with(
64
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{slug}"
65
+ ).and_return(
66
+ @valid_response
67
+ )
68
+
69
+ result = @collection.find(slug)
70
+ expect(result).to be_a(NounProjectApi::Collection)
71
+ expect(result.original_hash).to eq(@valid_hash[:collection])
72
+ end
73
+
74
+ it "raises an error with a missing slug" do
75
+ slug = "missing_slug"
76
+ expect(@collection.access_token).to receive(
77
+ :get
78
+ ).with(
79
+ "#{NounProjectApi::API_BASE}#{NounProjectApi::CollectionRetriever::API_PATH}#{slug}"
80
+ ).and_return(
81
+ @missing_response
82
+ )
83
+
84
+ expect { @collection.find_by_slug(slug) }.to raise_error(NounProjectApi::ServiceError)
85
+ end
86
+ end
87
+ end