Add support for missing posts
Posts, like 8661f4724aa9, can go missing if the account or post was removed. In this case, the API returns data like this: ```json { "data": { "post": null } } ``` When this happens, we can detect it because the parsed response now has a nil value: `response.data.post == nil` and construct an `EmptyPage` instead of a `Page`. The `Articles::Show` action can then render conditionally based on if the response from `PageConverter` is a `Page` or an `EmptyPage`.
This commit is contained in:
parent
1dcded9153
commit
f05a12a880
11 changed files with 102 additions and 59 deletions
|
@ -1,5 +1,9 @@
|
||||||
2022-05-21
|
2022-05-21
|
||||||
|
|
||||||
|
* Show error page for missing posts
|
||||||
|
|
||||||
|
2022-05-21
|
||||||
|
|
||||||
* Remove the need for a fake DATABASE_URL
|
* Remove the need for a fake DATABASE_URL
|
||||||
|
|
||||||
2022-04-04
|
2022-04-04
|
||||||
|
|
15
spec/actions/articles/show_spec.cr
Normal file
15
spec/actions/articles/show_spec.cr
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
require "../../spec_helper"
|
||||||
|
|
||||||
|
include ActionHelpers
|
||||||
|
|
||||||
|
describe Articles::Show do
|
||||||
|
context "if the article is missing" do
|
||||||
|
it "should raise a MissingPageError" do
|
||||||
|
context = action_context(path: "/abc123")
|
||||||
|
|
||||||
|
expect_raises(MissingPageError) do
|
||||||
|
Articles::Show.new(context, params).call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,8 +17,8 @@ describe PageConverter do
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
JSON
|
JSON
|
||||||
data_json = default_data_json(title, paragraph_json)
|
data_json = default_post_json(title, paragraph_json)
|
||||||
data = PostResponse::Data.from_json(data_json)
|
data = PostResponse::Post.from_json(data_json)
|
||||||
|
|
||||||
page = PageConverter.new.convert(data)
|
page = PageConverter.new.convert(data)
|
||||||
|
|
||||||
|
@ -26,9 +26,8 @@ describe PageConverter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets the author" do
|
it "sets the author" do
|
||||||
data_json = <<-JSON
|
post_json = <<-JSON
|
||||||
{
|
{
|
||||||
"post": {
|
|
||||||
"title": "This is a story",
|
"title": "This is a story",
|
||||||
"createdAt": 0,
|
"createdAt": 0,
|
||||||
"creator": {
|
"creator": {
|
||||||
|
@ -41,20 +40,18 @@ describe PageConverter do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
JSON
|
JSON
|
||||||
data = PostResponse::Data.from_json(data_json)
|
post = PostResponse::Post.from_json(post_json)
|
||||||
|
|
||||||
page = PageConverter.new.convert(data)
|
page = PageConverter.new.convert(post)
|
||||||
|
|
||||||
page.author.name.should eq "Author"
|
page.author.name.should eq "Author"
|
||||||
page.author.id.should eq "abc123"
|
page.author.id.should eq "abc123"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets the publish date/time" do
|
it "sets the publish date/time" do
|
||||||
data_json = <<-JSON
|
post_json = <<-JSON
|
||||||
{
|
{
|
||||||
"post": {
|
|
||||||
"title": "This is a story",
|
"title": "This is a story",
|
||||||
"createdAt": 1000,
|
"createdAt": 1000,
|
||||||
"creator": {
|
"creator": {
|
||||||
|
@ -67,11 +64,10 @@ describe PageConverter do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
JSON
|
JSON
|
||||||
data = PostResponse::Data.from_json(data_json)
|
post = PostResponse::Post.from_json(post_json)
|
||||||
|
|
||||||
page = PageConverter.new.convert(data)
|
page = PageConverter.new.convert(post)
|
||||||
|
|
||||||
page.created_at.should eq Time.utc(1970, 1, 1, 0, 0, 1)
|
page.created_at.should eq Time.utc(1970, 1, 1, 0, 0, 1)
|
||||||
end
|
end
|
||||||
|
@ -98,8 +94,8 @@ describe PageConverter do
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
JSON
|
JSON
|
||||||
data_json = default_data_json(title, paragraph_json)
|
post_json = default_post_json(title, paragraph_json)
|
||||||
data = PostResponse::Data.from_json(data_json)
|
data = PostResponse::Post.from_json(post_json)
|
||||||
|
|
||||||
page = PageConverter.new.convert(data)
|
page = PageConverter.new.convert(data)
|
||||||
|
|
||||||
|
@ -115,13 +111,12 @@ def default_paragraph_json
|
||||||
"[]"
|
"[]"
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_data_json(
|
def default_post_json(
|
||||||
title : String = "This is a story",
|
title : String = "This is a story",
|
||||||
paragraph_json : String = default_paragraph_json
|
paragraph_json : String = default_paragraph_json
|
||||||
)
|
)
|
||||||
<<-JSON
|
<<-JSON
|
||||||
{
|
{
|
||||||
"post": {
|
|
||||||
"title": "#{title}",
|
"title": "#{title}",
|
||||||
"createdAt": 1628974309758,
|
"createdAt": 1628974309758,
|
||||||
"creator": {
|
"creator": {
|
||||||
|
@ -134,6 +129,5 @@ def default_data_json(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
JSON
|
JSON
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
Spec.before_each do
|
|
||||||
AppDatabase.truncate
|
|
||||||
end
|
|
12
spec/support/action_helpers.cr
Normal file
12
spec/support/action_helpers.cr
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module ActionHelpers
|
||||||
|
private def action_context(path = "/")
|
||||||
|
io = IO::Memory.new
|
||||||
|
request = HTTP::Request.new("GET", path)
|
||||||
|
response = HTTP::Server::Response.new(io)
|
||||||
|
HTTP::Server::Context.new(request, response)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def params
|
||||||
|
{} of String => String
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,8 +6,8 @@ class Articles::Show < BrowserAction
|
||||||
case post_id
|
case post_id
|
||||||
in Monads::Just
|
in Monads::Just
|
||||||
response = client_class.post_data(post_id.value!)
|
response = client_class.post_data(post_id.value!)
|
||||||
page = PageConverter.new.convert(response.data)
|
page = PageConverter.new.convert(response.data.post)
|
||||||
html ShowPage, page: page
|
render_page(page)
|
||||||
in Monads::Nothing, Monads::Maybe
|
in Monads::Nothing, Monads::Maybe
|
||||||
html(
|
html(
|
||||||
Errors::ParseErrorPage,
|
Errors::ParseErrorPage,
|
||||||
|
@ -18,6 +18,14 @@ class Articles::Show < BrowserAction
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_page(page : Page)
|
||||||
|
html ShowPage, page: page
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_page(page : MissingPage)
|
||||||
|
raise MissingPageError.new
|
||||||
|
end
|
||||||
|
|
||||||
def client_class
|
def client_class
|
||||||
if use_local?
|
if use_local?
|
||||||
LocalClient
|
LocalClient
|
||||||
|
|
|
@ -4,6 +4,10 @@ class Errors::Show < Lucky::ErrorAction
|
||||||
default_format :html
|
default_format :html
|
||||||
dont_report [Lucky::RouteNotFoundError, Avram::RecordNotFoundError]
|
dont_report [Lucky::RouteNotFoundError, Avram::RecordNotFoundError]
|
||||||
|
|
||||||
|
def render(error : MissingPageError)
|
||||||
|
error_html message: "This article is missing.", status: 404
|
||||||
|
end
|
||||||
|
|
||||||
def render(error : Lucky::RouteNotFoundError | Avram::RecordNotFoundError)
|
def render(error : Lucky::RouteNotFoundError | Avram::RecordNotFoundError)
|
||||||
if html?
|
if html?
|
||||||
error_html "Sorry, we couldn't find that page.", status: 404
|
error_html "Sorry, we couldn't find that page.", status: 404
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
class PageConverter
|
class PageConverter
|
||||||
def convert(data : PostResponse::Data) : Page
|
def convert(post : PostResponse::Post) : Page
|
||||||
title, content = title_and_content(data)
|
title, content = title_and_content(post)
|
||||||
author = data.post.creator
|
author = post.creator
|
||||||
created_at = Time.unix_ms(data.post.createdAt)
|
created_at = Time.unix_ms(post.createdAt)
|
||||||
gist_store = gist_store(content)
|
gist_store = gist_store(content)
|
||||||
Page.new(
|
Page.new(
|
||||||
title: title,
|
title: title,
|
||||||
author: author,
|
author: author,
|
||||||
created_at: Time.unix_ms(data.post.createdAt),
|
created_at: Time.unix_ms(post.createdAt),
|
||||||
nodes: ParagraphConverter.new.convert(content, gist_store)
|
nodes: ParagraphConverter.new.convert(content, gist_store)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def title_and_content(data : PostResponse::Data) : {String, Array(PostResponse::Paragraph)}
|
def convert(post : Nil) : MissingPage
|
||||||
title = data.post.title
|
MissingPage.new
|
||||||
paragraphs = data.post.content.bodyModel.paragraphs
|
end
|
||||||
|
|
||||||
|
def title_and_content(post : PostResponse::Post) : {String, Array(PostResponse::Paragraph)}
|
||||||
|
title = post.title
|
||||||
|
paragraphs = post.content.bodyModel.paragraphs
|
||||||
non_content_paragraphs = paragraphs.reject { |para| para.text == title }
|
non_content_paragraphs = paragraphs.reject { |para| para.text == title }
|
||||||
{title, non_content_paragraphs}
|
{title, non_content_paragraphs}
|
||||||
end
|
end
|
||||||
|
|
5
src/models/missing_page.cr
Normal file
5
src/models/missing_page.cr
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class MissingPage
|
||||||
|
end
|
||||||
|
|
||||||
|
class MissingPageError < Exception
|
||||||
|
end
|
|
@ -8,7 +8,7 @@ class PostResponse
|
||||||
end
|
end
|
||||||
|
|
||||||
class Data < Base
|
class Data < Base
|
||||||
property post : Post
|
property post : Post?
|
||||||
end
|
end
|
||||||
|
|
||||||
class Post < Base
|
class Post < Base
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module Scribe
|
module Scribe
|
||||||
VERSION = "2022-05-21"
|
VERSION = "2022-06-17"
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue