agave-client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +14 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/CONTRIBUTORS.md +34 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +29 -0
- data/README.md +41 -0
- data/Rakefile +7 -0
- data/agave-client.gemspec +63 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/agave +10 -0
- data/lib/agave.rb +11 -0
- data/lib/agave/api_client.rb +97 -0
- data/lib/agave/api_error.rb +23 -0
- data/lib/agave/cli.rb +93 -0
- data/lib/agave/dump/dsl/add_to_data_file.rb +14 -0
- data/lib/agave/dump/dsl/create_data_file.rb +14 -0
- data/lib/agave/dump/dsl/create_post.rb +33 -0
- data/lib/agave/dump/dsl/directory.rb +30 -0
- data/lib/agave/dump/dsl/root.rb +37 -0
- data/lib/agave/dump/format.rb +28 -0
- data/lib/agave/dump/format/json.rb +18 -0
- data/lib/agave/dump/format/toml.rb +31 -0
- data/lib/agave/dump/format/yaml.rb +55 -0
- data/lib/agave/dump/operation/add_to_data_file.rb +41 -0
- data/lib/agave/dump/operation/create_data_file.rb +28 -0
- data/lib/agave/dump/operation/create_post.rb +34 -0
- data/lib/agave/dump/operation/directory.rb +34 -0
- data/lib/agave/dump/operation/root.rb +27 -0
- data/lib/agave/dump/runner.rb +47 -0
- data/lib/agave/dump/ssg_detector.rb +36 -0
- data/lib/agave/json_api_deserializer.rb +38 -0
- data/lib/agave/json_api_serializer.rb +145 -0
- data/lib/agave/local/entities_repo.rb +46 -0
- data/lib/agave/local/field_type/boolean.rb +12 -0
- data/lib/agave/local/field_type/color.rb +63 -0
- data/lib/agave/local/field_type/date.rb +12 -0
- data/lib/agave/local/field_type/date_time.rb +12 -0
- data/lib/agave/local/field_type/file.rb +65 -0
- data/lib/agave/local/field_type/float.rb +12 -0
- data/lib/agave/local/field_type/gallery.rb +23 -0
- data/lib/agave/local/field_type/global_seo.rb +56 -0
- data/lib/agave/local/field_type/image.rb +82 -0
- data/lib/agave/local/field_type/integer.rb +12 -0
- data/lib/agave/local/field_type/json.rb +13 -0
- data/lib/agave/local/field_type/lat_lon.rb +30 -0
- data/lib/agave/local/field_type/link.rb +12 -0
- data/lib/agave/local/field_type/links.rb +21 -0
- data/lib/agave/local/field_type/rich_text.rb +21 -0
- data/lib/agave/local/field_type/seo.rb +33 -0
- data/lib/agave/local/field_type/slug.rb +12 -0
- data/lib/agave/local/field_type/string.rb +12 -0
- data/lib/agave/local/field_type/text.rb +12 -0
- data/lib/agave/local/field_type/theme.rb +43 -0
- data/lib/agave/local/field_type/video.rb +77 -0
- data/lib/agave/local/item.rb +179 -0
- data/lib/agave/local/items_repo.rb +210 -0
- data/lib/agave/local/json_api_entity.rb +78 -0
- data/lib/agave/local/loader.rb +60 -0
- data/lib/agave/local/site.rb +73 -0
- data/lib/agave/paginator.rb +33 -0
- data/lib/agave/repo.rb +93 -0
- data/lib/agave/site/client.rb +24 -0
- data/lib/agave/upload/file.rb +92 -0
- data/lib/agave/upload/image.rb +8 -0
- data/lib/agave/utils/favicon_tags_builder.rb +85 -0
- data/lib/agave/utils/locale_value.rb +15 -0
- data/lib/agave/utils/meta_tags/article_modified_time.rb +15 -0
- data/lib/agave/utils/meta_tags/article_publisher.rb +18 -0
- data/lib/agave/utils/meta_tags/base.rb +55 -0
- data/lib/agave/utils/meta_tags/description.rb +24 -0
- data/lib/agave/utils/meta_tags/image.rb +35 -0
- data/lib/agave/utils/meta_tags/og_locale.rb +15 -0
- data/lib/agave/utils/meta_tags/og_site_name.rb +18 -0
- data/lib/agave/utils/meta_tags/og_type.rb +18 -0
- data/lib/agave/utils/meta_tags/robots.rb +14 -0
- data/lib/agave/utils/meta_tags/title.rb +46 -0
- data/lib/agave/utils/meta_tags/twitter_card.rb +14 -0
- data/lib/agave/utils/meta_tags/twitter_site.rb +18 -0
- data/lib/agave/utils/seo_tags_builder.rb +45 -0
- data/lib/agave/version.rb +4 -0
- data/lib/agave/watch/site_change_watcher.rb +37 -0
- metadata +504 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Agave
|
3
|
+
module Local
|
4
|
+
class JsonApiEntity
|
5
|
+
attr_reader :payload, :data_source
|
6
|
+
|
7
|
+
def initialize(payload, data_source)
|
8
|
+
@payload = payload
|
9
|
+
@data_source = data_source
|
10
|
+
end
|
11
|
+
|
12
|
+
def id
|
13
|
+
@payload[:id]
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
@payload[:type]
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
if other.is_a? JsonApiEntity
|
22
|
+
id == other.id && type == other.type
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#<JsonApiEntity id=#{id} type=#{type} payload=#{payload}>"
|
30
|
+
end
|
31
|
+
alias inspect to_s
|
32
|
+
|
33
|
+
def [](key)
|
34
|
+
attributes[key]
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_to_missing?(method, include_private = false)
|
38
|
+
if attributes.key?(method) || relationships.key?(method)
|
39
|
+
true
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def attributes
|
48
|
+
@payload.fetch(:attributes, {})
|
49
|
+
end
|
50
|
+
|
51
|
+
def relationships
|
52
|
+
@payload.fetch(:relationships, {})
|
53
|
+
end
|
54
|
+
|
55
|
+
def dereference_linkage(linkage)
|
56
|
+
if linkage.is_a? Array
|
57
|
+
linkage.map do |item|
|
58
|
+
data_source.find_entity(item[:type], item[:id])
|
59
|
+
end
|
60
|
+
elsif linkage.is_a? Hash
|
61
|
+
data_source.find_entity(linkage[:type], linkage[:id])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(method, *arguments, &block)
|
66
|
+
return super unless arguments.empty?
|
67
|
+
|
68
|
+
if attributes.key?(method)
|
69
|
+
attributes[method]
|
70
|
+
elsif relationships.key?(method)
|
71
|
+
dereference_linkage(relationships[method][:data])
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'agave/local/entities_repo'
|
3
|
+
require 'agave/local/items_repo'
|
4
|
+
|
5
|
+
module Agave
|
6
|
+
module Local
|
7
|
+
class Loader
|
8
|
+
attr_reader :client
|
9
|
+
attr_reader :entities_repo
|
10
|
+
attr_reader :items_repo
|
11
|
+
attr_reader :preview_mode
|
12
|
+
|
13
|
+
def initialize(client, preview_mode = false)
|
14
|
+
@client = client
|
15
|
+
@preview_mode = preview_mode
|
16
|
+
@entities_repo = EntitiesRepo.new
|
17
|
+
@items_repo = ItemsRepo.new(@entities_repo)
|
18
|
+
end
|
19
|
+
|
20
|
+
def load
|
21
|
+
threads = [
|
22
|
+
Thread.new { Thread.current[:output] = site },
|
23
|
+
Thread.new { Thread.current[:output] = all_items },
|
24
|
+
Thread.new { Thread.current[:output] = all_uploads }
|
25
|
+
]
|
26
|
+
|
27
|
+
results = threads.map do |t|
|
28
|
+
t.join
|
29
|
+
t[:output]
|
30
|
+
end
|
31
|
+
|
32
|
+
@entities_repo = EntitiesRepo.new(*results)
|
33
|
+
@items_repo = ItemsRepo.new(@entities_repo)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def site
|
39
|
+
include = ['item_types', 'item_types.fields']
|
40
|
+
client.request(:get, '/api/site', include: include)
|
41
|
+
end
|
42
|
+
|
43
|
+
def all_items
|
44
|
+
client.items.all(
|
45
|
+
{ version: preview_mode ? 'latest' : 'published' },
|
46
|
+
deserialize_response: false,
|
47
|
+
all_pages: true
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def all_uploads
|
52
|
+
client.uploads.all(
|
53
|
+
{ "filter[type]" => "used" },
|
54
|
+
deserialize_response: false,
|
55
|
+
all_pages: true
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'forwardable'
|
3
|
+
require 'active_support/inflector/transliterate'
|
4
|
+
require 'active_support/hash_with_indifferent_access'
|
5
|
+
require 'agave/utils/locale_value'
|
6
|
+
|
7
|
+
module Agave
|
8
|
+
module Local
|
9
|
+
class Site
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
attr_reader :entity
|
13
|
+
def_delegators :entity, :id, :name, :locales, :domain,
|
14
|
+
:no_index, :production_frontend_url
|
15
|
+
|
16
|
+
def initialize(entity, items_repo)
|
17
|
+
@entity = entity
|
18
|
+
@items_repo = items_repo
|
19
|
+
end
|
20
|
+
|
21
|
+
def global_seo
|
22
|
+
read_attribute(:global_seo, FieldType::GlobalSeo, locales.size > 1)
|
23
|
+
end
|
24
|
+
|
25
|
+
def theme
|
26
|
+
read_attribute(:theme, FieldType::Theme, false)
|
27
|
+
end
|
28
|
+
|
29
|
+
def favicon
|
30
|
+
read_attribute(:favicon, FieldType::File, false)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"#<Site id=#{id} site_name=#{name}>"
|
35
|
+
end
|
36
|
+
alias inspect to_s
|
37
|
+
|
38
|
+
def favicon_meta_tags(theme_color = nil)
|
39
|
+
Utils::FaviconTagsBuilder.new(self, theme_color).meta_tags
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_hash
|
43
|
+
attributes = [
|
44
|
+
:id, :name, :locales, :theme, :domain,
|
45
|
+
:no_index, :global_seo, :favicon, :production_frontend_url
|
46
|
+
]
|
47
|
+
|
48
|
+
attributes.each_with_object({}) do |attribute, result|
|
49
|
+
value = send(attribute)
|
50
|
+
result[attribute] = if value.respond_to?(:to_hash)
|
51
|
+
value.to_hash
|
52
|
+
else
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def read_attribute(method, type_klass, localized)
|
61
|
+
value = if localized
|
62
|
+
obj = entity.send(method) || {}
|
63
|
+
|
64
|
+
Utils::LocaleValue.find(obj)
|
65
|
+
else
|
66
|
+
entity.send(method)
|
67
|
+
end
|
68
|
+
|
69
|
+
type_klass.parse(value, @items_repo)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Agave
|
2
|
+
class Paginator
|
3
|
+
def initialize(client, base_endpoint, filters)
|
4
|
+
@client, @base_endpoint, @filters = client, base_endpoint, filters
|
5
|
+
end
|
6
|
+
|
7
|
+
def response
|
8
|
+
items_per_page = 100
|
9
|
+
|
10
|
+
base_response = @client.request(
|
11
|
+
:get, @base_endpoint, @filters.dup.merge('page[limit]' => items_per_page)
|
12
|
+
)
|
13
|
+
|
14
|
+
extra_pages = (
|
15
|
+
base_response[:meta][:total_count] / items_per_page.to_f
|
16
|
+
).ceil - 1
|
17
|
+
|
18
|
+
extra_pages.times do |page|
|
19
|
+
base_response[:data] += @client.request(
|
20
|
+
:get,
|
21
|
+
@base_endpoint,
|
22
|
+
@filters.dup.merge(
|
23
|
+
'page[offset]' => items_per_page * (page + 1),
|
24
|
+
'page[limit]' => items_per_page
|
25
|
+
)
|
26
|
+
)[:data]
|
27
|
+
end
|
28
|
+
|
29
|
+
base_response
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
data/lib/agave/repo.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'agave/json_api_serializer'
|
3
|
+
require 'agave/json_api_deserializer'
|
4
|
+
require 'agave/paginator'
|
5
|
+
|
6
|
+
module Agave
|
7
|
+
class Repo
|
8
|
+
attr_reader :client, :type, :schema
|
9
|
+
|
10
|
+
IDENTITY_REGEXP = /\{\(.*?definitions%2F(.*?)%2Fdefinitions%2Fidentity\)}/
|
11
|
+
|
12
|
+
METHOD_NAMES = {
|
13
|
+
"instances" => :all,
|
14
|
+
"self" => :find,
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(client, type, schema)
|
18
|
+
@client = client
|
19
|
+
@type = type
|
20
|
+
@schema = schema
|
21
|
+
|
22
|
+
schema.links.each do |link|
|
23
|
+
method_name = METHOD_NAMES.fetch(link.rel, link.rel)
|
24
|
+
|
25
|
+
self.define_singleton_method(method_name) do |*args|
|
26
|
+
min_arguments_count = [
|
27
|
+
link.href.scan(IDENTITY_REGEXP).size,
|
28
|
+
link.schema && link.method != :get ? 1 : 0
|
29
|
+
].reduce(0, :+)
|
30
|
+
|
31
|
+
args.size >= min_arguments_count or
|
32
|
+
raise ArgumentError.new(
|
33
|
+
"wrong number of arguments (given #{args.size}, expected #{min_arguments_count})"
|
34
|
+
)
|
35
|
+
|
36
|
+
placeholders = []
|
37
|
+
|
38
|
+
url = link["href"].gsub(IDENTITY_REGEXP) do |_stuff|
|
39
|
+
placeholder = args.shift.to_s
|
40
|
+
placeholders << placeholder
|
41
|
+
placeholder
|
42
|
+
end
|
43
|
+
|
44
|
+
response = if %i(post put).include?(link.method)
|
45
|
+
body = if link.schema
|
46
|
+
unserialized_body = args.shift
|
47
|
+
|
48
|
+
JsonApiSerializer.new(type, link).serialize(
|
49
|
+
unserialized_body,
|
50
|
+
link.method == :post ? nil : placeholders.last
|
51
|
+
)
|
52
|
+
else
|
53
|
+
{}
|
54
|
+
end
|
55
|
+
|
56
|
+
client.request(link.method, url, body)
|
57
|
+
|
58
|
+
elsif link.method == :delete
|
59
|
+
client.request(:delete, url)
|
60
|
+
|
61
|
+
elsif link.method == :get
|
62
|
+
query_string = args.shift
|
63
|
+
|
64
|
+
all_pages = (args[0] || {}).
|
65
|
+
symbolize_keys.
|
66
|
+
fetch(:all_pages, false)
|
67
|
+
|
68
|
+
is_paginated_endpoint = link.schema &&
|
69
|
+
link.schema.properties.has_key?("page[limit]")
|
70
|
+
|
71
|
+
if is_paginated_endpoint && all_pages
|
72
|
+
Paginator.new(client, url, query_string).response
|
73
|
+
else
|
74
|
+
client.request(:get, url, query_string)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
options = if args.any?
|
79
|
+
args.shift.symbolize_keys
|
80
|
+
else
|
81
|
+
{}
|
82
|
+
end
|
83
|
+
|
84
|
+
if options.fetch(:deserialize_response, true)
|
85
|
+
JsonApiDeserializer.new.deserialize(response)
|
86
|
+
else
|
87
|
+
response
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'agave/api_client'
|
3
|
+
require 'agave/upload/file'
|
4
|
+
require 'agave/upload/image'
|
5
|
+
|
6
|
+
module Agave
|
7
|
+
module Site
|
8
|
+
class Client
|
9
|
+
include ApiClient
|
10
|
+
|
11
|
+
json_schema 'site-api'
|
12
|
+
|
13
|
+
def upload_file(path_or_url)
|
14
|
+
file = Upload::File.new(self, path_or_url)
|
15
|
+
file.upload
|
16
|
+
end
|
17
|
+
|
18
|
+
def upload_image(path_or_url)
|
19
|
+
file = Upload::Image.new(self, path_or_url)
|
20
|
+
file.upload
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'downloadr'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'addressable'
|
5
|
+
require 'net/http'
|
6
|
+
require 'fastimage'
|
7
|
+
|
8
|
+
module Agave
|
9
|
+
module Upload
|
10
|
+
class File
|
11
|
+
IMAGE_FORMATS = %w(png jpg jpeg gif)
|
12
|
+
|
13
|
+
attr_reader :client, :source
|
14
|
+
|
15
|
+
def initialize(client, source)
|
16
|
+
@client = client
|
17
|
+
@source = source
|
18
|
+
end
|
19
|
+
|
20
|
+
def file
|
21
|
+
@file ||= if http_source?
|
22
|
+
uri = Addressable::URI.parse(source)
|
23
|
+
ext = ::File.extname(uri.path)
|
24
|
+
Tempfile.new(['file', ext]).tap do |file|
|
25
|
+
Downloadr::HTTP.new(source, file).download
|
26
|
+
end
|
27
|
+
else
|
28
|
+
::File.new(::File.expand_path(source))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def http_source?
|
33
|
+
uri = Addressable::URI.parse(source)
|
34
|
+
uri.scheme == 'http' || uri.scheme == 'https'
|
35
|
+
rescue Addressable::URI::InvalidURIError
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def filename
|
40
|
+
if http_source?
|
41
|
+
::File.basename(source)
|
42
|
+
else
|
43
|
+
::File.basename(file.path)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def upload
|
48
|
+
upload_request = client.upload_request.create(filename: filename)
|
49
|
+
uri = URI.parse(upload_request[:url])
|
50
|
+
|
51
|
+
request = Net::HTTP::Put.new(
|
52
|
+
uri,
|
53
|
+
'x-amz-acl' => 'public-read'
|
54
|
+
)
|
55
|
+
request.body = file.read
|
56
|
+
|
57
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
58
|
+
http.use_ssl = true
|
59
|
+
|
60
|
+
http.request(request)
|
61
|
+
|
62
|
+
uploads = client.uploads.create(
|
63
|
+
format_resource(upload_request)
|
64
|
+
)
|
65
|
+
|
66
|
+
uploads["id"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def format_resource(upload_request)
|
70
|
+
extension = ::File.extname(::File.basename(file.path)).delete('.')
|
71
|
+
|
72
|
+
base_format = {
|
73
|
+
path: upload_request[:id],
|
74
|
+
size: ::File.size(file.path),
|
75
|
+
format: extension
|
76
|
+
}
|
77
|
+
|
78
|
+
if IMAGE_FORMATS.include?(extension)
|
79
|
+
width, height = FastImage.size(file.path)
|
80
|
+
|
81
|
+
base_format.merge(
|
82
|
+
width: width,
|
83
|
+
height: height,
|
84
|
+
format: FastImage.type(file.path).to_s
|
85
|
+
)
|
86
|
+
else
|
87
|
+
base_format
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|