gooddata 2.1.11-java → 2.1.16-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of gooddata might be problematic. Click here for more details.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gdc-ii-config.yaml +1 -1
  3. data/.sonar.settings +4 -0
  4. data/.travis.yml +4 -3
  5. data/CHANGELOG.md +34 -0
  6. data/Dockerfile +9 -3
  7. data/LICENSE +4418 -17
  8. data/LICENSE.rb +1 -1
  9. data/LICENSE_FOR_RUBY_SDK_COMPONENT.txt +4423 -0
  10. data/NOTICES.txt +6692 -0
  11. data/README.md +19 -1
  12. data/Rakefile +5 -1
  13. data/SDK_VERSION +1 -1
  14. data/VERSION +1 -1
  15. data/bin/run_brick.rb +3 -0
  16. data/bin/test_projects_cleanup.rb +7 -3
  17. data/ci/postgresql/pom.xml +57 -0
  18. data/dev-gooddata-sso.pub.encrypted +40 -40
  19. data/gdc_fossa_lcm.yaml +2 -0
  20. data/gdc_fossa_ruby_sdk.yaml +5 -0
  21. data/gooddata.gemspec +3 -4
  22. data/k8s/charts/lcm-bricks/Chart.yaml +1 -1
  23. data/k8s/charts/lcm-bricks/templates/prometheus/alertingRules.yaml +32 -12
  24. data/lib/gooddata.rb +2 -0
  25. data/lib/gooddata/cloud_resources/{cloud_resouce_factory.rb → cloud_resource_factory.rb} +8 -0
  26. data/lib/gooddata/cloud_resources/cloud_resources.rb +1 -1
  27. data/lib/gooddata/cloud_resources/postgresql/drivers/.gitkeepme +0 -0
  28. data/lib/gooddata/cloud_resources/postgresql/postgresql_client.rb +107 -0
  29. data/lib/gooddata/commands/scaffold.rb +9 -10
  30. data/lib/gooddata/core/nil_logger.rb +3 -1
  31. data/lib/gooddata/helpers/data_helper.rb +1 -2
  32. data/lib/gooddata/helpers/global_helpers.rb +6 -5
  33. data/lib/gooddata/helpers/global_helpers_params.rb +2 -2
  34. data/lib/gooddata/lcm/actions/base_action.rb +0 -2
  35. data/lib/gooddata/lcm/actions/collect_tagged_objects.rb +2 -1
  36. data/lib/gooddata/lcm/actions/migrate_gdc_date_dimension.rb +116 -0
  37. data/lib/gooddata/lcm/actions/synchronize_ldm.rb +10 -1
  38. data/lib/gooddata/lcm/actions/synchronize_user_filters.rb +4 -0
  39. data/lib/gooddata/lcm/actions/synchronize_users.rb +7 -6
  40. data/lib/gooddata/lcm/lcm2.rb +1 -2
  41. data/lib/gooddata/lcm/types/base_type.rb +0 -2
  42. data/lib/gooddata/mixins/md_object_query.rb +8 -6
  43. data/lib/gooddata/models/blueprint/project_blueprint.rb +0 -2
  44. data/lib/gooddata/models/client.rb +14 -12
  45. data/lib/gooddata/models/data_source.rb +664 -0
  46. data/lib/gooddata/models/domain.rb +1 -2
  47. data/lib/gooddata/models/from_wire.rb +1 -0
  48. data/lib/gooddata/models/metadata/scheduled_mail.rb +1 -1
  49. data/lib/gooddata/models/process.rb +11 -3
  50. data/lib/gooddata/models/project.rb +128 -19
  51. data/lib/gooddata/models/user_filters/user_filter_builder.rb +0 -1
  52. data/lib/gooddata/models/user_group.rb +0 -1
  53. data/lib/gooddata/rest/connection.rb +6 -4
  54. data/rubydev_public.gpg.encrypted +51 -51
  55. data/rubydev_secret_keys.gpg.encrypted +109 -109
  56. metadata +18 -23
  57. data/DEPENDENCIES.md +0 -880
@@ -5,7 +5,6 @@
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
7
7
  require 'cgi'
8
- require 'active_support/core_ext/hash/compact'
9
8
 
10
9
  require_relative 'profile'
11
10
  require_relative '../extensions/enumerable'
@@ -241,7 +240,7 @@ module GoodData
241
240
 
242
241
  all_users
243
242
  else
244
- find_user_by_login(domain, id)
243
+ find_user_by_login(domain, id, opts)
245
244
  end
246
245
  end
247
246
 
@@ -105,6 +105,7 @@ module GoodData
105
105
  d[:title] = date_dim['dateDimension']['title']
106
106
  d[:urn] = date_dim['dateDimension']['urn']
107
107
  d[:identifier_prefix] = date_dim['dateDimension']['identifierPrefix']
108
+ d[:identifier] = date_dim['dateDimension']['identifier'] if date_dim['dateDimension']['identifier']
108
109
  d[:columns] = parse_bridges(date_dim)
109
110
  end
110
111
  end
@@ -30,7 +30,7 @@ module GoodData
30
30
  :deprecated => 0,
31
31
 
32
32
  # Content When options
33
- :recurrency => '0:0:0:12:0:0',
33
+ :recurrency => '0:0:0:1*12:0:0',
34
34
  :startDate => '2012-06-05',
35
35
  :timeZone => 'America/Los_Angeles',
36
36
 
@@ -118,11 +118,13 @@ module GoodData
118
118
  GoodData.logger.info("Deploying #{path}") if verbose
119
119
 
120
120
  deployed_path = Process.upload_package(path, files_to_exclude, client: client, project: project)
121
+ data_sources = options[:data_sources] || []
121
122
  data = {
122
123
  :process => {
123
124
  :name => deploy_name,
124
125
  :path => "/uploads/#{File.basename(deployed_path)}",
125
- :type => type
126
+ :type => type,
127
+ :dataSources => data_sources
126
128
  }
127
129
  }
128
130
 
@@ -171,10 +173,12 @@ module GoodData
171
173
  verbose = options[:verbose] || false
172
174
  GoodData.logger.info("Deploying #{path}") if verbose
173
175
 
176
+ data_sources = options[:data_sources] || []
174
177
  data = {
175
178
  process: {
176
179
  name: deploy_name,
177
180
  path: path,
181
+ dataSources: data_sources,
178
182
  type: 'RUBY'
179
183
  }
180
184
  }
@@ -185,7 +189,7 @@ module GoodData
185
189
  def deploy_component(data, options = { client: GoodData.client, project: GoodData.project })
186
190
  client, project = GoodData.get_client_and_project(options)
187
191
  data = { process: data } unless data[:process]
188
- data[:process] = GoodData::Helpers.symbolize_keys(data[:process]).select { |k| %i[type name component].include? k }
192
+ data[:process] = GoodData::Helpers.symbolize_keys(data[:process]).select { |k| %i[type name component dataSources].include? k }
189
193
  data[:process][:component] = GoodData::Helpers.symbolize_keys(data[:process][:component]).select { |k| %i[name version configLocation config].include? k }
190
194
 
191
195
  save(data, options)
@@ -266,7 +270,7 @@ module GoodData
266
270
  # @option options [String] :name Readable name of the process
267
271
  # @option options [Boolean] :verbose (false) Switch on verbose mode for detailed logging
268
272
  def deploy(path, options = {})
269
- Process.deploy(path, { client: client, process_id: process_id, :project => project, :name => name, :type => type }.merge(options))
273
+ Process.deploy(path, { client: client, process_id: process_id, :project => project, :name => name, :type => type, :data_sources => data_sources }.merge(options))
270
274
  end
271
275
 
272
276
  # Downloads the process from S3 in a zipped form.
@@ -326,6 +330,10 @@ module GoodData
326
330
  process['component']
327
331
  end
328
332
 
333
+ def data_sources
334
+ process['dataSources']
335
+ end
336
+
329
337
  # Determines whether the process is an ADDv2 component.
330
338
  # @return [Bool] True if the process is an ADDv2 component.
331
339
  def add_v2_component?
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
  #
3
- # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
3
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
4
4
  # This source code is licensed under the BSD-style license found in the
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
@@ -13,7 +13,6 @@ require 'zip'
13
13
  require 'net/smtp'
14
14
 
15
15
  require 'active_support/core_ext/hash/except'
16
- require 'active_support/core_ext/hash/compact'
17
16
  require 'active_support/core_ext/hash/slice'
18
17
 
19
18
  require_relative '../exceptions/no_project_error'
@@ -38,7 +37,8 @@ require_relative 'metadata/scheduled_mail/report_attachment'
38
37
 
39
38
  module GoodData
40
39
  class Project < Rest::Resource
41
- USERSPROJECTS_PATH = '/gdc/account/profile/%s/projects'
40
+ USER_ACCOUNT_PATH = '/gdc/account/profile/'
41
+ USERSPROJECTS_PATH = USER_ACCOUNT_PATH + '%s/projects'
42
42
  PROJECTS_PATH = '/gdc/projects'
43
43
  PROJECT_PATH = '/gdc/projects/%s'
44
44
  SLIS_PATH = '/ldm/singleloadinterface'
@@ -261,21 +261,26 @@ module GoodData
261
261
  # @option ads_output_stage_uri Uri of the source output stage. It must be in the same domain as the target project.
262
262
  def transfer_processes(from_project, to_project, options = {})
263
263
  options = GoodData::Helpers.symbolize_keys(options)
264
+ aliases = {}
264
265
  to_project_processes = to_project.processes
265
266
  additional_hidden_params = options[:additional_hidden_params] || {}
266
267
  result = from_project.processes.uniq(&:name).map do |process|
267
- fail "The process name #{process.name} must be unique in transfered project #{to_project}" if to_project_processes.count { |p| p.name == process.name } > 1
268
+ fail "The process name #{process.name} must be unique in transferred project #{to_project}" if to_project_processes.count { |p| p.name == process.name } > 1
268
269
  next if process.type == :dataload || process.add_v2_component?
270
+ collect_process_aliases(process.data, from_project.client, aliases)
269
271
 
270
272
  to_process = to_project_processes.find { |p| p.name == process.name }
271
273
 
274
+ data_sources = GoodData::Helpers.symbolize_keys_recursively!(process.data_sources)
275
+ data_sources = replace_data_source_ids(data_sources, to_project.client, aliases)
272
276
  to_process = if process.path
273
277
  to_process.delete if to_process
274
- GoodData::Process.deploy_from_appstore(process.path, name: process.name, client: to_project.client, project: to_project)
278
+ Process.deploy_from_appstore(process.path, name: process.name, client: to_project.client, project: to_project, data_sources: data_sources)
275
279
  elsif process.component
276
280
  to_process.delete if to_process
277
281
  process_hash = GoodData::Helpers::DeepMergeableHash[GoodData::Helpers.symbolize_keys(process.to_hash)].deep_merge(additional_hidden_params)
278
- GoodData::Process.deploy_component(process_hash, project: to_project, client: to_project.client)
282
+ process_hash = replace_process_data_source_ids(process_hash, to_project.client, aliases)
283
+ Process.deploy_component(process_hash, project: to_project, client: to_project.client)
279
284
  else
280
285
  Dir.mktmpdir('etl_transfer') do |dir|
281
286
  dir = Pathname(dir)
@@ -283,11 +288,10 @@ module GoodData
283
288
  File.open(filename, 'w') do |f|
284
289
  f << process.download
285
290
  end
286
-
287
291
  if to_process
288
- to_process.deploy(filename, type: process.type, name: process.name)
292
+ to_process.deploy(filename, type: process.type, name: process.name, data_sources: data_sources)
289
293
  else
290
- to_project.deploy_process(filename, type: process.type, name: process.name)
294
+ to_project.deploy_process(filename, type: process.type, name: process.name, data_sources: data_sources)
291
295
  end
292
296
  end
293
297
  end
@@ -318,6 +322,78 @@ module GoodData
318
322
  result.compact
319
323
  end
320
324
 
325
+ def collect_process_aliases(process_data, client, aliases)
326
+ data_sources = process_data.dig('process', 'dataSources')
327
+ unless data_sources.blank?
328
+ data_sources.map do |data_source|
329
+ get_data_source_alias(data_source['id'], client, aliases)
330
+ end
331
+ end
332
+ component = process_data.dig('process', 'component')
333
+ get_data_source_alias(component['configLocation']['dataSourceConfig']['id'], client, aliases) if component&.dig('configLocation', 'dataSourceConfig')
334
+ aliases
335
+ end
336
+
337
+ def get_data_source_alias(data_source_id, client, aliases)
338
+ unless aliases[data_source_id]
339
+ data_source = GoodData::DataSource.from_id(data_source_id, client: client)
340
+ if data_source&.dig('dataSource', 'alias')
341
+ aliases[data_source_id] = {
342
+ :type => get_data_source_type(data_source),
343
+ :alias => data_source['dataSource']['alias']
344
+ }
345
+ end
346
+ end
347
+ aliases[data_source_id]
348
+ end
349
+
350
+ def get_data_source_type(data_source_data)
351
+ data_source_data&.dig('dataSource', 'connectionInfo') ? data_source_data['dataSource']['connectionInfo'].first[0].upcase : ""
352
+ end
353
+
354
+ def replace_process_data_source_ids(process_data, client, aliases)
355
+ component = process_data.dig(:process, :component)
356
+ if component&.dig(:configLocation, :dataSourceConfig)
357
+ the_alias = aliases[component[:configLocation][:dataSourceConfig][:id]]
358
+ process_data[:process][:component][:configLocation][:dataSourceConfig][:id] = verify_data_source_alias(the_alias, client)
359
+ end
360
+ process_data[:process][:dataSources] = replace_data_source_ids(process_data[:process][:dataSources], client, aliases)
361
+ process_data
362
+ end
363
+
364
+ def replace_data_source_ids(data_sources, client, aliases)
365
+ array_data_sources = []
366
+ if data_sources && !data_sources.empty?
367
+ data_sources.map do |data_source|
368
+ new_id = verify_data_source_alias(aliases[data_source[:id]], client)
369
+ array_data_sources.push(:id => new_id)
370
+ end
371
+ end
372
+ array_data_sources
373
+ end
374
+
375
+ # Verify whether the data source exists in the domain using its alias
376
+ #
377
+ # @param [String] ds_alias The data source's alias
378
+ # @param [Object] client The Rest Client object
379
+ # @return [String] Id of the data source or failed with the reason
380
+ def verify_data_source_alias(ds_alias, client)
381
+ domain = client.connection.server.url
382
+ fail "The data source alias is empty, check your data source configuration." unless ds_alias
383
+
384
+ uri = "/gdc/dataload/dataSources/internal/availableAlias?alias=#{ds_alias[:alias]}"
385
+ res = client.get(uri)
386
+ fail "Unable to get information about the Data Source '#{ds_alias[:alias]}' in the domain '#{domain}'" unless res
387
+ fail "Unable to find the #{ds_alias[:type]} Data Source '#{ds_alias[:alias]}' in the domain '#{domain}'" if res['availableAlias']['available']
388
+
389
+ ds_type = res['availableAlias']['existingDataSource']['type']
390
+ if ds_type && ds_type != ds_alias[:type]
391
+ fail "Wrong Data Source type - the '#{ds_type}' type is expected but the Data Source '#{ds_alias[:alias]}' in the domain '#{domain}' has the '#{ds_alias[:type]}' type"
392
+ else
393
+ res['availableAlias']['existingDataSource']['id']
394
+ end
395
+ end
396
+
321
397
  def transfer_user_groups(from_project, to_project)
322
398
  from_project.user_groups.map do |ug|
323
399
  # migrate groups
@@ -625,6 +701,7 @@ module GoodData
625
701
  def blueprint(options = {})
626
702
  options = { include_ca: true }.merge(options)
627
703
  result = client.get("/gdc/projects/#{pid}/model/view", params: { includeDeprecated: true, includeGrain: true, includeCA: options[:include_ca] })
704
+
628
705
  polling_url = result['asyncTask']['link']['poll']
629
706
  model = client.poll_on_code(polling_url, options)
630
707
  bp = GoodData::Model::FromWire.from_wire(model, options)
@@ -1606,14 +1683,19 @@ module GoodData
1606
1683
  def import_users(new_users, options = {})
1607
1684
  role_list = roles
1608
1685
  users_list = users
1609
- new_users = new_users.map { |x| ((x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash).tap { |u| u[:login].downcase! } }
1610
1686
 
1611
1687
  GoodData.logger.warn("Importing users to project (#{pid})")
1688
+ new_users = new_users.map { |x| ((x.is_a?(Hash) && x[:user] && x[:user].to_hash.merge(role: x[:role])) || x.to_hash).tap { |u| u[:login].downcase! } }
1689
+ # First check that if groups are provided we have them set up
1690
+ user_groups_cache, change_groups = check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq, options[:user_groups_cache], options)
1612
1691
 
1613
- whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
1692
+ unless change_groups.empty?
1693
+ new_users.each do |user|
1694
+ user[:user_group].map! { |e| change_groups[e].nil? ? e : change_groups[e] }
1695
+ end
1696
+ end
1614
1697
 
1615
- # First check that if groups are provided we have them set up
1616
- user_groups_cache = check_groups(new_users.map(&:to_hash).flat_map { |u| u[:user_group] || [] }.uniq, options[:user_groups_cache], options)
1698
+ whitelisted_new_users, whitelisted_users = whitelist_users(new_users.map(&:to_hash), users_list, options[:whitelists])
1617
1699
 
1618
1700
  # conform the role on list of new users so we can diff them with the users coming from the project
1619
1701
  diffable_new_with_default_role = whitelisted_new_users.map do |u|
@@ -1656,7 +1738,7 @@ module GoodData
1656
1738
  end
1657
1739
  end
1658
1740
  diff_results = diff_results.map do |u|
1659
- u[:login_uri] = "/gdc/account/profile/" + u[:login]
1741
+ u[:login_uri] = USER_ACCOUNT_PATH + u[:login]
1660
1742
  u
1661
1743
  end
1662
1744
  return diff_results if options[:dry_run]
@@ -1760,7 +1842,20 @@ module GoodData
1760
1842
  def check_groups(specified_groups, user_groups_cache = nil, options = {})
1761
1843
  current_user_groups = user_groups if user_groups_cache.nil? || user_groups_cache.empty?
1762
1844
  groups = current_user_groups.map(&:name)
1763
- missing_groups = specified_groups - groups
1845
+ missing_groups = []
1846
+ change_groups = {}
1847
+ specified_groups.each do |group|
1848
+ found_group = groups.find { |name| name.casecmp(group).zero? }
1849
+ if found_group.nil?
1850
+ missing_groups << group
1851
+ else
1852
+ # Change groups when they have similar group name with difference of case sensitivity
1853
+ if found_group != group
1854
+ change_groups[group] = found_group
1855
+ GoodData.logger.warn("Group with name #{group} is existed in project with name #{found_group}.")
1856
+ end
1857
+ end
1858
+ end
1764
1859
  if options[:create_non_existing_user_groups]
1765
1860
  missing_groups.each do |g|
1766
1861
  GoodData.logger.info("Creating group #{g}")
@@ -1773,7 +1868,7 @@ module GoodData
1773
1868
  "#{groups.join(',')} and you asked for #{missing_groups.join(',')}"
1774
1869
  end
1775
1870
  end
1776
- current_user_groups
1871
+ [current_user_groups, change_groups]
1777
1872
  end
1778
1873
 
1779
1874
  # Update user
@@ -1879,17 +1974,17 @@ module GoodData
1879
1974
 
1880
1975
  def resolve_roles(login, desired_roles, options = {})
1881
1976
  user = if login.is_a?(String) && login.include?('@')
1882
- '/gdc/account/profile/' + login
1977
+ USER_ACCOUNT_PATH + login
1883
1978
  elsif login.is_a?(String)
1884
1979
  login
1885
1980
  elsif login.is_a?(Hash) && login[:login]
1886
- '/gdc/account/profile/' + login[:login]
1981
+ USER_ACCOUNT_PATH + login[:login]
1887
1982
  elsif login.is_a?(Hash) && login[:uri]
1888
1983
  login[:uri]
1889
1984
  elsif login.respond_to?(:uri) && login.uri
1890
1985
  login.uri
1891
1986
  elsif login.respond_to?(:login) && login.login
1892
- '/gdc/account/profile/' + login.login
1987
+ USER_ACCOUNT_PATH + login.login
1893
1988
  else
1894
1989
  fail "Unsupported user specification #{login}"
1895
1990
  end
@@ -1904,6 +1999,20 @@ module GoodData
1904
1999
  [user, roles]
1905
2000
  end
1906
2001
 
2002
+ def upgrade_custom_v2(message, options = {})
2003
+ uri = "/gdc/md/#{pid}/datedimension/upgrade"
2004
+ poll_result = client&.post(uri, message)
2005
+
2006
+ return poll_result['wTaskStatus']['status'] if poll_result['wTaskStatus'] && poll_result['wTaskStatus']['status']
2007
+
2008
+ polling_uri = poll_result['asyncTask']['link']['poll']
2009
+ result = client&.poll_on_response(polling_uri, options) do |body|
2010
+ body && body['wTaskStatus'] && body['wTaskStatus']['status'] == 'RUNNING'
2011
+ end
2012
+
2013
+ result['wTaskStatus']['status'] == 'OK' ? 'OK' : 'FAIL'
2014
+ end
2015
+
1907
2016
  def add
1908
2017
  @add ||= GoodData::AutomatedDataDistribution.new(self)
1909
2018
  @add
@@ -7,7 +7,6 @@
7
7
  require_relative '../project_log_formatter'
8
8
 
9
9
  require 'active_support/core_ext/hash/indifferent_access'
10
- require 'active_support/core_ext/hash/compact'
11
10
 
12
11
  require 'gooddata/extensions/true'
13
12
  require 'gooddata/extensions/false'
@@ -13,7 +13,6 @@ require_relative '../mixins/rest_resource'
13
13
  require_relative '../mixins/uri_getter'
14
14
 
15
15
  require 'active_support/core_ext/hash/except'
16
- require 'active_support/core_ext/hash/compact'
17
16
 
18
17
  module GoodData
19
18
  # Representation of User Group
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
  #
3
- # Copyright (c) 2010-2017 GoodData Corporation. All rights reserved.
3
+ # Copyright (c) 2010-2021 GoodData Corporation. All rights reserved.
4
4
  # This source code is licensed under the BSD-style license found in the
5
5
  # LICENSE file in the root directory of this source tree.
6
6
 
@@ -26,6 +26,8 @@ module GoodData
26
26
  class Connection
27
27
  include MonitorMixin
28
28
 
29
+ HTTPS_PROTOCOL = 'https://'
30
+ HTTP_PROTOCOL = 'http://'
29
31
  DEFAULT_URL = 'https://secure.gooddata.com'
30
32
  LOGIN_PATH = '/gdc/account/login'
31
33
  TOKEN_PATH = '/gdc/account/token'
@@ -712,12 +714,12 @@ ERR
712
714
 
713
715
  def fix_server_url(server)
714
716
  server = server.chomp('/')
715
- if server.start_with? 'http://'
716
- server = server.sub 'http://', 'https://'
717
+ if server.start_with? HTTP_PROTOCOL
718
+ server = server.sub HTTP_PROTOCOL, HTTPS_PROTOCOL
717
719
  GoodData.logger.warn 'You specified the HTTP protocol in your server string. It has been autofixed to HTTPS.'
718
720
  end
719
721
 
720
- server = 'https://' + server unless server.start_with? 'https://'
722
+ server = HTTPS_PROTOCOL + server unless server.start_with? HTTPS_PROTOCOL
721
723
  server
722
724
  end
723
725
  end
@@ -1,51 +1,51 @@
1
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- hfEsRSEIk2pyyyg+MhVQ94xNXREq9/YQSP3seWp/47uAbQBKZATCGozSKuX5
13
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- +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
- 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
- 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
- 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
- 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
- +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
- bi1yTPovLT0HUYb5sRxuk/QV5YuoGy04bHoT5+beohnM5UjsKVIZsJ4XYqNM
40
- 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
- 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
- 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
- VvPpJkoVMdJb8h0up4Zrdk46WbdEg/+LixwfAs/oLVwEwa6bJp+6xDS2LcOR
44
- GPMLLmTtD3iddFDfeU8j/mhGCsO0AzacmxbsJDYr++iGAkTBvx6sP3HwjhPe
45
- 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
- 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
- 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
- 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
- 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
- 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
- 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