contentful-importer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +7 -0
  7. data/Gemfile.lock +87 -0
  8. data/LICENSE +22 -0
  9. data/README.md +327 -0
  10. data/Rakefile +7 -0
  11. data/bin/contentful-importer +85 -0
  12. data/contentful_importer.gemspec +38 -0
  13. data/example_settings/contentful_model.json +288 -0
  14. data/example_settings/contentful_structure.json +78 -0
  15. data/example_settings/files_meaning.txt +15 -0
  16. data/example_settings/settings.yml +16 -0
  17. data/lib/cli.rb +13 -0
  18. data/lib/configuration.rb +36 -0
  19. data/lib/converters/content_types_structure_creator.rb +60 -0
  20. data/lib/converters/contentful_model_to_json.rb +109 -0
  21. data/lib/importer/data_organizer.rb +117 -0
  22. data/lib/importer/mime_content_type.rb +564 -0
  23. data/lib/importer/parallel_importer.rb +429 -0
  24. data/lib/json_schema_validator.rb +64 -0
  25. data/lib/migrator.rb +39 -0
  26. data/lib/version.rb +3 -0
  27. data/spec/fixtures/import_files/assets/image/image_1.json +9 -0
  28. data/spec/fixtures/import_files/assets/image/image_2.json +9 -0
  29. data/spec/fixtures/import_files/assets/image/image_3.json +9 -0
  30. data/spec/fixtures/import_files/assets/image/image_4.json +9 -0
  31. data/spec/fixtures/import_files/collections/job_skills.json +13 -0
  32. data/spec/fixtures/import_files/collections/jobs.json +37 -0
  33. data/spec/fixtures/import_files/collections/profile.json +19 -0
  34. data/spec/fixtures/import_files/collections/user.json +36 -0
  35. data/spec/fixtures/import_files/entries/job_skills/job_skills_1.json +7 -0
  36. data/spec/fixtures/import_files/entries/job_skills/job_skills_10.json +7 -0
  37. data/spec/fixtures/import_files/entries/job_skills/job_skills_2.json +7 -0
  38. data/spec/fixtures/import_files/entries/job_skills/job_skills_3.json +7 -0
  39. data/spec/fixtures/import_files/entries/job_skills/job_skills_4.json +7 -0
  40. data/spec/fixtures/import_files/entries/job_skills/job_skills_5.json +7 -0
  41. data/spec/fixtures/import_files/entries/job_skills/job_skills_6.json +7 -0
  42. data/spec/fixtures/import_files/entries/job_skills/job_skills_7.json +7 -0
  43. data/spec/fixtures/import_files/entries/job_skills/job_skills_8.json +7 -0
  44. data/spec/fixtures/import_files/entries/job_skills/job_skills_9.json +7 -0
  45. data/spec/fixtures/import_files/entries/jobs/jobs_1.json +56 -0
  46. data/spec/fixtures/import_files/entries/jobs/jobs_2.json +55 -0
  47. data/spec/fixtures/import_files/entries/jobs/jobs_4.json +49 -0
  48. data/spec/fixtures/import_files/entries/profile/profile_1.json +12 -0
  49. data/spec/fixtures/import_files/entries/profile/profile_2.json +12 -0
  50. data/spec/fixtures/import_files/entries/user/user_1.json +24 -0
  51. data/spec/fixtures/import_files/entries/user/user_2.json +20 -0
  52. data/spec/fixtures/import_files/logs/log_published_assets.csv +4 -0
  53. data/spec/fixtures/import_files/logs/log_published_entries.csv +23 -0
  54. data/spec/fixtures/import_files/logs/success_assets.csv +4 -0
  55. data/spec/fixtures/import_files/logs/success_published_assets.csv +0 -0
  56. data/spec/fixtures/import_files/logs/success_published_entries.csv +22 -0
  57. data/spec/fixtures/import_files/logs/success_thread_0.csv +12 -0
  58. data/spec/fixtures/import_files/logs/success_thread_1.csv +11 -0
  59. data/spec/fixtures/import_files/threads/0/1TVvxCqoRq0qUYAOQuOqys_2.json +20 -0
  60. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_1.json +7 -0
  61. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_10.json +7 -0
  62. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_2.json +7 -0
  63. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_3.json +7 -0
  64. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_4.json +7 -0
  65. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_5.json +7 -0
  66. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_6.json +7 -0
  67. data/spec/fixtures/import_files/threads/0/2soCP557HGKoOOK0SqmMOm_7.json +7 -0
  68. data/spec/fixtures/import_files/threads/0/6H6pGAV1PUsuoAW26Iu48W_1.json +9 -0
  69. data/spec/fixtures/import_files/threads/0/6H6pGAV1PUsuoAW26Iu48W_2.json +9 -0
  70. data/spec/fixtures/import_files/threads/0/6H6pGAV1PUsuoAW26Iu48W_3.json +9 -0
  71. data/spec/fixtures/import_files/threads/0/6H6pGAV1PUsuoAW26Iu48W_4.json +9 -0
  72. data/spec/fixtures/import_files/threads/0/6H6pGAV1PUsuoAW26Iu48W_5.json +9 -0
  73. data/spec/fixtures/import_files/threads/1/1TVvxCqoRq0qUYAOQuOqys_1.json +24 -0
  74. data/spec/fixtures/import_files/threads/1/1TVvxCqoRq0qUYAOQuOqys_2.json +20 -0
  75. data/spec/fixtures/import_files/threads/1/2soCP557HGKoOOK0SqmMOm_6.json +7 -0
  76. data/spec/fixtures/import_files/threads/1/2soCP557HGKoOOK0SqmMOm_7.json +7 -0
  77. data/spec/fixtures/import_files/threads/1/2soCP557HGKoOOK0SqmMOm_8.json +7 -0
  78. data/spec/fixtures/import_files/threads/1/2soCP557HGKoOOK0SqmMOm_9.json +7 -0
  79. data/spec/fixtures/import_files/threads/1/4L1bg4WQ5aWQMiE82ouag_1.json +56 -0
  80. data/spec/fixtures/import_files/threads/1/4L1bg4WQ5aWQMiE82ouag_2.json +55 -0
  81. data/spec/fixtures/import_files/threads/1/4L1bg4WQ5aWQMiE82ouag_4.json +49 -0
  82. data/spec/fixtures/import_files/threads/1/4WFZh4MwC4Mc0EQWAeOY8A_1.json +12 -0
  83. data/spec/fixtures/import_files/threads/1/4WFZh4MwC4Mc0EQWAeOY8A_2.json +12 -0
  84. data/spec/fixtures/import_files/threads/assets/0/image_1.json +9 -0
  85. data/spec/fixtures/import_files/threads/assets/0/image_2.json +9 -0
  86. data/spec/fixtures/import_files/threads/assets/0/image_3.json +9 -0
  87. data/spec/fixtures/import_files/threads/assets/0/image_4.json +9 -0
  88. data/spec/fixtures/settings/contentful_model.json +252 -0
  89. data/spec/fixtures/settings/contentful_structure.json +73 -0
  90. data/spec/fixtures/settings/contentful_structure_test.json +66 -0
  91. data/spec/fixtures/settings/settings.yml +12 -0
  92. data/spec/fixtures/vcr_cassettes/create_asset.yml +621 -0
  93. data/spec/fixtures/vcr_cassettes/create_entry.yml +122 -0
  94. data/spec/fixtures/vcr_cassettes/create_space.yml +87 -0
  95. data/spec/fixtures/vcr_cassettes/import_assets.yml +2822 -0
  96. data/spec/fixtures/vcr_cassettes/import_content_types.yml +1915 -0
  97. data/spec/fixtures/vcr_cassettes/import_entries.yml +5066 -0
  98. data/spec/fixtures/vcr_cassettes/import_entry.yml +363 -0
  99. data/spec/fixtures/vcr_cassettes/invalid_credentials.yml +69 -0
  100. data/spec/fixtures/vcr_cassettes/publish_an_entry.yml +214 -0
  101. data/spec/fixtures/vcr_cassettes/publish_asset.yml +895 -0
  102. data/spec/fixtures/vcr_cassettes/publish_entries.yml +5121 -0
  103. data/spec/fixtures/vcr_cassettes/valid_credentials.yml +7360 -0
  104. data/spec/lib/configuration_spec.rb +22 -0
  105. data/spec/lib/importer/parallel_importer_spec.rb +161 -0
  106. data/spec/lib/json_schema_validator_spec.rb +63 -0
  107. data/spec/lib/migrator_spec.rb +99 -0
  108. data/spec/spec_helper.rb +12 -0
  109. data/spec/support/db_rows_json.rb +9 -0
  110. data/spec/support/shared_configuration.rb +22 -0
  111. data/spec/support/vcr.rb +16 -0
  112. metadata +436 -0
@@ -0,0 +1,429 @@
1
+ require_relative 'mime_content_type'
2
+ require_relative 'data_organizer'
3
+ require 'contentful/management'
4
+ require 'csv'
5
+ require 'yaml'
6
+ require 'api_cache'
7
+
8
+ module Contentful
9
+ class ParallelImporter
10
+
11
+ Encoding.default_external = 'utf-8'
12
+
13
+ attr_reader :space, :config, :logger, :data_organizer
14
+ attr_accessor :content_type
15
+
16
+ def initialize(settings)
17
+ @config = settings
18
+ @logger = Logger.new(STDOUT)
19
+ @data_organizer = Contentful::DataOrganizer.new(@config)
20
+ Contentful::Management::Client.new(config.config['access_token'], default_locale: config.config['default_locale'] || 'en-US')
21
+ end
22
+
23
+ def create_contentful_model(space)
24
+ initialize_space(space)
25
+ import_content_types
26
+ end
27
+
28
+ def import_data(threads)
29
+ clean_threads_dir_before_import(threads)
30
+ data_organizer.execute(threads)
31
+ import_in_threads
32
+ end
33
+
34
+ def test_credentials
35
+ spaces = Contentful::Management::Space.all
36
+ if spaces.is_a? Contentful::Management::Array
37
+ logger.info 'Contentful Management API credentials: OK'
38
+ end
39
+ rescue NoMethodError => _error
40
+ logger.info 'Contentful Management API credentials: INVALID (check README)'
41
+ end
42
+
43
+ def number_of_threads
44
+ number_of_threads = 0
45
+ Dir.glob("#{config.threads_dir}/*") do |thread|
46
+ number_of_threads += 1 if File.basename(thread).size == 1
47
+ end
48
+ number_of_threads
49
+ end
50
+
51
+ def import_in_threads
52
+ threads = []
53
+ number_of_threads.times do |thread_id|
54
+ threads << Thread.new do
55
+ self.class.new(config).import_entries("#{config.threads_dir}/#{thread_id}", config.space_id)
56
+ end
57
+ end
58
+ threads.each do |thread|
59
+ thread.join
60
+ end
61
+ end
62
+
63
+ def import_entries(path, space_id)
64
+ log_file_name = "success_thread_#{File.basename(path)}"
65
+ create_log_file(log_file_name)
66
+ load_log_files
67
+ Dir.glob("#{path}/*.json") do |entry_path|
68
+ content_type_id = File.basename(entry_path).match(/(.+)_\d+/)[1]
69
+ entry_file_name = File.basename(entry_path)
70
+ import_entry(entry_path, space_id, content_type_id, log_file_name) unless config.imported_entries.flatten.include?(entry_file_name)
71
+ end
72
+ end
73
+
74
+ def import_only_assets
75
+ create_log_file('success_assets')
76
+ assets_ids = Set.new(CSV.read("#{config.data_dir}/logs/success_assets.csv", 'r'))
77
+ Dir.glob("#{config.assets_dir}/**/*json") do |file_path|
78
+ asset_attributes = JSON.parse(File.read(file_path))
79
+ if asset_url_param_start_with_http?(asset_attributes) && asset_not_imported_yet?(asset_attributes, assets_ids)
80
+ import_asset(asset_attributes)
81
+ end
82
+ end
83
+ end
84
+
85
+ def import_asset(asset_attributes)
86
+ logger.info "Import asset - #{asset_attributes['id']} "
87
+ asset_title = asset_attributes['name'].present? ? asset_attributes['name'] : asset_attributes['id']
88
+ asset_file = create_asset_file(asset_title, asset_attributes)
89
+ space = Contentful::Management::Space.find(config.config['space_id'])
90
+ asset = space.assets.create(id: "#{asset_attributes['id']}", title: "#{asset_title}", description: '', file: asset_file)
91
+ asset_status(asset, asset_attributes)
92
+ end
93
+
94
+ def asset_url_param_start_with_http?(asset_attributes)
95
+ asset_attributes['url'] && asset_attributes['url'].start_with?('http')
96
+ end
97
+
98
+ def asset_not_imported_yet?(asset_attributes, assets_ids)
99
+ !assets_ids.to_a.flatten.include?(asset_attributes['id'])
100
+ end
101
+
102
+ def create_asset_file(asset_title, params)
103
+ Contentful::Management::File.new.tap do |file|
104
+ file.properties[:contentType] = file_content_type(params)
105
+ file.properties[:fileName] = asset_title
106
+ file.properties[:upload] = params['url']
107
+ end
108
+ end
109
+
110
+ def asset_status(asset, asset_attributes)
111
+ if asset.is_a?(Contentful::Management::Asset)
112
+ logger.info "Process asset - #{asset.id} "
113
+ asset.process_file
114
+ CSV.open("#{config.log_files_dir}/success_assets.csv", 'a') { |csv| csv << [asset.id] }
115
+ else
116
+ logger.info "Error - #{asset.message} "
117
+ CSV.open("#{config.log_files_dir}/failure_assets.csv", 'a') { |csv| csv << [asset_attributes['id']] }
118
+ end
119
+ end
120
+
121
+ def publish_entries_in_threads
122
+ threads =[]
123
+ number_of_threads.times do |thread_id|
124
+ threads << Thread.new do
125
+ self.class.new(config).publish_all_entries("#{config.threads_dir}/#{thread_id}")
126
+ end
127
+ end
128
+ threads.each do |thread|
129
+ thread.join
130
+ end
131
+ end
132
+
133
+ def publish_assets_in_threads(number_of_threads)
134
+ clean_assets_threads_dir_before_publish(number_of_threads)
135
+ data_organizer.split_assets_to_threads(number_of_threads)
136
+ threads =[]
137
+ number_of_threads.times do |thread_id|
138
+ threads << Thread.new do
139
+ self.class.new(config).publish_assets("#{config.threads_dir}/assets/#{thread_id}")
140
+ end
141
+ end
142
+ threads.each do |thread|
143
+ thread.join
144
+ end
145
+ end
146
+
147
+ def publish_assets(thread_dir)
148
+ create_log_file('success_published_assets')
149
+ config.published_assets << CSV.read("#{config.log_files_dir}/success_published_assets.csv", 'r').flatten
150
+ Dir.glob("#{thread_dir}/*json") do |asset_file|
151
+ asset_id = JSON.parse(File.read(asset_file))['id']
152
+ publish_asset(asset_id) unless config.published_assets.flatten.include?(asset_id)
153
+ end
154
+ end
155
+
156
+ def publish_asset(asset_id)
157
+ logger.info "Publish an Asset - ID: #{asset_id}"
158
+ asset = Contentful::Management::Asset.find(config.config['space_id'], asset_id).publish
159
+ publish_status(asset, asset_id, 'published_assets')
160
+ end
161
+
162
+ def publish_all_entries(thread_dir)
163
+ create_log_file('success_published_entries')
164
+ config.published_entries << CSV.read("#{config.log_files_dir}/success_published_entries.csv", 'r').flatten
165
+ Dir.glob("#{thread_dir}/*json") do |entry_file|
166
+ entry_id = JSON.parse(File.read(entry_file))['id']
167
+ publish_entry(entry_id) unless config.published_entries.flatten.include?(entry_id)
168
+ end
169
+ end
170
+
171
+ def publish_entry(entry_id)
172
+ logger.info "Publish entries for #{entry_id}."
173
+ entry = Contentful::Management::Entry.find(config.config['space_id'], entry_id).publish
174
+ publish_status(entry, entry_id, 'published_entries')
175
+ end
176
+
177
+ private
178
+
179
+ def initialize_space(space)
180
+ fail 'You need to specify \'--space_id\' argument to find an existing Space or \'--space_name\' to create a new Space.' if space[:space_id].nil? && [:space_name].nil?
181
+ @space = space[:space_id].present? ? Contentful::Management::Space.find(space[:space_id]) : create_space(space[:space_name])
182
+ end
183
+
184
+ def create_space(name_space)
185
+ logger.info "Creating a space with name: #{name_space}"
186
+ new_space = Contentful::Management::Space.create(name: name_space, organization_id: config.config['organization_id'])
187
+ logger.info "Space was created successfully! Space id: #{new_space.id}"
188
+ new_space
189
+ end
190
+
191
+ def import_content_types
192
+ Dir.glob("#{config.collections_dir}/*json") do |file_path|
193
+ collection_attributes = JSON.parse(File.read(file_path))
194
+ content_type = create_new_content_type(space, collection_attributes)
195
+ logger.info "Importing content_type: #{content_type.name}"
196
+ create_content_type_fields(collection_attributes, content_type)
197
+ content_type.update(displayField: collection_attributes['displayField']) if collection_attributes['displayField']
198
+ active_status(content_type.activate)
199
+ end
200
+ end
201
+
202
+ def get_id(params)
203
+ File.basename(params['id'] || params['url'])
204
+ end
205
+
206
+ def create_content_type_fields(collection_attributes, content_type)
207
+ fields = collection_attributes['fields'].each_with_object([]) do |field, fields|
208
+ fields << create_field(field)
209
+ end
210
+ content_type.fields = fields
211
+ content_type.save
212
+ end
213
+
214
+ def import_entry(file_path, space_id, content_type_id, log_file)
215
+ entry_attributes = JSON.parse(File.read(file_path))
216
+ logger.info "Creating entry: #{entry_attributes['id']}."
217
+ entry_params = create_entry_parameters(content_type_id, entry_attributes, space_id)
218
+ content_type = content_type(content_type_id, space_id)
219
+ entry = content_type.entries.create(entry_params)
220
+ import_status(entry, file_path, log_file)
221
+ end
222
+
223
+ def create_entry_parameters(content_type_id, entry_attributes, space_id)
224
+ entry_attributes.each_with_object({}) do |(attr, value), entry_params|
225
+ next if attr.start_with?('@')
226
+ entry_param = if value.is_a? Hash
227
+ parse_attributes_from_hash(value, space_id, content_type_id)
228
+ elsif value.is_a? Array
229
+ parse_attributes_from_array(value, space_id, content_type_id)
230
+ else
231
+ value
232
+ end
233
+ entry_params[attr.to_sym] = entry_param unless validate_param(entry_param)
234
+ end
235
+ end
236
+
237
+ def parse_attributes_from_hash(params, space_id, content_type_id)
238
+ type = params['type']
239
+ if type
240
+ case type
241
+ when 'Location'
242
+ create_location_file(params)
243
+ when 'File'
244
+ create_asset(space_id, params)
245
+ else
246
+ create_entry(params, space_id, content_type_id)
247
+ end
248
+ else
249
+ params
250
+ end
251
+ end
252
+
253
+ def parse_attributes_from_array(params, space_id, content_type_id)
254
+ params.each_with_object([]) do |attr, array_attributes|
255
+ value = if attr['type'].present? && attr['type'] != 'File'
256
+ create_entry(attr, space_id, content_type_id)
257
+ elsif attr['type'] == 'File'
258
+ create_asset(space_id, attr)
259
+ else
260
+ attr
261
+ end
262
+ array_attributes << value unless value.nil?
263
+ end
264
+ end
265
+
266
+ def import_status(entry, file_path, log_file)
267
+ if entry.is_a? Contentful::Management::Entry
268
+ entry_file_name = File.basename(file_path)
269
+ logger.info 'Imported successfully!'
270
+ CSV.open("#{config.log_files_dir}/#{log_file}.csv", 'a') { |csv| csv << [entry_file_name] }
271
+ else
272
+ logger.info "### Failure! - #{entry.message} - #{entry.response.raw}###"
273
+ failure_filename = log_file.match(/(thread_\d)/)[1]
274
+ CSV.open("#{config.log_files_dir}/failure_#{failure_filename}.csv", 'a') { |csv| csv << [file_path, entry.message, entry.response.raw] }
275
+ end
276
+ end
277
+
278
+ def content_type(content_type_id, space_id)
279
+ @content_type = APICache.get("content_type_#{content_type_id}", :period => -5) do
280
+ Contentful::Management::ContentType.find(space_id, content_type_id)
281
+ end
282
+ end
283
+
284
+ def create_entry(params, space_id, content_type_id)
285
+ entry_id = get_id(params)
286
+ content_type = content_type(content_type_id, space_id)
287
+ content_type.entries.new.tap do |entry|
288
+ entry.id = entry_id
289
+ end
290
+ end
291
+
292
+ def create_asset(space_id, params)
293
+ if params['id']
294
+ space = Contentful::Management::Space.find(space_id)
295
+ found_asset = space.assets.find(params['id'])
296
+ asset = found_asset.is_a?(Contentful::Management::Asset) ? found_asset : initialize_asset_file(params)
297
+ asset
298
+ end
299
+ end
300
+
301
+ def initialize_asset_file(params)
302
+ Contentful::Management::Asset.new.tap do |asset|
303
+ asset.id = params['id']
304
+ asset.link_type = 'Asset'
305
+ end
306
+ end
307
+
308
+ def create_location_file(params)
309
+ Contentful::Management::Location.new.tap do |file|
310
+ file.lat = params['lat']
311
+ file.lon = params['lng']
312
+ end
313
+ end
314
+
315
+ def create_field(field)
316
+ field_params = {id: field['id'], name: field['name'], required: field['required']}
317
+ field_params.merge!(additional_field_params(field))
318
+ logger.info "Creating field: #{field_params[:type]}"
319
+ create_content_type_field(field_params)
320
+ end
321
+
322
+ def create_content_type_field(field_params)
323
+ Contentful::Management::Field.new.tap do |field|
324
+ field.id = field_params[:id]
325
+ field.name = field_params[:name]
326
+ field.type = field_params[:type]
327
+ field.link_type = field_params[:link_type]
328
+ field.required = field_params[:required]
329
+ field.items = field_params[:items]
330
+ end
331
+ end
332
+
333
+ def active_status(ct_object)
334
+ if ct_object.is_a? Contentful::Management::Error
335
+ logger.info "### Failure! - #{ct_object.message} ! ###"
336
+ else
337
+ logger.info 'Successfully activated!'
338
+ end
339
+ end
340
+
341
+ def publish_status(ct_object, object_id, log_file_name)
342
+ if ct_object.is_a? Contentful::Management::Error
343
+ logger.info "### Failure! - #{ct_object.message} ! ###"
344
+ CSV.open("#{config.log_files_dir}/failure_#{log_file_name}.csv", 'a') { |csv| csv << [object_id] }
345
+ else
346
+ logger.info 'Successfully activated!'
347
+ CSV.open("#{config.log_files_dir}/success_#{log_file_name}.csv", 'a') { |csv| csv << [ct_object.id] }
348
+ end
349
+ end
350
+
351
+ def additional_field_params(field)
352
+ field_type = field['type']
353
+ if field_type == 'Entry' || field_type == 'Asset'
354
+ {type: 'Link', link_type: field_type}
355
+ elsif field_type == 'Array'
356
+ {type: 'Array', items: create_array_field(field)}
357
+ else
358
+ {type: field_type}
359
+ end
360
+ end
361
+
362
+ def validate_param(param)
363
+ if param.is_a? Array
364
+ param.empty?
365
+ else
366
+ param.nil?
367
+ end
368
+ end
369
+
370
+ def create_new_content_type(space, collection_attributes)
371
+ space.content_types.new.tap do |content_type|
372
+ content_type.id = collection_attributes['id']
373
+ content_type.name = collection_attributes['name']
374
+ content_type.description = collection_attributes['description']
375
+ end
376
+ end
377
+
378
+ def file_content_type(params)
379
+ MimeContentType::EXTENSION_LIST[File.extname(params['url'])]
380
+ end
381
+
382
+ def format_json(item)
383
+ JSON.pretty_generate(JSON.parse(item.to_json))
384
+ end
385
+
386
+ def create_array_field(params)
387
+ Contentful::Management::Field.new.tap do |field|
388
+ field.type = params['link'] || 'Link'
389
+ field.link_type = params['link_type']
390
+ end
391
+ end
392
+
393
+ def clean_threads_dir_before_import(threads)
394
+ threads.times do |thread|
395
+ if File.directory?("#{config.threads_dir}/#{thread}")
396
+ logger.info "Remove directory threads/#{thread} from #{config.threads_dir} path."
397
+ FileUtils.rm_r("#{config.threads_dir}/#{thread}")
398
+ end
399
+ end
400
+ end
401
+
402
+ def clean_assets_threads_dir_before_publish(threads)
403
+ threads.times do |thread|
404
+ if File.directory?("#{config.threads_dir}/assets/#{thread}")
405
+ logger.info "Remove directory threads/#{thread} from #{config.threads_dir}/assets path."
406
+ FileUtils.rm_r("#{config.threads_dir}/assets/#{thread}")
407
+ end
408
+ end
409
+ end
410
+
411
+ def create_directory(path)
412
+ FileUtils.mkdir_p(path) unless File.directory?(path)
413
+ end
414
+
415
+ def create_log_file(path)
416
+ create_directory("#{config.data_dir}/logs")
417
+ File.open("#{config.data_dir}/logs/#{path}.csv", 'a') { |file| file.write('') }
418
+ end
419
+
420
+ def load_log_files
421
+ Dir.glob("#{config.log_files_dir}/*.csv") do |log_files|
422
+ file_name = File.basename(log_files)
423
+ imported_ids = CSV.read(log_files, 'r').flatten
424
+ config.imported_entries << imported_ids if file_name.start_with?('success_thread') && !config.imported_entries.include?(imported_ids)
425
+ end
426
+ end
427
+
428
+ end
429
+ end
@@ -0,0 +1,64 @@
1
+ require 'json-schema'
2
+
3
+ module Contentful
4
+ class JsonSchemaValidator
5
+
6
+ attr_reader :config, :logger
7
+
8
+ def initialize(configuration)
9
+ @config = configuration
10
+ @logger = Logger.new(STDOUT)
11
+ end
12
+
13
+ def validate_schemas
14
+ Dir.glob("#{config.collections_dir}/*") do |content_type_file|
15
+ validate_schema(content_type_file)
16
+ end
17
+ end
18
+
19
+ def validate_schema(content_type_file)
20
+ schema = parse_content_type_schema(JSON.parse(File.read(content_type_file)))
21
+ content_type_filename = File.basename(content_type_file, '.*')
22
+ validate_entry(content_type_filename, schema)
23
+ end
24
+
25
+ def validate_entry(content_type_filename, schema)
26
+ Dir.glob("#{config.entries_dir}/#{content_type_filename}/*") do |entry_file|
27
+ entry_schema = JSON.parse(File.read(entry_file))
28
+ begin
29
+ JSON::Validator.validate!(schema, entry_schema)
30
+ rescue JSON::Schema::ValidationError => error
31
+ logger.info "#{error.message}! Path to invalid entry: #{entry_file}"
32
+ end
33
+ end
34
+ end
35
+
36
+ def parse_content_type_schema(ct_file)
37
+ new_hash = base_schema_format
38
+ ct_file['fields'].each do |key|
39
+ type = convert_type(key['type'])
40
+ new_hash['properties'].merge!({key['id'] => {'type' => type}})
41
+ end
42
+ new_hash
43
+ end
44
+
45
+ def base_schema_format
46
+ {'type' => 'object', 'properties' => {}}
47
+ end
48
+
49
+ def convert_type(type)
50
+ case type
51
+ when 'Text', 'Date', 'Symbol'
52
+ 'string'
53
+ when 'Number'
54
+ 'float'
55
+ when 'Asset', 'Entry'
56
+ 'object'
57
+ else
58
+ type.downcase
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+