scrivito_sdk 0.60.0 → 0.65.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/app/controllers/scrivito/objs_controller.rb +10 -2
- data/app/controllers/scrivito/workspaces_controller.rb +2 -1
- data/app/helpers/scrivito_helper.rb +32 -1
- data/app/views/scrivito/page_details.html.erb +1 -0
- data/app/views/scrivito/{workspaces → webservice}/_workspace.json.jbuilder +0 -0
- data/app/views/scrivito/webservice/error.json.jbuilder +1 -1
- data/app/views/scrivito/{workspaces/index.json.jbuilder → webservice/workspaces.json.jbuilder} +0 -0
- data/config/ca-bundle.crt +1 -1
- data/config/precedence_routes.rb +1 -0
- data/config/routes.rb +4 -5
- data/lib/assets/javascripts/scrivito_ui.js +1875 -1065
- data/lib/assets/stylesheets/scrivito_sdk.css +1 -1
- data/lib/assets/stylesheets/scrivito_ui.css +1 -1
- data/lib/generators/scrivito/install/templates/app/views/page/details.html.erb +3 -1
- data/lib/generators/scrivito/page/templates/details.html.erb +3 -1
- data/lib/scrivito/attribute_content.rb +159 -17
- data/lib/scrivito/attribute_serializer.rb +7 -3
- data/lib/scrivito/backend/content_state_node.rb +68 -0
- data/lib/scrivito/backend/index.rb +45 -0
- data/lib/scrivito/backend/obj_data_cache.rb +68 -0
- data/lib/scrivito/backend/obj_data_from_rest.rb +64 -0
- data/lib/scrivito/backend/obj_load.rb +45 -0
- data/lib/scrivito/backend/obj_query.rb +63 -0
- data/lib/scrivito/backend/parent_path_index.rb +21 -0
- data/lib/scrivito/backend/path_index.rb +27 -0
- data/lib/scrivito/backend/permalink_index.rb +17 -0
- data/lib/scrivito/basic_obj.rb +67 -39
- data/lib/scrivito/basic_widget.rb +19 -3
- data/lib/scrivito/cache_middleware.rb +9 -3
- data/lib/scrivito/client_error.rb +3 -3
- data/lib/scrivito/cms_backend.rb +64 -18
- data/lib/scrivito/cms_data_cache.rb +33 -30
- data/lib/scrivito/cms_dispatch_controller.rb +18 -0
- data/lib/scrivito/cms_field_tag.rb +3 -2
- data/lib/scrivito/cms_rest_api.rb +18 -12
- data/lib/scrivito/cms_rest_api/rate_limit.rb +40 -0
- data/lib/scrivito/cms_routing.rb +9 -8
- data/lib/scrivito/configuration.rb +8 -1
- data/lib/scrivito/controller_actions.rb +6 -1
- data/lib/scrivito/errors.rb +5 -0
- data/lib/scrivito/migrations/cms_backend.rb +2 -0
- data/lib/scrivito/named_link.rb +9 -45
- data/lib/scrivito/obj_collection.rb +14 -15
- data/lib/scrivito/obj_create_params_parser.rb +3 -5
- data/lib/scrivito/obj_params_parser.rb +2 -2
- data/lib/scrivito/obj_update_params_parser.rb +5 -3
- data/lib/scrivito/revision.rb +62 -2
- data/lib/scrivito/type_computer.rb +6 -2
- data/lib/scrivito/widget_tag.rb +4 -4
- data/lib/scrivito/workspace.rb +19 -3
- data/lib/scrivito/workspace_data.rb +23 -0
- data/lib/scrivito/workspace_data_from_service.rb +11 -28
- metadata +31 -7
- data/lib/scrivito/workspace_data_from_rest_api.rb +0 -6
@@ -1,11 +1,11 @@
|
|
1
1
|
module Scrivito
|
2
2
|
|
3
3
|
class ClientError < StandardError
|
4
|
-
attr_reader :http_code, :
|
4
|
+
attr_reader :http_code, :backend_code
|
5
5
|
|
6
|
-
def initialize(message, http_code,
|
6
|
+
def initialize(message, http_code, backend_code = nil)
|
7
7
|
@http_code = http_code
|
8
|
-
@
|
8
|
+
@backend_code = backend_code
|
9
9
|
super(message)
|
10
10
|
end
|
11
11
|
end
|
data/lib/scrivito/cms_backend.rb
CHANGED
@@ -55,8 +55,11 @@ module Scrivito
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
attr_accessor :die_content_service
|
59
|
+
|
58
60
|
def initialize
|
59
61
|
@query_counter = 0
|
62
|
+
@caching = true
|
60
63
|
end
|
61
64
|
|
62
65
|
def begin_caching
|
@@ -68,6 +71,10 @@ module Scrivito
|
|
68
71
|
@caching = false
|
69
72
|
end
|
70
73
|
|
74
|
+
def clear_cache
|
75
|
+
CmsDataCache.cache.clear
|
76
|
+
end
|
77
|
+
|
71
78
|
def caching?
|
72
79
|
!!@caching
|
73
80
|
end
|
@@ -82,6 +89,33 @@ module Scrivito
|
|
82
89
|
end
|
83
90
|
|
84
91
|
def find_workspace_data_by_id(id)
|
92
|
+
if die_content_service
|
93
|
+
from_csid = CmsDataCache.read_workspace_csid(id)
|
94
|
+
|
95
|
+
changes = CmsRestApi.get("/workspaces/#{id}/changes", from: from_csid)
|
96
|
+
|
97
|
+
objs = changes["objs"]
|
98
|
+
if objs.present?
|
99
|
+
if objs != "*"
|
100
|
+
last_state = Backend::ContentStateNode.find(from_csid)
|
101
|
+
|
102
|
+
successor_state = changes["to"]
|
103
|
+
current_state = last_state.create_successor(successor_state, objs)
|
104
|
+
end
|
105
|
+
|
106
|
+
# TODO what if workspace data is missing?
|
107
|
+
# have the backend team include a new key `current`
|
108
|
+
current_csid = changes["workspace"]["content_state_id"]
|
109
|
+
CmsDataCache.write_workspace_csid(id, current_csid)
|
110
|
+
end
|
111
|
+
|
112
|
+
# TODO what if workspace data is missing?
|
113
|
+
# implement workspace data caching
|
114
|
+
workspace_data = changes["workspace"]
|
115
|
+
|
116
|
+
return WorkspaceData.new(workspace_data)
|
117
|
+
end
|
118
|
+
|
85
119
|
workspace_data_from_cache = WorkspaceDataFromService.find_from_cache(id)
|
86
120
|
from_content_state_id = workspace_data_from_cache.try(:content_state_id)
|
87
121
|
|
@@ -101,17 +135,37 @@ module Scrivito
|
|
101
135
|
|
102
136
|
if raw_workspace_data = raw_data['workspace']
|
103
137
|
workspace_data = WorkspaceDataFromService.new(raw_workspace_data)
|
104
|
-
|
138
|
+
if from_content_state_id != workspace_data.content_state_id
|
139
|
+
workspace_data.store_in_cache_and_create_content_state
|
140
|
+
end
|
105
141
|
workspace_data
|
106
142
|
end
|
107
143
|
end
|
108
144
|
|
109
145
|
def find_obj_data_by(revision, index, keys)
|
110
|
-
|
111
|
-
|
146
|
+
index = index.to_s
|
147
|
+
|
148
|
+
if die_content_service
|
149
|
+
obj_datas =
|
150
|
+
if index == "id"
|
151
|
+
obj_datas = Backend::ObjLoad.load(revision, keys)
|
152
|
+
obj_datas.map { |obj_data| obj_data ? [obj_data] : [] }
|
153
|
+
else
|
154
|
+
index_implementation = Backend::Index.by_name(index)
|
155
|
+
Backend::ObjQuery.query(revision, index_implementation, keys)
|
156
|
+
end
|
157
|
+
|
158
|
+
return obj_datas
|
159
|
+
end
|
112
160
|
|
113
|
-
|
114
|
-
|
161
|
+
assert_valid_index_name(index)
|
162
|
+
raw_data = find_raw_data_from_cache_or_database_by(revision, index, keys)
|
163
|
+
|
164
|
+
raw_data.map do |raw_result|
|
165
|
+
raw_result.each_with_object([]) do |raw_data, result|
|
166
|
+
result << ObjDataFromService.new(raw_data)
|
167
|
+
end
|
168
|
+
end
|
115
169
|
end
|
116
170
|
|
117
171
|
def find_blob_data(id, access, verb)
|
@@ -136,6 +190,11 @@ module Scrivito
|
|
136
190
|
end
|
137
191
|
|
138
192
|
def search_objs(workspace, params)
|
193
|
+
if die_content_service
|
194
|
+
# TODO caching
|
195
|
+
return request_search_result_from_backend(workspace, params)
|
196
|
+
end
|
197
|
+
|
139
198
|
content_state = workspace.revision.content_state
|
140
199
|
cache_index = 'search'
|
141
200
|
cache_key = params.to_param
|
@@ -249,19 +308,6 @@ module Scrivito
|
|
249
308
|
"blob_metadata/#{id}"
|
250
309
|
end
|
251
310
|
|
252
|
-
def find_obj_data_filtering_deleted_by(revision, index, keys, include_deleted)
|
253
|
-
index = index.to_s
|
254
|
-
assert_valid_index_name(index)
|
255
|
-
raw_data = find_raw_data_from_cache_or_database_by(revision, index, keys)
|
256
|
-
|
257
|
-
raw_data.map do |raw_result|
|
258
|
-
raw_result.each_with_object([]) do |raw_data, result|
|
259
|
-
next if raw_data['_modification'] == ['deleted'] && !include_deleted
|
260
|
-
result << ObjDataFromService.new(raw_data)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
311
|
def find_raw_data_from_cache_or_database_by(revision, index, keys)
|
266
312
|
keys_from_database = []
|
267
313
|
# load results from cache
|
@@ -26,36 +26,39 @@ module CmsDataCache
|
|
26
26
|
@second_level_cache = cache_store
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
29
|
+
SCHEMA = {
|
30
|
+
workspace_data: 'workspace/#{workspace_id}',
|
31
|
+
|
32
|
+
# CONTENT SERVICE
|
33
|
+
content_state: 'content/#{content_state_id}',
|
34
|
+
obj_data: 'content/#{content_state_id}/obj/#{index}/#{key}',
|
35
|
+
obj_classes_data: 'content/#{content_state_id}/obj_classes',
|
36
|
+
|
37
|
+
# REST API
|
38
|
+
obj_data_rest: 'obj/#{cache_id}/#{index}/#{key}',
|
39
|
+
content_state_node: 'csn/#{content_state_id}',
|
40
|
+
workspace_csid: 'wscsid/#{workspace_id}'
|
41
|
+
}
|
42
|
+
|
43
|
+
SCHEMA.each do |name, schema|
|
44
|
+
params = schema.scan(/\#{([^}]+)}/).map(&:first)
|
45
|
+
params_code = params.map(&:to_s).join(",")
|
46
|
+
|
47
|
+
# using eval instead of define_method for performance reasons
|
48
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
49
|
+
def read_#{name}(#{params_code})
|
50
|
+
cache.read("#{schema}")
|
51
|
+
end
|
52
|
+
END
|
53
|
+
|
54
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
55
|
+
def write_#{name}(#{params_code}, data)
|
56
|
+
if data == nil
|
57
|
+
raise InternalError, "tried to write nil into #{schema}"
|
58
|
+
end
|
59
|
+
cache.write("#{schema}", data)
|
60
|
+
end
|
61
|
+
END
|
59
62
|
end
|
60
63
|
|
61
64
|
private
|
@@ -1,8 +1,22 @@
|
|
1
1
|
module Scrivito
|
2
2
|
|
3
3
|
class CmsDispatchController < ActionController::Metal
|
4
|
+
include ActionController::Redirecting
|
5
|
+
include Rails.application.routes.url_helpers
|
6
|
+
include Scrivito::RoutingHelper
|
7
|
+
|
4
8
|
def process(action)
|
5
9
|
CmsEnv.new(env).load
|
10
|
+
|
11
|
+
if !obj_not_found? && action == 'legacy'
|
12
|
+
if Scrivito::Configuration.legacy_routing
|
13
|
+
action = 'index'
|
14
|
+
else
|
15
|
+
redirect_to scrivito_path(loaded_obj)
|
16
|
+
return self.response
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
6
20
|
controller = target_controller(env)
|
7
21
|
env["action_dispatch.request.path_parameters"]["controller"] = controller.controller_path
|
8
22
|
|
@@ -17,6 +31,10 @@ module Scrivito
|
|
17
31
|
|
18
32
|
private
|
19
33
|
|
34
|
+
def main_app
|
35
|
+
Rails.application.routes.url_helpers
|
36
|
+
end
|
37
|
+
|
20
38
|
def target_controller(env)
|
21
39
|
return default_controller if obj_not_found?
|
22
40
|
controller = "#{loaded_obj.controller_name}Controller".constantize
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Scrivito
|
2
2
|
|
3
3
|
# this class is the server-side equivalent of the JavaScript class `cms_field_element`
|
4
|
-
class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :field_name)
|
4
|
+
class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :field_name, :widget_template_name)
|
5
5
|
FIELD_TYPES_WITH_ORIGINAL_CONTENT = %w[
|
6
6
|
binary
|
7
7
|
date
|
@@ -21,7 +21,7 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :field_name)
|
|
21
21
|
raise ArgumentError, 'No block allowed for widgetlist fields' if block_given?
|
22
22
|
modifications = modification_info || []
|
23
23
|
rendered_widgets = default_content.each_with_index.map do |widget, index|
|
24
|
-
WidgetTag.new(view, widget, modifications[index]).render
|
24
|
+
WidgetTag.new(view, widget, modifications[index], widget_template_name).render
|
25
25
|
end
|
26
26
|
view.safe_join(rendered_widgets)
|
27
27
|
else
|
@@ -60,6 +60,7 @@ class CmsFieldTag < Struct.new(:view, :tag_name, :obj_or_widget, :field_name)
|
|
60
60
|
|
61
61
|
if field_type == 'widgetlist'
|
62
62
|
options['private-field-widget-allowed-classes'] = build_valid_widget_classes.to_json
|
63
|
+
options['private-field-widget-template'] = widget_template_name
|
63
64
|
end
|
64
65
|
|
65
66
|
options
|
@@ -32,6 +32,8 @@ module Scrivito
|
|
32
32
|
:delete => Net::HTTP::Delete,
|
33
33
|
}.freeze
|
34
34
|
|
35
|
+
MAX_RATE_LIMIT_RETRY_DURATION = 20.seconds.freeze
|
36
|
+
|
35
37
|
def self.get(resource_path, payload = nil, options = nil)
|
36
38
|
request_cms_api(:get, resource_path, payload, options)
|
37
39
|
end
|
@@ -100,9 +102,12 @@ module Scrivito
|
|
100
102
|
|
101
103
|
response = nil
|
102
104
|
retried = false
|
105
|
+
wait_until = Time.now + MAX_RATE_LIMIT_RETRY_DURATION
|
103
106
|
begin
|
104
|
-
|
105
|
-
|
107
|
+
response = CmsRestApi::RateLimit.retry_on_rate_limit(wait_until) do
|
108
|
+
# lower timeout back to DEFAULT_TIMEOUT once the backend has been fixed
|
109
|
+
connection_manager.request(request, 25)
|
110
|
+
end
|
106
111
|
rescue NetworkError => e
|
107
112
|
if method == :post || retried
|
108
113
|
raise e
|
@@ -112,11 +117,11 @@ module Scrivito
|
|
112
117
|
end
|
113
118
|
end
|
114
119
|
|
115
|
-
handle_response(response)
|
120
|
+
handle_response(resource_path, response)
|
116
121
|
end
|
117
122
|
|
118
|
-
def handle_response(response)
|
119
|
-
|
123
|
+
def handle_response(resource_path, response)
|
124
|
+
http_code = response.code.to_i
|
120
125
|
if response.code.start_with?('2')
|
121
126
|
MultiJson.load(response.body)
|
122
127
|
elsif response.code == '403'
|
@@ -127,15 +132,16 @@ module Scrivito
|
|
127
132
|
specific_output = error_body['error']
|
128
133
|
|
129
134
|
if response.code.start_with?('4')
|
130
|
-
|
131
|
-
|
135
|
+
backend_code = error_body['code']
|
136
|
+
message = "'#{specific_output}' on '#{resource_path}'"
|
137
|
+
raise ClientError.new(specific_output, http_code, backend_code)
|
132
138
|
elsif response.code == '500' && specific_output
|
133
|
-
raise BackendError.new(specific_output,
|
139
|
+
raise BackendError.new(specific_output, http_code)
|
134
140
|
else # 3xx and >500 are treated as NetworkErrors
|
135
|
-
raise NetworkError.new(response.body,
|
141
|
+
raise NetworkError.new(response.body, http_code)
|
136
142
|
end
|
137
143
|
rescue MultiJson::DecodeError
|
138
|
-
raise NetworkError.new(response.body,
|
144
|
+
raise NetworkError.new(response.body, http_code)
|
139
145
|
end
|
140
146
|
end
|
141
147
|
end
|
@@ -151,8 +157,8 @@ module Scrivito
|
|
151
157
|
end
|
152
158
|
return task_data["result"] if task_data["status"] == "success"
|
153
159
|
message = task_data["message"] || "Missing error message in task response #{task_data}"
|
154
|
-
|
155
|
-
raise ClientError.new(message, 400,
|
160
|
+
backend_code = task_data['code']
|
161
|
+
raise ClientError.new(message, 400, backend_code)
|
156
162
|
end
|
157
163
|
|
158
164
|
def set_headers(request)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Scrivito
|
2
|
+
class CmsRestApi
|
3
|
+
module RateLimit
|
4
|
+
class << self
|
5
|
+
def retry_on_rate_limit(try_until, &block)
|
6
|
+
internal_retry(block, try_until, 0)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def internal_retry(request_proc, try_until, retry_count)
|
12
|
+
response = request_proc.call
|
13
|
+
|
14
|
+
if failed_because_of_rate_limit?(response)
|
15
|
+
time_to_sleep = calculate_time_to_sleep(response['Retry-After'].to_f, retry_count)
|
16
|
+
|
17
|
+
if (Time.now + time_to_sleep.seconds) <= try_until
|
18
|
+
sleep time_to_sleep
|
19
|
+
internal_retry(request_proc, try_until, retry_count + 1)
|
20
|
+
else
|
21
|
+
raise Scrivito::RateLimitExceeded.new('rate limit exceeded', 429)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
response
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def calculate_time_to_sleep(retry_after, retry_count)
|
30
|
+
backoff_wait_time = 2 ** retry_count * 0.5
|
31
|
+
[backoff_wait_time, retry_after].max
|
32
|
+
end
|
33
|
+
|
34
|
+
def failed_because_of_rate_limit?(response)
|
35
|
+
response.code == '429'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/scrivito/cms_routing.rb
CHANGED
@@ -89,18 +89,19 @@ class CmsRouting < Struct.new(:request, :main_app)
|
|
89
89
|
LINK_TO_EMPTY_BLOB
|
90
90
|
end
|
91
91
|
else
|
92
|
-
|
92
|
+
slug = obj.slug.present? ? obj.slug.sub(/^\//, '') : nil
|
93
|
+
id_path_or_url_for_objs(obj, path_or_url, options.merge(slug: slug))
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
96
97
|
def id_path_or_url_for_objs(obj, path_or_url, options)
|
97
|
-
|
98
|
-
"
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
)
|
98
|
+
method_name = if Scrivito::Configuration.legacy_routing
|
99
|
+
"cms_legacy_id_#{path_or_url}"
|
100
|
+
else
|
101
|
+
"cms_id_#{path_or_url}"
|
102
|
+
end
|
103
|
+
|
104
|
+
main_app.public_send(method_name, options.merge(id: obj.id))
|
104
105
|
end
|
105
106
|
|
106
107
|
def editor_authenticated?
|
@@ -176,7 +176,6 @@ module Scrivito
|
|
176
176
|
|
177
177
|
def to_prepare
|
178
178
|
unless Rails.configuration.cache_classes
|
179
|
-
NamedLink.reset_cache
|
180
179
|
BasicObj.reset_type_computer!
|
181
180
|
BasicWidget.reset_type_computer!
|
182
181
|
end
|
@@ -207,6 +206,7 @@ module Scrivito
|
|
207
206
|
self.ca_file = DEFAULT_CA_FILE
|
208
207
|
self.endpoint = 'api.scrivito.com'
|
209
208
|
self.check_batch_size = 100
|
209
|
+
self.legacy_routing = false
|
210
210
|
end
|
211
211
|
|
212
212
|
#
|
@@ -225,6 +225,13 @@ module Scrivito
|
|
225
225
|
#
|
226
226
|
attr_accessor :ui_locale
|
227
227
|
|
228
|
+
|
229
|
+
# @api public
|
230
|
+
#
|
231
|
+
# Scrivito changed its routing to a slug first url scheme. This configuration option
|
232
|
+
# allows you to switch back to the old id first url scheme.
|
233
|
+
attr_accessor :legacy_routing
|
234
|
+
|
228
235
|
attr_accessor :choose_homepage_callback, :check_batch_size
|
229
236
|
|
230
237
|
# Configure a callback to be invoked when the Scrivito SDK delivers the homepage.
|