#!/usr/bin/env lem
--
-- This file is part of blipserver.
-- Copyright 2011 Emil Renner Berthing
--
-- blipserver is free software: you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation, either version 3 of
-- the License, or (at your option) any later version.
--
-- blipserver is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with blipserver. If not, see <http://www.gnu.org/licenses/>.
--
local function usage()
print('Labitrack daemon')
print('usage: '..arg[0]..' [bind [queue_dir]]')
print('defaults: bind=*:8080 queue_dir=./queue')
os.exit(1)
end
--
-- settings
--
local pg_connect_str = 'host=localhost user=labitrack dbname=labitrack password=nerfyoawdAj3'
local bind = arg[1] or '*:8080'
local queue_dir = arg[2] or './queue'
--
-- end of settings
--
local bind_colon = string.find(bind, ':', 1, true)
if bind_colon == nil then
usage()
end
local bind_addr = string.sub(bind, 1, bind_colon-1)
local bind_port = tonumber(string.sub(bind, bind_colon+1))
local utils = require 'lem.utils'
local streams = require 'lem.streams'
local postgres = require 'lem.postgres'
local qpostgres = require 'lem.postgres.queued'
local hathaway = require 'lem.hathaway'
local json = require 'dkjson'
local base64 = require 'base64'
local filequeue = require 'filequeue'
local assert = assert
local format = string.format
local tonumber = tonumber
local function sendfile(content, path)
return function(req, res)
res.headers['Content-Type'] = content
res.file = path
end
end
local function sendfile_js(path)
return sendfile('text/javascript; charset=UTF-8', path)
end
local function sendfile_css(path)
return sendfile('text/css; charset=UTF-8', path)
end
local function deserialize_array_from_pg(pgarray)
local ret, rc = {}, 0
if pgarray == nil then
return ret
end
local strlen = string.len(pgarray)
if strlen <= 2 then
return ret
end
local n = 0
for i = 2, strlen-1 do
if i > n then
rc = rc + 1
n = string.find(pgarray, ',', i)
if n == nil then
n = strlen
end
ret[rc] = string.sub(pgarray, i, n-1)
end
end
return ret
end
local function add_json_row(res, values, i)
local n = #values
if n > 0 then
local clen = #values[0]
local point = values[i]
local d = {}
for j = 1, clen do
local k = values[0][j]
local v = point[j]
if k == 'id' then
d[k] = tonumber(v)
elseif k == 'created' then
d[k] = tonumber(v)
elseif k == 'updated' then
d[k] = tonumber(v)
elseif not (k == 'tags') then
d[k] = v
else
d[k] = deserialize_array_from_pg(v)
end
end
res:add('%s', json.encode(d))
end
end
local function add_json(res, values)
local n = #values
res:add('[')
if n > 0 then
for i = 1, n do
add_json_row(res, values, i)
if not (i == n) then
res:add(',')
end
end
end
res:add(']')
end
local function set_json_nocache_headers(res)
res.headers['Content-Type'] = 'application/json; charset=UTF-8'
res.headers['Cache-Control'] = 'max-age=0, must-revalidate'
end
local function unescape(s)
s = string.gsub(s, "+", " ")
s = string.gsub(s, "%%(%x%x)", function (h)
return string.char(tonumber(h, 16))
end)
return s
end
-- Connect to database and prepare statements
local db = assert(qpostgres.connect(pg_connect_str))
local cols = 'id, name, "desc", tags, extract(epoch from created)::int8 created, extract(epoch from updated)::int8 updated'
assert(db:prepare('get', 'SELECT '..cols..' FROM objects WHERE id = $1'))
assert(db:prepare('recent', 'SELECT '..cols..' FROM objects ORDER BY updated DESC LIMIT 10;'))
assert(db:prepare('since', 'SELECT '..cols..' FROM objects ORDER BY id LIMIT 10 OFFSET $1;'))
assert(db:prepare('insert', 'INSERT INTO objects (name, "desc", tags) VALUES ($1, $2, string_to_array($3, \',\')::text[]) RETURNING id;'))
assert(db:prepare('update', 'UPDATE objects SET name=$2, "desc"=$3, tags=string_to_array($4, \',\')::text[], updated=now() WHERE id = $1;'))
assert(db:prepare('count', 'SELECT COUNT(id) FROM objects;'))
local function count()
return assert(db:run('count'))[1][1]
end
-- initialize queue
local queue = filequeue.open(queue_dir)
hathaway.import()
local htmlpage = sendfile('text/html; charset=UTF-8', 'pub/index.html')
GET('/', htmlpage)
GET('/browse', htmlpage)
MATCH('^/browse/(%d+)$', htmlpage)
GET('/recent', htmlpage)
GET('/about', htmlpage)
MATCH('^/view/(%d+)$', htmlpage)
MATCH('^/edit/(%d+)$', htmlpage)
GET('/js/corelibs.min.js', sendfile_js('js/dist/corelibs.min.js'))
GET('/js/corelibs.src.js', sendfile_js('js/dist/corelibs.src.js'))
GET('/js/qrcode.min.js', sendfile_js('js/dist/qrcode.min.js'))
GET('/js/labitrack.min.js', sendfile_js('js/dist/labitrack.min.js'))
GET('/js/labitrack.src.js', sendfile_js('js/dist/labitrack.src.js'))
GET('/js/templates.js', sendfile_js('templates/dist/labitrack.min.js'))
GET('/css/bootstrap.1.4.0.min.css', sendfile_css('pub/css/bootstrap.1.4.0.min.css'))
GET('/css/labitrack.css', sendfile_css('css/dist/labitrack.min.css'))
GET('/favicon.ico', sendfile('image/x-icon', 'pub/favicon.ico'))
MATCH('^/browse/(%d+).json$', function(req, res, since)
if req.method ~= 'HEAD' and req.method ~= 'GET' then
return hathaway.method_not_allowed(req, res)
end
set_json_nocache_headers(res)
res:add('{"count": %d, "objects":', count());
add_json(res, assert(db:run('since', (since-1)*10)))
res:add('}');
end)
GET('/queue.json', function(req, res)
set_json_nocache_headers(res)
res:add('%s', json.encode(queue:stat()))
end)
GET('/queue.json?empty', function(req, res)
set_json_nocache_headers(res)
res:add('%s', json.encode(queue:empty()))
end)
GET('/recent.json', function(req, res)
set_json_nocache_headers(res)
add_json(res, assert(db:run('recent')))
end)
local function unescape(s)
s = string.gsub(s, "+", " ")
s = string.gsub(s, "%%(%x%x)", function (h)
return string.char(tonumber(h, 16))
end)
return s
end
local function save_or_update(req, res)
set_json_nocache_headers(res)
local expected = "application/json"
assert(string.sub(req.headers['Content-Type'], 1, string.len(expected)) == expected)
local body = req:body()
local label = json.decode(body)
local id
if label['id'] == nil then
id = assert(db:run('insert', label['name'], label['desc'], table.concat(label['tags'], ',')))[1][1]
else
assert(db:run('update', label['id'], label['name'], label['desc'], table.concat(label['tags'], ',')))
id = label['id']
end
res:add('{"id": %d}', id)
end
POST('/o', save_or_update)
MATCH('^/o/(%d+).json$', function(req, res, id)
if req.method == 'PUT' then
return save_or_update(req, res)
elseif req.method ~= 'HEAD' and req.method ~= 'GET' then
return hathaway.method_not_allowed(req, res)
end
set_json_nocache_headers(res)
qr = db:run('get', id)
if #qr == 0 then
res.status = 404
else
add_json_row(res, qr, 1)
end
end)
POST('/print.json', function(req, res)
set_json_nocache_headers(res)
local expected = "application/x-www-form-urlencoded"
assert(string.sub(req.headers['Content-Type'], 1, string.len(expected)) == expected)
local body = unescape(req:body())
expected = "image=data:image/png;base64,"
local expected_len = string.len(expected)
assert(string.sub(body, 1, expected_len) == expected)
image = base64.dec(string.sub(body, expected_len+1))
local qi = queue:new()
local outfile = qi:open("wb")
outfile:write(image)
outfile:close()
qi:queue();
res:add('["%s"]', 'OK')
end)
hathaway.debug = print
assert(Hathaway(bind_addr, bind_port))
-- vim: syntax=lua ts=2 sw=2 noet: