wcc-contentful 1.2.1 → 1.3.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 +4 -4
- data/lib/wcc/contentful/configuration.rb +6 -0
- data/lib/wcc/contentful/services.rb +9 -0
- data/lib/wcc/contentful/simple_client/cdn.rb +126 -0
- data/lib/wcc/contentful/simple_client/preview.rb +17 -0
- data/lib/wcc/contentful/simple_client/response.rb +21 -14
- data/lib/wcc/contentful/simple_client.rb +2 -104
- data/lib/wcc/contentful/store/base.rb +19 -27
- data/lib/wcc/contentful/store/memory_store.rb +8 -5
- data/lib/wcc/contentful/store/postgres_store.rb +42 -25
- data/lib/wcc/contentful/sync_engine.rb +27 -19
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +5 -6
- metadata +5 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9026bfcc1e7745f7d8ecf3fb38928672f02d663d382fd1a3ca953ab33a78cb25
         | 
| 4 | 
            +
              data.tar.gz: 36ec7c7f47fb6e49d565d7ca82c5021ac8f88b46eabfe41754ac5c123ac0004d
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 633116bd83752074876ccf76265eb64bc6a19a28b9a477bf2026925f0a84977cd651a234f5656b7a657a43e65b3320031e2385ca398bb2b846ab730693105799
         | 
| 7 | 
            +
              data.tar.gz: 32f1f08bac98d42d89a136c0b49d2bb1ee1a72e057326edd50a17834b53b2da7fecbbe1db1aded355d2849bc1d222d4f16d3e721024a3c105f786349ab6548fc
         | 
| @@ -10,6 +10,7 @@ class WCC::Contentful::Configuration | |
| 10 10 | 
             
                default_locale
         | 
| 11 11 | 
             
                environment
         | 
| 12 12 | 
             
                instrumentation_adapter
         | 
| 13 | 
            +
                logger
         | 
| 13 14 | 
             
                management_token
         | 
| 14 15 | 
             
                preview_token
         | 
| 15 16 | 
             
                schema_file
         | 
| @@ -182,6 +183,11 @@ class WCC::Contentful::Configuration | |
| 182 183 | 
             
              # to :instrument like ActiveSupport::Notifications.instrument
         | 
| 183 184 | 
             
              attr_accessor :instrumentation_adapter
         | 
| 184 185 |  | 
| 186 | 
            +
              # Sets the logger to be used by the wcc-contentful gem, including stores.
         | 
| 187 | 
            +
              # Defaults to the rails logger if in a rails context, otherwise creates a new
         | 
| 188 | 
            +
              # logger that writes to STDERR.
         | 
| 189 | 
            +
              attr_accessor :logger
         | 
| 190 | 
            +
             | 
| 185 191 | 
             
              def initialize
         | 
| 186 192 | 
             
                @access_token = ENV.fetch('CONTENTFUL_ACCESS_TOKEN', nil)
         | 
| 187 193 | 
             
                @app_url = ENV.fetch('APP_URL', nil)
         | 
| @@ -132,6 +132,15 @@ module WCC::Contentful | |
| 132 132 | 
             
                # Allow it to be injected into a store
         | 
| 133 133 | 
             
                alias_method :_instrumentation, :instrumentation
         | 
| 134 134 |  | 
| 135 | 
            +
                # Gets the configured logger, defaulting to Rails.logger in a rails context,
         | 
| 136 | 
            +
                # or logging to STDERR in a non-rails context.
         | 
| 137 | 
            +
                def logger
         | 
| 138 | 
            +
                  @logger ||=
         | 
| 139 | 
            +
                    configuration.logger ||
         | 
| 140 | 
            +
                    (Rails.logger if defined?(Rails)) ||
         | 
| 141 | 
            +
                    Logger.new($stderr)
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 135 144 | 
             
                ##
         | 
| 136 145 | 
             
                # This method enables simple dependency injection -
         | 
| 137 146 | 
             
                # If the target has a setter matching the name of one of the services,
         | 
| @@ -0,0 +1,126 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # The CDN SimpleClient accesses 'https://cdn.contentful.com' to get raw
         | 
| 4 | 
            +
            # JSON responses.  It exposes methods to query entries, assets, and content_types.
         | 
| 5 | 
            +
            # The responses are instances of WCC::Contentful::SimpleClient::Response
         | 
| 6 | 
            +
            # which handles paging automatically.
         | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            # @api Client
         | 
| 9 | 
            +
            class WCC::Contentful::SimpleClient::Cdn < WCC::Contentful::SimpleClient
         | 
| 10 | 
            +
              def initialize(space:, access_token:, **options)
         | 
| 11 | 
            +
                super(
         | 
| 12 | 
            +
                  api_url: options[:api_url] || 'https://cdn.contentful.com/',
         | 
| 13 | 
            +
                  space: space,
         | 
| 14 | 
            +
                  access_token: access_token,
         | 
| 15 | 
            +
                  **options
         | 
| 16 | 
            +
                )
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def client_type
         | 
| 20 | 
            +
                'cdn'
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              # Gets an entry by ID
         | 
| 24 | 
            +
              def entry(key, query = {})
         | 
| 25 | 
            +
                resp =
         | 
| 26 | 
            +
                  _instrument 'entries', id: key, type: 'Entry', query: query do
         | 
| 27 | 
            +
                    get("entries/#{key}", query)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                resp.assert_ok!
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              # Queries entries with optional query parameters
         | 
| 33 | 
            +
              def entries(query = {})
         | 
| 34 | 
            +
                resp =
         | 
| 35 | 
            +
                  _instrument 'entries', type: 'Entry', query: query do
         | 
| 36 | 
            +
                    get('entries', query)
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                resp.assert_ok!
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              # Gets an asset by ID
         | 
| 42 | 
            +
              def asset(key, query = {})
         | 
| 43 | 
            +
                resp =
         | 
| 44 | 
            +
                  _instrument 'entries', type: 'Asset', id: key, query: query do
         | 
| 45 | 
            +
                    get("assets/#{key}", query)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                resp.assert_ok!
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              # Queries assets with optional query parameters
         | 
| 51 | 
            +
              def assets(query = {})
         | 
| 52 | 
            +
                resp =
         | 
| 53 | 
            +
                  _instrument 'entries', type: 'Asset', query: query do
         | 
| 54 | 
            +
                    get('assets', query)
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                resp.assert_ok!
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              # Queries content types with optional query parameters
         | 
| 60 | 
            +
              def content_types(query = {})
         | 
| 61 | 
            +
                resp =
         | 
| 62 | 
            +
                  _instrument 'content_types', query: query do
         | 
| 63 | 
            +
                    get('content_types', query)
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                resp.assert_ok!
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              # Accesses the Sync API to get a list of items that have changed since
         | 
| 69 | 
            +
              # the last sync.  Accepts a block that receives each changed item, and returns
         | 
| 70 | 
            +
              # the next sync token.
         | 
| 71 | 
            +
              #
         | 
| 72 | 
            +
              # If `sync_token` is nil, an initial sync is performed.
         | 
| 73 | 
            +
              #
         | 
| 74 | 
            +
              # @return String the next sync token parsed from nextSyncUrl
         | 
| 75 | 
            +
              # @example
         | 
| 76 | 
            +
              #    my_sync_token = storage.get('sync_token')
         | 
| 77 | 
            +
              #    my_sync_token = client.sync(sync_token: my_sync_token) do |item|
         | 
| 78 | 
            +
              #      storage.put(item.dig('sys', 'id'), item) }
         | 
| 79 | 
            +
              #    end
         | 
| 80 | 
            +
              #    storage.put('sync_token', my_sync_token)
         | 
| 81 | 
            +
              def sync(sync_token: nil, **query, &block)
         | 
| 82 | 
            +
                return sync_old(sync_token: sync_token, **query) unless block_given?
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                sync_token =
         | 
| 85 | 
            +
                  if sync_token
         | 
| 86 | 
            +
                    { sync_token: sync_token }
         | 
| 87 | 
            +
                  else
         | 
| 88 | 
            +
                    { initial: true }
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
                query = query.merge(sync_token)
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                _instrument 'sync', sync_token: sync_token, query: query do
         | 
| 93 | 
            +
                  resp = get('sync', query)
         | 
| 94 | 
            +
                  resp = SyncResponse.new(resp)
         | 
| 95 | 
            +
                  resp.assert_ok!
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  resp.each_page do |page|
         | 
| 98 | 
            +
                    page.page_items.each(&block)
         | 
| 99 | 
            +
                    sync_token = resp.next_sync_token
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                sync_token
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
              private
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              def sync_old(sync_token: nil, **query)
         | 
| 109 | 
            +
                ActiveSupport::Deprecation.warn('Sync without a block is deprecated, please use new block syntax instead')
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                sync_token =
         | 
| 112 | 
            +
                  if sync_token
         | 
| 113 | 
            +
                    { sync_token: sync_token }
         | 
| 114 | 
            +
                  else
         | 
| 115 | 
            +
                    { initial: true }
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                query = query.merge(sync_token)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                resp =
         | 
| 120 | 
            +
                  _instrument 'sync', sync_token: sync_token, query: query do
         | 
| 121 | 
            +
                    get('sync', query)
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
                resp = SyncResponse.new(resp, memoize: true)
         | 
| 124 | 
            +
                resp.assert_ok!
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # @api Client
         | 
| 4 | 
            +
            class WCC::Contentful::SimpleClient::Preview < WCC::Contentful::SimpleClient::Cdn
         | 
| 5 | 
            +
              def initialize(space:, preview_token:, **options)
         | 
| 6 | 
            +
                super(
         | 
| 7 | 
            +
                  **options,
         | 
| 8 | 
            +
                  api_url: options[:preview_api_url] || 'https://preview.contentful.com/',
         | 
| 9 | 
            +
                  space: space,
         | 
| 10 | 
            +
                  access_token: preview_token
         | 
| 11 | 
            +
                )
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def client_type
         | 
| 15 | 
            +
                'preview'
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -110,8 +110,9 @@ class WCC::Contentful::SimpleClient | |
| 110 110 | 
             
              end
         | 
| 111 111 |  | 
| 112 112 | 
             
              class SyncResponse < Response
         | 
| 113 | 
            -
                def initialize(response)
         | 
| 113 | 
            +
                def initialize(response, memoize: false)
         | 
| 114 114 | 
             
                  super(response.client, response.request, response.raw_response)
         | 
| 115 | 
            +
                  @memoize = memoize
         | 
| 115 116 | 
             
                end
         | 
| 116 117 |  | 
| 117 118 | 
             
                def next_page?
         | 
| @@ -120,6 +121,7 @@ class WCC::Contentful::SimpleClient | |
| 120 121 |  | 
| 121 122 | 
             
                def next_page
         | 
| 122 123 | 
             
                  return unless next_page?
         | 
| 124 | 
            +
                  return @next_page if @next_page
         | 
| 123 125 |  | 
| 124 126 | 
             
                  url = raw['nextPageUrl']
         | 
| 125 127 | 
             
                  next_page =
         | 
| @@ -129,26 +131,31 @@ class WCC::Contentful::SimpleClient | |
| 129 131 |  | 
| 130 132 | 
             
                  next_page = SyncResponse.new(next_page)
         | 
| 131 133 | 
             
                  next_page.assert_ok!
         | 
| 134 | 
            +
                  @next_page = next_page if @memoize
         | 
| 135 | 
            +
                  next_page
         | 
| 132 136 | 
             
                end
         | 
| 133 137 |  | 
| 134 138 | 
             
                def next_sync_token
         | 
| 135 | 
            -
                  # If we  | 
| 136 | 
            -
                  #  | 
| 137 | 
            -
                   | 
| 138 | 
            -
             | 
| 139 | 
            -
                      raw['nextPageUrl'] || raw['nextSyncUrl']
         | 
| 140 | 
            -
                    )
         | 
| 141 | 
            -
                end
         | 
| 142 | 
            -
             | 
| 143 | 
            -
                def each_page
         | 
| 144 | 
            -
                  raise ArgumentError, 'Not a collection response' unless page_items
         | 
| 139 | 
            +
                  # If we have iterated some pages, return the sync token of the final
         | 
| 140 | 
            +
                  # page that was iterated.  Do this without maintaining a reference to
         | 
| 141 | 
            +
                  # all the pages.
         | 
| 142 | 
            +
                  return @last_sync_token if @last_sync_token
         | 
| 145 143 |  | 
| 146 | 
            -
                   | 
| 144 | 
            +
                  SyncResponse.parse_sync_token(raw['nextPageUrl'] || raw['nextSyncUrl'])
         | 
| 145 | 
            +
                end
         | 
| 147 146 |  | 
| 147 | 
            +
                def each_page(&block)
         | 
| 148 148 | 
             
                  if block_given?
         | 
| 149 | 
            -
                     | 
| 149 | 
            +
                    super do |page|
         | 
| 150 | 
            +
                      @last_sync_token = page.next_sync_token
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                      yield page
         | 
| 153 | 
            +
                    end
         | 
| 150 154 | 
             
                  else
         | 
| 151 | 
            -
                     | 
| 155 | 
            +
                    super.map do |page|
         | 
| 156 | 
            +
                      @last_sync_token = page.next_sync_token
         | 
| 157 | 
            +
                      page
         | 
| 158 | 
            +
                    end
         | 
| 152 159 | 
             
                  end
         | 
| 153 160 | 
             
                end
         | 
| 154 161 |  | 
| @@ -2,6 +2,8 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require_relative 'simple_client/response'
         | 
| 4 4 | 
             
            require_relative 'simple_client/management'
         | 
| 5 | 
            +
            require_relative 'simple_client/cdn'
         | 
| 6 | 
            +
            require_relative 'simple_client/preview'
         | 
| 5 7 | 
             
            require_relative 'instrumentation'
         | 
| 6 8 |  | 
| 7 9 | 
             
            module WCC::Contentful
         | 
| @@ -146,109 +148,5 @@ module WCC::Contentful | |
| 146 148 | 
             
                    return resp
         | 
| 147 149 | 
             
                  end
         | 
| 148 150 | 
             
                end
         | 
| 149 | 
            -
             | 
| 150 | 
            -
                # The CDN SimpleClient accesses 'https://cdn.contentful.com' to get raw
         | 
| 151 | 
            -
                # JSON responses.  It exposes methods to query entries, assets, and content_types.
         | 
| 152 | 
            -
                # The responses are instances of WCC::Contentful::SimpleClient::Response
         | 
| 153 | 
            -
                # which handles paging automatically.
         | 
| 154 | 
            -
                #
         | 
| 155 | 
            -
                # @api Client
         | 
| 156 | 
            -
                class Cdn < SimpleClient
         | 
| 157 | 
            -
                  def initialize(space:, access_token:, **options)
         | 
| 158 | 
            -
                    super(
         | 
| 159 | 
            -
                      api_url: options[:api_url] || 'https://cdn.contentful.com/',
         | 
| 160 | 
            -
                      space: space,
         | 
| 161 | 
            -
                      access_token: access_token,
         | 
| 162 | 
            -
                      **options
         | 
| 163 | 
            -
                    )
         | 
| 164 | 
            -
                  end
         | 
| 165 | 
            -
             | 
| 166 | 
            -
                  def client_type
         | 
| 167 | 
            -
                    'cdn'
         | 
| 168 | 
            -
                  end
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                  # Gets an entry by ID
         | 
| 171 | 
            -
                  def entry(key, query = {})
         | 
| 172 | 
            -
                    resp =
         | 
| 173 | 
            -
                      _instrument 'entries', id: key, type: 'Entry', query: query do
         | 
| 174 | 
            -
                        get("entries/#{key}", query)
         | 
| 175 | 
            -
                      end
         | 
| 176 | 
            -
                    resp.assert_ok!
         | 
| 177 | 
            -
                  end
         | 
| 178 | 
            -
             | 
| 179 | 
            -
                  # Queries entries with optional query parameters
         | 
| 180 | 
            -
                  def entries(query = {})
         | 
| 181 | 
            -
                    resp =
         | 
| 182 | 
            -
                      _instrument 'entries', type: 'Entry', query: query do
         | 
| 183 | 
            -
                        get('entries', query)
         | 
| 184 | 
            -
                      end
         | 
| 185 | 
            -
                    resp.assert_ok!
         | 
| 186 | 
            -
                  end
         | 
| 187 | 
            -
             | 
| 188 | 
            -
                  # Gets an asset by ID
         | 
| 189 | 
            -
                  def asset(key, query = {})
         | 
| 190 | 
            -
                    resp =
         | 
| 191 | 
            -
                      _instrument 'entries', type: 'Asset', id: key, query: query do
         | 
| 192 | 
            -
                        get("assets/#{key}", query)
         | 
| 193 | 
            -
                      end
         | 
| 194 | 
            -
                    resp.assert_ok!
         | 
| 195 | 
            -
                  end
         | 
| 196 | 
            -
             | 
| 197 | 
            -
                  # Queries assets with optional query parameters
         | 
| 198 | 
            -
                  def assets(query = {})
         | 
| 199 | 
            -
                    resp =
         | 
| 200 | 
            -
                      _instrument 'entries', type: 'Asset', query: query do
         | 
| 201 | 
            -
                        get('assets', query)
         | 
| 202 | 
            -
                      end
         | 
| 203 | 
            -
                    resp.assert_ok!
         | 
| 204 | 
            -
                  end
         | 
| 205 | 
            -
             | 
| 206 | 
            -
                  # Queries content types with optional query parameters
         | 
| 207 | 
            -
                  def content_types(query = {})
         | 
| 208 | 
            -
                    resp =
         | 
| 209 | 
            -
                      _instrument 'content_types', query: query do
         | 
| 210 | 
            -
                        get('content_types', query)
         | 
| 211 | 
            -
                      end
         | 
| 212 | 
            -
                    resp.assert_ok!
         | 
| 213 | 
            -
                  end
         | 
| 214 | 
            -
             | 
| 215 | 
            -
                  # Accesses the Sync API to get a list of items that have changed since
         | 
| 216 | 
            -
                  # the last sync.
         | 
| 217 | 
            -
                  #
         | 
| 218 | 
            -
                  # If `sync_token` is nil, an initial sync is performed.
         | 
| 219 | 
            -
                  # Returns a WCC::Contentful::SimpleClient::SyncResponse
         | 
| 220 | 
            -
                  # which handles paging automatically.
         | 
| 221 | 
            -
                  def sync(sync_token: nil, **query)
         | 
| 222 | 
            -
                    sync_token =
         | 
| 223 | 
            -
                      if sync_token
         | 
| 224 | 
            -
                        { sync_token: sync_token }
         | 
| 225 | 
            -
                      else
         | 
| 226 | 
            -
                        { initial: true }
         | 
| 227 | 
            -
                      end
         | 
| 228 | 
            -
                    query = query.merge(sync_token)
         | 
| 229 | 
            -
                    resp =
         | 
| 230 | 
            -
                      _instrument 'sync', sync_token: sync_token, query: query do
         | 
| 231 | 
            -
                        get('sync', query)
         | 
| 232 | 
            -
                      end
         | 
| 233 | 
            -
                    resp = SyncResponse.new(resp)
         | 
| 234 | 
            -
                    resp.assert_ok!
         | 
| 235 | 
            -
                  end
         | 
| 236 | 
            -
                end
         | 
| 237 | 
            -
             | 
| 238 | 
            -
                # @api Client
         | 
| 239 | 
            -
                class Preview < Cdn
         | 
| 240 | 
            -
                  def initialize(space:, preview_token:, **options)
         | 
| 241 | 
            -
                    super(
         | 
| 242 | 
            -
                      **options,
         | 
| 243 | 
            -
                      api_url: options[:preview_api_url] || 'https://preview.contentful.com/',
         | 
| 244 | 
            -
                      space: space,
         | 
| 245 | 
            -
                      access_token: preview_token
         | 
| 246 | 
            -
                    )
         | 
| 247 | 
            -
                  end
         | 
| 248 | 
            -
             | 
| 249 | 
            -
                  def client_type
         | 
| 250 | 
            -
                    'preview'
         | 
| 251 | 
            -
                  end
         | 
| 252 | 
            -
                end
         | 
| 253 151 | 
             
              end
         | 
| 254 152 | 
             
            end
         | 
| @@ -50,30 +50,30 @@ module WCC::Contentful::Store | |
| 50 50 | 
             
                # implementation calls into #set and #delete to perform the appropriate
         | 
| 51 51 | 
             
                # operations in the store.
         | 
| 52 52 | 
             
                def index(json)
         | 
| 53 | 
            +
                  # This implementation assumes that #delete and #set are individually thread-safe.
         | 
| 54 | 
            +
                  # No mutex is needed so long as the revisions are accurate.
         | 
| 53 55 | 
             
                  # Subclasses can override to do this in a more performant thread-safe way.
         | 
| 54 56 | 
             
                  # Example: postgres_store could do this in a stored procedure for speed
         | 
| 55 | 
            -
                   | 
| 56 | 
            -
                     | 
| 57 | 
            -
                      case type = json.dig('sys', 'type')
         | 
| 58 | 
            -
                      when 'DeletedEntry', 'DeletedAsset'
         | 
| 59 | 
            -
                        delete(json.dig('sys', 'id'))
         | 
| 60 | 
            -
                      else
         | 
| 61 | 
            -
                        set(json.dig('sys', 'id'), json)
         | 
| 62 | 
            -
                      end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                    if (prev_rev = prev&.dig('sys', 'revision')) &&
         | 
| 65 | 
            -
                        (next_rev = json.dig('sys', 'revision')) &&
         | 
| 66 | 
            -
                        (next_rev < prev_rev)
         | 
| 67 | 
            -
                      # Uh oh! we overwrote an entry with a prior revision.  Put the previous back.
         | 
| 68 | 
            -
                      return index(prev)
         | 
| 69 | 
            -
                    end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                    case type
         | 
| 57 | 
            +
                  prev =
         | 
| 58 | 
            +
                    case type = json.dig('sys', 'type')
         | 
| 72 59 | 
             
                    when 'DeletedEntry', 'DeletedAsset'
         | 
| 73 | 
            -
                       | 
| 60 | 
            +
                      delete(json.dig('sys', 'id'))
         | 
| 74 61 | 
             
                    else
         | 
| 75 | 
            -
                      json
         | 
| 62 | 
            +
                      set(json.dig('sys', 'id'), json)
         | 
| 76 63 | 
             
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  if (prev_rev = prev&.dig('sys', 'revision')) &&
         | 
| 66 | 
            +
                      (next_rev = json.dig('sys', 'revision')) &&
         | 
| 67 | 
            +
                      (next_rev < prev_rev)
         | 
| 68 | 
            +
                    # Uh oh! we overwrote an entry with a prior revision.  Put the previous back.
         | 
| 69 | 
            +
                    return index(prev)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  case type
         | 
| 73 | 
            +
                  when 'DeletedEntry', 'DeletedAsset'
         | 
| 74 | 
            +
                    nil
         | 
| 75 | 
            +
                  else
         | 
| 76 | 
            +
                    json
         | 
| 77 77 | 
             
                  end
         | 
| 78 78 | 
             
                end
         | 
| 79 79 |  | 
| @@ -107,17 +107,9 @@ module WCC::Contentful::Store | |
| 107 107 | 
             
                  )
         | 
| 108 108 | 
             
                end
         | 
| 109 109 |  | 
| 110 | 
            -
                def initialize
         | 
| 111 | 
            -
                  @mutex = Concurrent::ReentrantReadWriteLock.new
         | 
| 112 | 
            -
                end
         | 
| 113 | 
            -
             | 
| 114 110 | 
             
                def ensure_hash(val)
         | 
| 115 111 | 
             
                  raise ArgumentError, 'Value must be a Hash' unless val.is_a?(Hash)
         | 
| 116 112 | 
             
                end
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                private
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                attr_reader :mutex
         | 
| 121 113 | 
             
              end
         | 
| 122 114 | 
             
            end
         | 
| 123 115 |  | 
| @@ -9,13 +9,15 @@ module WCC::Contentful::Store | |
| 9 9 | 
             
              class MemoryStore < Base
         | 
| 10 10 | 
             
                def initialize
         | 
| 11 11 | 
             
                  super
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  @mutex = Concurrent::ReentrantReadWriteLock.new
         | 
| 12 14 | 
             
                  @hash = {}
         | 
| 13 15 | 
             
                end
         | 
| 14 16 |  | 
| 15 17 | 
             
                def set(key, value)
         | 
| 16 18 | 
             
                  value = value.deep_dup.freeze
         | 
| 17 19 | 
             
                  ensure_hash value
         | 
| 18 | 
            -
                  mutex.with_write_lock do
         | 
| 20 | 
            +
                  @mutex.with_write_lock do
         | 
| 19 21 | 
             
                    old = @hash[key]
         | 
| 20 22 | 
             
                    @hash[key] = value
         | 
| 21 23 | 
             
                    old
         | 
| @@ -23,17 +25,17 @@ module WCC::Contentful::Store | |
| 23 25 | 
             
                end
         | 
| 24 26 |  | 
| 25 27 | 
             
                def delete(key)
         | 
| 26 | 
            -
                  mutex.with_write_lock do
         | 
| 28 | 
            +
                  @mutex.with_write_lock do
         | 
| 27 29 | 
             
                    @hash.delete(key)
         | 
| 28 30 | 
             
                  end
         | 
| 29 31 | 
             
                end
         | 
| 30 32 |  | 
| 31 33 | 
             
                def keys
         | 
| 32 | 
            -
                  mutex.with_read_lock { @hash.keys }
         | 
| 34 | 
            +
                  @mutex.with_read_lock { @hash.keys }
         | 
| 33 35 | 
             
                end
         | 
| 34 36 |  | 
| 35 37 | 
             
                def find(key, **_options)
         | 
| 36 | 
            -
                  mutex.with_read_lock do
         | 
| 38 | 
            +
                  @mutex.with_read_lock do
         | 
| 37 39 | 
             
                    @hash[key]
         | 
| 38 40 | 
             
                  end
         | 
| 39 41 | 
             
                end
         | 
| @@ -45,7 +47,8 @@ module WCC::Contentful::Store | |
| 45 47 | 
             
                    raise ArgumentError, "Operator :#{bad_op} not supported"
         | 
| 46 48 | 
             
                  end
         | 
| 47 49 |  | 
| 48 | 
            -
                   | 
| 50 | 
            +
                  # Since @hash.values returns a new array, we only need to lock here
         | 
| 51 | 
            +
                  relation = @mutex.with_read_lock { @hash.values }
         | 
| 49 52 |  | 
| 50 53 | 
             
                  # relation is an enumerable that we apply conditions to in the form of
         | 
| 51 54 | 
             
                  #  Enumerable#select and Enumerable#reject.
         | 
| @@ -23,18 +23,22 @@ module WCC::Contentful::Store | |
| 23 23 | 
             
                  connection_options ||= { dbname: 'postgres' }
         | 
| 24 24 | 
             
                  pool_options ||= {}
         | 
| 25 25 | 
             
                  @connection_pool = PostgresStore.build_connection_pool(connection_options, pool_options)
         | 
| 26 | 
            -
                  @dirty =  | 
| 26 | 
            +
                  @dirty = Concurrent::AtomicBoolean.new
         | 
| 27 | 
            +
                  @mutex = Mutex.new
         | 
| 27 28 | 
             
                end
         | 
| 28 29 |  | 
| 29 30 | 
             
                def set(key, value)
         | 
| 30 31 | 
             
                  ensure_hash value
         | 
| 32 | 
            +
             | 
| 31 33 | 
             
                  result =
         | 
| 32 | 
            -
                     | 
| 33 | 
            -
                       | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 34 | 
            +
                    _instrument 'upsert_entry' do
         | 
| 35 | 
            +
                      @connection_pool.with do |conn|
         | 
| 36 | 
            +
                        conn.exec_prepared('upsert_entry', [
         | 
| 37 | 
            +
                                             key,
         | 
| 38 | 
            +
                                             value.to_json,
         | 
| 39 | 
            +
                                             quote_array(extract_links(value))
         | 
| 40 | 
            +
                                           ])
         | 
| 41 | 
            +
                      end
         | 
| 38 42 | 
             
                    end
         | 
| 39 43 |  | 
| 40 44 | 
             
                  previous_value =
         | 
| @@ -45,16 +49,23 @@ module WCC::Contentful::Store | |
| 45 49 | 
             
                      JSON.parse(val) if val
         | 
| 46 50 | 
             
                    end
         | 
| 47 51 |  | 
| 48 | 
            -
                  if views_need_update?(value, previous_value) | 
| 49 | 
            -
                     | 
| 50 | 
            -
                     | 
| 52 | 
            +
                  if views_need_update?(value, previous_value)
         | 
| 53 | 
            +
                    # Mark the views as needing to be refreshed, they will be refreshed on the next query.
         | 
| 54 | 
            +
                    was_dirty = @dirty.make_true
         | 
| 55 | 
            +
                    # Send out an instrumentation event if we are the thread that marked it dirty
         | 
| 56 | 
            +
                    # (make_true returns true if the value changed)
         | 
| 57 | 
            +
                    _instrument 'mark_dirty' if was_dirty
         | 
| 51 58 | 
             
                  end
         | 
| 52 59 |  | 
| 53 60 | 
             
                  previous_value
         | 
| 54 61 | 
             
                end
         | 
| 55 62 |  | 
| 56 63 | 
             
                def keys
         | 
| 57 | 
            -
                  result = | 
| 64 | 
            +
                  result =
         | 
| 65 | 
            +
                    _instrument 'select_ids' do
         | 
| 66 | 
            +
                      @connection_pool.with { |conn| conn.exec_prepared('select_ids') }
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 58 69 | 
             
                  arr = []
         | 
| 59 70 | 
             
                  result.each { |r| arr << r['id'].strip }
         | 
| 60 71 | 
             
                  arr
         | 
| @@ -63,14 +74,21 @@ module WCC::Contentful::Store | |
| 63 74 | 
             
                end
         | 
| 64 75 |  | 
| 65 76 | 
             
                def delete(key)
         | 
| 66 | 
            -
                  result = | 
| 77 | 
            +
                  result =
         | 
| 78 | 
            +
                    _instrument 'delete_by_id', key: key do
         | 
| 79 | 
            +
                      @connection_pool.with { |conn| conn.exec_prepared('delete_by_id', [key]) }
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 67 82 | 
             
                  return if result.num_tuples == 0
         | 
| 68 83 |  | 
| 69 84 | 
             
                  JSON.parse(result.getvalue(0, 1))
         | 
| 70 85 | 
             
                end
         | 
| 71 86 |  | 
| 72 87 | 
             
                def find(key, **_options)
         | 
| 73 | 
            -
                  result = | 
| 88 | 
            +
                  result =
         | 
| 89 | 
            +
                    _instrument 'select_entry', key: key do
         | 
| 90 | 
            +
                      @connection_pool.with { |conn| conn.exec_prepared('select_entry', [key]) }
         | 
| 91 | 
            +
                    end
         | 
| 74 92 | 
             
                  return if result.num_tuples == 0
         | 
| 75 93 |  | 
| 76 94 | 
             
                  JSON.parse(result.getvalue(0, 1))
         | 
| @@ -87,22 +105,21 @@ module WCC::Contentful::Store | |
| 87 105 | 
             
                end
         | 
| 88 106 |  | 
| 89 107 | 
             
                def exec_query(statement, params = [])
         | 
| 90 | 
            -
                  if  | 
| 91 | 
            -
                     | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
                         | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
                        @connection_pool.with { |conn| conn.exec_prepared('refresh_views_concurrently') }
         | 
| 108 | 
            +
                  if @dirty.true?
         | 
| 109 | 
            +
                    # Only one thread should call refresh_views_concurrently but all should wait for it to finish.
         | 
| 110 | 
            +
                    @mutex.synchronize do
         | 
| 111 | 
            +
                      # We have to check again b/c another thread may have gotten here first
         | 
| 112 | 
            +
                      if @dirty.true?
         | 
| 113 | 
            +
                        _instrument 'refresh_views' do
         | 
| 114 | 
            +
                          @connection_pool.with { |conn| conn.exec_prepared('refresh_views_concurrently') }
         | 
| 115 | 
            +
                        end
         | 
| 116 | 
            +
                        # Mark that the views have been refreshed.
         | 
| 117 | 
            +
                        @dirty.make_false
         | 
| 101 118 | 
             
                      end
         | 
| 102 119 | 
             
                    end
         | 
| 103 120 | 
             
                  end
         | 
| 104 121 |  | 
| 105 | 
            -
                  logger&.debug("[PostgresStore] #{statement} | 
| 122 | 
            +
                  logger&.debug("[PostgresStore] #{statement} #{params.inspect}")
         | 
| 106 123 | 
             
                  _instrument 'exec' do
         | 
| 107 124 | 
             
                    @connection_pool.with { |conn| conn.exec(statement, params) }
         | 
| 108 125 | 
             
                  end
         | 
| @@ -76,21 +76,23 @@ module WCC::Contentful | |
| 76 76 |  | 
| 77 77 | 
             
                  @mutex.synchronize do
         | 
| 78 78 | 
             
                    @state ||= read_state || token_wrapper_factory(nil)
         | 
| 79 | 
            -
                     | 
| 80 | 
            -
             | 
| 81 | 
            -
                     | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 79 | 
            +
                    sync_token = @state['token']
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    next_sync_token =
         | 
| 82 | 
            +
                      client.sync(sync_token: sync_token) do |item|
         | 
| 83 | 
            +
                        id = item.dig('sys', 'id')
         | 
| 84 | 
            +
                        id_found ||= id == up_to_id
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                        store.index(item) if store&.index?
         | 
| 87 | 
            +
                        event = WCC::Contentful::Event.from_raw(item, source: self)
         | 
| 88 | 
            +
                        yield(event) if block_given?
         | 
| 89 | 
            +
                        emit_event(event)
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                        # Only keep the "sys" not the content in case we have a large space
         | 
| 92 | 
            +
                        all_events << WCC::Contentful::Event.from_raw(item.slice('sys'), source: self)
         | 
| 93 | 
            +
                      end
         | 
| 92 94 |  | 
| 93 | 
            -
                    @state = @state.merge('token' =>  | 
| 95 | 
            +
                    @state = @state.merge('token' => next_sync_token)
         | 
| 94 96 | 
             
                    write_state
         | 
| 95 97 | 
             
                  end
         | 
| 96 98 |  | 
| @@ -136,13 +138,19 @@ module WCC::Contentful | |
| 136 138 | 
             
                  # This job uses the Contentful Sync API to update the configured store with
         | 
| 137 139 | 
             
                  # the latest data from Contentful.
         | 
| 138 140 | 
             
                  class Job < ActiveJob::Base
         | 
| 139 | 
            -
                    include WCC::Contentful::ServiceAccessors
         | 
| 140 | 
            -
             | 
| 141 141 | 
             
                    self.queue_adapter = :async
         | 
| 142 142 | 
             
                    queue_as :default
         | 
| 143 143 |  | 
| 144 | 
            +
                    def configuration
         | 
| 145 | 
            +
                      @configuration ||= WCC::Contentful.configuration
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    def services
         | 
| 149 | 
            +
                      @services ||= WCC::Contentful::Services.instance
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
             | 
| 144 152 | 
             
                    def perform(event = nil)
         | 
| 145 | 
            -
                      return unless sync_engine&.should_sync?
         | 
| 153 | 
            +
                      return unless services.sync_engine&.should_sync?
         | 
| 146 154 |  | 
| 147 155 | 
             
                      up_to_id = nil
         | 
| 148 156 | 
             
                      retry_count = 0
         | 
| @@ -162,9 +170,9 @@ module WCC::Contentful | |
| 162 170 | 
             
                    #  the sync again after a few minutes.
         | 
| 163 171 | 
             
                    #
         | 
| 164 172 | 
             
                    def sync!(up_to_id: nil, retry_count: 0)
         | 
| 165 | 
            -
                      id_found, count = sync_engine.next(up_to_id: up_to_id)
         | 
| 173 | 
            +
                      id_found, count = services.sync_engine.next(up_to_id: up_to_id)
         | 
| 166 174 |  | 
| 167 | 
            -
                      next_sync_token = sync_engine.state['token']
         | 
| 175 | 
            +
                      next_sync_token = services.sync_engine.state['token']
         | 
| 168 176 |  | 
| 169 177 | 
             
                      logger.info "Synced #{count} entries.  Next sync token:\n  #{next_sync_token}"
         | 
| 170 178 | 
             
                      unless id_found
         | 
    
        data/lib/wcc/contentful.rb
    CHANGED
    
    | @@ -46,9 +46,8 @@ module WCC::Contentful | |
| 46 46 | 
             
                end
         | 
| 47 47 |  | 
| 48 48 | 
             
                def logger
         | 
| 49 | 
            -
                   | 
| 50 | 
            -
             | 
| 51 | 
            -
                  @logger ||= Logger.new($stderr)
         | 
| 49 | 
            +
                  ActiveSupport::Deprecation.warn('Use WCC::Contentful::Services.instance.logger instead')
         | 
| 50 | 
            +
                  WCC::Contentful::Services.instance.logger
         | 
| 52 51 | 
             
                end
         | 
| 53 52 | 
             
              end
         | 
| 54 53 |  | 
| @@ -90,7 +89,7 @@ module WCC::Contentful | |
| 90 89 | 
             
                  rescue WCC::Contentful::SimpleClient::ApiError => e
         | 
| 91 90 | 
             
                    raise InitializationError, e if configuration.update_schema_file == :always
         | 
| 92 91 |  | 
| 93 | 
            -
                     | 
| 92 | 
            +
                    Services.instance.logger.warn("Unable to download schema from management API - #{e.message}")
         | 
| 94 93 | 
             
                  end
         | 
| 95 94 | 
             
                end
         | 
| 96 95 |  | 
| @@ -98,7 +97,7 @@ module WCC::Contentful | |
| 98 97 | 
             
                  begin
         | 
| 99 98 | 
             
                    JSON.parse(File.read(configuration.schema_file))['contentTypes'] if File.exist?(configuration.schema_file)
         | 
| 100 99 | 
             
                  rescue JSON::ParserError
         | 
| 101 | 
            -
                     | 
| 100 | 
            +
                    Services.instance.warn("Schema file invalid, ignoring it: #{configuration.schema_file}")
         | 
| 102 101 | 
             
                    nil
         | 
| 103 102 | 
             
                  end
         | 
| 104 103 |  | 
| @@ -112,7 +111,7 @@ module WCC::Contentful | |
| 112 111 | 
             
                    content_types = client.content_types(limit: 1000).items if client
         | 
| 113 112 | 
             
                  rescue WCC::Contentful::SimpleClient::ApiError => e
         | 
| 114 113 | 
             
                    # indicates bad credentials
         | 
| 115 | 
            -
                     | 
| 114 | 
            +
                    Services.instance.logger.warn("Unable to load content types from API - #{e.message}")
         | 
| 116 115 | 
             
                  end
         | 
| 117 116 | 
             
                end
         | 
| 118 117 |  | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: wcc-contentful
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Watermark Dev
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2022-08- | 
| 11 | 
            +
            date: 2022-08-19 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: byebug
         | 
| @@ -452,7 +452,9 @@ files: | |
| 452 452 | 
             
            - lib/wcc/contentful/rspec.rb
         | 
| 453 453 | 
             
            - lib/wcc/contentful/services.rb
         | 
| 454 454 | 
             
            - lib/wcc/contentful/simple_client.rb
         | 
| 455 | 
            +
            - lib/wcc/contentful/simple_client/cdn.rb
         | 
| 455 456 | 
             
            - lib/wcc/contentful/simple_client/management.rb
         | 
| 457 | 
            +
            - lib/wcc/contentful/simple_client/preview.rb
         | 
| 456 458 | 
             
            - lib/wcc/contentful/simple_client/response.rb
         | 
| 457 459 | 
             
            - lib/wcc/contentful/simple_client/typhoeus_adapter.rb
         | 
| 458 460 | 
             
            - lib/wcc/contentful/store.rb
         | 
| @@ -489,7 +491,7 @@ homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful | |
| 489 491 | 
             
            licenses:
         | 
| 490 492 | 
             
            - MIT
         | 
| 491 493 | 
             
            metadata:
         | 
| 492 | 
            -
              documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1. | 
| 494 | 
            +
              documentation_uri: https://watermarkchurch.github.io/wcc-contentful/1.3/wcc-contentful
         | 
| 493 495 | 
             
              rubygems_mfa_required: 'true'
         | 
| 494 496 | 
             
            post_install_message: 
         | 
| 495 497 | 
             
            rdoc_options: []
         |