File: toycms.lua

package info (click to toggle)
lua-orbit 2.2.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster, jessie, jessie-kfreebsd, stretch
  • size: 1,956 kB
  • ctags: 638
  • sloc: sql: 78; sh: 32; makefile: 31; xml: 20
file content (371 lines) | stat: -rw-r--r-- 11,556 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
#!/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 "post",
   comment = toycms:model "comment",
   section = toycms:model "section",
   user = toycms:model "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 "<a href=\"" .. self.url .. "\">" .. author .. "</a>"
   elseif self.email and self.email ~= "" then
      return "<a href=\"mailto:" .. self.email .. "\">" .. author .. "</a>"
   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