samhuri.net/server/server.rb

357 lines
7.8 KiB
Ruby
Executable file

#!/usr/bin/env ruby -w
# An HTTP interface for my Harp blog.
require 'json'
require 'sinatra'
require './auth'
require './harp_blog'
CONFIG_DEFAULTS = {
auth: false,
dry_run: false,
path: File.expand_path('../test-blog', __FILE__),
host: '127.0.0.1',
hostname: `hostname --fqdn`.strip,
port: 6706,
}
def env_value(name)
env_name = "BLOG_#{name.to_s.upcase}"
raw_value = ENV[env_name]
case name
when :auth, :dry_run
raw_value ? raw_value.to_i != 0 : false
when :port
raw_value ? raw_value.to_i : nil
else
raw_value
end
end
$config = CONFIG_DEFAULTS.dup
$config.each_key do |name|
value = env_value(name)
unless value.nil?
$config[name] = value
end
end
unless File.directory?($config[:path])
raise RuntimeError.new("file not found: #{$config[:path]}")
end
if $config[:host] == '0.0.0.0' && !$config[:auth]
raise RuntimeError.new("cowardly refusing to bind to 0.0.0.0 without authentication")
end
$auth = Auth.new(File.expand_path('../auth.json', __FILE__))
def authenticated?(auth)
if $config[:auth]
username, password = auth.split('|')
$auth.authenticated?(username, password)
else
true
end
end
host = $config[:hostname] || $config[:host]
$url_root = "http://#{host}:#{$config[:port]}/"
def url_for(*components)
File.join($url_root, *components)
end
# Server
set :host, $config[:host]
set :port, $config[:port]
blog = HarpBlog.new($config[:path], $config[:dry_run])
before do
if request.body.size > 0
type = request['HTTP_CONTENT_TYPE']
@fields =
case
when type =~ /^application\/json\b/
request.body.rewind
JSON.parse(request.body.read)
else
params
end
end
end
# status
get '/status' do
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(status: blog.status)
end
# publish the site
post '/publish' do
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
blog.publish(@fields['env'])
status 204
end
# list years
get '/years' do
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(years: blog.years)
end
# list months
get '/months' do
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(months: blog.months)
end
# list published posts
get '/posts/:year/?:month?' do |year, month|
posts =
if month
blog.posts_for_month(year, month)
else
blog.posts_for_year(year)
end
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(posts: posts.map(&:fields))
end
# list all published posts
get '/posts' do
posts = blog.months.map do |year, month|
blog.posts_for_month(year, month)
end.flatten
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(posts: posts.map(&:fields))
end
# list drafts
get '/drafts' do
posts = blog.drafts
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(posts: posts.map(&:fields))
end
# get a post
get '/posts/:year/:month/:id' do |year, month, id|
begin
post = blog.get_post(year, month, id)
rescue HarpBlog::InvalidDataError => e
status 500
return "Failed to get post, invalid data on disk: #{e.message}"
end
if post
if request.accept?('application/json')
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(post: post.fields)
elsif request.accept?('text/html')
status 200
headers 'Content-Type' => 'text/html'
blog.render_post(post.fields)
else
status 400
"content not available in an acceptable format: #{request.accept.join(', ')}"
end
else
status 404
'not found'
end
end
# get a draft
get '/drafts/:id' do |id|
begin
post = blog.get_draft(id)
rescue HarpBlog::InvalidDataError => e
status 500
return "Failed to get draft, invalid data on disk: #{e.message}"
end
if post
if request.accept?('application/json')
status 200
headers 'Content-Type' => 'application/json'
JSON.generate(post: post.fields)
elsif request.accept?('text/html')
status 200
headers 'Content-Type' => 'text/html'
blog.render_post(post.fields)
else
status 400
"content not available in an acceptable format: #{request.accept.join(', ')}"
end
else
status 404
'not found'
end
end
# make a draft
post '/drafts' do
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
id, title, body, link = @fields.values_at('id', 'title', 'body', 'link')
begin
if post = blog.create_post(title, body, link, id: id, draft: true)
url = url_for(post.url)
status 201
headers 'Location' => url, 'Content-Type' => 'application/json'
JSON.generate(post: post.fields)
else
status 500
'failed to create post'
end
rescue HarpBlog::PostExistsError => e
post = HarpBlog::Post.new({
title: title,
body: body,
link: link,
})
status 409
"refusing to clobber existing draft, update it instead: #{post.url}"
rescue HarpBlog::PostSaveError => e
status 500
if orig_err = e.original_error
"#{e.message} -- #{orig_err.class}: #{orig_err.message}"
else
"Failed to create draft: #{e.message}"
end
end
end
# update a post
put '/posts/:year/:month/:id' do |year, month, id|
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
title, body, link = @field.values_at('title', 'body', 'link')
begin
if post = blog.get_post(year, month, id)
blog.update_post(post, title, body, link)
status 204
else
status 404
'not found'
end
rescue HarpBlog::InvalidDataError => e
status 500
"Failed to update post, invalid data on disk: #{e.message}"
rescue HarpBlog::PostSaveError => e
status 500
if orig_err = e.original_error
"#{e.message} -- #{orig_err.class}: #{orig_err.message}"
else
"Failed to update post: #{e.message}"
end
end
end
# update a draft
put '/drafts/:id' do |id|
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
title, body, link = @field.values_at('title', 'body', 'link')
begin
if post = blog.get_draft(id)
blog.update_post(post, title, body, link)
status 204
else
status 404
'not found'
end
rescue HarpBlog::InvalidDataError => e
status 500
"Failed to update draft, invalid data on disk: #{e.message}"
rescue HarpBlog::PostSaveError => e
status 500
if orig_err = e.original_error
"#{e.message} -- #{orig_err.class}: #{orig_err.message}"
else
"Failed to update draft: #{e.message}"
end
end
end
# delete a post
delete '/posts/:year/:month/:id' do |year, month, id|
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
blog.delete_post(year, month, id)
status 204
end
# delete a draft
delete '/drafts/:id' do |id|
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
blog.delete_draft(id)
status 204
end
# publish a post
post '/drafts/:id/publish' do |id|
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
if post = blog.get_draft(id)
new_post = blog.publish_post(post)
status 201
headers 'Location' => url_for(new_post.url), 'Content-Type' => 'application/json'
JSON.generate(post: new_post.fields)
else
status 404
'not found'
end
end
# unpublish a post
post '/posts/:year/:month/:id/unpublish' do |year, month, id|
unless authenticated?(request['Auth'])
status 403
return 'forbidden'
end
if post = blog.get_post(year, month, id)
new_post = blog.unpublish_post(post)
status 201
headers 'Location' => url_for(new_post.url), 'Content-Type' => 'application/json'
JSON.generate(post: new_post.fields)
else
status 404
'not found'
end
end