contentful-importer 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,430 +0,0 @@
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_description = asset_attributes['description'].present? ? asset_attributes['description'] : ''
89
- asset_file = create_asset_file(asset_title, asset_attributes)
90
- space = Contentful::Management::Space.find(config.config['space_id'])
91
- asset = space.assets.create(id: "#{asset_attributes['id']}", title: "#{asset_title}", description: asset_description, file: asset_file)
92
- asset_status(asset, asset_attributes)
93
- end
94
-
95
- def asset_url_param_start_with_http?(asset_attributes)
96
- asset_attributes['url'] && asset_attributes['url'].start_with?('http')
97
- end
98
-
99
- def asset_not_imported_yet?(asset_attributes, assets_ids)
100
- !assets_ids.to_a.flatten.include?(asset_attributes['id'])
101
- end
102
-
103
- def create_asset_file(asset_title, params)
104
- Contentful::Management::File.new.tap do |file|
105
- file.properties[:contentType] = file_content_type(params)
106
- file.properties[:fileName] = asset_title
107
- file.properties[:upload] = params['url']
108
- end
109
- end
110
-
111
- def asset_status(asset, asset_attributes)
112
- if asset.is_a?(Contentful::Management::Asset)
113
- logger.info "Process asset - #{asset.id} "
114
- asset.process_file
115
- CSV.open("#{config.log_files_dir}/success_assets.csv", 'a') { |csv| csv << [asset.id] }
116
- else
117
- logger.info "Error - #{asset.message} "
118
- CSV.open("#{config.log_files_dir}/failure_assets.csv", 'a') { |csv| csv << [asset_attributes['id']] }
119
- end
120
- end
121
-
122
- def publish_entries_in_threads
123
- threads =[]
124
- number_of_threads.times do |thread_id|
125
- threads << Thread.new do
126
- self.class.new(config).publish_all_entries("#{config.threads_dir}/#{thread_id}")
127
- end
128
- end
129
- threads.each do |thread|
130
- thread.join
131
- end
132
- end
133
-
134
- def publish_assets_in_threads(number_of_threads)
135
- clean_assets_threads_dir_before_publish(number_of_threads)
136
- data_organizer.split_assets_to_threads(number_of_threads)
137
- threads =[]
138
- number_of_threads.times do |thread_id|
139
- threads << Thread.new do
140
- self.class.new(config).publish_assets("#{config.threads_dir}/assets/#{thread_id}")
141
- end
142
- end
143
- threads.each do |thread|
144
- thread.join
145
- end
146
- end
147
-
148
- def publish_assets(thread_dir)
149
- create_log_file('success_published_assets')
150
- config.published_assets << CSV.read("#{config.log_files_dir}/success_published_assets.csv", 'r').flatten
151
- Dir.glob("#{thread_dir}/*json") do |asset_file|
152
- asset_id = JSON.parse(File.read(asset_file))['id']
153
- publish_asset(asset_id) unless config.published_assets.flatten.include?(asset_id)
154
- end
155
- end
156
-
157
- def publish_asset(asset_id)
158
- logger.info "Publish an Asset - ID: #{asset_id}"
159
- asset = Contentful::Management::Asset.find(config.config['space_id'], asset_id).publish
160
- publish_status(asset, asset_id, 'published_assets')
161
- end
162
-
163
- def publish_all_entries(thread_dir)
164
- create_log_file('success_published_entries')
165
- config.published_entries << CSV.read("#{config.log_files_dir}/success_published_entries.csv", 'r').flatten
166
- Dir.glob("#{thread_dir}/*json") do |entry_file|
167
- entry_id = JSON.parse(File.read(entry_file))['id']
168
- publish_entry(entry_id) unless config.published_entries.flatten.include?(entry_id)
169
- end
170
- end
171
-
172
- def publish_entry(entry_id)
173
- logger.info "Publish entries for #{entry_id}."
174
- entry = Contentful::Management::Entry.find(config.config['space_id'], entry_id).publish
175
- publish_status(entry, entry_id, 'published_entries')
176
- end
177
-
178
- private
179
-
180
- def initialize_space(space)
181
- 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?
182
- @space = space[:space_id].present? ? Contentful::Management::Space.find(space[:space_id]) : create_space(space[:space_name])
183
- end
184
-
185
- def create_space(name_space)
186
- logger.info "Creating a space with name: #{name_space}"
187
- new_space = Contentful::Management::Space.create(name: name_space, organization_id: config.config['organization_id'])
188
- logger.info "Space was created successfully! Space id: #{new_space.id}"
189
- new_space
190
- end
191
-
192
- def import_content_types
193
- Dir.glob("#{config.collections_dir}/*json") do |file_path|
194
- collection_attributes = JSON.parse(File.read(file_path))
195
- content_type = create_new_content_type(space, collection_attributes)
196
- logger.info "Importing content_type: #{content_type.name}"
197
- create_content_type_fields(collection_attributes, content_type)
198
- content_type.update(displayField: collection_attributes['displayField']) if collection_attributes['displayField']
199
- active_status(content_type.activate)
200
- end
201
- end
202
-
203
- def get_id(params)
204
- File.basename(params['id'] || params['url'])
205
- end
206
-
207
- def create_content_type_fields(collection_attributes, content_type)
208
- fields = collection_attributes['fields'].each_with_object([]) do |field, fields|
209
- fields << create_field(field)
210
- end
211
- content_type.fields = fields
212
- content_type.save
213
- end
214
-
215
- def import_entry(file_path, space_id, content_type_id, log_file)
216
- entry_attributes = JSON.parse(File.read(file_path))
217
- logger.info "Creating entry: #{entry_attributes['id']}."
218
- entry_params = create_entry_parameters(content_type_id, entry_attributes, space_id)
219
- content_type = content_type(content_type_id, space_id)
220
- entry = content_type.entries.create(entry_params)
221
- import_status(entry, file_path, log_file)
222
- end
223
-
224
- def create_entry_parameters(content_type_id, entry_attributes, space_id)
225
- entry_attributes.each_with_object({}) do |(attr, value), entry_params|
226
- next if attr.start_with?('@')
227
- entry_param = if value.is_a? Hash
228
- parse_attributes_from_hash(value, space_id, content_type_id)
229
- elsif value.is_a? Array
230
- parse_attributes_from_array(value, space_id, content_type_id)
231
- else
232
- value
233
- end
234
- entry_params[attr.to_sym] = entry_param unless validate_param(entry_param)
235
- end
236
- end
237
-
238
- def parse_attributes_from_hash(params, space_id, content_type_id)
239
- type = params['type']
240
- if type
241
- case type
242
- when 'Location'
243
- create_location_file(params)
244
- when 'File'
245
- create_asset(space_id, params)
246
- else
247
- create_entry(params, space_id, content_type_id)
248
- end
249
- else
250
- params
251
- end
252
- end
253
-
254
- def parse_attributes_from_array(params, space_id, content_type_id)
255
- params.each_with_object([]) do |attr, array_attributes|
256
- value = if attr['type'].present? && attr['type'] != 'File'
257
- create_entry(attr, space_id, content_type_id)
258
- elsif attr['type'] == 'File'
259
- create_asset(space_id, attr)
260
- else
261
- attr
262
- end
263
- array_attributes << value unless value.nil?
264
- end
265
- end
266
-
267
- def import_status(entry, file_path, log_file)
268
- if entry.is_a? Contentful::Management::Entry
269
- entry_file_name = File.basename(file_path)
270
- logger.info 'Imported successfully!'
271
- CSV.open("#{config.log_files_dir}/#{log_file}.csv", 'a') { |csv| csv << [entry_file_name] }
272
- else
273
- logger.info "### Failure! - #{entry.message} - #{entry.response.raw}###"
274
- failure_filename = log_file.match(/(thread_\d)/)[1]
275
- CSV.open("#{config.log_files_dir}/failure_#{failure_filename}.csv", 'a') { |csv| csv << [file_path, entry.message, entry.response.raw] }
276
- end
277
- end
278
-
279
- def content_type(content_type_id, space_id)
280
- @content_type = APICache.get("content_type_#{content_type_id}", :period => -5) do
281
- Contentful::Management::ContentType.find(space_id, content_type_id)
282
- end
283
- end
284
-
285
- def create_entry(params, space_id, content_type_id)
286
- entry_id = get_id(params)
287
- content_type = content_type(content_type_id, space_id)
288
- content_type.entries.new.tap do |entry|
289
- entry.id = entry_id
290
- end
291
- end
292
-
293
- def create_asset(space_id, params)
294
- if params['id']
295
- space = Contentful::Management::Space.find(space_id)
296
- found_asset = space.assets.find(params['id'])
297
- asset = found_asset.is_a?(Contentful::Management::Asset) ? found_asset : initialize_asset_file(params)
298
- asset
299
- end
300
- end
301
-
302
- def initialize_asset_file(params)
303
- Contentful::Management::Asset.new.tap do |asset|
304
- asset.id = params['id']
305
- asset.link_type = 'Asset'
306
- end
307
- end
308
-
309
- def create_location_file(params)
310
- Contentful::Management::Location.new.tap do |file|
311
- file.lat = params['lat']
312
- file.lon = params['lng']
313
- end
314
- end
315
-
316
- def create_field(field)
317
- field_params = {id: field['id'], name: field['name'], required: field['required']}
318
- field_params.merge!(additional_field_params(field))
319
- logger.info "Creating field: #{field_params[:type]}"
320
- create_content_type_field(field_params)
321
- end
322
-
323
- def create_content_type_field(field_params)
324
- Contentful::Management::Field.new.tap do |field|
325
- field.id = field_params[:id]
326
- field.name = field_params[:name]
327
- field.type = field_params[:type]
328
- field.link_type = field_params[:link_type]
329
- field.required = field_params[:required]
330
- field.items = field_params[:items]
331
- end
332
- end
333
-
334
- def active_status(ct_object)
335
- if ct_object.is_a? Contentful::Management::Error
336
- logger.info "### Failure! - #{ct_object.message} ! ###"
337
- else
338
- logger.info 'Successfully activated!'
339
- end
340
- end
341
-
342
- def publish_status(ct_object, object_id, log_file_name)
343
- if ct_object.is_a? Contentful::Management::Error
344
- logger.info "### Failure! - #{ct_object.message} ! ###"
345
- CSV.open("#{config.log_files_dir}/failure_#{log_file_name}.csv", 'a') { |csv| csv << [object_id] }
346
- else
347
- logger.info 'Successfully activated!'
348
- CSV.open("#{config.log_files_dir}/success_#{log_file_name}.csv", 'a') { |csv| csv << [ct_object.id] }
349
- end
350
- end
351
-
352
- def additional_field_params(field)
353
- field_type = field['type']
354
- if field_type == 'Entry' || field_type == 'Asset'
355
- {type: 'Link', link_type: field_type}
356
- elsif field_type == 'Array'
357
- {type: 'Array', items: create_array_field(field)}
358
- else
359
- {type: field_type}
360
- end
361
- end
362
-
363
- def validate_param(param)
364
- if param.is_a? Array
365
- param.empty?
366
- else
367
- param.nil?
368
- end
369
- end
370
-
371
- def create_new_content_type(space, collection_attributes)
372
- space.content_types.new.tap do |content_type|
373
- content_type.id = collection_attributes['id']
374
- content_type.name = collection_attributes['name']
375
- content_type.description = collection_attributes['description']
376
- end
377
- end
378
-
379
- def file_content_type(params)
380
- params['contentType'].present? ? params['contentType'] : MimeContentType::EXTENSION_LIST[File.extname(params['url'])]
381
- end
382
-
383
- def format_json(item)
384
- JSON.pretty_generate(JSON.parse(item.to_json))
385
- end
386
-
387
- def create_array_field(params)
388
- Contentful::Management::Field.new.tap do |field|
389
- field.type = params['link'] || 'Link'
390
- field.link_type = params['link_type']
391
- end
392
- end
393
-
394
- def clean_threads_dir_before_import(threads)
395
- threads.times do |thread|
396
- if File.directory?("#{config.threads_dir}/#{thread}")
397
- logger.info "Remove directory threads/#{thread} from #{config.threads_dir} path."
398
- FileUtils.rm_r("#{config.threads_dir}/#{thread}")
399
- end
400
- end
401
- end
402
-
403
- def clean_assets_threads_dir_before_publish(threads)
404
- threads.times do |thread|
405
- if File.directory?("#{config.threads_dir}/assets/#{thread}")
406
- logger.info "Remove directory threads/#{thread} from #{config.threads_dir}/assets path."
407
- FileUtils.rm_r("#{config.threads_dir}/assets/#{thread}")
408
- end
409
- end
410
- end
411
-
412
- def create_directory(path)
413
- FileUtils.mkdir_p(path) unless File.directory?(path)
414
- end
415
-
416
- def create_log_file(path)
417
- create_directory("#{config.data_dir}/logs")
418
- File.open("#{config.data_dir}/logs/#{path}.csv", 'a') { |file| file.write('') }
419
- end
420
-
421
- def load_log_files
422
- Dir.glob("#{config.log_files_dir}/*.csv") do |log_files|
423
- file_name = File.basename(log_files)
424
- imported_ids = CSV.read(log_files, 'r').flatten
425
- config.imported_entries << imported_ids if file_name.start_with?('success_thread') && !config.imported_entries.include?(imported_ids)
426
- end
427
- end
428
-
429
- end
430
- end
@@ -1,64 +0,0 @@
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
-
data/lib/migrator.rb DELETED
@@ -1,39 +0,0 @@
1
- require_relative 'importer/parallel_importer'
2
- require_relative 'configuration'
3
- require_relative 'converters/contentful_model_to_json'
4
- require_relative 'json_schema_validator'
5
-
6
- class Migrator
7
-
8
- attr_reader :importer, :converter, :config, :json_validator
9
-
10
- def initialize(settings)
11
- @config = Contentful::Configuration.new(settings)
12
- @importer = Contentful::ParallelImporter.new(@config)
13
- @converter = Contentful::Converter::ContentfulModelToJson.new(@config)
14
- @json_validator = Contentful::JsonSchemaValidator.new(@config)
15
- end
16
-
17
- def run(action, options = {})
18
- case action.to_s
19
- when '--create-contentful-model-from-json'
20
- converter.create_content_type_json
21
- when '--import-content-types'
22
- importer.create_contentful_model(options)
23
- when '--import'
24
- importer.import_data(options[:threads])
25
- when '--convert-content-model-to-json'
26
- converter.convert_to_import_form
27
- when '--publish-entries'
28
- importer.publish_entries_in_threads
29
- when '--test-credentials'
30
- importer.test_credentials
31
- when '--import-assets'
32
- importer.import_only_assets
33
- when '--publish-assets'
34
- importer.publish_assets_in_threads(options[:threads])
35
- when '--validate-schema'
36
- json_validator.validate_schemas
37
- end
38
- end
39
- end
data/lib/version.rb DELETED
@@ -1,3 +0,0 @@
1
- module Version
2
- VERSION = '0.0.2'
3
- end