diff --git a/.env.test b/.env.test index 4cd76d6f0a9..ef32c81a618 100644 --- a/.env.test +++ b/.env.test @@ -15,6 +15,10 @@ STRIPE_PUBLIC_TEST_API_KEY="bogus_stripe_publishable_key" SITE_URL="test.host" +# OIDC Settings for DFC authentication +# Find secrets in BitWarden. +# To get a refresh token: log into the OIDC provider, connect your OFN user to it at /admin/oidc_settings, then copy the token from the database: +# ./bin/rails runner 'puts "OPENID_REFRESH_TOKEN=\"#{OidcAccount.last.refresh_token}\""' OPENID_APP_ID="test-provider" OPENID_APP_SECRET="dummy-openid-app-secret-token" OPENID_REFRESH_TOKEN="dummy-refresh-token" diff --git a/app/jobs/open_order_cycle_job.rb b/app/jobs/open_order_cycle_job.rb new file mode 100644 index 00000000000..80c4e1d7b88 --- /dev/null +++ b/app/jobs/open_order_cycle_job.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# Run any pre-conditions and mark order cycle as open. +# +# Currently, an order cycle is considered open in the shopfront when orders_open_at >= now. +# But now there are some pre-conditions for opening an order cycle, so we would like to change that. +# Instead, the presence of opened_at (and absence of processed_at) should indicate it is open. +class OpenOrderCycleJob < ApplicationJob + sidekiq_options retry_for: 10.minutes + + def perform(order_cycle_id) + ActiveRecord::Base.transaction do + # Fetch order cycle if it's still unopened, and lock DB row until finished + order_cycle = OrderCycle.lock.find_by!(id: order_cycle_id, opened_at: nil) + + sync_remote_variants(order_cycle) + + # Mark as opened + opened_at = Time.zone.now + order_cycle.update_columns(opened_at:) + + # And notify any subscribers + OrderCycles::WebhookService.create_webhook_job(order_cycle, 'order_cycle.opened', opened_at) + end + end + + private + + def sync_remote_variants(order_cycle) + # Sync any remote variants for each supplier + order_cycle.suppliers.each do |supplier| + links = variant_links_for(order_cycle, supplier) + next if links.empty? + + # Find authorised user to access remote products + dfc_user = supplier.owner # we assume the owner's account is the one used to import from dfc. + + import_variants(links, dfc_user) + end + end + + # Fetch all remote variants for this supplier in the order cycle + def variant_links_for(order_cycle, supplier) + variants = order_cycle.exchanges.incoming.from_enterprise(supplier) + .joins(:exchange_variants).select('exchange_variants.variant_id') + SemanticLink.where(subject_id: variants) + end + + def import_variants(links, dfc_user) + # Find any catalogues associated with the variants + catalogs = links.group_by do |link| + FdcUrlBuilder.new(link.semantic_id).catalog_url + end + + # Import selected variants from each catalog + catalogs.each do |catalog_url, catalog_links| + catalog = DfcCatalog.load(dfc_user, catalog_url) + catalog.apply_wholesale_values! + + catalog_links.each do |link| + catalog_item = catalog.item(link.semantic_id) + + SuppliedProductImporter.update_product(catalog_item, link.subject) if catalog_item + end + end + end +end diff --git a/app/jobs/order_cycle_closing_job.rb b/app/jobs/order_cycle_closing_job.rb index 31ca6fb2a28..3a695e820ad 100644 --- a/app/jobs/order_cycle_closing_job.rb +++ b/app/jobs/order_cycle_closing_job.rb @@ -25,8 +25,7 @@ def send_notifications def mark_as_processed OrderCycle.where(id: recently_closed_order_cycles).update_all( - processed_at: Time.zone.now, - updated_at: Time.zone.now + processed_at: Time.zone.now ) end end diff --git a/app/jobs/order_cycle_opened_job.rb b/app/jobs/order_cycle_opened_job.rb deleted file mode 100644 index 3b4cee16dbe..00000000000 --- a/app/jobs/order_cycle_opened_job.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -# Trigger jobs for any order cycles that recently opened -class OrderCycleOpenedJob < ApplicationJob - def perform - ActiveRecord::Base.transaction do - recently_opened_order_cycles.find_each do |order_cycle| - OrderCycles::WebhookService.create_webhook_job(order_cycle, 'order_cycle.opened') - end - mark_as_opened(recently_opened_order_cycles) - end - end - - private - - def recently_opened_order_cycles - @recently_opened_order_cycles ||= OrderCycle - .where(opened_at: nil) - .where(orders_open_at: 1.hour.ago..Time.zone.now) - .lock.order(:id) - end - - def mark_as_opened(order_cycles) - now = Time.zone.now - order_cycles.update_all(opened_at: now, updated_at: now) - end -end diff --git a/app/jobs/trigger_order_cycles_to_open_job.rb b/app/jobs/trigger_order_cycles_to_open_job.rb new file mode 100644 index 00000000000..ca1d5c83931 --- /dev/null +++ b/app/jobs/trigger_order_cycles_to_open_job.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Trigger jobs for any order cycles that recently opened +class TriggerOrderCyclesToOpenJob < ApplicationJob + def perform + recently_opened_order_cycles.find_each do |order_cycle| + OpenOrderCycleJob.perform_later(order_cycle.id) + end + end + + private + + def recently_opened_order_cycles + OrderCycle + .where(opened_at: nil) + .where(orders_open_at: 1.hour.ago..Time.zone.now) + end +end diff --git a/app/jobs/webhook_delivery_job.rb b/app/jobs/webhook_delivery_job.rb index 1196e6a1d6d..6004b0d6f19 100644 --- a/app/jobs/webhook_delivery_job.rb +++ b/app/jobs/webhook_delivery_job.rb @@ -13,11 +13,11 @@ class FailedWebhookRequestError < StandardError; end queue_as :default - def perform(url, event, payload) + def perform(url, event, payload, at: Time.zone.now) body = { id: job_id, - at: Time.zone.now.to_s, event:, + at: at.to_s, data: payload, } diff --git a/app/services/order_cycles/webhook_service.rb b/app/services/order_cycles/webhook_service.rb index f12c94ea1bf..b45c5c6e411 100644 --- a/app/services/order_cycles/webhook_service.rb +++ b/app/services/order_cycles/webhook_service.rb @@ -5,9 +5,9 @@ module OrderCycles class WebhookService - def self.create_webhook_job(order_cycle, event) + def self.create_webhook_job(order_cycle, event, at) webhook_payload = order_cycle - .slice(:id, :name, :orders_open_at, :orders_close_at, :coordinator_id) + .slice(:id, :name, :orders_open_at, :opened_at, :orders_close_at, :coordinator_id) .merge(coordinator_name: order_cycle.coordinator.name) # Endpoints for coordinator owner @@ -17,7 +17,7 @@ def self.create_webhook_job(order_cycle, event) webhook_endpoints |= order_cycle.distributors.map(&:owner).flat_map(&:webhook_endpoints) webhook_endpoints.each do |endpoint| - WebhookDeliveryJob.perform_later(endpoint.url, event, webhook_payload) + WebhookDeliveryJob.perform_later(endpoint.url, event, webhook_payload, at:) end end end diff --git a/config/sidekiq.yml b/config/sidekiq.yml index eab2b061dbc..bcd4e31909c 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -15,7 +15,7 @@ every: "5m" SubscriptionConfirmJob: every: "5m" - OrderCycleOpenedJob: + TriggerOrderCyclesToOpenJob: every: "5m" OrderCycleClosingJob: every: "5m" diff --git a/spec/factories/oidc_account_factory.rb b/spec/factories/oidc_account_factory.rb index 4b0e12130f2..a636e3663bf 100644 --- a/spec/factories/oidc_account_factory.rb +++ b/spec/factories/oidc_account_factory.rb @@ -6,6 +6,7 @@ uid { user&.email || generate(:random_email) } # This is a live test account authenticated via Les Communes. + # See .env.test for tips on connecting the account for recording VCR cassettes. factory :testdfc_account do uid { "testdfc@protonmail.com" } refresh_token { ENV.fetch("OPENID_REFRESH_TOKEN") } diff --git a/spec/fixtures/vcr_cassettes/OpenOrderCycleJob/syncing_remote_products/synchronises_products_from_a_FDC_catalog.yml b/spec/fixtures/vcr_cassettes/OpenOrderCycleJob/syncing_remote_products/synchronises_products_from_a_FDC_catalog.yml new file mode 100644 index 00000000000..7fe3347c06c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/OpenOrderCycleJob/syncing_remote_products/synchronises_products_from_a_FDC_catalog.yml @@ -0,0 +1,198 @@ +--- +http_interactions: +- request: + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts + body: + encoding: US-ASCII + string: '' + headers: + Content-Type: + - application/json + Authorization: + - "" + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 403 + message: Forbidden + headers: + Server: + - openresty + Date: + - Wed, 26 Feb 2025 04:26:44 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '78' + Connection: + - keep-alive + X-Powered-By: + - Express + Access-Control-Allow-Origin: + - "*" + Etag: + - W/"4e-vJeBLxgahmv23yP9gdPJW/woako" + Strict-Transport-Security: + - max-age=15811200 + body: + encoding: UTF-8 + string: '{"message":"User access denied - token missing","error":"User not authorized"}' + recorded_at: Wed, 26 Feb 2025 04:26:41 GMT +- request: + method: get + uri: https://login.lescommuns.org/auth/realms/data-food-consortium/.well-known/openid-configuration + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - SWD 2.0.3 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 26 Feb 2025 04:26:45 GMT + Content-Type: + - application/json;charset=UTF-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - Accept-Encoding + Set-Cookie: + - INGRESSCOOKIE=1740544006.542.53801.455812|78230f584c0d7db97d376e98de5321dc; + Path=/; Secure; HttpOnly + Cache-Control: + - no-cache, must-revalidate, no-transform, no-store + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + body: + encoding: ASCII-8BIT + string: '{"issuer":"https://login.lescommuns.org/auth/realms/data-food-consortium","authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth","token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","end_session_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/certs","check_session_iframe":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token + token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","microprofile-jwt","phone","roles","profile","email","address","web-origins","acr","offline_access","ReadProduct"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token","revocation_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/revoke","introspection_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token/introspect","device_authorization_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/auth/device","registration_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/clients-registrations/openid-connect","userinfo_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}' + recorded_at: Wed, 26 Feb 2025 04:26:41 GMT +- request: + method: post + uri: https://login.lescommuns.org/auth/realms/data-food-consortium/protocol/openid-connect/token + body: + encoding: UTF-8 + string: grant_type=refresh_token&refresh_token= + headers: + User-Agent: + - Rack::OAuth2 (2.2.1) + Authorization: + - "" + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 26 Feb 2025 04:26:46 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Vary: + - Accept-Encoding + Set-Cookie: + - INGRESSCOOKIE=1740544007.438.54830.143060|78230f584c0d7db97d376e98de5321dc; + Path=/; Secure; HttpOnly + Cache-Control: + - no-store + Pragma: + - no-cache + Referrer-Policy: + - no-referrer + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + body: + encoding: ASCII-8BIT + string: '{"access_token":"","expires_in":1800,"refresh_expires_in":0,"refresh_token":"","token_type":"Bearer","id_token":"","not-before-policy":0,"session_state":"c1863fca-32d6-427a-a860-2d16734e6715","scope":"openid + profile email offline_access"}' + recorded_at: Wed, 26 Feb 2025 04:26:41 GMT +- request: + method: get + uri: https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts + body: + encoding: US-ASCII + string: '' + headers: + Content-Type: + - application/json + Authorization: + - "" + User-Agent: + - Faraday v2.9.0 + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - openresty + Date: + - Wed, 26 Feb 2025 04:26:48 GMT + Content-Type: + - text/html; charset=utf-8 + Content-Length: + - '32854' + Connection: + - keep-alive + X-Powered-By: + - Express + Access-Control-Allow-Origin: + - "*" + Etag: + - W/"8056-R5l3QaspJAaqIP/JgVAD/G9OI60" + Set-Cookie: + - SRVGROUP=common; path=/; HttpOnly + X-Resolver-Ip: + - 185.172.100.60 + Strict-Transport-Security: + - max-age=15811200 + body: + encoding: ASCII-8BIT + string: !binary |- + eyJAY29udGV4dCI6Imh0dHBzOi8vd3d3LmRhdGFmb29kY29uc29ydGl1bS5vcmciLCJAZ3JhcGgiOlt7IkBpZCI6Il86YjI2Mzk4NiIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAuMDQifSx7IkBpZCI6Il86YjI2Mzk4NyIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQb3VuZFN0ZXJsaW5nIiwiZGZjLWI6dmFsdWUiOiI2LjI1In0seyJAaWQiOiJfOmIyNjM5ODgiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjQifSx7IkBpZCI6Il86YjI2Mzk4OSIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQb3VuZFN0ZXJsaW5nIiwiZGZjLWI6dmFsdWUiOiIyLjA5In0seyJAaWQiOiJfOmIyNjM5OTAiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwLjMifSx7IkBpZCI6Il86YjI2Mzk5MSIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQb3VuZFN0ZXJsaW5nIiwiZGZjLWI6dmFsdWUiOiIyLjQ5In0seyJAaWQiOiJfOmIyNjM5OTIiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiIwIn0seyJAaWQiOiJfOmIyNjM5OTMiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06UG91bmRTdGVybGluZyIsImRmYy1iOnZhbHVlIjoiNS45NSJ9LHsiQGlkIjoiXzpiMjYzOTk0IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMC4yNCJ9LHsiQGlkIjoiXzpiMjYzOTk1IiwiQHR5cGUiOiJkZmMtYjpQcmljZSIsImRmYy1iOlZBVHJhdGUiOiIwIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOlBvdW5kU3RlcmxpbmciLCJkZmMtYjp2YWx1ZSI6IjMwLjIwIn0seyJAaWQiOiJfOmIyNjM5OTYiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOktpbG9ncmFtIiwiZGZjLWI6dmFsdWUiOiI0LjgifSx7IkBpZCI6Il86YjI2Mzk5NyIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQb3VuZFN0ZXJsaW5nIiwiZGZjLWI6dmFsdWUiOiIxOC44NSJ9LHsiQGlkIjoiXzpiMjYzOTk4IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpLaWxvZ3JhbSIsImRmYy1iOnZhbHVlIjoiMi40In0seyJAaWQiOiJfOmIyNjM5OTkiLCJAdHlwZSI6ImRmYy1iOlByaWNlIiwiZGZjLWI6VkFUcmF0ZSI6IjAiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06UG91bmRTdGVybGluZyIsImRmYy1iOnZhbHVlIjoiMTQuOTUifSx7IkBpZCI6Il86YjI2NDAwMCIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06S2lsb2dyYW0iLCJkZmMtYjp2YWx1ZSI6IjAifSx7IkBpZCI6Il86YjI2NDAwMSIsIkB0eXBlIjoiZGZjLWI6UHJpY2UiLCJkZmMtYjpWQVRyYXRlIjoiMCIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQb3VuZFN0ZXJsaW5nIiwiZGZjLWI6dmFsdWUiOiI0NS4wMCJ9LHsiQGlkIjoiXzpiMjY0MDAyIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiNiJ9LHsiQGlkIjoiXzpiMjY0MDAzIiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMSJ9LHsiQGlkIjoiXzpiMjY0MDA0IiwiQHR5cGUiOiJkZmMtYjpRdWFudGl0YXRpdmVWYWx1ZSIsImRmYy1iOmhhc1VuaXQiOiJkZmMtbTpQaWVjZSIsImRmYy1iOnZhbHVlIjoiMTIifSx7IkBpZCI6Il86YjI2NDAwNSIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06UGllY2UiLCJkZmMtYjp2YWx1ZSI6IjEifSx7IkBpZCI6Il86YjI2NDAwNiIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06UGllY2UiLCJkZmMtYjp2YWx1ZSI6IjgifSx7IkBpZCI6Il86YjI2NDAwNyIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06UGllY2UiLCJkZmMtYjp2YWx1ZSI6IjEifSx7IkBpZCI6Il86YjI2NDAwOCIsIkB0eXBlIjoiZGZjLWI6UXVhbnRpdGF0aXZlVmFsdWUiLCJkZmMtYjpoYXNVbml0IjoiZGZjLW06UGllY2UiLCJkZmMtYjp2YWx1ZSI6IjEwIn0seyJAaWQiOiJfOmIyNjQwMDkiLCJAdHlwZSI6ImRmYy1iOlF1YW50aXRhdGl2ZVZhbHVlIiwiZGZjLWI6aGFzVW5pdCI6ImRmYy1tOlBpZWNlIiwiZGZjLWI6dmFsdWUiOiIxIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkiLCJAdHlwZSI6ImRmYy1iOlN1cHBsaWVkUHJvZHVjdCIsImRmYy1iOmRlc2NyaXB0aW9uIjoiPHRhYmxlIHdpZHRoPVwiMTAwJVwiPlxuPHRib2R5PlxuPHRyIHN0eWxlPVwiYm9yZGVyOiAwcHg7XCIgZGF0YS1tY2Utc3R5bGU9XCJib3JkZXI6IDBweDtcIj5cbjx0ZCBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgZGF0YS1tY2Utc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIj48Yj5UaGlzIHJpY2gsIGludGVuc2UgYW5kIGRlZXBseSBmbGF2b3VyZWQgNi15ZWFyIG9sZCBhcHBsZSBiYWxzYW1pYyB2aW5lZ2FyIGlzIG1hZGUgdXNpbmcgdGhlIHRyYWRpdGlvbmFsIEl0YWxpYW4gbWV0aG9kIG9mIHJlZHVjdGlvbiBhbmQgY29uY2VudHJhdGlvbiBvZiB0aGUganVpY2Ugb3ZlciBhIGxlbmd0aHkgcGVyaW9kIG9mIHRpbWUsIHJhdGhlciB0aGFuIGJ5IGFkZGluZyBmbGF2b3VyaW5nIGFuZCBjb2xvdXJpbmcuwqA8L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgYmFsc2FtaWMgdmluZWdhciBpcyBhZ2VkIGZvciA2IHllYXJzIGluIGJhcnJlbHMuwqBUaGUgb25seSBpbmdyZWRpZW50IGlzIGFwcGxlcy48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGxpa2UgSXRhbGlhbiBiYWxzYW1pYyB2aW5lZ2FyLsKgPHNwYW4gZGF0YS1tY2UtZnJhZ21lbnQ9XCIxXCI+QXMgd2VsbCBhcyB1c2luZyBvbiBzYWxhZHMsIGl04oCZcyBhIGdyZWF0IHBhcnRuZXIgZm9yIGdyaWxsZWQgbWVhdHMgb3IgY2hhcmN1dGVyaWU7IGEgZHJvcCBicmluZ3Mgb3V0IHRoZSB0YXN0ZSBvZiBzdHJhd2JlcnJpZXMgYW5kIG90aGVyIHNvZnQgZnJ1aXRzOyBhbmQgaXQgY2FuIHJlYWxseSBlbmhhbmNlIGEgc3Rldywgc2F1Y2Ugb3IgYSBzb3VwLsKgPC9zcGFuPjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLjxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLjwvcD48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPk1vcmU8L2g1PlxuPHA+UHJvZHVjdCBvZsKgRG9yc2V0PGJyPlN1aXRhYmxlIGZvciB2ZWdhbnMgYW5kIHZlZ2V0YXJpYW5zPGJyPjwvcD4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjI2Mzk4NiIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLUJhbHNhbWljLVZpbmVnYXItNDBtbF83OTYxN2VlYS1hYjhjLTQwNzAtOWU0ZC03MTFiZjAzMGFkMDcuanBnP3Y9MTY3Nzc2MDc3MiIsImRmYy1iOm5hbWUiOiJBcHBsZSBCYWxzYW1pYyBWaW5lZ2FyIC0gUmV0YWlsIGJvdHRsZSwgNDBtbCIsImRmYy1iOnJlZmVyZW5jZWRCeSI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9DYXRhbG9nSXRlbSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkQ29uc3VtcHRpb25GbG93IiwiZGZjLWI6Y29uc3VtZXMiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkiLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjI2NDAwMiJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0FzUGxhbm5lZFByb2R1Y3Rpb25GbG93IiwiQHR5cGUiOiJkZmMtYjpBc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMjY0MDAzIiwiZGZjLWI6cHJvZHVjZXMiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRUcmFuc2Zvcm1hdGlvbiIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJkZmMtYjpoYXNJbmNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyMzgyNTkvQXNQbGFubmVkQ29uc3VtcHRpb25GbG93IiwiZGZjLWI6aGFzT3V0Y29tZSI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L0NhdGFsb2dJdGVtIiwiQHR5cGUiOiJkZmMtYjpDYXRhbG9nSXRlbSIsImRmYy1iOm9mZmVyZWRUaHJvdWdoIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2MjM4MjU5L09mZmVyIiwiZGZjLWI6c2t1IjoiTElCL05BQlZJL0JGIiwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uIjoiLTEifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjIzODI1OS9PZmZlciIsIkB0eXBlIjoiZGZjLWI6T2ZmZXIiLCJkZmMtYjpoYXNQcmljZSI6eyJAaWQiOiJfOmIyNjM5ODcifX0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjciLCJAdHlwZSI6ImRmYy1iOlN1cHBsaWVkUHJvZHVjdCIsImRmYy1iOmRlc2NyaXB0aW9uIjoiPHRhYmxlIHdpZHRoPVwiMTAwJVwiPlxuPHRib2R5PlxuPHRyIHN0eWxlPVwiYm9yZGVyOiAwcHg7XCIgZGF0YS1tY2Utc3R5bGU9XCJib3JkZXI6IDBweDtcIj5cbjx0ZCBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCIgZGF0YS1tY2Utc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIj48Yj5UaGlzIHJpY2gsIGludGVuc2UgYW5kIGRlZXBseSBmbGF2b3VyZWQgNi15ZWFyIG9sZCBhcHBsZSBiYWxzYW1pYyB2aW5lZ2FyIGlzIG1hZGUgdXNpbmcgdGhlIHRyYWRpdGlvbmFsIEl0YWxpYW4gbWV0aG9kIG9mIHJlZHVjdGlvbiBhbmQgY29uY2VudHJhdGlvbiBvZiB0aGUganVpY2Ugb3ZlciBhIGxlbmd0aHkgcGVyaW9kIG9mIHRpbWUsIHJhdGhlciB0aGFuIGJ5IGFkZGluZyBmbGF2b3VyaW5nIGFuZCBjb2xvdXJpbmcuwqA8L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkxpYmVydHkgRmllbGRzIHByb2R1Y2Ugc21hbGwgYmF0Y2hlcyBvZiBzdXBlcmIgc3lydXAsIGJhbHNhbWljIHZpbmVnYXIsIGNpZGVyIGFuZCB2b2RrYSBieSBoYW5kIGZyb20gdGhlIGZydWl0IG9mIHRoZWlyIG93biBEb3JzZXQgYXBwbGUgb3JjaGFyZHMsIHBsYW50ZWQgZnJvbSAyMDEwLjxicj48L3A+XG48cD5UaGUgYmFsc2FtaWMgdmluZWdhciBpcyBhZ2VkIGZvciA2IHllYXJzIGluIGJhcnJlbHMuwqBUaGUgb25seSBpbmdyZWRpZW50IGlzIGFwcGxlcy48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+VXNlIGxpa2UgSXRhbGlhbiBiYWxzYW1pYyB2aW5lZ2FyLsKgPHNwYW4gZGF0YS1tY2UtZnJhZ21lbnQ9XCIxXCI+QXMgd2VsbCBhcyB1c2luZyBvbiBzYWxhZHMsIGl04oCZcyBhIGdyZWF0IHBhcnRuZXIgZm9yIGdyaWxsZWQgbWVhdHMgb3IgY2hhcmN1dGVyaWU7IGEgZHJvcCBicmluZ3Mgb3V0IHRoZSB0YXN0ZSBvZiBzdHJhd2JlcnJpZXMgYW5kIG90aGVyIHNvZnQgZnJ1aXRzOyBhbmQgaXQgY2FuIHJlYWxseSBlbmhhbmNlIGEgc3Rldywgc2F1Y2Ugb3IgYSBzb3VwLsKgPC9zcGFuPjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gc3RvcmU8YnI+XG48L2g1PlxuPHA+Rm9yIGJlc3QgYmVmb3JlIGRhdGUgc2VlIHBhY2suIFN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLjxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkFwcGxlczxicj48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+Tm8gYWxsZXJnZW5zLjwvcD48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPk1vcmU8L2g1PlxuPHA+UHJvZHVjdCBvZsKgRG9yc2V0PGJyPlN1aXRhYmxlIGZvciB2ZWdhbnMgYW5kIHZlZ2V0YXJpYW5zPGJyPjwvcD4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjI2Mzk5NCIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0xpYmVydHktRmllbGRzLUFwcGxlLUJhbHNhbWljLVZpbmVnYXItNDBtbF83OTYxN2VlYS1hYjhjLTQwNzAtOWU0ZC03MTFiZjAzMGFkMDcuanBnP3Y9MTY3Nzc2MDc3MiIsImRmYy1iOm5hbWUiOiJBcHBsZSBCYWxzYW1pYyBWaW5lZ2FyIC0gQ2FzZSwgNiB4IDQwbWwiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjYyNzEwMjcvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjI3MTAyNy9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjI3MTAyNy9PZmZlciIsImRmYy1iOnNrdSI6IkxJQi9OQUJWSS9DNiIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6IjEifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjI3MTAyNy9PZmZlciIsIkB0eXBlIjoiZGZjLWI6T2ZmZXIiLCJkZmMtYjpoYXNQcmljZSI6eyJAaWQiOiJfOmIyNjM5OTUifX0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0Njc2MzUiLCJAdHlwZSI6ImRmYy1iOlN1cHBsaWVkUHJvZHVjdCIsImRmYy1iOmRlc2NyaXB0aW9uIjoiPHRhYmxlIHdpZHRoPVwiMTAwJVwiPlxuPHRib2R5PlxuPHRyIHN0eWxlPVwiYm9yZGVyOiAwcHg7XCI+XG48dGQgYmdjb2xvcj1cIiNkNmZiZWRcIiBzdHlsZT1cImNvbG9yOiAjMDAwMDAwOyBib3JkZXI6IDBweDtcIj48c3Ryb25nPlRoZXkncmUgYmFjayE8L3N0cm9uZz48L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPlxuPHA+PHN0cm9uZz5UaGluayBiYWtlZCBiZWFucyBhcmUgQnJpdGlzaD8gVGhleSBhcmUgbm93ISBXZSB1c2Ugb25seSBCcml0aXNoLWdyb3duIGZhdmEgYmVhbnMgLSBCcml0YWluJ3Mgb3JpZ2luYWwgYmVhbiwgZ3Jvd24gaGVyZSBzaW5jZSB0aGUgSXJvbiBBZ2UuIE91ciBCYWtlZCBCcml0aXNoIEJlYW5zIGFyZSBkZWxpY2lvdXNseSBkaWZmZXJlbnQsIHdpdGggbGFyZ2UgbWVhdHkgZmF2YSBiZWFucyBpbiBhIHRhc3R5IHRvbWF0byBzYXVjZS48L3N0cm9uZz48L3A+XG48cD48c3Ryb25nPjxhIHRpdGxlPVwiV2hhdCBhcmUgZmF2YSBiZWFucz8gQXJlbid0IHRoZXkganVzdCBicm9hZCBiZWFucz9cIiBocmVmPVwiL2Jsb2dzL25ld3Mvd2hhdC1hcmUtZmF2YS1iZWFucy1hcmUtdGhleS1qdXN0LWJyb2FkLWJlYW5zXCIgZGF0YS1tY2UtZnJhZ21lbnQ9XCIxXCIgZGF0YS1tY2UtaHJlZj1cIi9ibG9ncy9uZXdzL3doYXQtYXJlLWZhdmEtYmVhbnMtYXJlLXRoZXktanVzdC1icm9hZC1iZWFuc1wiPldoYXQgYXJlIGZhdmEgYmVhbnM/IEZpbmQgb3V0IGhlcmUuLi48L2E+PC9zdHJvbmc+PC9wPlxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48cD5PdXIgQmFrZWQgQnJpdGlzaCBCZWFucyBhcmUgY29va2VkIGFuZCByZWFkeSB0byBlYXQsIGhvdCBvciBjb2xkLiBUaGV5J3JlIGdvb2Qgc2VydmVkIG9uIHRvYXN0IGJ1dCBhbHNvIGRlbGljaW91cyBhZGRlZCB0byBzdGV3cywgY3VycmllcyBvciBjYXNzZXJvbGVzLiBPciBldmVuIGluIGEgcGllLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+Q29va2luZyBpbnN0cnVjdGlvbnM8L2g1PlxuPHA+PHN0cm9uZz5Db29raW5nIG9uIHRoZSBIb2I8L3N0cm9uZz48YnI+RW1wdHkgY29udGVudHMgaW50byBzYXVjZXBhbi4gSGVhdCBnZW50bHkgZm9yIDQtNSBtaW51dGVzIHdoaWxlIHN0aXJyaW5nLiBGb3IgYmVzdCBmbGF2b3VyIGRvIG5vdCBib2lsIG9yIG92ZXJjb29rLiBEbyBub3QgcmVoZWF0LjwvcD5cbjxwPjxzdHJvbmc+TWljcm93YXZlIENvb2tpbmc8L3N0cm9uZz48YnI+RW1wdHkgY29udGVudHMgaW50byBhIG5vbi1tZXRhbGxpYyBib3dsIGFuZCBjb3Zlci4gSGVhdCBmb3IgMiB0byAzIG1pbnV0ZXMsIHN0aXJyaW5nIGhhbGZ3YXkuIENoZWNrIHRoZSBmb29kIGlzIGhvdCwgc3RpciB3ZWxsIGFuZCBzZXJ2ZS4gRG8gbm90IHJlaGVhdC48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPlRvIFN0b3JlPC9oNT5cbjxwPlN0b3JlIGluIGEgY29vbCwgZHJ5IHBsYWNlLiBPbmNlIG9wZW5lZCwgdHJhbnNmZXIgY29udGVudHMgdG8gYSBub24tbWV0YWxsaWMgY29udGFpbmVyLCBjb3ZlciByZWZyaWdlcmF0ZSBhbmQgdXNlIHdpdGggMiBkYXlzLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+SW5ncmVkaWVudHM8L2g1PlxuPHA+RmF2YSBCZWFucyAoQnJvYWQgQmVhbnMpICg0MiUpLCBXYXRlciwgVG9tYXRvIFB1cmVlLCBTdWdhciwgTW9kaWZpZWQgTWFpemUgU3RhcmNoLCBTYWx0LCBIZXJicyAmYW1wOyBTcGljZXMsIENvbmNlbnRyYXRlZCBMZW1vbiBKdWljZTwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XG48cD5ObyBBbGxlcmdlbnM8L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+Mjkya0ogKDY5a2NhbCk8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5GYXQ8L3RkPlxuPHRkPjAuNGc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzYXR1cmF0ZXM8L3RkPlxuPHRkPjAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxuPHRkPjEwLjFnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cbjx0ZD40LjZnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmlicmU8L3RkPlxuPHRkPjVnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+NGc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD4wLjZnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPk1vcmU8L2g1PlxuPHA+RGVsaWNpb3VzLCBudXRyaXRpb3VzIGFuZCBnb29kIGZvciB0aGUgc29pbCwgZmF2YSBiZWFucyBhcmUgYSB2YXJpZXR5IG9mIGJyb2FkIGJlYW4sIFZpY2lhIGZhYmEsIGxlZnQgdG8gcmlwZW4gYW5kIGRyeSBiZWZvcmUgaGFydmVzdC4gVGhleeKAmXJlIGFsc28ga25vd24gYXMgZmllbGQgYmVhbnMsIGhvcnNlIGJlYW5zLCBXaW5kc29yIGJlYW5zIG9yIGZ1bC48L3A+XG48cD5TdWl0YWJsZSBmb3IgdmVnYW5zIGFuZCB2ZWdldGFyaWFuczwvcD5cbiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMjYzOTg4IiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvUGFjay1DYW4tQmFrZWQtQmVhbnMtMTgwMHg2Xzk4M3g2NTZfNTEzNzU4ZTYtMjYxNi00Njg3LWE4YjItYmE2ZGRlODY0OTIzLmpwZz92PTE2Nzc3NjA3NzgiLCJkZmMtYjpuYW1lIjoiQmFrZWQgQnJpdGlzaCBCZWFucyAtIFJldGFpbCBjYW4sIDQwMGcgKGNhbikiLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0Njc2MzUvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQ2NzYzNS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmNvbnN1bWVzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDY3NjM1IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIyNjQwMDQifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQ2NzYzNS9Bc1BsYW5uZWRQcm9kdWN0aW9uRmxvdyIsIkB0eXBlIjoiZGZjLWI6QXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjI2NDAwNSIsImRmYy1iOnByb2R1Y2VzIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NTAwNDAzIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0Njc2MzUvQXNQbGFubmVkVHJhbnNmb3JtYXRpb24iLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiZGZjLWI6aGFzSW5jb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDY3NjM1L0FzUGxhbm5lZENvbnN1bXB0aW9uRmxvdyIsImRmYy1iOmhhc091dGNvbWUiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY0Njc2MzUvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3cifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQ2NzYzNS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjQ2NzYzNS9PZmZlciIsImRmYy1iOnNrdSI6Ik5DQkIvVDQiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiItMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NDY3NjM1L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjI2Mzk4OSJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjUwMDQwMyIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIj5cbjx0ZCBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4O1wiPjxzdHJvbmc+VGhleSdyZSBiYWNrITwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD48c3Ryb25nPlRoaW5rIGJha2VkIGJlYW5zIGFyZSBCcml0aXNoPyBUaGV5IGFyZSBub3chIFdlIHVzZSBvbmx5IEJyaXRpc2gtZ3Jvd24gZmF2YSBiZWFucyAtIEJyaXRhaW4ncyBvcmlnaW5hbCBiZWFuLCBncm93biBoZXJlIHNpbmNlIHRoZSBJcm9uIEFnZS4gT3VyIEJha2VkIEJyaXRpc2ggQmVhbnMgYXJlIGRlbGljaW91c2x5IGRpZmZlcmVudCwgd2l0aCBsYXJnZSBtZWF0eSBmYXZhIGJlYW5zIGluIGEgdGFzdHkgdG9tYXRvIHNhdWNlLjwvc3Ryb25nPjwvcD5cbjxwPjxzdHJvbmc+PGEgdGl0bGU9XCJXaGF0IGFyZSBmYXZhIGJlYW5zPyBBcmVuJ3QgdGhleSBqdXN0IGJyb2FkIGJlYW5zP1wiIGhyZWY9XCIvYmxvZ3MvbmV3cy93aGF0LWFyZS1mYXZhLWJlYW5zLWFyZS10aGV5LWp1c3QtYnJvYWQtYmVhbnNcIiBkYXRhLW1jZS1mcmFnbWVudD1cIjFcIiBkYXRhLW1jZS1ocmVmPVwiL2Jsb2dzL25ld3Mvd2hhdC1hcmUtZmF2YS1iZWFucy1hcmUtdGhleS1qdXN0LWJyb2FkLWJlYW5zXCI+V2hhdCBhcmUgZmF2YSBiZWFucz8gRmluZCBvdXQgaGVyZS4uLjwvYT48L3N0cm9uZz48L3A+XG48IS0tIHNwbGl0IC0tPjxoMz5Db21wbGV0ZSBQcm9kdWN0IERldGFpbHM8L2gzPjxwPk91ciBCYWtlZCBCcml0aXNoIEJlYW5zIGFyZSBjb29rZWQgYW5kIHJlYWR5IHRvIGVhdCwgaG90IG9yIGNvbGQuIFRoZXkncmUgZ29vZCBzZXJ2ZWQgb24gdG9hc3QgYnV0IGFsc28gZGVsaWNpb3VzIGFkZGVkIHRvIHN0ZXdzLCBjdXJyaWVzIG9yIGNhc3Nlcm9sZXMuIE9yIGV2ZW4gaW4gYSBwaWUuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XG48cD48c3Ryb25nPkNvb2tpbmcgb24gdGhlIEhvYjwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIHNhdWNlcGFuLiBIZWF0IGdlbnRseSBmb3IgNC01IG1pbnV0ZXMgd2hpbGUgc3RpcnJpbmcuIEZvciBiZXN0IGZsYXZvdXIgZG8gbm90IGJvaWwgb3Igb3ZlcmNvb2suIERvIG5vdCByZWhlYXQuPC9wPlxuPHA+PHN0cm9uZz5NaWNyb3dhdmUgQ29va2luZzwvc3Ryb25nPjxicj5FbXB0eSBjb250ZW50cyBpbnRvIGEgbm9uLW1ldGFsbGljIGJvd2wgYW5kIGNvdmVyLiBIZWF0IGZvciAyIHRvIDMgbWludXRlcywgc3RpcnJpbmcgaGFsZndheS4gQ2hlY2sgdGhlIGZvb2QgaXMgaG90LCBzdGlyIHdlbGwgYW5kIHNlcnZlLiBEbyBub3QgcmVoZWF0LjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+VG8gU3RvcmU8L2g1PlxuPHA+U3RvcmUgaW4gYSBjb29sLCBkcnkgcGxhY2UuIE9uY2Ugb3BlbmVkLCB0cmFuc2ZlciBjb250ZW50cyB0byBhIG5vbi1tZXRhbGxpYyBjb250YWluZXIsIGNvdmVyIHJlZnJpZ2VyYXRlIGFuZCB1c2Ugd2l0aCAyIGRheXMuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD5GYXZhIEJlYW5zIChCcm9hZCBCZWFucykgKDQyJSksIFdhdGVyLCBUb21hdG8gUHVyZWUsIFN1Z2FyLCBNb2RpZmllZCBNYWl6ZSBTdGFyY2gsIFNhbHQsIEhlcmJzICZhbXA7IFNwaWNlcywgQ29uY2VudHJhdGVkIExlbW9uIEp1aWNlPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIEFsbGVyZ2VuczwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4yOTJrSiAoNjlrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+MC40ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MC4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+MTAuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnM8L3RkPlxuPHRkPjQuNmc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5GaWJyZTwvdGQ+XG48dGQ+NWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5Qcm90ZWluPC90ZD5cbjx0ZD40ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjAuNmc8L3RkPlxuPC90cj5cbjwvdGJvZHk+XG48L3RhYmxlPjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+TW9yZTwvaDU+XG48cD5EZWxpY2lvdXMsIG51dHJpdGlvdXMgYW5kIGdvb2QgZm9yIHRoZSBzb2lsLCBmYXZhIGJlYW5zIGFyZSBhIHZhcmlldHkgb2YgYnJvYWQgYmVhbiwgVmljaWEgZmFiYSwgbGVmdCB0byByaXBlbiBhbmQgZHJ5IGJlZm9yZSBoYXJ2ZXN0LiBUaGV54oCZcmUgYWxzbyBrbm93biBhcyBmaWVsZCBiZWFucywgaG9yc2UgYmVhbnMsIFdpbmRzb3IgYmVhbnMgb3IgZnVsLjwvcD5cbjxwPlN1aXRhYmxlIGZvciB2ZWdhbnMgYW5kIHZlZ2V0YXJpYW5zPC9wPlxuIiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIyNjM5OTYiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9QYWNrLUNhbi1CYWtlZC1CZWFucy0xODAweDZfOTgzeDY1Nl81MTM3NThlNi0yNjE2LTQ2ODctYThiMi1iYTZkZGU4NjQ5MjMuanBnP3Y9MTY3Nzc2MDc3OCIsImRmYy1iOm5hbWUiOiJCYWtlZCBCcml0aXNoIEJlYW5zIC0gQ2FzZSwgMTIgeCA0MDBnIChjYW4pIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY2NTAwNDAzL0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY1MDA0MDMvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjY1MDA0MDMvT2ZmZXIiLCJkZmMtYjpza3UiOiJOQ0JCL0NEIiwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uIjoiLTEifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NjUwMDQwMy9PZmZlciIsIkB0eXBlIjoiZGZjLWI6T2ZmZXIiLCJkZmMtYjpoYXNQcmljZSI6eyJAaWQiOiJfOmIyNjM5OTcifX0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjcxODg1MzEiLCJAdHlwZSI6ImRmYy1iOlN1cHBsaWVkUHJvZHVjdCIsImRmYy1iOmRlc2NyaXB0aW9uIjoiPHA+PHN0cm9uZz5DYW1lbGluYSwgYWxzbyBrbm93biBhcyBHb2xkIG9mIFBsZWFzdXJlLCBoYXMgYmVlbiBncm93biBpbiBFbmdsYW5kIGZvciB0aG91c2FuZHMgb2YgeWVhcnMgZm9yIGl0cyB0YXN0eSBzZWVkcyBhbmQgb2lsLiBTcHJpbmtsZSBvbiBzYWxhZHMsIHVzZSBpbiBiYWtpbmcsIGFkZCB0byBzbW9vdGhpZXMsIG9yIHVzZSBhcyBhIHZlZ2FuIGVnZyByZXBsYWNlbWVudC4gPC9zdHJvbmc+PC9wPlxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48cD5TcHJpbmtsZSBvbiBzYWxhZHMsIGFkZCB0byBzbW9vdGhpZXMsIHVzZSBpbiBiYWtpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XG48cD5Tb2FrIDEgdGFibGVzcG9vbiBvZiBzZWVkcyBpbiAzIHRhYmxlc3Bvb25zIG9mIHdhcm0gd2F0ZXIgZm9yIDMwIG1pbnV0ZXMgdG8gcmVwbGFjZSBvbmUgZWdnIGluIHZlZ2FuIGJha2luZy48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkNhbWVsaW5hIHNlZWRzPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIEFsbGVyZ2VuczwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xNDM5a0ogKDM0NmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4xMi4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MS43ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+MTYuNGc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnM8L3RkPlxuPHRkPjEuMmc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5GaWJyZTwvdGQ+XG48dGQ+MzUuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5Qcm90ZWluPC90ZD5cbjx0ZD4yNS40ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT48cD5DYW1lbGluYSBTZWVkcyBhcmUgaGlnaCBpbiBwcm90ZWluLCBhIGdvb2Qgc291cmNlIG9mIE9tZWdhIDMgb2lscyBhbmQgcmljaCBpbiBhbnRpb3hpZGFudHMgc3VjaCBhcyB2aXRhbWluIEU8L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPkdyb3duIGJ5IFBldGVyIEZhaXJzIGluIEVzc2V4IGFuZCBBbmR5IEhvd2FyZCBpbiBLZW50LjwvcD4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjI2Mzk5MCIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L2ZpbGVzLzM3LWNhbW1hbGluYS1mcm9uLmpwZz92PTE3MDY4ODE5NTAiLCJkZmMtYjpuYW1lIjoiQ2FtZWxpbmEgU2VlZCAtIFJldGFpbCBwYWNrLCAzMDBnIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY3MTg4NTMxL0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjcxODg1MzEvQXNQbGFubmVkQ29uc3VtcHRpb25GbG93IiwiQHR5cGUiOiJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJkZmMtYjpjb25zdW1lcyI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NzE4ODUzMSIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMjY0MDA2In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjcxODg1MzEvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIyNjQwMDciLCJkZmMtYjpwcm9kdWNlcyI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NzIyMTI5OSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY3MTg4NTMxL0FzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiQHR5cGUiOiJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvbiIsImRmYy1iOmhhc0luY29tZSI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NzE4ODUzMS9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJkZmMtYjpoYXNPdXRjb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY3MTg4NTMxL0FzUGxhbm5lZFByb2R1Y3Rpb25GbG93In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjcxODg1MzEvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjcxODg1MzEvT2ZmZXIiLCJkZmMtYjpza3UiOiJOR0NTL1IzIiwiZGZjLWI6c3RvY2tMaW1pdGF0aW9uIjoiLTEifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NzE4ODUzMS9PZmZlciIsIkB0eXBlIjoiZGZjLWI6T2ZmZXIiLCJkZmMtYjpoYXNQcmljZSI6eyJAaWQiOiJfOmIyNjM5OTEifX0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjcyMjEyOTkiLCJAdHlwZSI6ImRmYy1iOlN1cHBsaWVkUHJvZHVjdCIsImRmYy1iOmRlc2NyaXB0aW9uIjoiPHA+PHN0cm9uZz5DYW1lbGluYSwgYWxzbyBrbm93biBhcyBHb2xkIG9mIFBsZWFzdXJlLCBoYXMgYmVlbiBncm93biBpbiBFbmdsYW5kIGZvciB0aG91c2FuZHMgb2YgeWVhcnMgZm9yIGl0cyB0YXN0eSBzZWVkcyBhbmQgb2lsLiBTcHJpbmtsZSBvbiBzYWxhZHMsIHVzZSBpbiBiYWtpbmcsIGFkZCB0byBzbW9vdGhpZXMsIG9yIHVzZSBhcyBhIHZlZ2FuIGVnZyByZXBsYWNlbWVudC4gPC9zdHJvbmc+PC9wPlxuPCEtLSBzcGxpdCAtLT48aDM+Q29tcGxldGUgUHJvZHVjdCBEZXRhaWxzPC9oMz48cD5TcHJpbmtsZSBvbiBzYWxhZHMsIGFkZCB0byBzbW9vdGhpZXMsIHVzZSBpbiBiYWtpbmcuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Db29raW5nIGluc3RydWN0aW9uczwvaDU+XG48cD5Tb2FrIDEgdGFibGVzcG9vbiBvZiBzZWVkcyBpbiAzIHRhYmxlc3Bvb25zIG9mIHdhcm0gd2F0ZXIgZm9yIDMwIG1pbnV0ZXMgdG8gcmVwbGFjZSBvbmUgZWdnIGluIHZlZ2FuIGJha2luZy48L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkluZ3JlZGllbnRzPC9oNT5cbjxwPkNhbWVsaW5hIHNlZWRzPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5BbGxlcmd5IGluZm9ybWF0aW9uPC9oNT5cbjxwPk5vIEFsbGVyZ2VuczwvcD5cbjx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0cj5cbjx0ZD48c3Ryb25nPlR5cGljYWwgdmFsdWVzPC9zdHJvbmc+PC90ZD5cbjx0ZD48c3Ryb25nPlBlciAxMDBnPC9zdHJvbmc+PC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RW5lcmd5PC90ZD5cbjx0ZD4xNDM5a0ogKDM0NmtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD4xMi4xZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHNhdHVyYXRlczwvdGQ+XG48dGQ+MS43ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkNhcmJvaHlkcmF0ZTwvdGQ+XG48dGQ+MTYuNGc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzdWdhcnM8L3RkPlxuPHRkPjEuMmc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5GaWJyZTwvdGQ+XG48dGQ+MzUuMWc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5Qcm90ZWluPC90ZD5cbjx0ZD4yNS40ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlNhbHQ8L3RkPlxuPHRkPjBnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT48cD5DYW1lbGluYSBTZWVkcyBhcmUgaGlnaCBpbiBwcm90ZWluLCBhIGdvb2Qgc291cmNlIG9mIE9tZWdhIDMgb2lscyBhbmQgcmljaCBpbiBhbnRpb3hpZGFudHMgc3VjaCBhcyB2aXRhbWluIEU8L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPkdyb3duIGJ5IFBldGVyIEZhaXJzIGluIEVzc2V4IGFuZCBBbmR5IEhvd2FyZCBpbiBLZW50LjwvcD4iLCJkZmMtYjpoYXNRdWFudGl0eSI6Il86YjI2Mzk5OCIsImRmYy1iOmltYWdlIjoiaHR0cHM6Ly9jZG4uc2hvcGlmeS5jb20vcy9maWxlcy8xLzA3MzEvODQ4My83OTM5L3Byb2R1Y3RzL0NhbWVpbG5hLVNlZWRzLTE4MDB4MTIwMF84YzAwYTEwOC1kOGY3LTQ5MjAtOWJhYy03NThhMmM2YThiNTYuanBnP3Y9MTY3Nzc2MDc5NyIsImRmYy1iOm5hbWUiOiJDYW1lbGluYSBTZWVkIC0gQ2FzZSwgOCB4IDMwMGciLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NjcyMjEyOTkvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NzIyMTI5OS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ2NzIyMTI5OS9PZmZlciIsImRmYy1iOnNrdSI6Ik5HQ1MvQzgiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiItMSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDY3MjIxMjk5L09mZmVyIiwiQHR5cGUiOiJkZmMtYjpPZmZlciIsImRmYy1iOmhhc1ByaWNlIjp7IkBpZCI6Il86YjI2Mzk5OSJ9fSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ3MDkyNDA4MyIsIkB0eXBlIjoiZGZjLWI6U3VwcGxpZWRQcm9kdWN0IiwiZGZjLWI6ZGVzY3JpcHRpb24iOiI8dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHIgc3R5bGU9XCJib3JkZXI6IDBweDtcIj5cbjx0ZCBiZ2NvbG9yPVwiI2Q2ZmJlZFwiIHN0eWxlPVwiY29sb3I6ICMwMDAwMDA7IGJvcmRlcjogMHB4OyB3aWR0aDogNTI2cHg7XCI+PGI+VGhlIHJpY2gsIHNtb2t5IGFuZCBzYWx0eSB0YXN0ZSBvZiBkdWxzZSBhZGRzIGRlcHRoIHRvIGRpc2hlcyBvZiBhbGwga2luZHMuPC9iPjwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+XG48cD5FYXRpbmcgZHVsc2UgaXMgYW4gYW5jaWVudCB0cmFkaXRpb24gaW4gU2NvdHMgYW5kIElyaXNoIGN1bHR1cmUuIEl0IGFkZHMgYSB3b25kZXJmdWwgZGVwdGggb2YgZmxhdm91ciBhbmQgaXMgcmljaCBpbiBtaW5lcmFscyBhbmQgcHJvdGVpbi48L3A+XG48cD5Tb21ldGltZXMga25vd24gYXMgJ3ZlZ2V0YXJpYW4gYmFjb24nIGl0IGhhcyBhIHdvbmRlcmZ1bCByb2J1c3QgZmxhdm91ciB0aGF0IHBhaXJzIHdlbGwgd2l0aCBzZWFmb29kLCBsZWFmeSBncmVlbnMgYW5kIHRvbWF0b2VzLjwvcD5cbjxwPk1hcmEgU2Vhd2VlZCBpcyBoYXJ2ZXN0ZWQgc3VzdGFpbmFibHkgZnJvbSB0aGUgcHVyZSwgd2lsZCB3YXRlcnMgYXJvdW5kIFNjb3RsYW5kIGFuZCBJcmVsYW5kLsKgPHNwYW4+VG8gYXZvaWQgY29udGFtaW5hdGlvbiwgdGhlIHNlYXdlZWQgaXMgcGFja2VkIGludG8gc2VhbGVkIHNhY2tzIGJlZm9yZSBiZWluZyBicm91Z2h0IHVwIHRoZSBiZWFjaCBhbmQgZGVsaXZlcmVkIGZyZXNoLCBkaXJlY3RseSB0byB0aGUgZmFjdG9yeS4gVGhlIHNlYXdlZWQgaXMgcGlja2VkIGFuZCBwcm9jZXNzZWQgd2l0aGluIDI0IGhvdXJzIHRvIGxvY2sgaW4gZmxhdm91ciwgZW5zdXJlIHF1YWxpdHkgYW5kIHNlY3VyZSBtYXhpbXVtIG51dHJpdGlvbmFsIGJlbmVmaXRzLjwvc3Bhbj48L3A+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Ib3cgdG8gdXNlPC9oNT5cbjxwPkR1bHNlIGhhcyBhIG5hdHVyYWwsIGxpbmdlcmluZyBzbW9reSB0YXN0ZS4gSXQncyBkZWxpY2lvdXMgd2hlbiBnZW5lcm91c2x5IHNoYWtlbiBvbiBkYXJrIGdyZWVuIHZlZ2V0YWJsZXMgYW5kIHJpY2ggcHVsc2UgZGlzaGVzLjwvcD5cbjxwPkZvciBhbiBleHRyYSBzYXZvdXJ5IGhpdCB3aXRob3V0IHRoZSBzYWx0LCB1c2UgZHVsc2UgZmxha2VzIHRvIHNlYXNvbiByb2FzdGVkIHZlZ2V0YWJsZSwgb3Igc3RpciBpbnRvIGJvbG9nbmVzZSBzYXVjZSBvciBjaGlsbGkgYmVmb3JlIHNlcnZpbmcuIER1bHNlIGlzIGFsc28gcGVyZmVjdCBzY2F0dGVyZWQgbWl4ZWQgdGhyb3VnaCBjcmlzcHkga2FsZS48L3A+XG48cD5Gb3IgYW4gdW51c3VhbCBzYWx0ZWQgY2FyYW1lbC1zdHlsZSB0d2lzdCwgdHJ5IHBhaXJpbmcgZHVsc2Ugd2l0aCBkYXJrIGNob2NvbGF0ZS4gSnVzdCBjb21iaW5lIGEgcGluY2ggb2YgZHVsc2Ugd2l0aCB0aGUgb3RoZXIgZHJ5IGluZ3JlZGllbnRzIGluIGFueSBicm93bmllIG9yIHRydWZmbGUgcmVjaXBlLjwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+SW5ncmVkaWVudHM8L2g1PlxuPHA+MTAwJSBEdWxzZSAoPGVtPlBhbG1hcmlhIHBhbG1hdGE8L2VtPik8L3A+XG48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkFsbGVyZ3kgaW5mb3JtYXRpb248L2g1PlxuPHA+TWF5IGNvbnRhaW4gZmlzaCwgY3J1c3RhY2VhbnMsIG1vbGx1c2NzLlxuPC9wPlxuPHRhYmxlIHdpZHRoPVwiMTAwJVwiPlxuPHRib2R5PlxuPHRyPlxuPHRkPjxzdHJvbmc+VHlwaWNhbCB2YWx1ZXM8L3N0cm9uZz48L3RkPlxuPHRkPjxzdHJvbmc+UGVyIDEwMGc8L3N0cm9uZz48L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5FbmVyZ3k8L3RkPlxuPHRkPjk3NGtKICgyMzRrY2FsKTwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZhdDwvdGQ+XG48dGQ+XG48cD4xLjVnPC9wPlxuPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc2F0dXJhdGVzPC90ZD5cbjx0ZD4wLjRnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+Q2FyYm9oeWRyYXRlPC90ZD5cbjx0ZD4yMi44ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPm9mIHdoaWNoIHN1Z2FyczwvdGQ+XG48dGQ+MS4wZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkZpYnJlPC90ZD5cbjx0ZD4zNi45ZzwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPlByb3RlaW48L3RkPlxuPHRkPjEzLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+U2FsdDwvdGQ+XG48dGQ+NC45ZzwvdGQ+XG48L3RyPlxuPC90Ym9keT5cbjwvdGFibGU+PGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5Nb3JlPC9oNT5cbjxwPlByb2R1Y3Qgb2YgU2NvdGxhbmQ8YnI+R3JlYXQgVGFzdGUgYXdhcmQgd2lubmVyPGJyPlN1aXRhYmxlIGZvciB2ZWdhbnMgYW5kIHZlZ2V0YXJpYW5zPGJyPkEgc291cmNlIG9mIGlvZGluZSwgY2FsY2l1bSwgcG90YXNzaXVtLCBtYWduZXNpdW0sIG1hbmdhbmVzZSwgY29wcGVyLCBpcm9uLCB6aW5jPC9wPiIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMjYzOTkyIiwiZGZjLWI6aW1hZ2UiOiJodHRwczovL2Nkbi5zaG9waWZ5LmNvbS9zL2ZpbGVzLzEvMDczMS84NDgzLzc5MzkvcHJvZHVjdHMvTWFyYS1EdWxzZS0zMGctdGluLTE4MDB4MTIwMC5qcGc/dj0xNjc3NzYwODMyIiwiZGZjLWI6bmFtZSI6IkR1bHNlIEZsYWtlcyAtIFJldGFpbCBwb3VjaCwgMzBnIiwiZGZjLWI6cmVmZXJlbmNlZEJ5IjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDcwOTI0MDgzL0NhdGFsb2dJdGVtIn0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NzA5MjQwODMvQXNQbGFubmVkQ29uc3VtcHRpb25GbG93IiwiQHR5cGUiOiJkZmMtYjpBc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJkZmMtYjpjb25zdW1lcyI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ3MDkyNDA4MyIsImRmYy1iOmhhc1F1YW50aXR5IjoiXzpiMjY0MDA4In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NzA5MjQwODMvQXNQbGFubmVkUHJvZHVjdGlvbkZsb3ciLCJAdHlwZSI6ImRmYy1iOkFzUGxhbm5lZFByb2R1Y3Rpb25GbG93IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIyNjQwMDkiLCJkZmMtYjpwcm9kdWNlcyI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ3MDk1Njg1MSJ9LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDcwOTI0MDgzL0FzUGxhbm5lZFRyYW5zZm9ybWF0aW9uIiwiQHR5cGUiOiJkZmMtYjpBc1BsYW5uZWRUcmFuc2Zvcm1hdGlvbiIsImRmYy1iOmhhc0luY29tZSI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ3MDkyNDA4My9Bc1BsYW5uZWRDb25zdW1wdGlvbkZsb3ciLCJkZmMtYjpoYXNPdXRjb21lIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDcwOTI0MDgzL0FzUGxhbm5lZFByb2R1Y3Rpb25GbG93In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NzA5MjQwODMvQ2F0YWxvZ0l0ZW0iLCJAdHlwZSI6ImRmYy1iOkNhdGFsb2dJdGVtIiwiZGZjLWI6b2ZmZXJlZFRocm91Z2giOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NzA5MjQwODMvT2ZmZXIiLCJkZmMtYjpza3UiOiJNQVIvV0RVTFMvUDMiLCJkZmMtYjpzdG9ja0xpbWl0YXRpb24iOiI1In0seyJAaWQiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NzA5MjQwODMvT2ZmZXIiLCJAdHlwZSI6ImRmYy1iOk9mZmVyIiwiZGZjLWI6aGFzUHJpY2UiOnsiQGlkIjoiXzpiMjYzOTkzIn19LHsiQGlkIjoiaHR0cHM6Ly9lbnYtMDEwNTgzMS5qY2xvdWQtdmVyLWpwZS5pay1zZXJ2ZXIuY29tL2FwaS9kZmMvRW50ZXJwcmlzZXMvdGVzdC1ob2RtZWRvZC9TdXBwbGllZFByb2R1Y3RzLzQ0NTE5NDcwOTU2ODUxIiwiQHR5cGUiOiJkZmMtYjpTdXBwbGllZFByb2R1Y3QiLCJkZmMtYjpkZXNjcmlwdGlvbiI6Ijx0YWJsZSB3aWR0aD1cIjEwMCVcIj5cbjx0Ym9keT5cbjx0ciBzdHlsZT1cImJvcmRlcjogMHB4O1wiPlxuPHRkIGJnY29sb3I9XCIjZDZmYmVkXCIgc3R5bGU9XCJjb2xvcjogIzAwMDAwMDsgYm9yZGVyOiAwcHg7IHdpZHRoOiA1MjZweDtcIj48Yj5UaGUgcmljaCwgc21va3kgYW5kIHNhbHR5IHRhc3RlIG9mIGR1bHNlIGFkZHMgZGVwdGggdG8gZGlzaGVzIG9mIGFsbCBraW5kcy48L2I+PC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT5cbjxwPkVhdGluZyBkdWxzZSBpcyBhbiBhbmNpZW50IHRyYWRpdGlvbiBpbiBTY290cyBhbmQgSXJpc2ggY3VsdHVyZS4gSXQgYWRkcyBhIHdvbmRlcmZ1bCBkZXB0aCBvZiBmbGF2b3VyIGFuZCBpcyByaWNoIGluIG1pbmVyYWxzIGFuZCBwcm90ZWluLjwvcD5cbjxwPlNvbWV0aW1lcyBrbm93biBhcyAndmVnZXRhcmlhbiBiYWNvbicgaXQgaGFzIGEgd29uZGVyZnVsIHJvYnVzdCBmbGF2b3VyIHRoYXQgcGFpcnMgd2VsbCB3aXRoIHNlYWZvb2QsIGxlYWZ5IGdyZWVucyBhbmQgdG9tYXRvZXMuPC9wPlxuPHA+TWFyYSBTZWF3ZWVkIGlzIGhhcnZlc3RlZCBzdXN0YWluYWJseSBmcm9tIHRoZSBwdXJlLCB3aWxkIHdhdGVycyBhcm91bmQgU2NvdGxhbmQgYW5kIElyZWxhbmQuwqA8c3Bhbj5UbyBhdm9pZCBjb250YW1pbmF0aW9uLCB0aGUgc2Vhd2VlZCBpcyBwYWNrZWQgaW50byBzZWFsZWQgc2Fja3MgYmVmb3JlIGJlaW5nIGJyb3VnaHQgdXAgdGhlIGJlYWNoIGFuZCBkZWxpdmVyZWQgZnJlc2gsIGRpcmVjdGx5IHRvIHRoZSBmYWN0b3J5LiBUaGUgc2Vhd2VlZCBpcyBwaWNrZWQgYW5kIHByb2Nlc3NlZCB3aXRoaW4gMjQgaG91cnMgdG8gbG9jayBpbiBmbGF2b3VyLCBlbnN1cmUgcXVhbGl0eSBhbmQgc2VjdXJlIG1heGltdW0gbnV0cml0aW9uYWwgYmVuZWZpdHMuPC9zcGFuPjwvcD48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPkhvdyB0byB1c2U8L2g1PlxuPHA+RHVsc2UgaGFzIGEgbmF0dXJhbCwgbGluZ2VyaW5nIHNtb2t5IHRhc3RlLiBJdCdzIGRlbGljaW91cyB3aGVuIGdlbmVyb3VzbHkgc2hha2VuIG9uIGRhcmsgZ3JlZW4gdmVnZXRhYmxlcyBhbmQgcmljaCBwdWxzZSBkaXNoZXMuPC9wPlxuPHA+Rm9yIGFuIGV4dHJhIHNhdm91cnkgaGl0IHdpdGhvdXQgdGhlIHNhbHQsIHVzZSBkdWxzZSBmbGFrZXMgdG8gc2Vhc29uIHJvYXN0ZWQgdmVnZXRhYmxlLCBvciBzdGlyIGludG8gYm9sb2duZXNlIHNhdWNlIG9yIGNoaWxsaSBiZWZvcmUgc2VydmluZy4gRHVsc2UgaXMgYWxzbyBwZXJmZWN0IHNjYXR0ZXJlZCBtaXhlZCB0aHJvdWdoIGNyaXNweSBrYWxlLjwvcD5cbjxwPkZvciBhbiB1bnVzdWFsIHNhbHRlZCBjYXJhbWVsLXN0eWxlIHR3aXN0LCB0cnkgcGFpcmluZyBkdWxzZSB3aXRoIGRhcmsgY2hvY29sYXRlLiBKdXN0IGNvbWJpbmUgYSBwaW5jaCBvZiBkdWxzZSB3aXRoIHRoZSBvdGhlciBkcnkgaW5ncmVkaWVudHMgaW4gYW55IGJyb3duaWUgb3IgdHJ1ZmZsZSByZWNpcGUuPC9wPlxuPGg1IGNsYXNzPVwicHJvZHVjdC1kZXRhaWwtdGl0bGVcIj5JbmdyZWRpZW50czwvaDU+XG48cD4xMDAlIER1bHNlICg8ZW0+UGFsbWFyaWEgcGFsbWF0YTwvZW0+KTwvcD5cbjxoNSBjbGFzcz1cInByb2R1Y3QtZGV0YWlsLXRpdGxlXCI+QWxsZXJneSBpbmZvcm1hdGlvbjwvaDU+XG48cD5NYXkgY29udGFpbiBmaXNoLCBjcnVzdGFjZWFucywgbW9sbHVzY3MuXG48L3A+XG48dGFibGUgd2lkdGg9XCIxMDAlXCI+XG48dGJvZHk+XG48dHI+XG48dGQ+PHN0cm9uZz5UeXBpY2FsIHZhbHVlczwvc3Ryb25nPjwvdGQ+XG48dGQ+PHN0cm9uZz5QZXIgMTAwZzwvc3Ryb25nPjwvdGQ+XG48L3RyPlxuPHRyPlxuPHRkPkVuZXJneTwvdGQ+XG48dGQ+OTc0a0ogKDIzNGtjYWwpPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmF0PC90ZD5cbjx0ZD5cbjxwPjEuNWc8L3A+XG48L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5vZiB3aGljaCBzYXR1cmF0ZXM8L3RkPlxuPHRkPjAuNGc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5DYXJib2h5ZHJhdGU8L3RkPlxuPHRkPjIyLjhnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+b2Ygd2hpY2ggc3VnYXJzPC90ZD5cbjx0ZD4xLjBnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+RmlicmU8L3RkPlxuPHRkPjM2LjlnPC90ZD5cbjwvdHI+XG48dHI+XG48dGQ+UHJvdGVpbjwvdGQ+XG48dGQ+MTMuOGc8L3RkPlxuPC90cj5cbjx0cj5cbjx0ZD5TYWx0PC90ZD5cbjx0ZD40LjlnPC90ZD5cbjwvdHI+XG48L3Rib2R5PlxuPC90YWJsZT48aDUgY2xhc3M9XCJwcm9kdWN0LWRldGFpbC10aXRsZVwiPk1vcmU8L2g1PlxuPHA+UHJvZHVjdCBvZiBTY290bGFuZDxicj5HcmVhdCBUYXN0ZSBhd2FyZCB3aW5uZXI8YnI+U3VpdGFibGUgZm9yIHZlZ2FucyBhbmQgdmVnZXRhcmlhbnM8YnI+QSBzb3VyY2Ugb2YgaW9kaW5lLCBjYWxjaXVtLCBwb3Rhc3NpdW0sIG1hZ25lc2l1bSwgbWFuZ2FuZXNlLCBjb3BwZXIsIGlyb24sIHppbmM8L3A+IiwiZGZjLWI6aGFzUXVhbnRpdHkiOiJfOmIyNjQwMDAiLCJkZmMtYjppbWFnZSI6Imh0dHBzOi8vY2RuLnNob3BpZnkuY29tL3MvZmlsZXMvMS8wNzMxLzg0ODMvNzkzOS9wcm9kdWN0cy9NYXJhLUR1bHNlLTMwZy10aW4tMTgwMHgxMjAwLmpwZz92PTE2Nzc3NjA4MzIiLCJkZmMtYjpuYW1lIjoiRHVsc2UgRmxha2VzIC0gQ2FzZSwgMTAgeCAzMGciLCJkZmMtYjpyZWZlcmVuY2VkQnkiOiJodHRwczovL2Vudi0wMTA1ODMxLmpjbG91ZC12ZXItanBlLmlrLXNlcnZlci5jb20vYXBpL2RmYy9FbnRlcnByaXNlcy90ZXN0LWhvZG1lZG9kL1N1cHBsaWVkUHJvZHVjdHMvNDQ1MTk0NzA5NTY4NTEvQ2F0YWxvZ0l0ZW0ifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ3MDk1Njg1MS9DYXRhbG9nSXRlbSIsIkB0eXBlIjoiZGZjLWI6Q2F0YWxvZ0l0ZW0iLCJkZmMtYjpvZmZlcmVkVGhyb3VnaCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ3MDk1Njg1MS9PZmZlciIsImRmYy1iOnNrdSI6Ik1BUi9XRFVMUy9DWCIsImRmYy1iOnN0b2NrTGltaXRhdGlvbiI6IjIifSx7IkBpZCI6Imh0dHBzOi8vZW52LTAxMDU4MzEuamNsb3VkLXZlci1qcGUuaWstc2VydmVyLmNvbS9hcGkvZGZjL0VudGVycHJpc2VzL3Rlc3QtaG9kbWVkb2QvU3VwcGxpZWRQcm9kdWN0cy80NDUxOTQ3MDk1Njg1MS9PZmZlciIsIkB0eXBlIjoiZGZjLWI6T2ZmZXIiLCJkZmMtYjpoYXNQcmljZSI6eyJAaWQiOiJfOmIyNjQwMDEifX1dfQ== + recorded_at: Wed, 26 Feb 2025 04:26:41 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/jobs/open_order_cycle_job_spec.rb b/spec/jobs/open_order_cycle_job_spec.rb new file mode 100644 index 00000000000..0e57542389e --- /dev/null +++ b/spec/jobs/open_order_cycle_job_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe OpenOrderCycleJob do + let(:now){ Time.zone.now } + let(:order_cycle) { create(:simple_order_cycle, orders_open_at: now) } + subject { OpenOrderCycleJob.perform_now(order_cycle.id) } + + around do |example| + Timecop.freeze(now) { example.run } + end + + it "marks as open" do + expect { + subject + order_cycle.reload + } + .to change { order_cycle.opened_at } + + expect(order_cycle.opened_at).to be_within(1).of(now) + end + + it "enqueues webhook job" do + expect(OrderCycles::WebhookService) + .to receive(:create_webhook_job).with(order_cycle, 'order_cycle.opened', now).once + + subject + end + + describe "syncing remote products" do + let!(:user) { create(:testdfc_user, owned_enterprises: [enterprise]) } + + let(:enterprise) { create(:supplier_enterprise) } + let!(:variant) { create(:variant, name: "Sauce", supplier_id: enterprise.id) } + let!(:order_cycle) { + create(:simple_order_cycle, orders_open_at: now, + suppliers: [enterprise], variants: [variant]) + } + + it "synchronises products from a FDC catalog", vcr: true do + user.update!(oidc_account: build(:testdfc_account)) + # One product is existing in OFN + product_id = + "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635" + variant.semantic_links << SemanticLink.new(semantic_id: product_id) + + expect { + subject + variant.reload + order_cycle.reload + }.to change { order_cycle.opened_at } + .and change { enterprise.supplied_products.count }.by(0) # It shouldn't add, only update + .and change { variant.display_name } + .and change { variant.unit_value } + # 18.85 wholesale variant price divided by 12 cans in the slab. + .and change { variant.price }.to(1.57) + .and change { variant.on_demand }.to(true) + .and change { variant.on_hand }.by(0) + .and query_database 46 + end + end + + describe "concurrency", concurrency: true do + let(:breakpoint) { Mutex.new } + + it "doesn't open order cycle twice" do + # Pause jobs when placing new job: + breakpoint.lock + allow(OpenOrderCycleJob).to( + receive(:new).and_wrap_original do |method, *args| + breakpoint.synchronize {} # rubocop:disable Lint/EmptyBlock + method.call(*args) + end + ) + + expect(OrderCycles::WebhookService) + .to receive(:create_webhook_job).with(order_cycle, 'order_cycle.opened', now).once + + expect{ + # Start two jobs in parallel: + threads = [ + Thread.new { OpenOrderCycleJob.perform_now(order_cycle.id) }, + Thread.new { OpenOrderCycleJob.perform_now(order_cycle.id) }, + ] + + # Wait for both to jobs to pause. + # This can reveal a race condition. + sleep 0.1 + + # Resume and complete both jobs: + breakpoint.unlock + threads.each(&:join) + }.to raise_error ActiveRecord::RecordNotFound + end + end +end diff --git a/spec/jobs/order_cycle_opened_job_spec.rb b/spec/jobs/order_cycle_opened_job_spec.rb deleted file mode 100644 index caf5ee6ef10..00000000000 --- a/spec/jobs/order_cycle_opened_job_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe OrderCycleOpenedJob do - let(:oc_opened_before) { - create(:order_cycle, orders_open_at: 1.hour.ago) - } - let(:oc_opened_now) { - create(:order_cycle, orders_open_at: Time.zone.now) - } - let(:oc_opening_soon) { - create(:order_cycle, orders_open_at: 1.minute.from_now) - } - - it "enqueues jobs for recently opened order cycles only" do - expect(OrderCycles::WebhookService) - .to receive(:create_webhook_job).with(oc_opened_now, 'order_cycle.opened') - - expect(OrderCycles::WebhookService) - .not_to receive(:create_webhook_job).with(oc_opened_before, 'order_cycle.opened') - - expect(OrderCycles::WebhookService) - .not_to receive(:create_webhook_job).with(oc_opening_soon, 'order_cycle.opened') - - OrderCycleOpenedJob.perform_now - end - - describe "concurrency", concurrency: true do - let(:breakpoint) { Mutex.new } - - it "doesn't place duplicate job when run concurrently" do - oc_opened_now - - # Pause jobs when placing new job: - breakpoint.lock - allow(OrderCycleOpenedJob).to( - receive(:new).and_wrap_original do |method, *args| - breakpoint.synchronize {} - method.call(*args) - end - ) - - expect(OrderCycles::WebhookService) - .to receive(:create_webhook_job).with(oc_opened_now, 'order_cycle.opened').once - - # Start two jobs in parallel: - threads = [ - Thread.new { OrderCycleOpenedJob.perform_now }, - Thread.new { OrderCycleOpenedJob.perform_now }, - ] - - # Wait for both to jobs to pause. - # This can reveal a race condition. - sleep 0.1 - - # Resume and complete both jobs: - breakpoint.unlock - threads.each(&:join) - end - end -end diff --git a/spec/jobs/trigger_order_cycles_to_open_job_spec.rb b/spec/jobs/trigger_order_cycles_to_open_job_spec.rb new file mode 100644 index 00000000000..9b272025031 --- /dev/null +++ b/spec/jobs/trigger_order_cycles_to_open_job_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe TriggerOrderCyclesToOpenJob do + let(:oc_opened_before) { + create(:simple_order_cycle, orders_open_at: 1.hour.ago) + } + let(:oc_opened_now) { + create(:simple_order_cycle, orders_open_at: Time.zone.now) + } + let(:oc_opening_soon) { + create(:simple_order_cycle, orders_open_at: 1.minute.from_now) + } + + it "enqueues jobs for recently opened order cycles only" do + expect{ TriggerOrderCyclesToOpenJob.perform_now } + .to enqueue_job(OpenOrderCycleJob).with(oc_opened_now.id) + .and enqueue_job(OpenOrderCycleJob).with(oc_opened_before.id).exactly(0).times + .and enqueue_job(OpenOrderCycleJob).with(oc_opening_soon.id).exactly(0).times + end +end diff --git a/spec/jobs/webhook_delivery_job_spec.rb b/spec/jobs/webhook_delivery_job_spec.rb index 0b09ed7bde5..5383f5e87c4 100644 --- a/spec/jobs/webhook_delivery_job_spec.rb +++ b/spec/jobs/webhook_delivery_job_spec.rb @@ -3,9 +3,10 @@ require 'spec_helper' RSpec.describe WebhookDeliveryJob do - subject { WebhookDeliveryJob.new(url, event, data) } + subject { WebhookDeliveryJob.new(url, event, data, at:) } let(:url) { 'https://test/endpoint' } let(:event) { 'order_cycle.opened' } + let(:at) { 1.second.ago } let(:data) { { order_cycle_id: 123, name: "Order cycle 1", open_at: 1.minute.ago.to_s, tags: ["tag1", "tag2"] @@ -25,7 +26,7 @@ Timecop.freeze do expected_body = { id: /.+/, - at: Time.zone.now.to_s, + at: at.to_s, event:, data:, } diff --git a/spec/services/order_cycles/webhook_service_spec.rb b/spec/services/order_cycles/webhook_service_spec.rb index bdc77bb151f..73fe341f83f 100644 --- a/spec/services/order_cycles/webhook_service_spec.rb +++ b/spec/services/order_cycles/webhook_service_spec.rb @@ -8,11 +8,14 @@ :simple_order_cycle, name: "Order cycle 1", orders_open_at: "2022-09-19 09:00:00".to_time, + opened_at: "2022-09-19 09:00:01".to_time, orders_close_at: "2022-09-19 17:00:00".to_time, coordinator:, ) } let(:coordinator) { create :distributor_enterprise, name: "Starship Enterprise" } + let(:at) { "2022-09-19 09:00:02".to_time } + subject { OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened", at) } describe "creating payloads" do it "doesn't create webhook payload for enterprise users" do @@ -21,7 +24,7 @@ coordinator_user = create(:user, enterprises: [coordinator]) coordinator_user.webhook_endpoints.create!(url: "http://coordinator_user_url") - expect{ OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") } + expect{ subject } .not_to enqueue_job(WebhookDeliveryJob).with("http://coordinator_user_url", any_args) end @@ -31,7 +34,7 @@ end it "creates webhook payload for order cycle coordinator" do - expect{ OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") } + expect{ subject } .to enqueue_job(WebhookDeliveryJob).with("http://coordinator_owner_url", any_args) end @@ -43,20 +46,21 @@ id: order_cycle.id, name: "Order cycle 1", orders_open_at: "2022-09-19 09:00:00".to_time, + opened_at: "2022-09-19 09:00:01".to_time, orders_close_at: "2022-09-19 17:00:00".to_time, coordinator_id: coordinator.id, coordinator_name: "Starship Enterprise", } - expect{ OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") } + expect{ subject } .to enqueue_job(WebhookDeliveryJob).exactly(1).times - .with("http://coordinator_owner_url", "order_cycle.opened", hash_including(data)) + .with("http://coordinator_owner_url", "order_cycle.opened", hash_including(data), at:) end end context "coordinator owner doesn't have endpoint configured" do it "doesn't create webhook payload" do - expect{ OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") } + expect{ subject } .not_to enqueue_job(WebhookDeliveryJob) end end @@ -84,13 +88,13 @@ coordinator_name: "Starship Enterprise", } - expect{ - OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") - } + expect{ subject } .to enqueue_job(WebhookDeliveryJob).with("http://distributor1_owner_url", - "order_cycle.opened", hash_including(data)) + "order_cycle.opened", hash_including(data), + at:) .and enqueue_job(WebhookDeliveryJob).with("http://distributor2_owner_url", - "order_cycle.opened", hash_including(data)) + "order_cycle.opened", hash_including(data), + at:) end end @@ -107,9 +111,7 @@ it "creates only one webhook payload for the user's endpoint" do user.webhook_endpoints.create! url: "http://coordinator_owner_url" - expect{ - OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") - } + expect{ subject } .to enqueue_job(WebhookDeliveryJob).with("http://coordinator_owner_url", any_args) end end @@ -131,9 +133,7 @@ } it "doesn't create a webhook payload for supplier owner" do - expect{ - OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") - } + expect{ subject } .not_to enqueue_job(WebhookDeliveryJob).with("http://supplier_owner_url", any_args) end end @@ -142,7 +142,7 @@ context "without webhook subscribed to enterprise" do it "doesn't create webhook payload" do - expect{ OrderCycles::WebhookService.create_webhook_job(order_cycle, "order_cycle.opened") } + expect{ subject } .not_to enqueue_job(WebhookDeliveryJob) end end