noun-project-api 0.2.2 → 3.1.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 -5
- data/Rakefile +9 -7
- data/lib/noun_project_api.rb +38 -0
- data/lib/noun_project_api/base_item.rb +30 -0
- data/lib/noun_project_api/collection.rb +36 -0
- data/lib/noun_project_api/collection_retriever.rb +14 -0
- data/lib/{noun-project-api → noun_project_api}/connection.rb +3 -1
- data/lib/noun_project_api/errors.rb +14 -0
- data/lib/noun_project_api/icon.rb +41 -0
- data/lib/noun_project_api/icon_retriever.rb +14 -0
- data/lib/noun_project_api/icons_retriever.rb +66 -0
- data/lib/noun_project_api/reporter.rb +22 -0
- data/lib/noun_project_api/retriever.rb +18 -0
- data/spec/lib/noun_project_api/base_item_spec.rb +13 -0
- data/spec/lib/noun_project_api/collection_retriever_spec.rb +87 -0
- data/spec/lib/noun_project_api/collection_spec.rb +66 -0
- data/spec/lib/{noun-project-api → noun_project_api}/icon_retriever_spec.rb +20 -18
- data/spec/lib/{noun-project-api → noun_project_api}/icon_spec.rb +21 -19
- data/spec/lib/{noun-project-api → noun_project_api}/icons_retriever_spec.rb +27 -25
- data/spec/lib/{noun-project-api → noun_project_api}/reporter_spec.rb +24 -24
- data/spec/lib/noun_project_api/retriever_spec.rb +22 -0
- data/spec/lib/{nount_project_api_spec.rb → noun_project_api_spec.rb} +5 -3
- data/spec/spec_helper.rb +3 -4
- data/spec/support/fakes.rb +93 -10
- metadata +90 -45
- data/lib/noun-project-api.rb +0 -30
- data/lib/noun-project-api/icon.rb +0 -52
- data/lib/noun-project-api/icon_retriever.rb +0 -20
- data/lib/noun-project-api/icons_retriever.rb +0 -51
- data/lib/noun-project-api/reporter.rb +0 -20
- data/lib/noun-project-api/retriever.rb +0 -6
- data/spec/lib/noun-project-api/retriever_spec.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7599a88ab282cc57200c3907a08b057d466d91af307c992c42fa59ca15bdbe55
|
4
|
+
data.tar.gz: cee01ba5d89c09fb74b62c7ef2b5d630437632ce6c43406b5d65b2fd417c544b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf81da78826aee4244949577e0b103b1dc16435e813115576b81701d72a31a4569e78cc85ec5cd7e921666e12392f18aff9fd02dd9f3ec1d2a006e21cb7c341b
|
7
|
+
data.tar.gz: de4ab0a45285f4979c697beff40d1f02ff497a9d4037487172000cede3ca46f20a47c2a51b8d635ddc9a478d3b0a6cdba07dba8b3417311b6cbc75df73491140
|
data/Rakefile
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler"
|
2
4
|
Bundler::GemHelper.install_tasks
|
3
5
|
|
4
|
-
|
6
|
+
$LOAD_PATH.unshift "lib"
|
5
7
|
|
6
|
-
desc
|
7
|
-
task :
|
8
|
+
desc "Default: run unit tests."
|
9
|
+
task default: [:spec]
|
8
10
|
|
9
|
-
require
|
11
|
+
require "rspec/core/rake_task"
|
10
12
|
|
11
|
-
desc
|
13
|
+
desc "Run specs"
|
12
14
|
RSpec::Core::RakeTask.new do |t|
|
13
|
-
t.pattern =
|
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
|
-
|
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,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
|