mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-03-25 09:05:47 +00:00
357 lines
7.8 KiB
Ruby
Executable file
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
|