scrivito_sdk 0.60.0 → 0.65.0.rc1
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.
- 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.
|