-
1
require_relative 'request'
-
1
require_relative 'links'
-
-
1
require 'storyblok/richtext'
-
1
require 'rest-client'
-
1
require 'logger'
-
1
require 'base64'
-
1
require 'json'
-
-
1
module Storyblok
-
1
class Client
-
DEFAULT_CONFIGURATION = {
-
1
secure: true,
-
api_version: 2,
-
logger: false,
-
log_level: Logger::INFO,
-
version: 'draft',
-
skipped
# :nocov:
-
skipped
component_resolver: ->(component, data) { '' },
-
skipped
# :nocov:
-
cache_version: Time.now.to_i,
-
cache: nil
-
}
-
-
1
attr_reader :configuration, :logger
-
1
attr_accessor :cache_version
-
-
# @param [Hash] given_configuration
-
# @option given_configuration [String] :token Required if oauth_token is not set
-
# @option given_configuration [String] :oauth_token Required if token is not set
-
# @option given_configuration [String] :api_url
-
# @option given_configuration [Proc] :component_resolver
-
# @option given_configuration [Number] :api_version
-
# @option given_configuration [String] :api_region
-
# @option given_configuration [false, ::Logger] :logger
-
# @option given_configuration [::Logger::DEBUG, ::Logger::INFO, ::Logger::WARN, ::Logger::ERROR] :log_level
-
1
def initialize(given_configuration = {})
-
1
@configuration = default_configuration.merge(given_configuration)
-
1
@cache_version = '0'
-
1
validate_configuration!
-
-
1
if configuration[:oauth_token]
-
@rest_client = RestClient::Resource.new(base_url, :headers => {
-
:authorization => configuration[:oauth_token]
-
})
-
end
-
-
1
@renderer = Richtext::HtmlRenderer.new
-
1
@renderer.set_component_resolver(@configuration[:component_resolver])
-
1
setup_logger
-
end
-
-
# Dynamic cdn endpoint call
-
#
-
# @param [String] id
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def get_from_cdn(slug, query = {}, id = nil)
-
Request.new(self, "/cdn/#{slug}", query, id).get
-
end
-
-
# Gets the space info
-
#
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def space(query = {})
-
Request.new(self, '/cdn/spaces/me', query, nil, true).get
-
end
-
-
# Gets a collection of stories
-
#
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def stories(query = {})
-
1
Request.new(self, '/cdn/stories', query).get
-
end
-
-
# Gets a specific story
-
#
-
# @param [String] id
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def story(id, query = {})
-
Request.new(self, '/cdn/stories', query, id).get
-
end
-
-
# Gets a collection of datasource entries
-
#
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def datasource_entries(query = {})
-
Request.new(self, '/cdn/datasource_entries', query).get
-
end
-
-
# Gets a collection of datasources
-
#
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def datasources(query = {})
-
Request.new(self, '/cdn/datasources', query).get
-
end
-
-
# Gets a collection of tags
-
#
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def tags(query = {})
-
Request.new(self, '/cdn/tags', query).get
-
end
-
-
# Gets a collection of links
-
#
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def links(query = {})
-
Request.new(self, '/cdn/links', query).get
-
end
-
-
# Gets a link tree
-
#
-
# @param [Hash] query
-
#
-
# @return [Hash]
-
1
def tree(query = {})
-
Links.new(Request.new(self, '/cdn/links', query).get).as_tree
-
end
-
-
1
def post(path, payload, additional_headers = {})
-
run_management_request(:post, path, payload, additional_headers)
-
end
-
-
1
def put(path, payload, additional_headers = {})
-
run_management_request(:put, path, payload, additional_headers)
-
end
-
-
1
def delete(path, additional_headers = {})
-
run_management_request(:delete, path, nil, additional_headers)
-
end
-
-
1
def get(path, additional_headers = {})
-
run_management_request(:get, path, nil, additional_headers)
-
end
-
-
1
def run_management_request(action, path, payload = {}, additional_headers = {})
-
logger.info(request: { path: path, action: action }) if logger
-
retries_left = 3
-
-
begin
-
if [:post, :put].include?(action)
-
res = @rest_client[path].send(action, payload, additional_headers)
-
else
-
res = @rest_client[path].send(action, additional_headers)
-
end
-
rescue RestClient::TooManyRequests
-
if retries_left != 0
-
retries_left -= 1
-
logger.info("Too many requests. Retry nr. #{(3 - retries_left).to_s} of max. 3 times.") if logger
-
sleep(0.5)
-
retry
-
end
-
-
raise
-
end
-
-
parse_result(res)
-
end
-
-
1
def cached_get(request, bypass_cache = false)
-
1
endpoint = base_url + request.url
-
1
query = request_query(request.query)
-
1
query_string = build_nested_query(query)
-
-
1
if cache.nil? || bypass_cache || query[:version] == 'draft'
-
1
result = run_request(endpoint, query_string)
-
else
-
cache_key = 'storyblok:' + configuration[:token] + ':v:' + query[:cv] + ':' + request.url + ':' + Base64.encode64(query_string)
-
-
result = cache.cache(cache_key) do
-
run_request(endpoint, query_string)
-
end
-
end
-
-
1
result = JSON.parse(result)
-
-
1
if !result.dig('data', 'story').nil? || !result.dig('data', 'stories').nil?
-
1
result = resolve_stories(result, query)
-
end
-
-
1
result
-
end
-
-
1
def flush
-
unless cache.nil?
-
cache.set('storyblok:' + configuration[:token] + ':version', space['data']['space']['version'])
-
end
-
end
-
-
# Returns html from richtext field data
-
#
-
# @param [Hash] :data
-
#
-
# @return [String]
-
1
def render data
-
@renderer.render(data)
-
end
-
-
# Sets component resolver
-
#
-
# @param [Proc] :component_resolver
-
#
-
# @return [nil]
-
1
def set_component_resolver component_resolver
-
@renderer.set_component_resolver(component_resolver)
-
end
-
-
1
private
-
1
def parse_result(res)
-
{ 'headers' => res.headers, 'data' => JSON.parse(res.body) }
-
end
-
-
1
def run_request(endpoint, query_string)
-
1
logger.info(request: { endpoint: endpoint, query: query_string }) if logger
-
-
1
retries_left = 3
-
-
begin
-
1
res = RestClient.get "#{endpoint}?#{query_string}"
-
rescue RestClient::TooManyRequests
-
if retries_left != 0
-
retries_left -= 1
-
logger.info("Too many requests. Retry nr. #{(3 - retries_left).to_s} of max. 3 times.") if logger
-
sleep(0.5)
-
retry
-
end
-
-
raise
-
end
-
-
1
body = JSON.parse(res.body)
-
1
self.cache_version = body['cv'] if body['cv']
-
-
1
unless cache.nil?
-
cache.set('storyblok:' + configuration[:token] + ':version', cache_version)
-
end
-
-
1
{ 'headers' => res.headers, 'data' => body }.to_json
-
end
-
-
# Patches a query hash with the client configurations for queries
-
1
def request_query(query)
-
1
query[:token] = configuration[:token] if query[:token].nil?
-
1
query[:version] = configuration[:version] if query[:version].nil?
-
-
1
unless cache.nil?
-
query[:cv] = (cache.get('storyblok:' + configuration[:token] + ':version') or cache_version) if query[:cv].nil?
-
else
-
1
query[:cv] = cache_version if query[:cv].nil?
-
end
-
-
1
query
-
end
-
-
# Returns the base url for all of the client's requests
-
1
def base_url
-
1
if !configuration[:api_url]
-
1
region = configuration[:api_region] ? "-#{configuration[:api_region]}" : ""
-
1
"http#{configuration[:secure] ? 's' : ''}://api#{region}.storyblok.com/v#{configuration[:api_version]}"
-
else
-
"http#{configuration[:secure] ? 's' : ''}://#{configuration[:api_url]}/v#{configuration[:api_version]}"
-
end
-
end
-
-
1
def default_configuration
-
1
DEFAULT_CONFIGURATION.dup
-
end
-
-
1
def cache
-
3
configuration[:cache]
-
end
-
-
1
def setup_logger
-
1
@logger = configuration[:logger]
-
1
logger.level = configuration[:log_level] if logger
-
end
-
-
1
def validate_configuration!
-
1
fail ArgumentError, 'You will need to initialize a client with an :token or :oauth_token' if !configuration[:token] and !configuration[:oauth_token]
-
1
fail ArgumentError, 'The :api_version must be a positive number' unless configuration[:api_version].to_i >= 0
-
end
-
-
1
def build_nested_query(value, prefix = nil)
-
4
case value
-
when Array
-
value.map { |v|
-
build_nested_query(v, "#{prefix}[]")
-
}.join("&")
-
when Hash
-
1
value.map { |k, v|
-
3
build_nested_query(v,
-
3
prefix ? "#{prefix}[#{URI.encode_www_form_component(k)}]" : URI.encode_www_form_component(k))
-
}.reject(&:empty?).join('&')
-
when nil
-
prefix
-
else
-
3
raise ArgumentError, "value must be a Hash" if prefix.nil?
-
-
3
"#{prefix}=#{URI.encode_www_form_component(value)}"
-
end
-
end
-
-
1
def resolve_stories(result, params)
-
1
data = result['data']
-
1
rels = data['rels']
-
1
links = data['links']
-
1
resolve_relations = params[:resolve_relations] || params["resolve_relations"]
-
-
1
if data['stories'].nil?
-
find_and_fill_relations(data.dig('story', 'content'), resolve_relations, rels)
-
find_and_fill_links(data.dig('story', 'content'), links)
-
else
-
1
data['stories'].each do |story|
-
1
find_and_fill_relations(story['content'], resolve_relations, rels)
-
1
find_and_fill_links(story['content'], links)
-
end
-
end
-
-
1
result
-
end
-
-
1
def find_and_fill_links(content, links)
-
1
return if content.nil? || links.nil? || links.size.zero?
-
-
if content.is_a? Array
-
content.each do |item|
-
find_and_fill_links(item, links)
-
end
-
elsif content.is_a? Hash
-
content['story'] = nil
-
content.each do |_k, value|
-
if !content['fieldtype'].nil?
-
if content['fieldtype'] == 'multilink' && content['linktype'] == 'story'
-
id =
-
if content['id'].is_a? String
-
content['id']
-
elsif content['uuid'].is_a? String
-
content['uuid']
-
end
-
-
links.each do |link|
-
if link['uuid'] == id
-
content['story'] = link
-
break
-
end
-
end
-
end
-
end
-
-
find_and_fill_links(value, links)
-
end
-
content.delete('story') if content['story'].nil?
-
end
-
end
-
-
1
def find_and_fill_relations(content, relation_params, rels)
-
1
return if content.nil? || rels.nil? || rels.size.zero?
-
-
if content.is_a? Array
-
content.each do |item|
-
find_and_fill_relations(item, relation_params, rels)
-
end
-
elsif content.is_a? Hash
-
content.each do |_k, value|
-
if !content['component'].nil? && !content['_uid'].nil?
-
relation_params.split(',').each do |relation|
-
component, field_name = relation.split('.')
-
-
if (content['component'] == component) && !content[field_name].nil?
-
rels.each do |rel|
-
index = content[field_name].index(rel['uuid'])
-
if !index.nil?
-
content[field_name][index] = rel
-
end
-
end
-
end
-
end
-
end
-
-
find_and_fill_relations(value, relation_params, rels)
-
end
-
end
-
end
-
end
-
end