contentful-importer 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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