# frozen_string_literal: true require 'wcc/contentful' class WCC::Contentful::DownloadsSchema def self.call(file = nil, management_client: nil) new(file, management_client: management_client).call end def initialize(file = nil, management_client: nil) @client = management_client || WCC::Contentful::Services.instance.management_client @file = file || WCC::Contentful.configuration&.schema_file raise ArgumentError, 'Please configure your management token' unless @client raise ArgumentError, 'Please pass filename or call WCC::Contentful.configure' unless @file end def call return unless needs_update? update! end def update! FileUtils.mkdir_p(File.dirname(@file)) File.write(@file, format_json({ 'contentTypes' => content_types, 'editorInterfaces' => editor_interfaces })) end def needs_update? return true unless File.exist?(@file) contents = begin JSON.parse(File.read(@file)) rescue JSON::ParserError return true # rubocop:disable Lint/NoReturnInBeginEndBlocks end existing_cts = contents['contentTypes'].sort_by { |ct| ct.dig('sys', 'id') } return true unless content_types.count == existing_cts.count return true unless deep_contains_all(content_types, existing_cts) existing_eis = contents['editorInterfaces'].sort_by { |i| i.dig('sys', 'contentType', 'sys', 'id') } return true unless editor_interfaces.count == existing_eis.count !deep_contains_all(editor_interfaces, existing_eis) end def content_types @content_types ||= @client.content_types(limit: 1000) .items .map { |ct| strip_sys(ct) } .sort_by { |ct| ct.dig('sys', 'id') } end def editor_interfaces @editor_interfaces ||= content_types .map { |ct| @client.editor_interface(ct.dig('sys', 'id')).raw } .map { |i| sort_controls(strip_sys(i)) } .sort_by { |i| i.dig('sys', 'contentType', 'sys', 'id') } end private def strip_sys(obj) obj.merge!({ 'sys' => obj['sys'].slice('id', 'type', 'contentType') }) end def sort_controls(editor_interface) { 'sys' => editor_interface['sys'], 'controls' => editor_interface['controls'] .sort_by { |c| c['fieldId'] } .map { |c| c.slice('fieldId', 'settings', 'widgetId') } } end def deep_contains_all(expected, actual) case expected when Array expected.each_with_index do |val, i| return false unless actual[i] return false unless deep_contains_all(val, actual[i]) end true when Hash expected.each do |(key, val)| return false unless actual.key?(key) return false unless deep_contains_all(val, actual[key]) end true else expected == actual end end def format_json(hash) json_string = JSON.pretty_generate(hash) # The pretty_generate format differs from contentful-shell and nodejs formats # only in its treatment of empty arrays in the "validations" field. json_string = json_string.gsub(/\[\n\n\s+\]/, '[]') # contentful-shell also adds a newline at the end. "#{json_string}\n" end end