Skip to content
This repository has been archived by the owner on Apr 4, 2022. It is now read-only.

Commit

Permalink
Serve an ATOM feed (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
hmans committed May 27, 2018
1 parent 852eb5f commit 60c39ff
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 6 deletions.
13 changes: 12 additions & 1 deletion Dockerfile
Expand Up @@ -25,14 +25,16 @@ RUN crystal build src/crankypants_cli.cr -o ./crankypants --release --no-debug
RUN strip crankypants

# We use this to identify dependencies used later:
# RUN crystal run ./support/list-deps.cr -- ./crankypants
RUN crystal run ./support/list-deps.cr -- ./crankypants

# Finally, let's build the actual Docker image... from scratch.
FROM scratch

# We'll copy over our dependencies from the previously used crystal container.
# If we ever need to refresh this list, uncomment the `crystal run` line found
# above.
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libxml2.so.2 /usr/lib/x86_64-linux-gnu/libxml2.so.2
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.3 /usr/lib/x86_64-linux-gnu/libxml2.so.2.9.3
COPY --from=crystal /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1
COPY --from=crystal /lib/x86_64-linux-gnu/libz.so.1.2.8 /lib/x86_64-linux-gnu/libz.so.1.2.8
COPY --from=crystal /lib/x86_64-linux-gnu/libssl.so.1.0.0 /lib/x86_64-linux-gnu/libssl.so.1.0.0
Expand All @@ -56,6 +58,15 @@ COPY --from=crystal /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.s
COPY --from=crystal /lib/x86_64-linux-gnu/libc-2.23.so /lib/x86_64-linux-gnu/libc-2.23.so
COPY --from=crystal /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
COPY --from=crystal /lib/x86_64-linux-gnu/ld-2.23.so /lib/x86_64-linux-gnu/ld-2.23.so
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libicuuc.so.55 /usr/lib/x86_64-linux-gnu/libicuuc.so.55
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libicuuc.so.55.1 /usr/lib/x86_64-linux-gnu/libicuuc.so.55.1
COPY --from=crystal /lib/x86_64-linux-gnu/liblzma.so.5 /lib/x86_64-linux-gnu/liblzma.so.5
COPY --from=crystal /lib/x86_64-linux-gnu/liblzma.so.5.0.0 /lib/x86_64-linux-gnu/liblzma.so.5.0.0
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libicudata.so.55 /usr/lib/x86_64-linux-gnu/libicudata.so.55
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libicudata.so.55.1 /usr/lib/x86_64-linux-gnu/libicudata.so.55.1
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6
COPY --from=crystal /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21

COPY --from=crystal /work/crankypants /crankypants

EXPOSE 3000
Expand Down
6 changes: 5 additions & 1 deletion crappy/src/crappy.cr
@@ -1,6 +1,7 @@
require "http/server"
require "http/request"
require "json"
require "xml"

module Crappy
class Handler(T)
Expand Down Expand Up @@ -30,7 +31,7 @@ module Crappy

abstract def call

private def render(object = nil, text = nil, html = nil, json = nil, content_type = nil, status = 200)
private def render(object = nil, text = nil, html = nil, json = nil, xml = nil, content_type = nil, status = 200)
response.status_code = status if status
response.content_type = content_type if content_type

Expand All @@ -45,6 +46,9 @@ module Crappy
elsif json
response.content_type = content_type || "application/json"
response << json.to_json
elsif xml
response.content_type = content_type || "application/xml"
response << xml
elsif object
response << object
end
Expand Down
14 changes: 14 additions & 0 deletions spec/ext/uri_spec.cr
@@ -0,0 +1,14 @@
require "spec"
require "../../src/ext/uri"

describe "URI Extensions" do
describe "URI.join" do
it "replaces the path part if only a path is given" do
URI.join("http://foo.com/foo", "/bar").to_s.should eq("http://foo.com/bar")
end

it "replaces the full URI if the second URI is fully qualified" do
URI.join("http://foo.com/foo", "http://bar.com/bar").to_s.should eq("http://bar.com/bar")
end
end
end
1 change: 1 addition & 0 deletions src/crankypants.cr
@@ -1,4 +1,5 @@
require "habitat"
require "./ext/*"
require "./crankypants/migrator"

module Crankypants
Expand Down
8 changes: 6 additions & 2 deletions src/crankypants/data.cr
Expand Up @@ -12,8 +12,12 @@ module Crankypants
Repo.aggregate(Post, :count, :id).as(Int64)
end

def self.load_posts
Repo.all(Post, Query.order_by("created_at DESC"))
def self.load_posts(limit : Int32? = nil)
query = Query
.order_by("created_at DESC")
.limit(limit)

Repo.all(Post, query)
end

def self.load_post(id : Int32)
Expand Down
31 changes: 31 additions & 0 deletions src/crankypants/web/routers/blog.cr
@@ -1,4 +1,6 @@
require "crappy"
require "../../../tools/atom_feed"
require "../helpers"

module Crankypants::Web::Routers
class Blog < Crappy::Router
Expand All @@ -9,6 +11,35 @@ module Crankypants::Web::Routers
render html: PostView.index(Data.load_posts)
end

get "posts.atom" do
uri = URI.parse("http://#{request.host_with_port}/")
posts = Data.load_posts(limit: 15)

feed = ATOM.build do |feed|
feed.title Crankypants.settings.site_title
feed.link URI.join(uri, "/").to_s
feed.id URI.join(uri, "/").to_s
feed.updated posts.map { |p| p.updated_at.not_nil! }.max

feed.author do |author|
author.name Crankypants.settings.site_title
author.uri URI.join(uri, "/").to_s
end

posts.each do |post|
feed.entry do |entry|
entry.id URI.join(uri, post.url).to_s
entry.title post.title.presence || "Post from #{post.created_at}"
entry.link URI.join(uri, post.url).to_s
entry.updated post.updated_at || Time.now
entry.content post.body_html || ""
end
end
end

render xml: feed
end

within "posts" do
get :id do |params|
post_id = params["id"].not_nil!.to_i
Expand Down
1 change: 1 addition & 0 deletions src/crankypants/web/views/layouts/blog.slang
Expand Up @@ -5,6 +5,7 @@ html
meta charset="utf-8"
title = page_title
link rel="stylesheet" type="text/css" href="#{asset_url("blog.css")}"
link rel="alternate" type="application/atom+xml" title="ATOM Feed" href="/posts.atom" />
script type="application/javascript" src="#{asset_url("blog-bundle.js")}" defer=true
body
header role="main"
Expand Down
11 changes: 11 additions & 0 deletions src/ext/presence.cr
@@ -0,0 +1,11 @@
struct Nil
def presence
nil
end
end

class String
def presence
blank? ? nil : self
end
end
5 changes: 5 additions & 0 deletions src/ext/time.cr
@@ -0,0 +1,5 @@
struct Time
def to_iso8601
to_s("%FT%X%:z")
end
end
18 changes: 18 additions & 0 deletions src/ext/uri.cr
@@ -0,0 +1,18 @@
require "uri"

class URI
def self.join(origin : URI | String, other : URI | String)
origin = URI.parse(origin) unless origin.is_a?(URI)
other = URI.parse(other) unless other.is_a?(URI)

origin.dup.tap do |result|
result.path = other.path

if other.host
result.scheme = other.scheme
result.host = other.host
result.port = other.port
end
end
end
end
2 changes: 2 additions & 0 deletions src/tools/README.md
@@ -0,0 +1,2 @@
This directory contains various bits and pieces that are prime candidates
for extraction into actual shards later on.
105 changes: 105 additions & 0 deletions src/tools/atom_feed.cr
@@ -0,0 +1,105 @@
# Here's a little ATOM module that we could eventually extract
# into a shard. Woohoo!
module ATOM
def self.build
output = String::Builder.new
xml = XML::Builder.new(output)
xml.indent = 2

ATOM::FeedBuilder.new(xml).build do |feed|
yield feed
end

output.to_s
end


module Shortcuts
def field(name : String, value : String? = nil, **opts)
@xml.element(name, **opts) do
if value
@xml.text value
end
end
end

def text_field(name : String, value : String, **opts)
field name, value, **opts
end
end

module CommonBuilderMethods
include Shortcuts

def id(id : String)
text_field "id", id
end

def title(title : String)
text_field "title", title
end

def link(url : String)
return if url.nil? || url.blank?
field "link", href: url, rel: "self"
end

def updated(t : Time)
return if t.nil?
text_field "updated", t.to_iso8601
end

def author
@xml.element "author" do
yield PersonBuilder.new(@xml)
end
end
end

class FeedBuilder
include CommonBuilderMethods

def initialize(@xml : XML::Builder)
end

def build
@xml.document do
@xml.element "feed", xmlns: "http://www.w3.org/2005/Atom" do
yield self
end
end
end

def entry
@xml.element "entry" do
yield EntryBuilder.new(@xml)
end
end
end

class EntryBuilder
include CommonBuilderMethods

def initialize(@xml : XML::Builder)
end

def content(html : String)
field "content", html, type: "html"
end
end

class PersonBuilder
include Shortcuts

def initialize(@xml : XML::Builder)
end

def name(name : String)
text_field "name", name
end

def uri(uri : String)
text_field "uri", uri
end
end
end
4 changes: 2 additions & 2 deletions support/list-deps.cr
Expand Up @@ -28,8 +28,8 @@ puts
puts "=" * 30
puts "FROM scratch"
deps.each do |dep|
puts "COPY --from=0 #{dep} #{dep}"
puts "COPY --from=crystal #{dep} #{dep}"
end
puts "COPY --from=0 #{executable} /#{File.basename(executable)}"
puts "COPY --from=crystal #{executable} /#{File.basename(executable)}"
puts "ENTRYPOINT [\"/#{File.basename(executable)}\"]"
puts "=" * 30

0 comments on commit 60c39ff

Please sign in to comment.