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:
Edward Loveall 2022-06-17 13:26:35 -04:00
parent 1dcded9153
commit f05a12a880
No known key found for this signature in database
GPG key ID: A7606DFEC2BA731F
11 changed files with 102 additions and 59 deletions

View file

@ -1,5 +1,9 @@
2022-05-21
* Show error page for missing posts
2022-05-21
* Remove the need for a fake DATABASE_URL
2022-04-04

View 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

View file

@ -17,8 +17,8 @@ describe PageConverter do
}
]
JSON
data_json = default_data_json(title, paragraph_json)
data = PostResponse::Data.from_json(data_json)
data_json = default_post_json(title, paragraph_json)
data = PostResponse::Post.from_json(data_json)
page = PageConverter.new.convert(data)
@ -26,9 +26,8 @@ describe PageConverter do
end
it "sets the author" do
data_json = <<-JSON
post_json = <<-JSON
{
"post": {
"title": "This is a story",
"createdAt": 0,
"creator": {
@ -41,20 +40,18 @@ describe PageConverter do
}
}
}
}
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.id.should eq "abc123"
end
it "sets the publish date/time" do
data_json = <<-JSON
post_json = <<-JSON
{
"post": {
"title": "This is a story",
"createdAt": 1000,
"creator": {
@ -67,11 +64,10 @@ describe PageConverter do
}
}
}
}
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)
end
@ -98,8 +94,8 @@ describe PageConverter do
}
]
JSON
data_json = default_data_json(title, paragraph_json)
data = PostResponse::Data.from_json(data_json)
post_json = default_post_json(title, paragraph_json)
data = PostResponse::Post.from_json(post_json)
page = PageConverter.new.convert(data)
@ -115,13 +111,12 @@ def default_paragraph_json
"[]"
end
def default_data_json(
def default_post_json(
title : String = "This is a story",
paragraph_json : String = default_paragraph_json
)
<<-JSON
{
"post": {
"title": "#{title}",
"createdAt": 1628974309758,
"creator": {
@ -134,6 +129,5 @@ def default_data_json(
}
}
}
}
JSON
end

View file

@ -1,3 +0,0 @@
Spec.before_each do
AppDatabase.truncate
end

View 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

View file

@ -6,8 +6,8 @@ class Articles::Show < BrowserAction
case post_id
in Monads::Just
response = client_class.post_data(post_id.value!)
page = PageConverter.new.convert(response.data)
html ShowPage, page: page
page = PageConverter.new.convert(response.data.post)
render_page(page)
in Monads::Nothing, Monads::Maybe
html(
Errors::ParseErrorPage,
@ -18,6 +18,14 @@ class Articles::Show < BrowserAction
end
end
def render_page(page : Page)
html ShowPage, page: page
end
def render_page(page : MissingPage)
raise MissingPageError.new
end
def client_class
if use_local?
LocalClient

View file

@ -4,6 +4,10 @@ class Errors::Show < Lucky::ErrorAction
default_format :html
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)
if html?
error_html "Sorry, we couldn't find that page.", status: 404

View file

@ -1,20 +1,24 @@
class PageConverter
def convert(data : PostResponse::Data) : Page
title, content = title_and_content(data)
author = data.post.creator
created_at = Time.unix_ms(data.post.createdAt)
def convert(post : PostResponse::Post) : Page
title, content = title_and_content(post)
author = post.creator
created_at = Time.unix_ms(post.createdAt)
gist_store = gist_store(content)
Page.new(
title: title,
author: author,
created_at: Time.unix_ms(data.post.createdAt),
created_at: Time.unix_ms(post.createdAt),
nodes: ParagraphConverter.new.convert(content, gist_store)
)
end
def title_and_content(data : PostResponse::Data) : {String, Array(PostResponse::Paragraph)}
title = data.post.title
paragraphs = data.post.content.bodyModel.paragraphs
def convert(post : Nil) : MissingPage
MissingPage.new
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 }
{title, non_content_paragraphs}
end

View file

@ -0,0 +1,5 @@
class MissingPage
end
class MissingPageError < Exception
end

View file

@ -8,7 +8,7 @@ class PostResponse
end
class Data < Base
property post : Post
property post : Post?
end
class Post < Base

View file

@ -1,3 +1,3 @@
module Scribe
VERSION = "2022-05-21"
VERSION = "2022-06-17"
end