#!/usr/bin/env wsapi.cgi
local orbit = require "orbit"
local orcache = require "orbit.cache"
local markdown = require "markdown"
local wsutil = require "wsapi.util"
--
-- Declares that this is module is an Orbit app
--
local blog = setmetatable(orbit.new(), { __index = _G })
if _VERSION == "Lua 5.2" then
_ENV = blog
else
setfenv(1, blog)
end
--
-- Loads configuration data
--
wsutil.loadfile("blog_config.lua", blog)()
--
-- Initializes DB connection for Orbit's default model mapper
--
local luasql = require("luasql." .. database.driver)
local env = luasql[database.driver]()
mapper.conn = env:connect(unpack(database.conn_data))
mapper.driver = database.driver
-- Initializes page cache
local cache = orcache.new(blog, cache_path)
--
-- Models for this application. Orbit calls mapper:new for each model,
-- so if you want to replace Orbit's default ORM mapper by another
-- one (file-based, for example) just redefine the mapper global variable
--
posts = blog:model "blog_post"
function posts:find_comments()
return comments:find_all_by_post_id{ self.id }
end
function posts:find_recent()
return self:find_all("published_at is not null",
{ order = "published_at desc",
count = recent_count })
end
function posts:find_by_month_and_year(month, year)
local s = os.time({ year = year, month = month, day = 1 })
local e = os.time({ year = year + math.floor(month / 12),
month = (month % 12) + 1,
day = 1 })
return self:find_all("published_at >= ? and published_at < ?",
{ s, e, order = "published_at desc" })
end
function posts:find_months()
local months = {}
local previous_month = {}
local posts = self:find_all({ order = "published_at desc" })
for _, post in ipairs(posts) do
local date = os.date("*t", post.published_at)
if previous_month.month ~= date.month or
previous_month.year ~= date.year then
previous_month = { month = date.month, year = date.year,
date_str = os.date("%Y/%m", post.published_at) }
months[#months + 1] = previous_month
end
end
return months
end
comments = blog:model "blog_comment"
function comments:make_link()
local author = self.author or strings.anonymous_author
if self.url and self.url ~= "" then
return "" .. author .. ""
elseif self.email and self.email ~= "" then
return "" .. author .. ""
else
return author
end
end
pages = blog:model "blog_page"
--
-- Controllers for this application
--
function index(web)
local ps = posts:find_recent()
local ms = posts:find_months()
local pgs = pgs or pages:find_all()
return render_index(web, { posts = ps, months = ms,
recent = ps, pages = pgs })
end
blog:dispatch_get(cache(index), "/", "/index")
function view_post(web, post_id, comment_missing)
local post = posts:find(tonumber(post_id))
if post then
local recent = posts:find_recent()
local pgs = pages:find_all()
post.comments = post:find_comments()
local months = posts:find_months()
return render_post(web, { post = post, months = months,
recent = recent, pages = pgs,
comment_missing = comment_missing })
else
return not_found(web)
end
end
blog:dispatch_get(cache(view_post), "/post/(%d+)")
function add_comment(web, post_id)
local input = web.input
if string.find(input.comment, "^%s*$") then
return view_post(web, post_id, true)
else
local comment = comments:new()
comment.post_id = tonumber(post_id)
comment.body = markdown(input.comment)
if not string.find(input.author, "^%s*$") then
comment.author = input.author
end
if not string.find(input.email, "^%s*$") then
comment.email = input.email
end
if not string.find(input.url, "^%s*$") then
comment.url = input.url
end
comment:save()
local post = posts:find(tonumber(post_id))
post.n_comments = (post.n_comments or 0) + 1
post:save()
cache:invalidate("/")
cache:invalidate("/post/" .. post_id)
cache:invalidate("/archive/" .. os.date("%Y/%m", post.published_at))
return web:redirect(web:link("/post/" .. post_id))
end
end
blog:dispatch_post(add_comment, "/post/(%d+)/addcomment")
function view_archive(web, year, month)
local ps = posts:find_by_month_and_year(tonumber(month),
tonumber(year))
local months = posts:find_months()
local recent = posts:find_recent()
local pgs = pages:find_all()
return render_index(web, { posts = ps, months = months,
recent = recent, pages = pgs })
end
blog:dispatch_get(cache(view_archive), "/archive/(%d%d%d%d)/(%d%d)")
blog:dispatch_static("/head%.jpg", "/style%.css")
function view_page(web, page_id)
local page = pages:find(tonumber(page_id))
if page then
local recent = posts:find_recent()
local months = posts:find_months()
local pgs = pages:find_all()
return render_page(web, { page = page, months = months,
recent = recent, pages = pgs })
else
not_found(web)
end
end
blog:dispatch_get(cache(view_page), "/page/(%d+)")
--
-- Views for this application
--
function layout(web, args, inner_html)
return html{
head{
title(blog_title),
meta{ ["http-equiv"] = "Content-Type",
content = "text/html; charset=utf-8" },
link{ rel = 'stylesheet', type = 'text/css',
href = web:static_link('/style.css'), media = 'screen' }
},
body{
div{ id = "container",
div{ id = "header", title = "sitename" },
div{ id = "mainnav",
_menu(web, args)
},
div{ id = "menu",
_sidebar(web, args)
},
div{ id = "contents", inner_html },
div{ id = "footer", copyright_notice }
}
}
}
end
function _menu(web, args)
local res = { li(a{ href= web:link("/"), strings.home_page_name }) }
for _, page in pairs(args.pages) do
res[#res + 1] = li(a{ href = web:link("/page/" .. page.id), page.title })
end
return ul(res)
end
function _blogroll(web, blogroll)
local res = {}
for _, blog_link in ipairs(blogroll) do
res[#res + 1] = li(a{ href=blog_link[1], blog_link[2] })
end
return ul(res)
end
function _sidebar(web, args)
return {
h3(strings.about_title),
ul(li(about_blurb)),
h3(strings.last_posts),
_recent(web, args),
h3(strings.blogroll_title),
_blogroll(web, blogroll),
h3(strings.archive_title),
_archives(web, args)
}
end
function _recent(web, args)
local res = {}
for _, post in ipairs(args.recent) do
res[#res + 1] = li(a{ href=web:link("/post/" .. post.id), post.title })
end
return ul(res)
end
function _archives(web, args)
local res = {}
for _, month in ipairs(args.months) do
res[#res + 1] = li(a{ href=web:link("/archive/" .. month.date_str),
blog.month(month) })
end
return ul(res)
end
function render_index(web, args)
if #args.posts == 0 then
return layout(web, args, p(strings.no_posts))
else
local res = {}
local cur_time
for _, post in pairs(args.posts) do
local str_time = date(post.published_at)
if cur_time ~= str_time then
cur_time = str_time
res[#res + 1] = h2(str_time)
end
res[#res + 1] = h3(post.title)
res[#res + 1] = _post(web, post)
end
return layout(web, args, div.blogentry(res))
end
end
function _post(web, post)
return {
markdown(post.body),
p.posted{
strings.published_at .. " " ..
os.date("%H:%M", post.published_at), " | ",
a{ href = web:link("/post/" .. post.id .. "#comments"), strings.comments ..
" (" .. (post.n_comments or "0") .. ")" }
}
}
end
function _comment(web, comment)
return { p(comment.body),
p.posted{
strings.written_by .. " " .. comment:make_link(),
" " .. strings.on_date .. " " ..
time(comment.created_at)
}
}
end
function render_page(web, args)
return layout(web, args, div.blogentry(markdown(args.page.body)))
end
function render_post(web, args)
local res = {
h2(span{ style="position: relative; float:left", args.post.title }
.. " "),
h3(date(args.post.published_at)),
_post(web, args.post)
}
res[#res + 1] = a{ name = "comments" }
if #args.post.comments > 0 then
res[#res + 1] = h2(strings.comments)
for _, comment in pairs(args.post.comments) do
res[#res + 1 ] = _comment(web, comment)
end
end
res[#res + 1] = h2(strings.new_comment)
local err_msg = ""
if args.comment_missing then
err_msg = span{ style="color: red", strings.no_comment }
end
res[#res + 1] = form{
method = "post",
action = web:link("/post/" .. args.post.id .. "/addcomment"),
p{ strings.form_name, br(), input{ type="text", name="author",
value = web.input.author },
br(), br(),
strings.form_email, br(), input{ type="text", name="email",
value = web.input.email },
br(), br(),
strings.form_url, br(), input{ type="text", name="url",
value = web.input.url },
br(), br(),
strings.comments .. ":", br(), err_msg,
textarea{ name="comment", rows="10", cols="60", web.input.comment },
br(),
em(" *" .. strings.italics .. "* "),
strong(" **" .. strings.bold .. "** "),
" [" .. a{ href="/url", strings.link } .. "](http://url) ",
br(), br(),
input.button{ type="submit", value=strings.send }
}
}
return layout(web, args, div.blogentry(res))
end
-- Adds html functions to the view functions
orbit.htmlify(blog, "layout", "_.+", "render_.+")
return blog