database-exporter 0.0.1

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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +7 -0
  6. data/Gemfile.lock +74 -0
  7. data/LICENSE +22 -0
  8. data/README.md +682 -0
  9. data/Rakefile +7 -0
  10. data/bin/database-exporter +48 -0
  11. data/database_exporter.gemspec +34 -0
  12. data/example_data/contentful_model.json +316 -0
  13. data/example_data/contentful_structure.json +89 -0
  14. data/example_data/example_settings.yml +25 -0
  15. data/example_data/mapping.json +119 -0
  16. data/lib/cli.rb +13 -0
  17. data/lib/configuration.rb +69 -0
  18. data/lib/converters/content_types_structure_creator.rb +58 -0
  19. data/lib/converters/contentful_model_to_json.rb +78 -0
  20. data/lib/database/export.rb +74 -0
  21. data/lib/database/modules/json_export.rb +79 -0
  22. data/lib/database/modules/relations_export.rb +270 -0
  23. data/lib/database/modules/utils.rb +20 -0
  24. data/lib/migrator.rb +29 -0
  25. data/lib/version.rb +3 -0
  26. data/spec/fixtures/database/data/assets/image/image_1.json +9 -0
  27. data/spec/fixtures/database/data/assets/image/image_2.json +9 -0
  28. data/spec/fixtures/database/data/assets/image/image_3.json +9 -0
  29. data/spec/fixtures/database/data/assets/image/image_4.json +9 -0
  30. data/spec/fixtures/database/data/collections/comment.json +18 -0
  31. data/spec/fixtures/database/data/collections/job_skills.json +13 -0
  32. data/spec/fixtures/database/data/collections/jobs.json +44 -0
  33. data/spec/fixtures/database/data/collections/profile.json +19 -0
  34. data/spec/fixtures/database/data/collections/user.json +36 -0
  35. data/spec/fixtures/database/data/entries/comment/comment_1.json +9 -0
  36. data/spec/fixtures/database/data/entries/comment/comment_2.json +9 -0
  37. data/spec/fixtures/database/data/entries/comment/comment_3.json +9 -0
  38. data/spec/fixtures/database/data/entries/comment/comment_4.json +9 -0
  39. data/spec/fixtures/database/data/entries/comment/comment_5.json +9 -0
  40. data/spec/fixtures/database/data/entries/job_skills/job_skills_1.json +7 -0
  41. data/spec/fixtures/database/data/entries/job_skills/job_skills_10.json +7 -0
  42. data/spec/fixtures/database/data/entries/job_skills/job_skills_2.json +7 -0
  43. data/spec/fixtures/database/data/entries/job_skills/job_skills_3.json +7 -0
  44. data/spec/fixtures/database/data/entries/job_skills/job_skills_4.json +7 -0
  45. data/spec/fixtures/database/data/entries/job_skills/job_skills_5.json +7 -0
  46. data/spec/fixtures/database/data/entries/job_skills/job_skills_6.json +7 -0
  47. data/spec/fixtures/database/data/entries/job_skills/job_skills_7.json +7 -0
  48. data/spec/fixtures/database/data/entries/job_skills/job_skills_8.json +7 -0
  49. data/spec/fixtures/database/data/entries/job_skills/job_skills_9.json +7 -0
  50. data/spec/fixtures/database/data/entries/jobs/jobs_1.json +56 -0
  51. data/spec/fixtures/database/data/entries/jobs/jobs_2.json +55 -0
  52. data/spec/fixtures/database/data/entries/jobs/jobs_4.json +49 -0
  53. data/spec/fixtures/database/data/entries/profile/profile_1.json +12 -0
  54. data/spec/fixtures/database/data/entries/profile/profile_2.json +12 -0
  55. data/spec/fixtures/database/data/entries/user/user_1.json +24 -0
  56. data/spec/fixtures/database/data/entries/user/user_2.json +20 -0
  57. data/spec/fixtures/database/data/helpers/job_add_id_comments.json +11 -0
  58. data/spec/fixtures/database/data/helpers/job_add_id_job_add_skills.json +24 -0
  59. data/spec/fixtures/database/data/helpers/user_id_job_adds.json +9 -0
  60. data/spec/fixtures/database/data/helpers/user_id_profiles.json +8 -0
  61. data/spec/fixtures/database/data/table_names.json +10 -0
  62. data/spec/fixtures/database/table_names.json +4 -0
  63. data/spec/fixtures/development.sqlite3 +0 -0
  64. data/spec/fixtures/json_responses/transformed_row.json +7 -0
  65. data/spec/fixtures/json_row/row.json +6 -0
  66. data/spec/fixtures/settings/contentful_model.json +316 -0
  67. data/spec/fixtures/settings/contentful_structure.json +89 -0
  68. data/spec/fixtures/settings/contentful_structure_test.json +82 -0
  69. data/spec/fixtures/settings/mapping.json +119 -0
  70. data/spec/fixtures/settings/settings.yml +27 -0
  71. data/spec/lib/configuration_spec.rb +17 -0
  72. data/spec/lib/database/export_spec.rb +49 -0
  73. data/spec/lib/database/json_export_spec.rb +49 -0
  74. data/spec/lib/database/relations_export_spec.rb +201 -0
  75. data/spec/lib/migrator_spec.rb +112 -0
  76. data/spec/spec_helper.rb +12 -0
  77. data/spec/support/db_rows_json.rb +9 -0
  78. data/spec/support/shared_configuration.rb +27 -0
  79. metadata +358 -0
@@ -0,0 +1,270 @@
1
+ require 'i18n'
2
+
3
+ module Contentful
4
+ module Exporter
5
+ module Database
6
+ module RelationsExport
7
+
8
+ RELATIONS = [:many, :many_through, :aggregate_many, :aggregate_through, :has_one, :aggregate_has_one]
9
+
10
+ def generate_relations_helper_indexes(relations)
11
+ create_directory(config.helpers_dir)
12
+ relations.each do |relation_type, linked_models|
13
+ save_relation_foreign_keys(relation_type, linked_models) if RELATIONS.include?(relation_type.to_sym)
14
+ end
15
+ end
16
+
17
+ def save_relation_foreign_keys(relation_type, linked_models)
18
+ linked_models.each do |linked_model|
19
+ save_relation_foreign_keys_for_model(linked_model, relation_type)
20
+ end
21
+ end
22
+
23
+ def save_relation_foreign_keys_for_model(linked_model, relation_type)
24
+ primary_id = linked_model[:primary_id]
25
+ case relation_type.to_sym
26
+ when :many_through, :aggregate_through
27
+ related_model = linked_model[:through]
28
+ related_model_id = linked_model[:foreign_id]
29
+ when :many, :aggregate_many, :has_one, :aggregate_has_one
30
+ related_model = linked_model[:relation_to]
31
+ related_model_id = linked_model[:foreign_id] || :id
32
+ end
33
+ save_foreign_keys(related_model, primary_id, related_model_id)
34
+ end
35
+
36
+ def save_foreign_keys(related_model, primary_id, related_model_id)
37
+ results = config.db[related_model.underscore.to_sym].each_with_object({}) do |row, results|
38
+ add_index_to_helper_hash(results, row, primary_id, related_model_id)
39
+ end
40
+ write_json_to_file(config.helpers_dir + "/#{primary_id}_#{related_model.underscore}.json", results)
41
+ end
42
+
43
+ def add_index_to_helper_hash(results, row, primary_id, id)
44
+ id, primary_id = id.to_sym, primary_id.to_sym
45
+ if results[row[primary_id]].nil?
46
+ results[row[primary_id]] = [row[id]] if row[id]
47
+ else
48
+ results[row[primary_id]] << row[id] if row[id]
49
+ end
50
+ results
51
+ end
52
+
53
+ def map_relations_to_links(model_name, relations)
54
+ records = 0
55
+ model_subdirectory = I18n.transliterate(model_content_type(model_name)).underscore.tr(' ', '_')
56
+ Dir.glob("#{config.entries_dir}/#{model_subdirectory}/*json") do |entry_path|
57
+ map_entry_relations(entry_path, model_name, relations, records)
58
+ records += 1
59
+ end
60
+ end
61
+
62
+ def relations_from_mapping
63
+ mapping.each_with_object({}) do |(model_name, model_mapping), relations|
64
+ relations[model_name] = model_mapping[:links] if model_mapping[:links].present?
65
+ end
66
+ end
67
+
68
+ def map_entry_relations(entry_path, model_name, relations, record)
69
+ relations.each do |relation_type, linked_models|
70
+ logger.info "Mapping #{model_name} - relation: #{relation_type} - #{linked_models}, record: #{record}" if record % 1000 == 0
71
+ map_entry_relation(entry_path, relation_type, linked_models, model_name)
72
+ end
73
+ end
74
+
75
+ def map_entry_relation(entry_path, relation_type, linked_models, model_name)
76
+ entry = JSON.parse(File.read(entry_path))
77
+ linked_models.each do |linked_model|
78
+ relationships(entry, entry_path, relation_type, model_name, linked_model)
79
+ end
80
+ end
81
+
82
+ def relationships(entry, entry_path, relation_type, model_name, linked_model)
83
+ case relation_type.to_sym
84
+ when :belongs_to
85
+ map_belongs_to_association(model_name, linked_model, entry, entry_path)
86
+ when :has_one
87
+ map_has_one_association(model_name, linked_model, entry, entry_path, :relation_to)
88
+ when :many
89
+ map_many_association(model_name, linked_model, entry, entry_path, :relation_to)
90
+ when :many_through
91
+ map_many_association(model_name, linked_model, entry, entry_path, :through)
92
+ when :aggregate_through
93
+ aggregate_data(model_name, linked_model, entry, entry_path, :through)
94
+ when :aggregate_many
95
+ aggregate_data(model_name, linked_model, entry, entry_path, :relation_to)
96
+ when :aggregate_belongs
97
+ aggregate_belongs(linked_model, entry, entry_path, :relation_to)
98
+ when :aggregate_has_one
99
+ aggregate_has_one(linked_model, entry, entry_path, :relation_to)
100
+ end
101
+ end
102
+
103
+ def model_content_type(model_name)
104
+ mapping[model_name][:content_type]
105
+ end
106
+
107
+ def map_belongs_to_association(model_name, linked_model, entry, entry_path)
108
+ ct_link_type = contentful_field_attribute(model_name, linked_model[:relation_to], :type)
109
+ ct_field_id = contentful_field_attribute(model_name, linked_model[:relation_to], :id)
110
+ save_belongs_to_entries(linked_model, ct_link_type, ct_field_id, entry, entry_path)
111
+ end
112
+
113
+ def contentful_field_attribute(model_name, associated_model, type)
114
+ contentful_model_hash(model_name)
115
+ contentful_model_fields(model_name)
116
+ contentful_associated_model_name(model_name, associated_model)
117
+ contentful_associated_parameters(model_name, associated_model)
118
+ config.contentful_structure[model_content_type(model_name)][:fields][model_content_type(associated_model)][type]
119
+ end
120
+
121
+ def contentful_model_hash(model_name)
122
+ fail ArgumentError, "Missing #{model_name} in contentful structure JSON file" unless config.contentful_structure[model_content_type(model_name)]
123
+ end
124
+
125
+ def contentful_model_fields(model_name)
126
+ fail ArgumentError, "Missing fields in #{model_name} in contentful structure JSON file" unless config.contentful_structure[model_content_type(model_name)][:fields]
127
+ end
128
+
129
+ def contentful_associated_model_name(model_name, associated_model)
130
+ fail ArgumentError, "Missing associated model content type name for #{model_name} in MAPPING JSON file" unless model_content_type(associated_model)
131
+ end
132
+
133
+ def contentful_associated_parameters(model_name, associated_model)
134
+ fail ArgumentError, "Missing link field for #{model_content_type(associated_model)} in #{model_name} in contentful structure JSON file!" unless config.contentful_structure[model_content_type(model_name)][:fields][model_content_type(associated_model)]
135
+ end
136
+
137
+ def save_belongs_to_entries(linked_model, ct_link_type, ct_field_id, entry, entry_path)
138
+ content_type = I18n.transliterate(model_content_type(linked_model[:relation_to])).underscore.tr(' ', '_')
139
+ foreign_id = linked_model[:foreign_id]
140
+ if entry[foreign_id].present?
141
+ case ct_link_type
142
+ when 'Asset'
143
+ type = 'File'
144
+ when 'Entry'
145
+ type = 'Entry'
146
+ end
147
+ object = {
148
+ 'type' => type,
149
+ 'id' => "#{content_type}_#{entry[foreign_id]}"
150
+ }
151
+ write_json_to_file(entry_path, entry.merge!(ct_field_id => object))
152
+ end
153
+ end
154
+
155
+ def save_many_entries(linked_model, ct_field_id, entry, entry_path, related_to, ct_type)
156
+ related_model = linked_model[related_to].underscore
157
+ contentful_name = I18n.transliterate(model_content_type(linked_model[:relation_to])).underscore.tr(' ', '_')
158
+ objects = entry[ct_field_id] || []
159
+ associated_objects = add_associated_object_to_file(entry, related_model, contentful_name, linked_model[:primary_id], ct_type)
160
+ objects.concat(associated_objects) if objects.present? && associated_objects.present? && objects.is_a?(Array)
161
+ save_objects = objects.present? ? objects : associated_objects
162
+ write_json_to_file(entry_path, entry.merge!(ct_field_id => save_objects)) if save_objects.present?
163
+ end
164
+
165
+ def save_has_one_entry(linked_model, ct_field_id, entry, entry_path, related_to, ct_type)
166
+ related_model = linked_model[related_to].underscore
167
+ contentful_name = I18n.transliterate(model_content_type(linked_model[:relation_to])).underscore.tr(' ', '_')
168
+ associated_object = add_associated_object_to_file(entry, related_model, contentful_name, linked_model[:primary_id], ct_type)
169
+ write_json_to_file(entry_path, entry.merge!(ct_field_id => associated_object.first)) if associated_object.present?
170
+ end
171
+
172
+ def map_many_association(model_name, linked_model, entry, entry_path, related_to)
173
+ ct_field_id = contentful_field_attribute(model_name, linked_model[:relation_to], :id)
174
+ ct_type = mapping[linked_model[:relation_to]][:type] if mapping[linked_model[:relation_to]]
175
+ save_many_entries(linked_model, ct_field_id, entry, entry_path, related_to, ct_type)
176
+ end
177
+
178
+ def map_has_one_association(model_name, linked_model, entry, entry_path, related_to)
179
+ ct_field_id = contentful_field_attribute(model_name, linked_model[:relation_to], :id)
180
+ ct_type = mapping[linked_model[:relation_to]][:type] if mapping[linked_model[:relation_to]]
181
+ save_has_one_entry(linked_model, ct_field_id, entry, entry_path, related_to, ct_type)
182
+ end
183
+
184
+ def add_associated_object_to_file(entry, related_model, contentful_name, primary_id, ct_type)
185
+ Dir.glob("#{config.helpers_dir}/#{primary_id}_#{related_model}.json") do |through_file|
186
+ hash_with_foreign_keys = JSON.parse(File.read(through_file))
187
+ return build_hash_with_associated_objects(hash_with_foreign_keys, entry, contentful_name, ct_type)
188
+ end
189
+ end
190
+
191
+ def build_hash_with_associated_objects(hash_with_foreign_keys, entry, contentful_name, ct_type)
192
+ if hash_with_foreign_keys.has_key?(entry['database_id'].to_s)
193
+ associated_objects = hash_with_foreign_keys[entry['database_id'].to_s].each_with_object([]) do |foreign_key, result|
194
+ type = case ct_type
195
+ when 'entry'
196
+ contentful_name
197
+ when 'asset'
198
+ 'File'
199
+ end
200
+ result << {
201
+ 'type' => type,
202
+ 'id' => "#{contentful_name}_#{foreign_key}"
203
+ }
204
+ end
205
+ end
206
+ associated_objects
207
+ end
208
+
209
+ def aggregate_data(model_name, linked_model, entry, entry_path, related_to)
210
+ ct_field_id = contentful_field_attribute(model_name, linked_model[:relation_to], :id)
211
+ save_aggregated_entries(linked_model, ct_field_id, entry, entry_path, related_to)
212
+ end
213
+
214
+ def aggregate_has_one(linked_model, entry, entry_path, related_to)
215
+ ct_field_id = linked_model[:save_as] || linked_model[:field]
216
+ related_model = linked_model[related_to].underscore
217
+ related_model_directory = I18n.transliterate(mapping[linked_model[related_to]][:content_type]).underscore.tr(' ', '_')
218
+ save_aggregated_has_one_data(entry_path, entry, related_model, related_model_directory, linked_model, ct_field_id)
219
+ end
220
+
221
+ def save_aggregated_has_one_data(entry_path, entry, related_model, related_model_directory, linked_model, ct_field_id)
222
+ primary_id = linked_model[:primary_id]
223
+ hash_with_foreign_keys = JSON.parse(File.read("#{config.helpers_dir}/#{primary_id}_#{related_model}.json"))
224
+ if hash_with_foreign_keys.has_key?(entry['database_id'].to_s)
225
+ related_file_id = hash_with_foreign_keys[entry['database_id'].to_s].first if hash_with_foreign_keys[entry['database_id'].to_s].present?
226
+ entry[ct_field_id] = JSON.parse(File.read("#{config.entries_dir}/#{related_model_directory}/#{related_model_directory}_#{related_file_id}.json"))[linked_model[:field]]
227
+ write_json_to_file(entry_path, entry)
228
+ end
229
+ end
230
+
231
+ def aggregate_belongs(linked_model, entry, entry_path, related_to)
232
+ if entry[linked_model[:primary_id]]
233
+ related_model = linked_model[related_to]
234
+ ct_field_id = linked_model[:save_as] || linked_model[:field]
235
+ related_model_directory = I18n.transliterate(mapping[related_model][:content_type]).underscore.tr(' ', '_')
236
+ associated_foreign_key = related_model_directory + '_' + entry[linked_model[:primary_id]].to_s
237
+ associated_object = JSON.parse(File.read("#{config.entries_dir}/#{related_model_directory}/#{associated_foreign_key}.json"))[linked_model[:field]]
238
+ write_json_to_file(entry_path, entry.merge!(ct_field_id => associated_object))
239
+ end
240
+ end
241
+
242
+ def save_aggregated_entries(linked_model, ct_field_id, entry, entry_path, related_to)
243
+ ct_field = linked_model['save_as'] || ct_field_id
244
+ related_model = linked_model[related_to].underscore
245
+ contentful_name = model_content_type(linked_model[:relation_to]).underscore
246
+ associated_objects = save_aggregated_object_to_file(entry, related_model, contentful_name, linked_model)
247
+ write_json_to_file(entry_path, entry.merge!(ct_field => associated_objects)) if associated_objects.present?
248
+ end
249
+
250
+ def save_aggregated_object_to_file(entry, related_model, contentful_name, linked_model)
251
+ primary_id = linked_model[:primary_id]
252
+ Dir.glob("#{config.helpers_dir}/#{primary_id}_#{related_model}.json") do |through_file|
253
+ hash_with_foreign_keys = JSON.parse(File.read(through_file))
254
+ return hash_with_aggregate_objects(hash_with_foreign_keys, entry, contentful_name, linked_model)
255
+ end
256
+ end
257
+
258
+ def hash_with_aggregate_objects(hash_with_foreign_keys, entry, contentful_name, linked_model)
259
+ if hash_with_foreign_keys.has_key?(entry['database_id'].to_s)
260
+ associated_objects = hash_with_foreign_keys[entry['database_id'].to_s].each_with_object([]) do |foreign_key, result|
261
+ aggregated_file = JSON.parse(File.read("#{config.entries_dir}/#{contentful_name}/#{contentful_name}_#{foreign_key}.json"))
262
+ result << aggregated_file[linked_model[:field]]
263
+ end
264
+ end
265
+ associated_objects
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,20 @@
1
+ module Contentful
2
+ module Exporter
3
+ module Database
4
+ module Utils
5
+
6
+ def write_json_to_file(path, data)
7
+ File.open(path, 'w') do |file|
8
+ file.write(JSON.pretty_generate(data))
9
+ end
10
+ end
11
+
12
+ def create_directory(path)
13
+ FileUtils.mkdir_p(path) unless File.directory?(path)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
20
+
data/lib/migrator.rb ADDED
@@ -0,0 +1,29 @@
1
+ require_relative 'database/export'
2
+ require_relative 'configuration'
3
+ require_relative 'converters/contentful_model_to_json'
4
+
5
+ class Migrator
6
+
7
+ attr_reader :exporter, :config, :converter
8
+
9
+ def initialize(settings)
10
+ @config = Contentful::Configuration.new(settings)
11
+ @exporter = Contentful::Exporter::Database::Export.new(config)
12
+ @converter = Contentful::Converter::ContentfulModelToJson.new(config)
13
+ end
14
+
15
+ def run(action)
16
+ case action.to_s
17
+ when '--extract-to-json'
18
+ exporter.save_data_as_json
19
+ when '--create-content-model-from-json'
20
+ converter.create_content_type_json
21
+ when '--prepare-json'
22
+ exporter.create_data_relations
23
+ when '--list-tables'
24
+ exporter.tables_name
25
+ when '--convert-content-model-to-json'
26
+ converter.convert_to_import_form
27
+ end
28
+ end
29
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Version
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "image_1",
3
+ "name": "Image 1",
4
+ "url": "http://new1.fjcdn.com/thumbnails/comments/4019311+_9f47119b44b5043d82c74008dc40397d.jpg",
5
+ "description": "Fire",
6
+ "created_at": "2014-12-16T10:22:19+00:00",
7
+ "updated_at": "2014-12-16T10:22:19+00:00",
8
+ "database_id": 1
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "image_2",
3
+ "name": "Image 2 - water",
4
+ "url": "http://csironewsblog.files.wordpress.com/2013/06/water_cover1.jpg",
5
+ "description": "water",
6
+ "created_at": "2014-12-16T10:23:17+00:00",
7
+ "updated_at": "2014-12-16T10:23:17+00:00",
8
+ "database_id": 2
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "image_3",
3
+ "name": "Ruby on rails",
4
+ "url": "http://dhwlijwe9jil7.cloudfront.net/files/2012/07/4/companies-using-ruby-on-rails.jpg",
5
+ "description": "RoR logo",
6
+ "created_at": "2014-12-16T10:23:57+00:00",
7
+ "updated_at": "2014-12-16T10:23:57+00:00",
8
+ "database_id": 3
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "image_4",
3
+ "name": "Ruby",
4
+ "url": "http://factormedia.com/wp-content/uploads/2012/04/ruby-on-rails_internship.png",
5
+ "description": "Ruby on 'rails'",
6
+ "created_at": "2014-12-16T10:24:32+00:00",
7
+ "updated_at": "2014-12-16T10:24:32+00:00",
8
+ "database_id": 4
9
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "6H6pGAV1PUsuoAW26Iu48W",
3
+ "name": "Comment",
4
+ "description": null,
5
+ "displayField": "subject",
6
+ "fields": [
7
+ {
8
+ "name": "Subject",
9
+ "id": "subject",
10
+ "type": "Text"
11
+ },
12
+ {
13
+ "name": "Content",
14
+ "id": "content",
15
+ "type": "Text"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "2soCP557HGKoOOK0SqmMOm",
3
+ "name": "Job Skills",
4
+ "description": null,
5
+ "displayField": "name",
6
+ "fields": [
7
+ {
8
+ "name": "Name",
9
+ "id": "name",
10
+ "type": "Text"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "id": "4L1bg4WQ5aWQMiE82ouag",
3
+ "name": "Jobs",
4
+ "description": null,
5
+ "displayField": "title",
6
+ "fields": [
7
+ {
8
+ "name": "Title",
9
+ "id": "title",
10
+ "type": "Text"
11
+ },
12
+ {
13
+ "name": "Description",
14
+ "id": "description",
15
+ "type": "Text"
16
+ },
17
+ {
18
+ "name": "Image",
19
+ "id": "image",
20
+ "type": "Asset",
21
+ "link": "Link"
22
+ },
23
+ {
24
+ "name": "Creator",
25
+ "id": "creator",
26
+ "type": "Entry",
27
+ "link": "Link"
28
+ },
29
+ {
30
+ "name": "Comments",
31
+ "id": "comments",
32
+ "type": "Array",
33
+ "link_type": "Entry",
34
+ "link": "Link"
35
+ },
36
+ {
37
+ "name": "Skills",
38
+ "id": "skills",
39
+ "type": "Array",
40
+ "link_type": "Entry",
41
+ "link": "Link"
42
+ }
43
+ ]
44
+ }