mirror of
https://github.com/samsonjs/samhuri.net.git
synced 2026-04-27 14:57:40 +00:00
use IDs instead of slugs for posts
Drafts don’t have reliable slugs until they’re published so give them UUIDs, and lookup posts by ID instead of slug.
This commit is contained in:
parent
d9731944c2
commit
c905f5c414
3 changed files with 230 additions and 146 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
require 'fileutils'
|
require 'fileutils'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
require 'securerandom'
|
||||||
require './web_title_finder'
|
require './web_title_finder'
|
||||||
require './web_version_finder'
|
require './web_version_finder'
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ class HarpBlog
|
||||||
end
|
end
|
||||||
|
|
||||||
class Post
|
class Post
|
||||||
PERSISTENT_FIELDS = %w[author title date timestamp link url tags].map(&:to_sym)
|
PERSISTENT_FIELDS = %w[id author title date timestamp link url tags].map(&:to_sym)
|
||||||
TRANSIENT_FIELDS = %w[time slug body draft].map(&:to_sym)
|
TRANSIENT_FIELDS = %w[time slug body draft].map(&:to_sym)
|
||||||
FIELDS = PERSISTENT_FIELDS + TRANSIENT_FIELDS
|
FIELDS = PERSISTENT_FIELDS + TRANSIENT_FIELDS
|
||||||
FIELDS.each { |f| attr_accessor f }
|
FIELDS.each { |f| attr_accessor f }
|
||||||
|
|
@ -83,10 +84,19 @@ class HarpBlog
|
||||||
@timestamp = timestamp
|
@timestamp = timestamp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def id
|
||||||
|
@id ||=
|
||||||
|
if draft?
|
||||||
|
SecureRandom.uuid
|
||||||
|
else
|
||||||
|
slug
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def url
|
def url
|
||||||
@url ||=
|
@url ||=
|
||||||
if draft?
|
if draft?
|
||||||
"/posts/drafts/#{slug}"
|
"/posts/drafts/#{id}"
|
||||||
else
|
else
|
||||||
"/posts/#{time.year}/#{padded_month}/#{slug}"
|
"/posts/#{time.year}/#{padded_month}/#{slug}"
|
||||||
end
|
end
|
||||||
|
|
@ -94,7 +104,7 @@ class HarpBlog
|
||||||
|
|
||||||
def slug
|
def slug
|
||||||
# TODO: be intelligent about unicode ... \p{Word} might help. negated char class with it?
|
# TODO: be intelligent about unicode ... \p{Word} might help. negated char class with it?
|
||||||
if title
|
if !draft? && title
|
||||||
@slug ||= title.downcase.
|
@slug ||= title.downcase.
|
||||||
gsub(/'/, '').
|
gsub(/'/, '').
|
||||||
gsub(/[^[:alpha:]\d_]/, '-').
|
gsub(/[^[:alpha:]\d_]/, '-').
|
||||||
|
|
@ -184,12 +194,12 @@ class HarpBlog
|
||||||
read_posts('drafts', draft: true)
|
read_posts('drafts', draft: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_post(year, month, slug)
|
def get_post(year, month, id)
|
||||||
read_post(File.join(year, month), slug)
|
read_post(File.join(year, month), id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_draft(slug)
|
def get_draft(id)
|
||||||
read_post('drafts', slug, draft: true)
|
read_post('drafts', id, draft: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_post(title, body, url, extra_fields = nil)
|
def create_post(title, body, url, extra_fields = nil)
|
||||||
|
|
@ -208,15 +218,15 @@ class HarpBlog
|
||||||
post = Post.new(fields)
|
post = Post.new(fields)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
existing_post = read_post(post.dir, post.slug, extra_fields)
|
existing_post = read_post(post.dir, post.id, extra_fields)
|
||||||
rescue InvalidDataError => e
|
rescue InvalidDataError => e
|
||||||
$stderr.puts "[HarpBlog#create_post] deleting post with invalid data: #{e.message}"
|
$stderr.puts "[HarpBlog#create_post] deleting post with invalid data: #{e.message}"
|
||||||
delete_post_from_dir(post.dir, post.slug)
|
delete_post_from_dir(post.dir, post.id)
|
||||||
existing_post = nil
|
existing_post = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if existing_post
|
if existing_post
|
||||||
raise PostExistsError.new("post exists: #{post.dir}/#{post.slug}")
|
raise PostExistsError.new("post exists: #{post.dir}/#{post.id}")
|
||||||
else
|
else
|
||||||
save_post('create post', post)
|
save_post('create post', post)
|
||||||
end
|
end
|
||||||
|
|
@ -229,36 +239,36 @@ class HarpBlog
|
||||||
save_post('update post', post)
|
save_post('update post', post)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post(year, month, slug)
|
def delete_post(year, month, id)
|
||||||
delete_post_from_dir(File.join(year, month), slug)
|
delete_post_from_dir(File.join(year, month), id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_draft(slug)
|
def delete_draft(id)
|
||||||
delete_post_from_dir('drafts', slug)
|
delete_post_from_dir('drafts', id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_post(post)
|
def publish_post(post)
|
||||||
if post.draft?
|
if post.draft?
|
||||||
new_post = create_post(post.title, post.body, post.link)
|
new_post = create_post(post.title, post.body, post.link)
|
||||||
delete_post_from_dir('drafts', post.slug)
|
delete_post_from_dir('drafts', post.id)
|
||||||
new_post
|
new_post
|
||||||
else
|
else
|
||||||
raise PostAlreadyPublishedError.new("post is already published: #{post.dir}/#{post.slug}")
|
raise PostAlreadyPublishedError.new("post is already published: #{post.dir}/#{post.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unpublish_post(post)
|
def unpublish_post(post)
|
||||||
if post.draft?
|
if post.draft?
|
||||||
raise PostNotPublishedError.new("post is not published: #{post.dir}/#{post.slug}")
|
raise PostNotPublishedError.new("post is not published: #{post.dir}/#{post.id}")
|
||||||
else
|
else
|
||||||
new_post = create_post(post.title, post.body, post.link, draft: true)
|
new_post = create_post(post.title, post.body, post.link, draft: true)
|
||||||
delete_post_from_dir(post.dir, post.slug)
|
delete_post_from_dir(post.dir, post.id)
|
||||||
new_post
|
new_post
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish(production = false)
|
def publish(env)
|
||||||
target = production ? 'publish' : 'publish_beta'
|
target = env.to_s == 'production' ? 'publish' : 'publish_beta'
|
||||||
run("make #{target}")
|
run("make #{target}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -282,26 +292,40 @@ class HarpBlog
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_posts(post_dir, extra_fields = nil)
|
def read_posts(post_dir, extra_fields = nil)
|
||||||
|
extra_fields ||= {}
|
||||||
post_data = read_post_data(post_path(post_dir))
|
post_data = read_post_data(post_path(post_dir))
|
||||||
post_data.sort_by do |k, v|
|
post_data.sort_by do |k, v|
|
||||||
(v['timestamp'] || Time.now).to_i
|
(v['timestamp'] || Time.now).to_i
|
||||||
end.map do |slug, fields|
|
end.map do |id, fields|
|
||||||
Post.new(fields.merge(extra_fields || {}).merge(slug: slug))
|
fields[:id] = id
|
||||||
|
unless extra_fields[:draft]
|
||||||
|
fields[:slug] = id
|
||||||
|
end
|
||||||
|
post_filename = post_path(post_dir, "#{id}.md")
|
||||||
|
fields[:body] = File.read(post_filename)
|
||||||
|
Post.new(fields.merge(extra_fields))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_post(post_dir, slug, extra_fields = nil)
|
def read_post(post_dir, id, extra_fields = nil)
|
||||||
post_filename = post_path(post_dir, "#{slug}.md")
|
post_filename = post_path(post_dir, "#{id}.md")
|
||||||
post_data = read_post_data(post_path(post_dir))
|
post_data = read_post_data(post_path(post_dir))
|
||||||
if File.exist?(post_filename) && fields = post_data[slug]
|
if File.exist?(post_filename) && fields = post_data[id]
|
||||||
fields[:body] = File.read(post_filename)
|
fields[:body] = File.read(post_filename)
|
||||||
Post.new(fields.merge(extra_fields || {}).merge(slug: slug))
|
if extra_fields
|
||||||
|
fields.merge!(extra_fields)
|
||||||
|
end
|
||||||
|
fields[:id] = id
|
||||||
|
unless fields[:draft]
|
||||||
|
fields[:slug] = id
|
||||||
|
end
|
||||||
|
Post.new(fields)
|
||||||
elsif fields
|
elsif fields
|
||||||
message = "missing post body for #{post_dir}/#{slug}: #{post_filename}"
|
message = "missing post body for #{post_dir}/#{id}: #{post_filename}"
|
||||||
$stderr.puts "[HarpBlog#read_post] #{message}"
|
$stderr.puts "[HarpBlog#read_post] #{message}"
|
||||||
raise InvalidDataError.new(message)
|
raise InvalidDataError.new(message)
|
||||||
elsif File.exist?(post_filename)
|
elsif File.exist?(post_filename)
|
||||||
message = "missing metadata for #{post_dir}/#{slug}: #{post_dir}/_data.json"
|
message = "missing metadata for #{post_dir}/#{id}: #{post_dir}/_data.json"
|
||||||
$stderr.puts "[HarpBlog#read_post] #{message}"
|
$stderr.puts "[HarpBlog#read_post] #{message}"
|
||||||
raise InvalidDataError.new(message)
|
raise InvalidDataError.new(message)
|
||||||
end
|
end
|
||||||
|
|
@ -330,43 +354,43 @@ class HarpBlog
|
||||||
unless post.draft?
|
unless post.draft?
|
||||||
ensure_post_dir_exists(post_dir)
|
ensure_post_dir_exists(post_dir)
|
||||||
end
|
end
|
||||||
write_post_body(post_dir, post.slug, post.body)
|
write_post_body(post_dir, post.id, post.body)
|
||||||
begin
|
begin
|
||||||
write_post_index(post_dir, post.slug, post.persistent_fields)
|
write_post_index(post_dir, post.id, post.persistent_fields)
|
||||||
rescue => e
|
rescue => e
|
||||||
$stderr.puts "#{e.class}: #{e.message}"
|
$stderr.puts "#{e.class}: #{e.message}"
|
||||||
$stderr.puts e.backtrace
|
$stderr.puts e.backtrace
|
||||||
delete_post_body(post_dir, post.slug)
|
delete_post_body(post_dir, post.id)
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post_from_dir(post_dir, slug)
|
def delete_post_from_dir(post_dir, id)
|
||||||
post_dir = post_path(post_dir)
|
post_dir = post_path(post_dir)
|
||||||
delete_post_body(post_dir, slug)
|
delete_post_body(post_dir, id)
|
||||||
delete_post_index(post_dir, slug)
|
delete_post_index(post_dir, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_post_body(dir, slug, body)
|
def write_post_body(dir, id, body)
|
||||||
post_filename = File.join(dir, "#{slug}.md")
|
post_filename = File.join(dir, "#{id}.md")
|
||||||
write_file(post_filename, body)
|
write_file(post_filename, body)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post_body(dir, slug)
|
def delete_post_body(dir, id)
|
||||||
post_filename = File.join(dir, "#{slug}.md")
|
post_filename = File.join(dir, "#{id}.md")
|
||||||
delete_file(post_filename)
|
delete_file(post_filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_post_index(dir, slug, fields)
|
def write_post_index(dir, id, fields)
|
||||||
post_data = read_post_data(dir)
|
post_data = read_post_data(dir)
|
||||||
post_data[slug] = fields
|
post_data[id] = fields
|
||||||
write_post_data(dir, post_data)
|
write_post_data(dir, post_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post_index(dir, slug)
|
def delete_post_index(dir, id)
|
||||||
post_data = read_post_data(dir)
|
post_data = read_post_data(dir)
|
||||||
if post_data[slug]
|
if post_data[id]
|
||||||
post_data.delete(slug)
|
post_data.delete(id)
|
||||||
write_post_data(dir, post_data)
|
write_post_data(dir, post_data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
117
server/server.rb
117
server/server.rb
|
|
@ -67,11 +67,36 @@ set :port, $config[:port]
|
||||||
|
|
||||||
blog = HarpBlog.new($config[:path], $config[:dry_run])
|
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
|
# status
|
||||||
get '/status' do
|
get '/status' do
|
||||||
status 200
|
status 200
|
||||||
headers 'Content-Type' => 'application/json'
|
headers 'Content-Type' => 'application/json'
|
||||||
JSON.generate(blog.status)
|
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
|
end
|
||||||
|
|
||||||
# list years
|
# list years
|
||||||
|
|
@ -102,6 +127,17 @@ get '/posts/:year/?:month?' do |year, month|
|
||||||
JSON.generate(posts: posts.map(&:fields))
|
JSON.generate(posts: posts.map(&:fields))
|
||||||
end
|
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
|
# list drafts
|
||||||
get '/drafts' do
|
get '/drafts' do
|
||||||
posts = blog.drafts
|
posts = blog.drafts
|
||||||
|
|
@ -112,9 +148,9 @@ get '/drafts' do
|
||||||
end
|
end
|
||||||
|
|
||||||
# get a post
|
# get a post
|
||||||
get '/posts/:year/:month/:slug' do |year, month, slug|
|
get '/posts/:year/:month/:id' do |year, month, id|
|
||||||
begin
|
begin
|
||||||
post = blog.get_post(year, month, slug)
|
post = blog.get_post(year, month, id)
|
||||||
rescue HarpBlog::InvalidDataError => e
|
rescue HarpBlog::InvalidDataError => e
|
||||||
status 500
|
status 500
|
||||||
return "Failed to get post, invalid data on disk: #{e.message}"
|
return "Failed to get post, invalid data on disk: #{e.message}"
|
||||||
|
|
@ -140,9 +176,9 @@ get '/posts/:year/:month/:slug' do |year, month, slug|
|
||||||
end
|
end
|
||||||
|
|
||||||
# get a draft
|
# get a draft
|
||||||
get '/drafts/:slug' do |slug|
|
get '/drafts/:id' do |id|
|
||||||
begin
|
begin
|
||||||
post = blog.get_draft(slug)
|
post = blog.get_draft(id)
|
||||||
rescue HarpBlog::InvalidDataError => e
|
rescue HarpBlog::InvalidDataError => e
|
||||||
status 500
|
status 500
|
||||||
return "Failed to get draft, invalid data on disk: #{e.message}"
|
return "Failed to get draft, invalid data on disk: #{e.message}"
|
||||||
|
|
@ -174,16 +210,25 @@ post '/drafts' do
|
||||||
return 'forbidden'
|
return 'forbidden'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
id, title, body, link = @fields.values_at('id', 'title', 'body', 'link')
|
||||||
begin
|
begin
|
||||||
post = blog.create_post(params[:title], params[:body], params[:link], draft: true)
|
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
|
rescue HarpBlog::PostExistsError => e
|
||||||
post = HarpBlog::Post.new({
|
post = HarpBlog::Post.new({
|
||||||
title: params[:title],
|
title: title,
|
||||||
body: params[:body],
|
body: body,
|
||||||
link: params[:link],
|
link: link,
|
||||||
})
|
})
|
||||||
status 409
|
status 409
|
||||||
return "refusing to clobber existing draft, update it instead: #{post.url}"
|
"refusing to clobber existing draft, update it instead: #{post.url}"
|
||||||
rescue HarpBlog::PostSaveError => e
|
rescue HarpBlog::PostSaveError => e
|
||||||
status 500
|
status 500
|
||||||
if orig_err = e.original_error
|
if orig_err = e.original_error
|
||||||
|
|
@ -192,28 +237,19 @@ post '/drafts' do
|
||||||
"Failed to create draft: #{e.message}"
|
"Failed to create draft: #{e.message}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if post
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# update a post
|
# update a post
|
||||||
put '/posts/:year/:month/:slug' do |year, month, slug|
|
put '/posts/:year/:month/:id' do |year, month, id|
|
||||||
unless authenticated?(request['Auth'])
|
unless authenticated?(request['Auth'])
|
||||||
status 403
|
status 403
|
||||||
return 'forbidden'
|
return 'forbidden'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
title, body, link = @field.values_at('title', 'body', 'link')
|
||||||
begin
|
begin
|
||||||
if post = blog.get_post(year, month, slug)
|
if post = blog.get_post(year, month, id)
|
||||||
blog.update_post(post, params[:title], params[:body], params[:link])
|
blog.update_post(post, title, body, link)
|
||||||
status 204
|
status 204
|
||||||
else
|
else
|
||||||
status 404
|
status 404
|
||||||
|
|
@ -233,15 +269,16 @@ put '/posts/:year/:month/:slug' do |year, month, slug|
|
||||||
end
|
end
|
||||||
|
|
||||||
# update a draft
|
# update a draft
|
||||||
put '/drafts/:slug' do |slug|
|
put '/drafts/:id' do |id|
|
||||||
unless authenticated?(request['Auth'])
|
unless authenticated?(request['Auth'])
|
||||||
status 403
|
status 403
|
||||||
return 'forbidden'
|
return 'forbidden'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
title, body, link = @field.values_at('title', 'body', 'link')
|
||||||
begin
|
begin
|
||||||
if post = blog.get_draft(slug)
|
if post = blog.get_draft(id)
|
||||||
blog.update_post(post, params[:title], params[:body], params[:link])
|
blog.update_post(post, title, body, link)
|
||||||
status 204
|
status 204
|
||||||
else
|
else
|
||||||
status 404
|
status 404
|
||||||
|
|
@ -261,35 +298,35 @@ put '/drafts/:slug' do |slug|
|
||||||
end
|
end
|
||||||
|
|
||||||
# delete a post
|
# delete a post
|
||||||
delete '/posts/:year/:month/:slug' do |year, month, slug|
|
delete '/posts/:year/:month/:id' do |year, month, id|
|
||||||
unless authenticated?(request['Auth'])
|
unless authenticated?(request['Auth'])
|
||||||
status 403
|
status 403
|
||||||
return 'forbidden'
|
return 'forbidden'
|
||||||
end
|
end
|
||||||
|
|
||||||
blog.delete_post(year, month, slug)
|
blog.delete_post(year, month, id)
|
||||||
status 204
|
status 204
|
||||||
end
|
end
|
||||||
|
|
||||||
# delete a draft
|
# delete a draft
|
||||||
delete '/drafts/:slug' do |slug|
|
delete '/drafts/:id' do |id|
|
||||||
unless authenticated?(request['Auth'])
|
unless authenticated?(request['Auth'])
|
||||||
status 403
|
status 403
|
||||||
return 'forbidden'
|
return 'forbidden'
|
||||||
end
|
end
|
||||||
|
|
||||||
blog.delete_draft(slug)
|
blog.delete_draft(id)
|
||||||
status 204
|
status 204
|
||||||
end
|
end
|
||||||
|
|
||||||
# publish a post
|
# publish a post
|
||||||
post '/drafts/:slug/publish' do |slug|
|
post '/drafts/:id/publish' do |id|
|
||||||
unless authenticated?(request['Auth'])
|
unless authenticated?(request['Auth'])
|
||||||
status 403
|
status 403
|
||||||
return 'forbidden'
|
return 'forbidden'
|
||||||
end
|
end
|
||||||
|
|
||||||
if post = blog.get_draft(slug)
|
if post = blog.get_draft(id)
|
||||||
new_post = blog.publish_post(post)
|
new_post = blog.publish_post(post)
|
||||||
status 201
|
status 201
|
||||||
headers 'Location' => url_for(new_post.url), 'Content-Type' => 'application/json'
|
headers 'Location' => url_for(new_post.url), 'Content-Type' => 'application/json'
|
||||||
|
|
@ -301,13 +338,13 @@ post '/drafts/:slug/publish' do |slug|
|
||||||
end
|
end
|
||||||
|
|
||||||
# unpublish a post
|
# unpublish a post
|
||||||
post '/posts/:year/:month/:slug/unpublish' do |year, month, slug|
|
post '/posts/:year/:month/:id/unpublish' do |year, month, id|
|
||||||
unless authenticated?(request['Auth'])
|
unless authenticated?(request['Auth'])
|
||||||
status 403
|
status 403
|
||||||
return 'forbidden'
|
return 'forbidden'
|
||||||
end
|
end
|
||||||
|
|
||||||
if post = blog.get_post(year, month, slug)
|
if post = blog.get_post(year, month, id)
|
||||||
new_post = blog.unpublish_post(post)
|
new_post = blog.unpublish_post(post)
|
||||||
status 201
|
status 201
|
||||||
headers 'Location' => url_for(new_post.url), 'Content-Type' => 'application/json'
|
headers 'Location' => url_for(new_post.url), 'Content-Type' => 'application/json'
|
||||||
|
|
@ -317,15 +354,3 @@ post '/posts/:year/:month/:slug/unpublish' do |year, month, slug|
|
||||||
'not found'
|
'not found'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# publish the site
|
|
||||||
post '/publish' do
|
|
||||||
unless authenticated?(request['Auth'])
|
|
||||||
status 403
|
|
||||||
return 'forbidden'
|
|
||||||
end
|
|
||||||
|
|
||||||
production = params[:env] == 'production'
|
|
||||||
blog.publish(production)
|
|
||||||
status 204
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,28 @@ end
|
||||||
|
|
||||||
RSpec.describe HarpBlog::Post do
|
RSpec.describe HarpBlog::Post do
|
||||||
|
|
||||||
# Persistent fields: author, title, date, timestamp, link, url, tags
|
# Persistent fields: id, author, title, date, timestamp, link, url, tags
|
||||||
# Transient fields: time, slug, body
|
# Transient fields: time, slug, body
|
||||||
|
|
||||||
before :all do
|
before :all do
|
||||||
@default_fields = {
|
@post_fields = {
|
||||||
title: 'samhuri.net',
|
title: 'samhuri.net',
|
||||||
link: 'http://samhuri.net',
|
link: 'http://samhuri.net',
|
||||||
body: 'this site is sick',
|
body: 'this site is sick',
|
||||||
}
|
}
|
||||||
@default_slug = 'samhuri-net'
|
@post_slug = 'samhuri-net'
|
||||||
|
@draft_fields = {
|
||||||
|
title: 'reddit.com',
|
||||||
|
link: 'http://reddit.com',
|
||||||
|
body: 'hi reddit',
|
||||||
|
draft: true,
|
||||||
|
id: 'dummy-draft-id',
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#new' do
|
describe '#new' do
|
||||||
it "takes a Hash of fields" do
|
it "takes a Hash of fields" do
|
||||||
fields = @default_fields
|
fields = @post_fields
|
||||||
post = HarpBlog::Post.new(fields)
|
post = HarpBlog::Post.new(fields)
|
||||||
expect(post.title).to eq(fields[:title])
|
expect(post.title).to eq(fields[:title])
|
||||||
expect(post.link).to eq(fields[:link])
|
expect(post.link).to eq(fields[:link])
|
||||||
|
|
@ -61,7 +68,7 @@ RSpec.describe HarpBlog::Post do
|
||||||
|
|
||||||
describe '#link?' do
|
describe '#link?' do
|
||||||
it "returns true for link posts" do
|
it "returns true for link posts" do
|
||||||
post = HarpBlog::Post.new(link: @default_fields[:link])
|
post = HarpBlog::Post.new(link: @post_fields[:link])
|
||||||
expect(post.link?).to be_truthy
|
expect(post.link?).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -101,18 +108,38 @@ RSpec.describe HarpBlog::Post do
|
||||||
|
|
||||||
describe '#url' do
|
describe '#url' do
|
||||||
it "should be derived from the time and slug if necessary" do
|
it "should be derived from the time and slug if necessary" do
|
||||||
post = HarpBlog::Post.new(@default_fields)
|
post = HarpBlog::Post.new(@post_fields)
|
||||||
year = post.time.year.to_s
|
year = post.time.year.to_s
|
||||||
month = post.time.month
|
month = post.time.month
|
||||||
padded_month = month < 10 ? " #{month}" : "#{month}"
|
padded_month = month < 10 ? "0#{month}" : "#{month}"
|
||||||
expect(post.url).to eq("/posts/#{year}/#{padded_month}/#{@default_slug}")
|
expect(post.url).to eq("/posts/#{year}/#{padded_month}/#{@post_slug}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#id' do
|
||||||
|
it "should be generated for drafts if necessary" do
|
||||||
|
draft = HarpBlog::Post.new(@draft_fields)
|
||||||
|
expect(draft.id).to eq(@draft_fields[:id])
|
||||||
|
|
||||||
|
draft = HarpBlog::Post.new(@draft_fields.merge(id: nil))
|
||||||
|
expect(draft.id).to_not eq(@draft_fields[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be the slug for posts" do
|
||||||
|
post = HarpBlog::Post.new(@post_fields)
|
||||||
|
expect(post.id).to eq(post.slug)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#slug' do
|
describe '#slug' do
|
||||||
it "should be derived from the title if necessary" do
|
it "should be derived from the title if necessary" do
|
||||||
post = HarpBlog::Post.new(@default_fields)
|
post = HarpBlog::Post.new(@post_fields)
|
||||||
expect(post.slug).to eq(@default_slug)
|
expect(post.slug).to eq(@post_slug)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be nil for drafts" do
|
||||||
|
draft = HarpBlog::Post.new(@draft_fields)
|
||||||
|
expect(draft.slug).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should strip apostrophes" do
|
it "should strip apostrophes" do
|
||||||
|
|
@ -303,20 +330,21 @@ RSpec.describe HarpBlog do
|
||||||
|
|
||||||
describe '#get_draft' do
|
describe '#get_draft' do
|
||||||
it "should return complete posts" do
|
it "should return complete posts" do
|
||||||
|
id = 'some-draft-id'
|
||||||
title = 'new draft'
|
title = 'new draft'
|
||||||
body = "blah blah blah\n"
|
body = "blah blah blah\n"
|
||||||
@blog.create_post(title, body, nil, draft: true)
|
@blog.create_post(title, body, nil, id: id, draft: true)
|
||||||
post = @blog.get_draft('new-draft')
|
draft = @blog.get_draft(id)
|
||||||
expect(post).to be_truthy
|
expect(draft).to be_truthy
|
||||||
expect(post.title).to eq(title)
|
expect(draft.title).to eq(title)
|
||||||
expect(post.url).to eq('/posts/drafts/new-draft')
|
expect(draft.url).to eq("/posts/drafts/#{id}")
|
||||||
expect(post.draft?).to be_truthy
|
expect(draft.draft?).to be_truthy
|
||||||
expect(post.body).to eq(body)
|
expect(draft.body).to eq(body)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return nil if the post does not exist" do
|
it "should return nil if the post does not exist" do
|
||||||
post = @blog.get_draft('does-not-exist')
|
draft = @blog.get_draft('does-not-exist')
|
||||||
expect(post).to be(nil)
|
expect(draft).to be(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -369,13 +397,14 @@ RSpec.describe HarpBlog do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should create a draft that can be fetched immediately" do
|
it "should create a draft that can be fetched immediately" do
|
||||||
|
id = 'another-draft-id'
|
||||||
title = 'fetch now'
|
title = 'fetch now'
|
||||||
body = 'blah blah blah'
|
body = 'blah blah blah'
|
||||||
post = @blog.create_post(title, body, nil, draft: true)
|
draft = @blog.create_post(title, body, nil, id: id, draft: true)
|
||||||
expect(post).to be_truthy
|
expect(draft).to be_truthy
|
||||||
|
|
||||||
fetched_post = @blog.get_draft(post.slug)
|
fetched_draft = @blog.get_draft(draft.id)
|
||||||
expect(post.url).to eq(fetched_post.url)
|
expect(draft.url).to eq(fetched_draft.url)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should fetch titles if necessary" do
|
it "should fetch titles if necessary" do
|
||||||
|
|
@ -434,49 +463,54 @@ RSpec.describe HarpBlog do
|
||||||
|
|
||||||
describe '#delete_draft' do
|
describe '#delete_draft' do
|
||||||
it "should delete existing drafts" do
|
it "should delete existing drafts" do
|
||||||
|
id = 'bunk-draft-id'
|
||||||
title = 'new draft'
|
title = 'new draft'
|
||||||
body = 'blah blah blah'
|
body = 'blah blah blah'
|
||||||
existing_post = @blog.create_post(title, body, nil, draft: true)
|
existing_draft = @blog.create_post(title, body, nil, id: id, draft: true)
|
||||||
post = @blog.get_draft(existing_post.slug)
|
draft = @blog.get_draft(existing_draft.id)
|
||||||
expect(post).to be_truthy
|
expect(draft).to be_truthy
|
||||||
|
|
||||||
@blog.delete_draft(post.slug)
|
@blog.delete_draft(draft.id)
|
||||||
|
|
||||||
post = @blog.get_draft(post.slug)
|
draft = @blog.get_draft(draft.id)
|
||||||
expect(post).to eq(nil)
|
expect(draft).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should do nothing for non-existent posts" do
|
it "should do nothing for non-existent posts" do
|
||||||
|
id = 'missing-draft-id'
|
||||||
title = 'new draft'
|
title = 'new draft'
|
||||||
body = 'blah blah blah'
|
body = 'blah blah blah'
|
||||||
existing_post = @blog.create_post(title, body, nil, draft: true)
|
existing_draft = @blog.create_post(title, body, nil, id: id, draft: true)
|
||||||
|
|
||||||
post = @blog.get_draft(existing_post.slug)
|
draft = @blog.get_draft(existing_draft.id)
|
||||||
expect(post).to be_truthy
|
expect(draft).to be_truthy
|
||||||
|
|
||||||
@blog.delete_draft(post.slug)
|
@blog.delete_draft(draft.id)
|
||||||
@blog.delete_draft(post.slug)
|
expect(@blog.get_draft(existing_draft.id)).to be_nil
|
||||||
|
@blog.delete_draft(draft.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#publish_post' do
|
describe '#publish_post' do
|
||||||
it "should publish drafts" do
|
it "should publish drafts" do
|
||||||
|
id = 'this-draft-is-a-keeper'
|
||||||
title = 'a-shiny-new-post'
|
title = 'a-shiny-new-post'
|
||||||
body = 'blah blah blah'
|
body = 'blah blah blah'
|
||||||
link = 'http://samhuri.net'
|
link = 'http://samhuri.net'
|
||||||
post = @blog.create_post(title, body, link, draft: true)
|
draft = @blog.create_post(title, body, link, id: id, draft: true)
|
||||||
new_post = @blog.publish_post(post)
|
post = @blog.publish_post(draft)
|
||||||
expect(new_post).to be_truthy
|
|
||||||
expect(new_post.draft?).to be_falsy
|
|
||||||
expect(new_post.title).to eq(title)
|
|
||||||
expect(new_post.body).to eq(body)
|
|
||||||
expect(new_post.link).to eq(link)
|
|
||||||
|
|
||||||
draft = @blog.get_draft(post.slug)
|
|
||||||
expect(draft).to eq(nil)
|
|
||||||
|
|
||||||
post = @blog.get_post(post.time.year.to_s, post.padded_month, post.slug)
|
|
||||||
expect(post).to be_truthy
|
expect(post).to be_truthy
|
||||||
|
expect(post.id).to eq(post.slug)
|
||||||
|
expect(post.draft?).to be_falsy
|
||||||
|
expect(post.title).to eq(title)
|
||||||
|
expect(post.body).to eq(body)
|
||||||
|
expect(post.link).to eq(link)
|
||||||
|
|
||||||
|
missing_draft = @blog.get_draft(draft.id)
|
||||||
|
expect(missing_draft).to eq(nil)
|
||||||
|
|
||||||
|
fetched_post = @blog.get_post(post.time.year.to_s, post.padded_month, post.slug)
|
||||||
|
expect(fetched_post).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise an error for published posts" do
|
it "should raise an error for published posts" do
|
||||||
|
|
@ -488,18 +522,19 @@ RSpec.describe HarpBlog do
|
||||||
describe '#unpublish_post' do
|
describe '#unpublish_post' do
|
||||||
it "should unpublish posts" do
|
it "should unpublish posts" do
|
||||||
post = @blog.get_post('2006', '02', 'first-post')
|
post = @blog.get_post('2006', '02', 'first-post')
|
||||||
new_post = @blog.unpublish_post(post)
|
draft = @blog.unpublish_post(post)
|
||||||
expect(new_post).to be_truthy
|
|
||||||
expect(new_post.draft?).to be_truthy
|
|
||||||
expect(new_post.title).to eq(post.title)
|
|
||||||
expect(new_post.body).to eq(post.body)
|
|
||||||
expect(new_post.link).to eq(post.link)
|
|
||||||
|
|
||||||
post = @blog.get_post(post.time.year.to_s, post.padded_month, post.slug)
|
|
||||||
expect(post).to eq(nil)
|
|
||||||
|
|
||||||
draft = @blog.get_draft(new_post.slug)
|
|
||||||
expect(draft).to be_truthy
|
expect(draft).to be_truthy
|
||||||
|
expect(draft.id).to be_truthy
|
||||||
|
expect(draft.draft?).to be_truthy
|
||||||
|
expect(draft.title).to eq(post.title)
|
||||||
|
expect(draft.body).to eq(post.body)
|
||||||
|
expect(draft.link).to eq(post.link)
|
||||||
|
|
||||||
|
missing_post = @blog.get_post(post.time.year.to_s, post.padded_month, post.slug)
|
||||||
|
expect(missing_post).to eq(nil)
|
||||||
|
|
||||||
|
fetched_draft = @blog.get_draft(draft.id)
|
||||||
|
expect(fetched_draft).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise an error for drafts" do
|
it "should raise an error for drafts" do
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue