-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Spike: serve content-store requests directly from Publishing API.
Basic copy-and-paste of find_by_path, which now works on editions. Add controller in new content-store namespace to prove concept of serving content-store requests directly from publishing-api. The new controller responds on /content-store/(live or draft)/(path) and uses the DownstreamPayload to present the relevant edition as JSON. It takes just under 0.5s to respond on a local laptop. We'd need to speed that up for production use, with some combination of optimisations and caching. That's out-of-scope for this spike, we've proved that it's potentially possible and that's enough. linting
- Loading branch information
1 parent
cbb3d46
commit 90779b8
Showing
7 changed files
with
228 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
class ContentStore::ContentItemsController < ApplicationController | ||
skip_before_action :authenticate_user! | ||
|
||
def show | ||
@edition = find_content_item(content_store: params[:content_store], path: base_path) | ||
raise_error(404, "Could not find a content item for #{base_path}") unless @edition | ||
# NOTE: version here is @edition.user_facing_version, not Event.maximum(:id) | ||
# as that is only for managing conflicts between publishing-api and content-store | ||
# | ||
@content_item = DownstreamPayload.new(@edition, @edition.user_facing_version, draft: draft?) | ||
render json: @content_item.content_store_payload | ||
end | ||
|
||
private | ||
|
||
def find_content_item(content_store:, path:) | ||
FindByPath.new(Edition.where(content_store:)).find(path) | ||
end | ||
|
||
def draft? | ||
params[:content_store] == "draft" | ||
end | ||
|
||
def raise_error(code, message) | ||
raise CommandError.new( | ||
code:, | ||
error_details: { | ||
error: { | ||
code:, | ||
message:, | ||
}, | ||
}, | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
class FindByPath | ||
attr_reader :model_class | ||
|
||
def initialize(model_class) | ||
@model_class = model_class | ||
end | ||
|
||
def find(path) | ||
exact_match = model_class.where(base_path: path).take(1).first | ||
return exact_match if exact_match | ||
|
||
matches = find_route_matches(path) | ||
|
||
if matches.count.positive? | ||
best_route_match(matches, path) | ||
end | ||
end | ||
|
||
private | ||
|
||
def find_route_matches(path) | ||
query = model_class | ||
.where("routes @> ?", json_path_element(path, "exact")) | ||
# ANY will match any of the given array elements (similar to IN(), but for JSON arrays) | ||
# the ARRAY [?]::jsonb[] is typecasting for PostgreSQL's JSON operators | ||
.or(model_class.where("routes @> ANY (ARRAY [?]::jsonb[])", potential_prefix_json_matches(path))) | ||
|
||
if model_class.attribute_names.include?("redirects") | ||
query = query | ||
.or(model_class.where("redirects @> ?", json_path_element(path, "exact"))) | ||
.or(model_class.where("redirects @> ANY (ARRAY [?]::jsonb[])", potential_prefix_json_matches(path))) | ||
end | ||
query | ||
end | ||
|
||
# Given a path, will decompose the path into path prefixes, and | ||
# return a JSON array element that can be matched against the | ||
# routes or redirects array in the model_class | ||
def potential_prefix_json_matches(path) | ||
potential_prefixes(path).map { |p| json_path_element(p, "prefix") } | ||
end | ||
|
||
def json_path_element(path, type) | ||
[{ path:, type: }].to_json | ||
end | ||
|
||
def best_route_match(matches, path) | ||
exact_route_match(matches, path) || best_prefix_match(matches, path) | ||
end | ||
|
||
def potential_prefixes(path) | ||
paths = path.split("/").reject(&:empty?) | ||
(0...paths.size).map { |i| "/#{paths[0..i].join('/')}" } | ||
end | ||
|
||
def exact_route_match(matches, path) | ||
matches.detect do |item| | ||
routes_and_redirects(item).any? do |route| | ||
route["path"] == path && route["type"] == "exact" | ||
end | ||
end | ||
end | ||
|
||
def best_prefix_match(matches, path) | ||
prefixes = potential_prefixes(path) | ||
sorted = matches.sort_by do |item| | ||
best_match = routes_and_redirects(item) | ||
.select { |route| route["type"] == "prefix" && prefixes.include?(route["path"]) } | ||
.min_by { |route| -route["path"].length } | ||
|
||
-best_match["path"].length | ||
end | ||
sorted.first | ||
end | ||
|
||
def routes_and_redirects(item) | ||
item.routes + (item.respond_to?(:redirects) ? item.redirects : []) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.