#!/usr/bin/env wsapi.cgi local orbit = require "orbit" local markdown = require "markdown" local orcache = require "orbit.cache" local cosmo = require "cosmo" local wsutil = require "wsapi.util" local toycms = setmetatable(orbit.new(), { __index = _G }) if _VERSION == "Lua 5.2" then _ENV = toycms else setfenv(1, toycms) end plugins = {} wsutil.loadfile("toycms_config.lua", toycms)() wsutil.loadfile("toycms_plugins.lua", toycms)() wsutil.loadfile("toycms_admin.lua", toycms)(toycms) local luasql = require("luasql." .. database.driver) local env = luasql[database.driver]() mapper.conn = env:connect(unpack(database.conn_data)) mapper.driver = database.driver models = { post = toycms:model "toycms_post", comment = toycms:model "toycms_comment", section = toycms:model "toycms_section", user = toycms:model "toycms_user" } cache = orcache.new(toycms, cache_path) function models.post:find_comments() return models.comment:find_all_by_approved_and_post_id{ true, self.id } end function models.post:find_months(section_ids) local months = {} local previous_month = {} local posts = self:find_all_by_published_and_section_id{ order = "published_at desc", true, section_ids } 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 } months[#months + 1] = previous_month end end return months end function models.comment: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 function models.section:find_by_tags(tags) return self:find_all("tag like ?", tags) end local template_cache = {} function load_template(name) local template = template_cache[name] if not template then local template_file = io.open(real_path .. "/templates/" .. template_name .. "/" .. name, "rb") if template_file then template = cosmo.compile(template_file:read("*a")) template_cache[name] = template template_file:close() end end return template end function load_plugin(name) local plugin, err = loadfile("plugins/" .. name .. ".lua") if not plugin then error("Error loading plugin " .. name .. ": " .. err) end return plugin end function new_template_env(web) local template_env = {} template_env.template_vpath = template_vpath or web:static_link("/templates/" .. template_name) template_env.today = date(os.time()) template_env.home_url = web:link("/") template_env.home_url_xml = web:link("/xml") function template_env.import(arg) local plugin_name = arg[1] local plugin = plugins[plugin_name] if not plugin then plugin = load_plugin(plugin_name) plugins[plugin_name] = plugin end for fname, f in pairs(plugin(web)) do template_env[fname] = f end return "" end return template_env end function new_section_env(web, section) local env = new_template_env(web) env.id = section.id env.title = section.title env.description = section.description env.tag = section.tag env.uri = web:link("/section/" .. section.id) return env end function new_post_env(web, post, section) local env = new_template_env(web) env.id = post.id env.title = post.title env.abstract = post.abstract env.body = cosmo.fill(post.body, { image_vpath = (image_vpath or web:static_link("/images")) .. "/" .. post.id }) env.markdown_body = markdown(env.body) env.day_padded = os.date("%d", post.published_at) env.day = tonumber(env.day_padded) env.month_name = month_names[tonumber(os.date("%m", post.published_at))] env.month_padded = os.date("%m", post.published_at) env.month = tonumber(env.month_padded) env.year = tonumber(os.date("%Y", post.published_at)) env.short_year = os.date("%y", post.published_at) env.hour_padded = os.date("%H", post.published_at) env.minute_padded = os.date("%M", post.published_at) env.hour = tonumber(env.hour_padded) env.minute = tonumber(env.minute_padded) env.date_string = date(post.published_at) if web:empty(post.external_url) then env.uri = web:link("/post/" .. post.id) else env.uri = post.external_url end env.n_comments = post.n_comments or 0 env.section_uri = web:link("/section/" .. post.section_id) section = section or models.section:find(post.section_id) env.section_title = section.title env.image_uri = (image_vpath or web:static_link("/images")) .. "/" .. post.id .. "/" .. (post.image or "") env.if_image = cosmo.cond(not web:empty(post.image), env) local form_env = {} form_env.author = web.input.author or "" form_env.email = web.input.email or "" form_env.url = web.input.url or "" setmetatable(form_env, { __index = env }) env.if_comment_open = cosmo.cond(post.comment_status ~= "closed", form_env) env.if_comment_moderated = cosmo.cond(post.comment_status == "moderated", form_env) env.if_comment_closed = cosmo.cond(post.comment_status == "closed", form_env) env.if_error_comment = cosmo.cond(not web:empty_param("error_comment"), env) env.if_comments = cosmo.cond((post.n_comments or 0) > 0, env) env.comments = function (arg, has_block) local comments = post:find_comments() local out, template if not has_block then local out = {} local template = load_template((arg and arg.template) or "comment.html") end for _, comment in ipairs(comments) do if has_block then cosmo.yield(new_comment_env(web, comment)) else local tdata = template(new_comment_env(web, comment)) table.insert(out, tdata) end end if not has_block then return table.concat(out, "\n") end end env.add_comment_uri = web:link("/post/" .. post.id .. "/addcomment") return env end function new_comment_env(web, comment) local env = new_template_env(web) env.author_link = comment:make_link() env.body = comment.body env.markdown_body = markdown(env.body) env.time_string = time(comment.created_at) env.day_padded = os.date("%d", comment.created_at) env.day = tonumber(env.day_padded) env.month_name = month_names[tonumber(os.date("%m", comment.created_at))] env.month_padded = os.date("%m", comment.created_at) env.month = tonumber(env.month_padded) env.year = tonumber(os.date("%Y", comment.created_at)) env.short_year = os.date("%y", comment.created_at) env.hour_padded = os.date("%H", comment.created_at) env.minute_padded = os.date("%M", comment.created_at) env.hour = tonumber(env.hour_padded) env.minute = tonumber(env.minute_padded) return env end function home_page(web) local template = load_template("home.html") if template then return layout(web, template(new_template_env(web))) else return not_found(web) end end toycms:dispatch_get(cache(home_page), "/") function home_xml(web) local template = load_template("home.xml") if template then web.headers["Content-Type"] = "text/xml" return template(new_template_env(web)) else return not_found(web) end end toycms:dispatch_get(cache(home_xml), "/xml") function view_section(type) return function (web, section_id) local section = models.section:find(tonumber(section_id)) if not section then return not_found(web) end local template = load_template("section_" .. tostring(section.tag) .. "." .. type) or load_template("section." .. type) if template then web.input.section_id = tonumber(section_id) local env = new_section_env(web, section) if type == "xml" then web.headers["Content-Type"] = "application/atom+xml" return template(env) else return layout(web, template(env)) end else return not_found(web) end end end toycms:dispatch_get(cache(view_section("html")), "/section/(%d+)") toycms:dispatch_get(cache(view_section("xml")), "/section/(%d+)/xml") function view_post(type) return function (web, post_id) local post = models.post:find(tonumber(post_id)) if not post then return not_found(web) end local section = models.section:find(post.section_id) local template = load_template("post_" .. tostring(section.tag) .. "." .. type) or load_template("post." .. type) if template then web.input.section_id = post.section_id web.input.post_id = tonumber(post_id) local env = new_post_env(web, post, section) if type == "xml" then web.headers["Content-Type"] = "application/atom+xml" return template(env) else return layout(web, template(env)) end else return not_found(web) end end end toycms:dispatch_get(cache(view_post("html")), "/post/(%d+)") toycms:dispatch_get(cache(view_post("xml")), "/post/(%d+)/xml") function archive(web, year, month) local template = load_template("archive.html") if template then web.input.month = tonumber(month) web.input.year = tonumber(year) local env = new_template_env(web) env.archive_month = web.input.month env.archive_month_name = month_names[web.input.month] env.archive_year = web.input.year env.archive_month_padded = month return layout(web, template(env)) else not_found(web) end end toycms:dispatch_get(cache(archive), "/archive/(%d+)/(%d+)") function add_comment(web, post_id) if web:empty_param("comment") then web.input.error_comment = "1" return web:redirect(web:link("/post/" .. post_id, web.input)) else local post = models.post:find(tonumber(post_id)) if web:empty(post.comment_status) or post.comment_status == "closed" then return web:redirect(web:link("/post/" .. post_id)) else local comment = models.comment:new() comment.post_id = post.id local body = web:sanitize(web.input.comment) comment.body = markdown(body) if not web:empty_param("author") then comment.author = web.input.author end if not web:empty_param("email") then comment.email = web.input.email end if not web:empty_param("url") then comment.url = web.input.url end if post.comment_status == "unmoderated" then comment.approved = true post.n_comments = (post.n_comments or 0) + 1 post:save() cache:invalidate("/", "/xml", "/section/" .. post.section_id, "/section/" .. post.section_id .. "/xml", "/post/" .. post.id, "/post/" .. post.id .. "/xml", "/archive/" .. os.date("%Y/%m", post.published_at)) else comment.approved = false end comment:save() return web:redirect(web:link("/post/" .. post_id)) end end end toycms:dispatch_post(add_comment, "/post/(%d+)/addcomment") toycms:dispatch_static("/templates/.+", "/images/.+") function layout(web, inner_html) local layout_template = load_template("layout.html") if layout_template then local env = new_template_env(web) env.view = inner_html return layout_template(env) else return inner_html end end function check_user(web) local user_id = web.cookies.user_id local password = web.cookies.password return models.user:find_by_id_and_password{ user_id, password } end return toycms