contentful-management 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -29
  3. data/.rubocop_todo.yml +24 -0
  4. data/.travis.yml +1 -1
  5. data/.yardopts +4 -0
  6. data/CHANGELOG.md +12 -0
  7. data/Gemfile +1 -0
  8. data/Guardfile +39 -34
  9. data/README.md +42 -1
  10. data/Rakefile +2 -0
  11. data/contentful-management.gemspec +2 -1
  12. data/lib/contentful/management/api_key.rb +65 -0
  13. data/lib/contentful/management/array.rb +3 -2
  14. data/lib/contentful/management/asset.rb +124 -58
  15. data/lib/contentful/management/client.rb +60 -19
  16. data/lib/contentful/management/content_type.rb +104 -49
  17. data/lib/contentful/management/content_type_entry_methods_factory.rb +21 -0
  18. data/lib/contentful/management/dynamic_entry.rb +22 -18
  19. data/lib/contentful/management/entry.rb +138 -80
  20. data/lib/contentful/management/error.rb +23 -23
  21. data/lib/contentful/management/field.rb +16 -13
  22. data/lib/contentful/management/http_client.rb +29 -0
  23. data/lib/contentful/management/link.rb +3 -3
  24. data/lib/contentful/management/locale.rb +55 -26
  25. data/lib/contentful/management/location.rb +1 -1
  26. data/lib/contentful/management/request.rb +11 -12
  27. data/lib/contentful/management/resource.rb +27 -12
  28. data/lib/contentful/management/resource/array_like.rb +1 -1
  29. data/lib/contentful/management/resource/asset_fields.rb +1 -0
  30. data/lib/contentful/management/resource/entry_fields.rb +2 -0
  31. data/lib/contentful/management/resource/field_aware.rb +16 -5
  32. data/lib/contentful/management/resource/fields.rb +16 -8
  33. data/lib/contentful/management/resource/refresher.rb +1 -0
  34. data/lib/contentful/management/resource/system_properties.rb +13 -6
  35. data/lib/contentful/management/resource_builder.rb +43 -40
  36. data/lib/contentful/management/space.rb +67 -28
  37. data/lib/contentful/management/space_api_key_methods_factory.rb +15 -0
  38. data/lib/contentful/management/space_asset_methods_factory.rb +4 -0
  39. data/lib/contentful/management/space_association_all_published_method_factory.rb +11 -0
  40. data/lib/contentful/management/space_association_methods_factory.rb +4 -2
  41. data/lib/contentful/management/space_content_type_methods_factory.rb +4 -0
  42. data/lib/contentful/management/space_entry_methods_factory.rb +4 -0
  43. data/lib/contentful/management/space_locale_methods_factory.rb +2 -0
  44. data/lib/contentful/management/space_webhook_methods_factory.rb +2 -0
  45. data/lib/contentful/management/validation.rb +3 -2
  46. data/lib/contentful/management/version.rb +4 -1
  47. data/lib/contentful/management/webhook.rb +42 -21
  48. data/spec/fixtures/vcr_cassettes/api_key/all_for_space.yml +194 -0
  49. data/spec/fixtures/vcr_cassettes/api_key/create_for_space.yml +113 -0
  50. data/spec/fixtures/vcr_cassettes/api_key/find.yml +113 -0
  51. data/spec/fixtures/vcr_cassettes/api_key/find_for_space_not_found.yml +71 -0
  52. data/spec/fixtures/vcr_cassettes/asset/all_public.yml +112 -0
  53. data/spec/fixtures/vcr_cassettes/content_type/all_public.yml +106 -0
  54. data/spec/fixtures/vcr_cassettes/entry/all_public.yml +102 -0
  55. data/spec/fixtures/vcr_cassettes/locale/destroy.yml +330 -0
  56. data/spec/fixtures/vcr_cassettes/locale/update_both.yml +306 -0
  57. data/spec/fixtures/vcr_cassettes/locale/update_code.yml +306 -0
  58. data/spec/fixtures/vcr_cassettes/locale/update_name.yml +306 -0
  59. data/spec/fixtures/vcr_cassettes/space/api_key/all.yml +410 -0
  60. data/spec/fixtures/vcr_cassettes/space/api_key/create.yml +329 -0
  61. data/spec/fixtures/vcr_cassettes/space/api_key/find.yml +329 -0
  62. data/spec/fixtures/vcr_cassettes/space/asset/all_public.yml +328 -0
  63. data/spec/fixtures/vcr_cassettes/space/content_type/all_public.yml +322 -0
  64. data/spec/fixtures/vcr_cassettes/space/entry/all_public.yml +318 -0
  65. data/spec/lib/contentful/management/api_key_spec.rb +55 -0
  66. data/spec/lib/contentful/management/asset_spec.rb +10 -0
  67. data/spec/lib/contentful/management/content_type_spec.rb +10 -0
  68. data/spec/lib/contentful/management/entry_spec.rb +10 -0
  69. data/spec/lib/contentful/management/locale_spec.rb +58 -0
  70. data/spec/lib/contentful/management/space_spec.rb +53 -0
  71. metadata +58 -9
@@ -12,24 +12,37 @@ require 'logger'
12
12
 
13
13
  module Contentful
14
14
  module Management
15
+ # Client for interacting with the Contentful Management API
16
+ # @see _ https://www.contentful.com/developers/docs/references/content-management-api/
15
17
  class Client
16
18
  extend Contentful::Management::HTTPClient
17
19
 
18
20
  attr_reader :access_token, :configuration, :logger
19
21
  attr_accessor :organization_id, :version, :zero_length, :content_type_id, :dynamic_entry_cache
20
22
 
23
+ # Default configuration for Contentful::Management::Client
21
24
  DEFAULT_CONFIGURATION = {
22
- api_url: 'api.contentful.com',
23
- api_version: '1',
24
- secure: true,
25
- default_locale: 'en-US',
26
- gzip_encoded: false,
27
- logger: false,
28
- log_level: Logger::INFO,
29
- raise_errors: false,
30
- dynamic_entries: []
25
+ api_url: 'api.contentful.com',
26
+ api_version: '1',
27
+ secure: true,
28
+ default_locale: 'en-US',
29
+ gzip_encoded: false,
30
+ logger: false,
31
+ log_level: Logger::INFO,
32
+ raise_errors: false,
33
+ dynamic_entries: []
31
34
  }
32
35
 
36
+ # @param [String] access_token
37
+ # @param [Hash] configuration
38
+ # @option configuration [String] :api_url
39
+ # @option configuration [String] :api_version
40
+ # @option configuration [String] :default_locale
41
+ # @option configuration [Boolean] :gzip_encoded
42
+ # @option configuration [false, ::Logger] :logger
43
+ # @option configuration [::Logger::DEBUG, ::Logger::INFO, ::Logger::WARN, ::Logger::ERROR] :log_level
44
+ # @option configuration [Boolean] :raise_errors
45
+ # @option configuration [::Array<String>] :dynamic_entries
33
46
  def initialize(access_token = nil, configuration = {})
34
47
  @configuration = default_configuration.merge(configuration)
35
48
  setup_logger
@@ -39,18 +52,21 @@ module Contentful
39
52
  update_all_dynamic_entry_cache!
40
53
  end
41
54
 
55
+ # @private
42
56
  def setup_logger
43
57
  @logger = configuration[:logger]
44
58
  logger.level = configuration[:log_level] if logger
45
59
  end
46
60
 
61
+ # @private
47
62
  def update_all_dynamic_entry_cache!
48
- if !configuration[:dynamic_entries].empty?
49
- spaces = configuration[:dynamic_entries].map { |space_id| ::Contentful::Management::Space.find(space_id) }
50
- update_dynamic_entry_cache_for_spaces!(spaces)
51
- end
63
+ return if configuration[:dynamic_entries].empty?
64
+
65
+ spaces = configuration[:dynamic_entries].map { |space_id| ::Contentful::Management::Space.find(space_id) }
66
+ update_dynamic_entry_cache_for_spaces!(spaces)
52
67
  end
53
68
 
69
+ # @private
54
70
  def update_dynamic_entry_cache_for_spaces!(spaces)
55
71
  spaces.each do |space|
56
72
  update_dynamic_entry_cache_for_space!(space)
@@ -59,36 +75,43 @@ module Contentful
59
75
 
60
76
  # Use this method together with the client's :dynamic_entries configuration.
61
77
  # See README for details.
78
+ # @private
62
79
  def update_dynamic_entry_cache_for_space!(space)
63
80
  update_dynamic_entry_cache!(space.content_types.all)
64
81
  end
65
82
 
83
+ # @private
66
84
  def update_dynamic_entry_cache!(content_types)
67
85
  content_types.each do |ct|
68
86
  @dynamic_entry_cache[ct.id.to_sym] = DynamicEntry.create(ct)
69
87
  end
70
88
  end
71
89
 
90
+ # @private
72
91
  def api_version
73
92
  configuration[:api_version]
74
93
  end
75
94
 
95
+ # @private
76
96
  def gzip_encoded
77
97
  configuration[:gzip_encoded]
78
98
  end
79
99
 
100
+ # @private
80
101
  def default_configuration
81
102
  DEFAULT_CONFIGURATION.dup
82
103
  end
83
104
 
105
+ # @private
84
106
  def register_dynamic_entry(key, klass)
85
107
  @dynamic_entry_cache[key.to_sym] = klass
86
108
  end
87
109
 
110
+ # @private
88
111
  def execute_request(request)
89
112
  request_url = request.url
90
113
  url = request.absolute? ? request_url : base_url + request_url
91
- logger.info(request: {url: url, query: request.query, header: request_headers}) if logger
114
+ logger.info(request: { url: url, query: request.query, header: request_headers }) if logger
92
115
  raw_response = yield(url)
93
116
  logger.debug(response: raw_response) if logger
94
117
  clear_headers
@@ -97,81 +120,98 @@ module Contentful
97
120
  result
98
121
  end
99
122
 
123
+ # @private
100
124
  def clear_headers
101
125
  self.content_type_id = nil
102
126
  self.version = nil
103
127
  self.organization_id = nil
104
128
  end
105
129
 
130
+ # @private
106
131
  def delete(request)
107
132
  execute_request(request) do |url|
108
133
  self.class.delete_http(url, {}, request_headers)
109
134
  end
110
135
  end
111
136
 
137
+ # @private
112
138
  def get(request)
113
139
  execute_request(request) do |url|
114
140
  self.class.get_http(url, request.query, request_headers)
115
141
  end
116
142
  end
117
143
 
144
+ # @private
118
145
  def post(request)
119
146
  execute_request(request) do |url|
120
147
  self.class.post_http(url, request.query, request_headers)
121
148
  end
122
149
  end
123
150
 
151
+ # @private
124
152
  def put(request)
125
153
  execute_request(request) do |url|
126
154
  self.class.put_http(url, request.query, request_headers)
127
155
  end
128
156
  end
129
157
 
158
+ # @private
130
159
  def base_url
131
- "#{ protocol }://#{ configuration[:api_url]}/spaces"
160
+ "#{protocol}://#{configuration[:api_url]}/spaces"
132
161
  end
133
162
 
163
+ # @private
134
164
  def default_locale
135
165
  configuration[:default_locale]
136
166
  end
137
167
 
168
+ # @private
138
169
  def protocol
139
170
  configuration[:secure] ? 'https' : 'http'
140
171
  end
141
172
 
173
+ # @private
142
174
  def authentication_header
143
- Hash['Authorization', "Bearer #{ access_token }"]
175
+ Hash['Authorization', "Bearer #{access_token}"]
144
176
  end
145
177
 
178
+ # @private
146
179
  def api_version_header
147
- Hash['Content-Type', "application/vnd.contentful.management.v#{ api_version }+json"]
180
+ Hash['Content-Type', "application/vnd.contentful.management.v#{api_version}+json"]
148
181
  end
149
182
 
183
+ # @private
150
184
  def user_agent
151
- Hash['User-Agent', "RubyContentfulManagementGem/#{ Contentful::Management::VERSION }"]
185
+ Hash['User-Agent', "RubyContentfulManagementGem/#{Contentful::Management::VERSION}"]
152
186
  end
153
187
 
188
+ # @private
154
189
  def organization_header(organization_id)
155
190
  Hash['X-Contentful-Organization', organization_id]
156
191
  end
157
192
 
193
+ # @private
158
194
  def version_header(version)
159
195
  Hash['X-Contentful-Version', version]
160
196
  end
161
197
 
198
+ # @private
162
199
  def content_type_header(content_type_id)
163
200
  Hash['X-Contentful-Content-Type', content_type_id]
164
201
  end
165
202
 
203
+ # @private
166
204
  def zero_length_header
167
205
  Hash['Content-Length', 0]
168
206
  end
169
207
 
208
+ # @private
170
209
  def accept_encoding_header(encoding)
171
210
  Hash['Accept-Encoding', encoding]
172
211
  end
173
212
 
174
- # XXX: headers should be supplied differently, maybe through the request object.
213
+ # @todo headers should be supplied differently, maybe through the request object.
214
+ # @private
175
215
  def request_headers
176
216
  headers = {}
177
217
  headers.merge! user_agent
@@ -185,6 +225,7 @@ module Contentful
185
225
  headers
186
226
  end
187
227
 
228
+ # @private
188
229
  def self.shared_instance
189
230
  Thread.current[:client]
190
231
  end
@@ -7,19 +7,20 @@ require_relative 'support'
7
7
  module Contentful
8
8
  module Management
9
9
  # Resource class for ContentType.
10
- # https://www.contentful.com/developers/documentation/content-management-api/#resources-content-types
10
+ # @see _ https://www.contentful.com/developers/documentation/content-management-api/#resources-content-types
11
11
  class ContentType
12
+ # Shortcuts for Contentful Field Types
12
13
  FIELD_TYPES = [
13
- SYMBOL = 'Symbol',
14
- TEXT = 'Text',
15
- INTEGER = 'Integer',
16
- FLOAT = 'Number',
17
- DATE = 'Date',
18
- BOOLEAN = 'Boolean',
19
- LINK = 'Link',
20
- ARRAY = 'Array',
21
- OBJECT = 'Object',
22
- LOCATION = 'Location'
14
+ SYMBOL = 'Symbol',
15
+ TEXT = 'Text',
16
+ INTEGER = 'Integer',
17
+ FLOAT = 'Number',
18
+ DATE = 'Date',
19
+ BOOLEAN = 'Boolean',
20
+ LINK = 'Link',
21
+ ARRAY = 'Array',
22
+ OBJECT = 'Object',
23
+ LOCATION = 'Location'
23
24
  ]
24
25
 
25
26
  include Contentful::Management::Resource
@@ -32,12 +33,20 @@ module Contentful
32
33
  property :displayField, :string
33
34
 
34
35
  # Gets a collection of content types.
35
- # Takes an id of space and an optional hash of query options
36
- # Returns a Contentful::Management::Array of Contentful::Management::ContentType.
36
+ #
37
+ # @param [String] space_id
38
+ # @param [Hash] query Search Options
39
+ # @see _ For complete option list: http://docs.contentfulcda.apiary.io/#reference/search-parameters
40
+ # @option query [String] 'sys.id' Content Type ID
41
+ # @option query [String] :name Kind of Content Type
42
+ # @option query [Integer] :limit
43
+ # @option query [Integer] :skip
44
+ #
45
+ # @return [Contentful::Management::Array<Contentful::Management::ContentType>]
37
46
  def self.all(space_id, query = {})
38
47
  request = Request.new(
39
- "/#{ space_id }/content_types",
40
- query
48
+ "/#{space_id}/content_types",
49
+ query
41
50
  )
42
51
  response = request.get
43
52
  result = ResourceBuilder.new(response, {}, {})
@@ -46,11 +55,37 @@ module Contentful
46
55
  content_types
47
56
  end
48
57
 
49
- # Gets a specific entry.
50
- # Takes an id of space and content type.
51
- # Returns a Contentful::Management::ContentType.
58
+ # Gets a collection of published content types.
59
+ #
60
+ # @param [String] space_id
61
+ # @param [Hash] query Search Options
62
+ # @see _ For complete option list: http://docs.contentfulcda.apiary.io/#reference/search-parameters
63
+ # @option query [String] 'sys.id' Content Type ID
64
+ # @option query [String] :name Kind of Content Type
65
+ # @option query [Integer] :limit
66
+ # @option query [Integer] :skip
67
+ #
68
+ # @return [Contentful::Management::Array<Contentful::Management::ContentType>]
69
+ def self.all_published(space_id, query = {})
70
+ request = Request.new(
71
+ "/#{space_id}/public/content_types",
72
+ query
73
+ )
74
+ response = request.get
75
+ result = ResourceBuilder.new(response, {}, {})
76
+ content_types = result.run
77
+ client.update_dynamic_entry_cache!(content_types)
78
+ content_types
79
+ end
80
+
81
+ # Gets a specific content type.
82
+ #
83
+ # @param [String] space_id
84
+ # @param [String] content_type_id
85
+ #
86
+ # @return [Contentful::Management::ContentType]
52
87
  def self.find(space_id, content_type_id)
53
- request = Request.new("/#{ space_id }/content_types/#{ content_type_id }")
88
+ request = Request.new("/#{space_id}/content_types/#{content_type_id}")
54
89
  response = request.get
55
90
  result = ResourceBuilder.new(response, {}, {})
56
91
  content_type = result.run
@@ -59,9 +94,10 @@ module Contentful
59
94
  end
60
95
 
61
96
  # Destroys a content type.
62
- # Returns true if succeed.
97
+ #
98
+ # @return [true, Contentful::Management::Error] success
63
99
  def destroy
64
- request = Request.new("/#{ space.id }/content_types/#{ id }")
100
+ request = Request.new("/#{space.id}/content_types/#{id}")
65
101
  response = request.delete
66
102
  if response.status == :no_content
67
103
  return true
@@ -75,10 +111,10 @@ module Contentful
75
111
  # Returns a Contentful::Management::ContentType.
76
112
  def activate
77
113
  request = Request.new(
78
- "/#{ space.id }/content_types/#{ id }/published",
79
- {},
80
- id = nil,
81
- version: sys[:version]
114
+ "/#{space.id}/content_types/#{id}/published",
115
+ {},
116
+ nil,
117
+ version: sys[:version]
82
118
  )
83
119
  response = request.put
84
120
  result = ResourceBuilder.new(response, {}, {}).run
@@ -87,34 +123,42 @@ module Contentful
87
123
 
88
124
  # Deactivates a content type.
89
125
  # Only content type that has no entries can be deactivated.
90
- # Returns a Contentful::Management::ContentType.
126
+ #
127
+ # @return [Contentful::Management::ContentType]
91
128
  def deactivate
92
- request = Request.new("/#{ space.id }/content_types/#{ id }/published")
129
+ request = Request.new("/#{space.id}/content_types/#{id}/published")
93
130
  response = request.delete
94
131
  result = ResourceBuilder.new(response, {}, {}).run
95
132
  refresh_data(result)
96
133
  end
97
134
 
98
135
  # Checks if a content type is active.
99
- # Returns true if active.
136
+ #
137
+ # @return [Boolean]
100
138
  def active?
101
139
  sys[:publishedAt] ? true : false
102
140
  end
103
141
 
104
142
  # Creates a content type.
105
- # Takes an id of space and hash with attributes.
106
- # Returns a Contentful::Management::ContentType.
143
+ #
144
+ # @param [String] space_id
145
+ # @param [Hash] attributes
146
+ # @option attributes [String] :id
147
+ # @option attributes [String] :name
148
+ # @option attributes [::Array<Contentful::Management::Field>] :fields
149
+ #
150
+ # @return [Contentful::Management::ContentType]
107
151
  def self.create(space_id, attributes)
108
152
  fields = fields_to_nested_properties_hash(attributes[:fields] || [])
109
153
 
110
154
  params = attributes.clone
111
155
  params[:fields] = fields
112
156
  params.delete(:id)
113
- params = params.delete_if { |k, v| v.nil? }
157
+ params = params.delete_if { |_, v| v.nil? }
114
158
 
115
159
  request = Request.new(
116
- "/#{ space_id }/content_types/#{ attributes[:id]}",
117
- params
160
+ "/#{space_id}/content_types/#{attributes[:id]}",
161
+ params
118
162
  )
119
163
  response = attributes[:id].nil? ? request.post : request.put
120
164
  result = ResourceBuilder.new(response, {}, {}).run
@@ -122,6 +166,7 @@ module Contentful
122
166
  result
123
167
  end
124
168
 
169
+ # @private
125
170
  def display_field_value(attributes)
126
171
  if attributes[:displayField].nil? && (display_field.nil? || display_field.empty?)
127
172
  nil
@@ -131,8 +176,13 @@ module Contentful
131
176
  end
132
177
 
133
178
  # Updates a content type.
134
- # Takes a hash with attributes.
135
- # Returns a Contentful::Management::ContentType.
179
+ #
180
+ # @param [Hash] attributes
181
+ # @option attributes [String] :id
182
+ # @option attributes [String] :name
183
+ # @option attributes [::Array<Contentful::Management::Field>] :fields
184
+ #
185
+ # @return [Contentful::Management::ContentType]
136
186
  def update(attributes)
137
187
  parameters = {}
138
188
  parameters.merge!(displayField: display_field_value(attributes))
@@ -140,12 +190,12 @@ module Contentful
140
190
  parameters.merge!(description: (attributes[:description] || description))
141
191
  parameters.merge!(fields: self.class.fields_to_nested_properties_hash(attributes[:fields] || fields))
142
192
 
143
- parameters = parameters.delete_if { |k, v| v.nil? }
193
+ parameters = parameters.delete_if { |_, v| v.nil? }
144
194
  request = Request.new(
145
- "/#{ space.id }/content_types/#{ id }",
146
- parameters,
147
- id = nil,
148
- version: sys[:version]
195
+ "/#{space.id}/content_types/#{id}",
196
+ parameters,
197
+ nil,
198
+ version: sys[:version]
149
199
  )
150
200
  response = request.put
151
201
  result = ResourceBuilder.new(response, {}, {}).run
@@ -153,7 +203,9 @@ module Contentful
153
203
  end
154
204
 
155
205
  # If a content type is a new object gets created in the Contentful, otherwise the existing entry gets updated.
156
- # See README for details.
206
+ # @see _ README for details.
207
+ #
208
+ # @return [Contentful::Management::ContentType]
157
209
  def save
158
210
  if id
159
211
  update(@properties)
@@ -164,6 +216,7 @@ module Contentful
164
216
  end
165
217
 
166
218
  # This method merges existing fields with new one, when adding, creating or updating new fields.
219
+ # @private
167
220
  def merged_fields(new_field)
168
221
  field_ids = []
169
222
  merged_fields = fields.each_with_object([]) do |field, fields|
@@ -175,17 +228,18 @@ module Contentful
175
228
  merged_fields
176
229
  end
177
230
 
231
+ # @private
178
232
  alias_method :orig_fields, :fields
179
233
 
180
234
  # Use this method only in the context of content type.
181
235
  # Allows you to add and create a field with specified attributes or destroy by pass field id.
182
- # Returns a Contentful::Management::ContentType.
183
- # See README for details.
236
+ # @see _ README for details.
237
+ #
238
+ # @return [Contentful::Management::ContentType]
184
239
  def fields
185
240
  fields = orig_fields
186
241
 
187
242
  fields.instance_exec(self) do |content_type|
188
-
189
243
  fields.define_singleton_method(:add) do |field|
190
244
  content_type.update(fields: content_type.merged_fields(field))
191
245
  end
@@ -204,7 +258,6 @@ module Contentful
204
258
  fields = content_type.fields.select { |field| field.id != id }
205
259
  content_type.update(fields: fields)
206
260
  end
207
-
208
261
  end
209
262
 
210
263
  fields
@@ -212,14 +265,16 @@ module Contentful
212
265
 
213
266
  # Use this method only in the context of content type.
214
267
  # Allows you to create an entry.
215
- # Returns a Contentful::Management::Entry.
216
- # See README for details.
268
+ # @see _ README for details.
269
+ #
270
+ # @private
271
+ #
272
+ # @return [Contentful::Management::ContentTypeEntryMethodsFactory]
217
273
  def entries
218
274
  Contentful::Management::ContentTypeEntryMethodsFactory.new(self)
219
275
  end
220
276
 
221
- private
222
-
277
+ # @private
223
278
  def self.fields_to_nested_properties_hash(fields)
224
279
  fields.map do |field|
225
280
  field.properties.replace(field.properties_to_hash)