Started work on refactor
Started work on moveing each endpoint into it's own file.
This commit is contained in:
parent
86a14e9d62
commit
2cd10b6968
|
@ -0,0 +1,93 @@
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
|
local queries = require("queries")
|
||||||
|
local util = require("util")
|
||||||
|
|
||||||
|
local ret = {}
|
||||||
|
|
||||||
|
local stmnt_cache, stmnt_insert_cache
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
local cache = sqlassert(sql.open_memory())
|
||||||
|
--A cache table to store rendered pages that do not need to be
|
||||||
|
--rerendered. In theory this could OOM the program eventually and start
|
||||||
|
--swapping to disk. TODO: fixme
|
||||||
|
assert(cache:exec([[
|
||||||
|
CREATE TABLE IF NOT EXISTS cache (
|
||||||
|
path TEXT PRIMARY KEY,
|
||||||
|
data BLOB,
|
||||||
|
updated INTEGER,
|
||||||
|
dirty INTEGER
|
||||||
|
);
|
||||||
|
]]))
|
||||||
|
stmnt_cache = cache:prepare([[
|
||||||
|
SELECT data
|
||||||
|
FROM cache
|
||||||
|
WHERE
|
||||||
|
path = :path AND
|
||||||
|
((dirty = 0) OR (strftime('%s','now') - updated) < 20)
|
||||||
|
;
|
||||||
|
]])
|
||||||
|
stmnt_insert_cache = cache:prepare([[
|
||||||
|
INSERT OR REPLACE INTO cache (
|
||||||
|
path, data, updated, dirty
|
||||||
|
) VALUES (
|
||||||
|
:path, :data, strftime('%s','now'), 0
|
||||||
|
);
|
||||||
|
]])
|
||||||
|
stmnt_dirty_cache = cache:prepare([[
|
||||||
|
UPDATE OR IGNORE cache
|
||||||
|
SET dirty = 1
|
||||||
|
WHERE path = :path;
|
||||||
|
]])
|
||||||
|
return oldconfigure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
--Render a page, with cacheing. If you need to dirty a cache, call dirty_cache()
|
||||||
|
function ret.render(pagename,callback)
|
||||||
|
print("Running render...")
|
||||||
|
stmnt_cache:bind_names{path=pagename}
|
||||||
|
local err = util.do_sql(stmnt_cache)
|
||||||
|
if err == sql.DONE then
|
||||||
|
stmnt_cache:reset()
|
||||||
|
--page is not cached
|
||||||
|
elseif err == sql.ROW then
|
||||||
|
print("Cache hit:" .. pagename)
|
||||||
|
data = stmnt_cache:get_values()
|
||||||
|
stmnt_cache:reset()
|
||||||
|
return data[1]
|
||||||
|
else --sql.ERROR or sql.MISUSE
|
||||||
|
error("Failed to check cache for page " .. pagename)
|
||||||
|
end
|
||||||
|
--We didn't have the paged cached, render it
|
||||||
|
print("Cache miss, running function")
|
||||||
|
local text = callback()
|
||||||
|
print("Saving data...")
|
||||||
|
--And save the data back into the cache
|
||||||
|
stmnt_insert_cache:bind_names{
|
||||||
|
path=pagename,
|
||||||
|
data=text,
|
||||||
|
}
|
||||||
|
err = util.do_sql(stmnt_insert_cache)
|
||||||
|
if err == sql.ERROR or err == sql.MISUSE then
|
||||||
|
error("Failed to update cache for page " .. pagename)
|
||||||
|
end
|
||||||
|
stmnt_insert_cache:reset()
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
function ret.dirty(url)
|
||||||
|
print("Dirtying cache:",url)
|
||||||
|
stmnt_dirty_cache:bind_names{
|
||||||
|
path = url
|
||||||
|
}
|
||||||
|
err = util.do_sql(stmnt_dirty_cache)
|
||||||
|
stmnt_dirty_cache:reset()
|
||||||
|
end
|
||||||
|
|
||||||
|
function ret.close()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return ret
|
|
@ -0,0 +1,16 @@
|
||||||
|
local cache = require("cache")
|
||||||
|
local config = require("config")
|
||||||
|
local pages = require("pages")
|
||||||
|
|
||||||
|
|
||||||
|
local function claim_get(req)
|
||||||
|
--Get the page to claim a name
|
||||||
|
local cachestr = string.format("%s/_claim",config.domain)
|
||||||
|
local text = cache.render(cachestr,function()
|
||||||
|
print("cache miss, rendering claim page")
|
||||||
|
return pages.claim{err=""}
|
||||||
|
end)
|
||||||
|
http_response(req,200,text)
|
||||||
|
end
|
||||||
|
|
||||||
|
return claim_get
|
|
@ -0,0 +1,66 @@
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
|
local pages = require("pages")
|
||||||
|
local db = require("db")
|
||||||
|
local queries = require("queries")
|
||||||
|
|
||||||
|
local stmnt_author_create
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
|
||||||
|
stmnt_author_create = assert(db.conn:prepare(queries.insert_author))
|
||||||
|
return oldconfigure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function claim_post(req)
|
||||||
|
--Actually claim a name
|
||||||
|
http_request_populate_post(req)
|
||||||
|
local name = assert(http_argument_get_string(req,"user"))
|
||||||
|
local text
|
||||||
|
--What in the world, Kore should be rejecting names that
|
||||||
|
--are not lower case & no symbols, but some still get through somehow.
|
||||||
|
if not name:match("^[a-z0-9]*$") then
|
||||||
|
print("Bad username:",name)
|
||||||
|
text = pages.claim{
|
||||||
|
err = "Usernames must match ^[a-z0-9]{1,30}$"
|
||||||
|
}
|
||||||
|
http_response(req,200,text)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local rngf = assert(io.open("/dev/urandom","rb"))
|
||||||
|
local passlength = string.byte(rngf:read(1)) + 64
|
||||||
|
local salt = rngf:read(64)
|
||||||
|
local password = rngf:read(passlength)
|
||||||
|
rngf:close()
|
||||||
|
local hash = sha3(salt .. password)
|
||||||
|
stmnt_author_create:bind_names{
|
||||||
|
name = name,
|
||||||
|
}
|
||||||
|
stmnt_author_create:bind_blob(2,salt)
|
||||||
|
stmnt_author_create:bind_blob(3,hash)
|
||||||
|
local err = do_sql(stmnt_author_create)
|
||||||
|
if err == sql.DONE then
|
||||||
|
--We sucessfully made athe new author
|
||||||
|
local id = stmnt_author_create:last_insert_rowid()
|
||||||
|
stmnt_author_create:reset()
|
||||||
|
--Give them a file back
|
||||||
|
http_response_header(req,"Content-Type","application/octet-stream")
|
||||||
|
http_response_header(req,"Content-Disposition","attachment; filename=\"" .. name .. "." .. domain .. ".passfile\"")
|
||||||
|
local session = start_session(id)
|
||||||
|
text = password
|
||||||
|
elseif err == sql.CONSTRAINT then
|
||||||
|
--If the creation failed, they probably just tried
|
||||||
|
--to use a name that was already taken
|
||||||
|
text = pages.claim {
|
||||||
|
err = "Failed to claim. That name may already be taken."
|
||||||
|
}
|
||||||
|
elseif err == sql.ERROR or err == sql.MISUSE then
|
||||||
|
--This is bad though
|
||||||
|
text = pages.claim {
|
||||||
|
err = "Failed to claim"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
stmnt_author_create:reset()
|
||||||
|
end
|
||||||
|
return claim_post
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
return {
|
||||||
|
domain = "test.monster:8888",
|
||||||
|
production = false,
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
|
local queries = require("queries")
|
||||||
|
|
||||||
|
local db = {}
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
db.conn = sqlassert(sql.open("data/posts.db"))
|
||||||
|
|
||||||
|
--Create sql tables
|
||||||
|
assert(db.conn:exec(queries.create_table_authors))
|
||||||
|
--Create a fake "anonymous" user, so we don't run into trouble
|
||||||
|
--so that no one runs into trouble being able to paste under this account.
|
||||||
|
assert(db.conn:exec(queries.insert_anon_author))
|
||||||
|
--If/when an author delets their account, all posts
|
||||||
|
--and comments by that author are also deleted (on
|
||||||
|
--delete cascade) this is intentional. This also
|
||||||
|
--means that all comments by other users on a post
|
||||||
|
--an author makes will also be deleted.
|
||||||
|
--
|
||||||
|
--Post text uses zlib compression
|
||||||
|
assert(db.conn:exec(queries.create_table_posts))
|
||||||
|
--Store the raw text so people can download it later, maybe
|
||||||
|
--we can use it for "download as image" or "download as pdf"
|
||||||
|
--in the future too. Stil stored zlib compressed
|
||||||
|
assert(db.conn:exec(queries.create_table_raw_text))
|
||||||
|
--Maybe we want to store images one day?
|
||||||
|
assert(db.conn:exec(queries.create_table_images))
|
||||||
|
--Comments on a post
|
||||||
|
assert(db.conn:exec(queries.create_table_comments))
|
||||||
|
--Tags for a post
|
||||||
|
assert(db.conn:exec(queries.create_table_tags))
|
||||||
|
--Index for tags
|
||||||
|
assert(db.conn:exec(queries.create_index_tags))
|
||||||
|
--Store a cookie for logged in users. Logged in users can edit
|
||||||
|
--their own posts, and edit their biographies.
|
||||||
|
assert(db:exec(queries.create_table_session))
|
||||||
|
print("Created db tables")
|
||||||
|
|
||||||
|
return configure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function db.close()
|
||||||
|
db.conn:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
return db
|
|
@ -0,0 +1,64 @@
|
||||||
|
local zlib = require("zlib")
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
|
local db = require("db")
|
||||||
|
local queries = require("queries")
|
||||||
|
local util = require("util")
|
||||||
|
local pages = require("pages")
|
||||||
|
local tags = require("tags")
|
||||||
|
|
||||||
|
local stmnt_edit
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
stmnt_edit = assert(db.conn:prepare(queries.select_edit))
|
||||||
|
return configure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function edit_get(req)
|
||||||
|
local host = http_request_get_host(req)
|
||||||
|
local path = http_request_get_path(req)
|
||||||
|
local author, authorid = get_session(req)
|
||||||
|
|
||||||
|
http_request_populate_qs(req)
|
||||||
|
local story = assert(http_argument_get_string(req,"story"))
|
||||||
|
local story_id = decode_id(story)
|
||||||
|
local ret
|
||||||
|
|
||||||
|
print("we want to edit story:",story)
|
||||||
|
--Check that the logged in user is the owner of the story
|
||||||
|
--sql-side. If we're not the owner, we'll get 0 rows back.
|
||||||
|
stmnt_edit:bind_names{
|
||||||
|
postid = story_id,
|
||||||
|
authorid = author_id
|
||||||
|
}
|
||||||
|
local err = util.do_sql(stmnt_edit)
|
||||||
|
if err == sql.DONE then
|
||||||
|
--No rows, we're probably not the owner (it might
|
||||||
|
--also be because there's no such story)
|
||||||
|
ret = pages.cantedit{
|
||||||
|
path = story,
|
||||||
|
}
|
||||||
|
stmnt_edit:reset()
|
||||||
|
http_response(req,200,ret)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
assert(err == sql.ROW)
|
||||||
|
local data = stmnt_edit:get_values()
|
||||||
|
local txt_compressed, markup, isanon, title = unpack(data)
|
||||||
|
local text = zlib.decompress(txt_compressed)
|
||||||
|
local tags = tags.get(story_id)
|
||||||
|
local tags_txt = table.concat(tags,";")
|
||||||
|
stmnt_edit:reset()
|
||||||
|
ret = pages.edit{
|
||||||
|
title = title,
|
||||||
|
text = text,
|
||||||
|
markup = markup,
|
||||||
|
user = author,
|
||||||
|
isanon = isanon == 1,
|
||||||
|
domain = domain,
|
||||||
|
story = story_id,
|
||||||
|
err = "",
|
||||||
|
tags = tags_txt
|
||||||
|
}
|
||||||
|
http_response(req,200,ret)
|
||||||
|
end
|
|
@ -0,0 +1,85 @@
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
local zlib = require("zlib")
|
||||||
|
|
||||||
|
local queries = require("queries")
|
||||||
|
local pages = require("pages")
|
||||||
|
local parsers = require("parsers")
|
||||||
|
local util = require("util")
|
||||||
|
local tagslib = require("tags")
|
||||||
|
local cache = require("cache")
|
||||||
|
local config = require("config")
|
||||||
|
|
||||||
|
local stmnt_author_of, stmnt_update_raw, stmnt_update
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
stmnt_author_of = assert(db.conn:prepare(queries.select_author_of_post))
|
||||||
|
stmnt_update_raw = assert(db.conn:prepare(queries.update_raw))
|
||||||
|
stmnt_update = assert(db.conn:prepare(queries.update_post))
|
||||||
|
return oldconfigure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function edit_post(req)
|
||||||
|
local host = http_request_get_host(req)
|
||||||
|
local path = http_request_get_path(req)
|
||||||
|
local author, author_id = get_session(req)
|
||||||
|
|
||||||
|
http_request_populate_post(req)
|
||||||
|
local storyid = tonumber(assert(http_argument_get_string(req,"story")))
|
||||||
|
local title = assert(http_argument_get_string(req,"title"))
|
||||||
|
local text = assert(http_argument_get_string(req,"text"))
|
||||||
|
local pasteas = assert(http_argument_get_string(req,"pasteas"))
|
||||||
|
local markup = assert(http_argument_get_string(req,"markup"))
|
||||||
|
local tags_str = http_argument_get_string(req,"tags")
|
||||||
|
stmnt_author_of:bind_names{
|
||||||
|
id = storyid
|
||||||
|
}
|
||||||
|
local err = do_sql(stmnt_author_of)
|
||||||
|
if err ~= sql.ROW then
|
||||||
|
stmnt_author_of:reset()
|
||||||
|
error("No author found for story:" .. storyid)
|
||||||
|
end
|
||||||
|
local data = stmnt_author_of:get_values()
|
||||||
|
stmnt_author_of:reset()
|
||||||
|
local realauthor = data[1]
|
||||||
|
assert(realauthor == author_id) --Make sure the author of the story is the currently logged in user
|
||||||
|
local parsed = parsers[markup](text)
|
||||||
|
local compr_raw = zlib.compress(text)
|
||||||
|
local compr = zlib.compress(parsed)
|
||||||
|
local tags = {}
|
||||||
|
if tags_str then
|
||||||
|
tags = util.parse_tags(tags_str)
|
||||||
|
end
|
||||||
|
assert(stmnt_update_raw:bind_blob(1,compr_raw) == sql.OK)
|
||||||
|
assert(stmnt_update_raw:bind(2,markup) == sql.OK)
|
||||||
|
assert(stmnt_update_raw:bind(3,storyid) == sql.OK)
|
||||||
|
assert(util.do_sql(stmnt_update_raw) == sql.DONE, "Failed to update raw")
|
||||||
|
stmnt_update_raw:reset()
|
||||||
|
assert(stmnt_update:bind(1,title) == sql.OK)
|
||||||
|
assert(stmnt_update:bind_blob(2,compr) == sql.OK)
|
||||||
|
assert(stmnt_update:bind(3,pasteas == "anonymous" and 1 or 0) == sql.OK)
|
||||||
|
assert(stmnt_update:bind(4,storyid) == sql.OK)
|
||||||
|
assert(do_sql(stmnt_update) == sql.DONE, "Failed to update text")
|
||||||
|
stmnt_update:reset()
|
||||||
|
tagslib.set(storyid,tags)
|
||||||
|
--[[
|
||||||
|
assert(stmnt_drop_tags:bind_names{postid = storyid} == sql.OK)
|
||||||
|
do_sql(stmnt_drop_tags)
|
||||||
|
stmnt_drop_tags:reset()
|
||||||
|
for _,tag in pairs(tags) do
|
||||||
|
print("Looking at tag",tag)
|
||||||
|
assert(stmnt_ins_tag:bind(1,storyid) == sql.OK)
|
||||||
|
assert(stmnt_ins_tag:bind(2,tag) == sql.OK)
|
||||||
|
err = do_sql(stmnt_ins_tag)
|
||||||
|
stmnt_ins_tag:reset()
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
local id_enc = util.encode_id(storyid)
|
||||||
|
local loc = string.format("https://%s/%s",config.domain,id_enc)
|
||||||
|
cache.dirty(string.format("%s/%s",config.domain,id_enc)) -- This place to read this post
|
||||||
|
cache.dirty(string.format("%s",config.domain)) -- The site index (ex, if the author changed the paste from their's to "Anonymous", the cache should reflect that).
|
||||||
|
cache.dirty(string.format("%s.%s",author,config.domain)) -- The author's index, same reasoning as above.
|
||||||
|
http_response_header(req,"Location",loc)
|
||||||
|
http_response(req,303,"")
|
||||||
|
return
|
||||||
|
end
|
|
@ -0,0 +1,110 @@
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
|
local cache = require("cache")
|
||||||
|
local queries = require("queries")
|
||||||
|
local db = require("db")
|
||||||
|
local util = require("util")
|
||||||
|
local config = require("config")
|
||||||
|
local pages = require("pages")
|
||||||
|
|
||||||
|
local stmnt_index, stmnt_author, stmnt_author_bio
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
stmnt_index = assert(db.conn:prepare(queries.select_site_index))
|
||||||
|
--TODO: actually let authors edit their bio
|
||||||
|
stmnt_author_bio = assert(db.conn:prepare([[
|
||||||
|
SELECT authors.biography FROM authors WHERE authors.name = :author;
|
||||||
|
]]))
|
||||||
|
stmnt_author = assert(db.conn:prepare(queries.select_author_index))
|
||||||
|
return configure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_site_home(req)
|
||||||
|
print("Cache miss, rendering index")
|
||||||
|
stmnt_index:bind_names{}
|
||||||
|
local err = util.do_sql(stmnt_index)
|
||||||
|
local latest = {}
|
||||||
|
--err may be sql.ROW or sql.DONE if we don't have any stories yet
|
||||||
|
while err == sql.ROW do
|
||||||
|
local data = stmnt_index:get_values()
|
||||||
|
local storytags = tags.get(data[1])
|
||||||
|
table.insert(latest,{
|
||||||
|
url = encode_id(data[1]),
|
||||||
|
title = data[2],
|
||||||
|
isanon = data[3] == 1,
|
||||||
|
posted = os.date("%B %d %Y",tonumber(data[4])),
|
||||||
|
author = data[5],
|
||||||
|
tags = storytags,
|
||||||
|
})
|
||||||
|
err = stmnt_index:step()
|
||||||
|
end
|
||||||
|
stmnt_index:reset()
|
||||||
|
return pages.index{
|
||||||
|
domain = config.domain,
|
||||||
|
stories = latest
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local function get_author_home(req)
|
||||||
|
local host = http_request_get_host(req)
|
||||||
|
local subdomain = host:match("([^\\.]+)")
|
||||||
|
stmnt_author_bio:bind_names{author=subdomain}
|
||||||
|
local err = do_sql(stmnt_author_bio)
|
||||||
|
if err == sql.DONE then
|
||||||
|
print("No such author")
|
||||||
|
stmnt_author_bio:reset()
|
||||||
|
return pages.noauthor{
|
||||||
|
author = subdomain
|
||||||
|
}
|
||||||
|
end
|
||||||
|
print("err:",err)
|
||||||
|
assert(err == sql.ROW,"failed to get author:" .. subdomain .. " error:" .. tostring(err))
|
||||||
|
local data = stmnt_author_bio:get_values()
|
||||||
|
local bio = data[1]
|
||||||
|
stmnt_author_bio:reset()
|
||||||
|
print("Getting author's stories")
|
||||||
|
stmnt_author:bind_names{author=subdomain}
|
||||||
|
err = do_sql(stmnt_author)
|
||||||
|
print("err:",err)
|
||||||
|
local stories = {}
|
||||||
|
while err == sql.ROW do
|
||||||
|
local data = stmnt_author:get_values()
|
||||||
|
local id, title, time = unpack(data)
|
||||||
|
local tags = get_tags(id)
|
||||||
|
table.insert(stories,{
|
||||||
|
url = encode_id(id),
|
||||||
|
title = title,
|
||||||
|
posted = os.date("%B %d %Y",tonumber(time)),
|
||||||
|
tags = tags,
|
||||||
|
})
|
||||||
|
err = stmnt_author:step()
|
||||||
|
end
|
||||||
|
stmnt_author:reset()
|
||||||
|
return pages.author_index{
|
||||||
|
domain=config.domain,
|
||||||
|
author=subdomain,
|
||||||
|
stories=stories,
|
||||||
|
bio=bio
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local function index_get(req)
|
||||||
|
local method = http_method_text(req)
|
||||||
|
local host = http_request_get_host(req)
|
||||||
|
local path = http_request_get_path(req)
|
||||||
|
--Default home page
|
||||||
|
local subdomain = host:match("([^\\.]+)")
|
||||||
|
local text
|
||||||
|
if host == config.domain then
|
||||||
|
local cachepath = string.format("%s",config.domain),
|
||||||
|
text = cache.render(cachepath, function()
|
||||||
|
return get_site_home(req)
|
||||||
|
end)
|
||||||
|
else --author home page
|
||||||
|
local cachepath = string.format("%s.%s",subdomain,config.domain)
|
||||||
|
text = cache.render(cachepath, function()
|
||||||
|
return get_author_home(req)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
149
src/lua/init.lua
149
src/lua/init.lua
|
@ -1,19 +1,34 @@
|
||||||
|
|
||||||
print("Really fast print from init.lua")
|
print("Really fast print from init.lua")
|
||||||
|
|
||||||
|
--Luarocks libraries
|
||||||
local et = require("etlua")
|
local et = require("etlua")
|
||||||
local sql = require("lsqlite3")
|
local sql = require("lsqlite3")
|
||||||
local zlib = require("zlib")
|
local zlib = require("zlib")
|
||||||
|
|
||||||
if PRODUCTION then
|
--smr code
|
||||||
|
local cache = require("cache")
|
||||||
|
local pages = require("pages")
|
||||||
|
local util = require("util")
|
||||||
|
local config = require("config")
|
||||||
|
local db = require("db")
|
||||||
|
if config.production then
|
||||||
local function print() end --squash prints
|
local function print() end --squash prints
|
||||||
end
|
end
|
||||||
|
--[[
|
||||||
local parser_names = {"plain","imageboard"}
|
local parser_names = {"plain","imageboard"}
|
||||||
local parsers = {}
|
local parsers = {}
|
||||||
for _,v in pairs(parser_names) do
|
for _,v in pairs(parser_names) do
|
||||||
parsers[v] = require("parser_" .. v)
|
parsers[v] = require("parser_" .. v)
|
||||||
end
|
end
|
||||||
local db,cache --databases
|
]]
|
||||||
local domain = "test.monster:8888" --The domain to write links as
|
--pages
|
||||||
|
read_get = require("read_get")
|
||||||
|
read_post = require("read_post")
|
||||||
|
preview_post = require("preview_post")
|
||||||
|
--local db,cache --databases
|
||||||
|
--local domain = "test.monster:8888" --The domain to write links as
|
||||||
|
--[[
|
||||||
local pagenames = {
|
local pagenames = {
|
||||||
"index",
|
"index",
|
||||||
"author_index",
|
"author_index",
|
||||||
|
@ -36,7 +51,9 @@ for k,v in pairs(pagenames) do
|
||||||
pages[v] = assert(et.compile(f:read("*a")))
|
pages[v] = assert(et.compile(f:read("*a")))
|
||||||
f:close()
|
f:close()
|
||||||
end
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[=[
|
||||||
local queries = {}
|
local queries = {}
|
||||||
--These are all loaded during startup, won't affect ongoing performance.
|
--These are all loaded during startup, won't affect ongoing performance.
|
||||||
setmetatable(queries,{
|
setmetatable(queries,{
|
||||||
|
@ -47,13 +64,13 @@ setmetatable(queries,{
|
||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
]=]
|
||||||
---sql queries
|
---sql queries
|
||||||
local stmnt_index, stmnt_author_index, stmnt_read, stmnt_paste, stmnt_raw
|
local --[[stmnt_index,]] stmnt_author_index, stmnt_read, stmnt_paste, stmnt_raw
|
||||||
local stmnt_update_views
|
local stmnt_update_views
|
||||||
local stmnt_ins_tag, stmnt_drop_tags, stmnt_get_tags
|
local stmnt_ins_tag, stmnt_drop_tags, stmnt_get_tags
|
||||||
local stmnt_author_create, stmnt_author_acct, stmnt_author_bio
|
local stmnt_author_create, stmnt_author_acct, stmnt_author_bio
|
||||||
local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache
|
--local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache
|
||||||
local stmnt_get_session, stmnt_insert_session
|
local stmnt_get_session, stmnt_insert_session
|
||||||
local stmnt_edit, stmnt_update, stmnt_update_raw, stmnt_author_of
|
local stmnt_edit, stmnt_update, stmnt_update_raw, stmnt_author_of
|
||||||
local stmnt_comments, stmnt_comment_insert
|
local stmnt_comments, stmnt_comment_insert
|
||||||
|
@ -79,6 +96,7 @@ local function decodeentities(capture)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
local function sqlassert(...)
|
local function sqlassert(...)
|
||||||
local r,errcode,err = ...
|
local r,errcode,err = ...
|
||||||
if not r then
|
if not r then
|
||||||
|
@ -86,6 +104,7 @@ local function sqlassert(...)
|
||||||
end
|
end
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
local function sqlbind(stmnt,call,position,data)
|
local function sqlbind(stmnt,call,position,data)
|
||||||
assert(call == "bind" or call == "bind_blob","Bad bind call, call was:" .. call)
|
assert(call == "bind" or call == "bind_blob","Bad bind call, call was:" .. call)
|
||||||
|
@ -99,22 +118,22 @@ end
|
||||||
|
|
||||||
print("Hello from init.lua")
|
print("Hello from init.lua")
|
||||||
function configure()
|
function configure()
|
||||||
db = sqlassert(sql.open("data/posts.db"))
|
--db = sqlassert(sql.open("data/posts.db"))
|
||||||
--db = sqlassert(sql.open_memory())
|
----db = sqlassert(sql.open_memory())
|
||||||
cache = sqlassert(sql.open_memory())
|
--cache = sqlassert(sql.open_memory())
|
||||||
print("Compiled pages...")
|
|
||||||
--Test that compression works
|
--Test that compression works. For some reason, the zlib library
|
||||||
|
--fails if this is done as a one-liner
|
||||||
local msg = "test message"
|
local msg = "test message"
|
||||||
local one = zlib.compress(msg)
|
local one = zlib.compress(msg)
|
||||||
local two = zlib.decompress(one)
|
local two = zlib.decompress(one)
|
||||||
--For some reason, the zlib library fails if this is done as a oneliner
|
|
||||||
assert(two == msg, "zlib not working as expected")
|
assert(two == msg, "zlib not working as expected")
|
||||||
|
|
||||||
--Create sql tables
|
--Create sql tables
|
||||||
assert(db:exec(queries.create_table_authors))
|
--assert(db:exec(queries.create_table_authors))
|
||||||
--Create a fake "anonymous" user, so we don't run into trouble
|
--Create a fake "anonymous" user, so we don't run into trouble
|
||||||
--so that no one runs into touble being able to paste under this account.
|
--so that no one runs into touble being able to paste under this account.
|
||||||
assert(db:exec(queries.insert_anon_author))
|
--assert(db:exec(queries.insert_anon_author))
|
||||||
--If/when an author delets their account, all posts
|
--If/when an author delets their account, all posts
|
||||||
--and comments by that author are also deleted (on
|
--and comments by that author are also deleted (on
|
||||||
--delete cascade) this is intentional. This also
|
--delete cascade) this is intentional. This also
|
||||||
|
@ -122,23 +141,24 @@ function configure()
|
||||||
--an author makes will also be deleted.
|
--an author makes will also be deleted.
|
||||||
--
|
--
|
||||||
--Post text uses zlib compression
|
--Post text uses zlib compression
|
||||||
assert(db:exec(queries.create_table_posts))
|
--assert(db:exec(queries.create_table_posts))
|
||||||
--Store the raw text so people can download it later, maybe
|
--Store the raw text so people can download it later, maybe
|
||||||
--we can use it for "download as image" or "download as pdf"
|
--we can use it for "download as image" or "download as pdf"
|
||||||
--in the future too. Stil stored zlib compressed
|
--in the future too. Stil stored zlib compressed
|
||||||
assert(db:exec(queries.create_table_raw_text))
|
--assert(db:exec(queries.create_table_raw_text))
|
||||||
assert(db:exec(queries.create_table_images))
|
--assert(db:exec(queries.create_table_images))
|
||||||
assert(db:exec(queries.create_table_comments))
|
--assert(db:exec(queries.create_table_comments))
|
||||||
assert(db:exec(queries.create_table_tags))
|
--assert(db:exec(queries.create_table_tags))
|
||||||
assert(db:exec(queries.create_index_tags))
|
--assert(db:exec(queries.create_index_tags))
|
||||||
--Store a cookie for logged in users. Logged in users can edit
|
--Store a cookie for logged in users. Logged in users can edit
|
||||||
--their own posts.
|
--their own posts.
|
||||||
assert(db:exec(queries.create_table_session))
|
--assert(db:exec(queries.create_table_session))
|
||||||
print("Created db tables")
|
--print("Created db tables")
|
||||||
|
|
||||||
--A cache table to store rendered pages that do not need to be
|
--A cache table to store rendered pages that do not need to be
|
||||||
--rerendered. In theory this could OOM the program eventually and start
|
--rerendered. In theory this could OOM the program eventually and start
|
||||||
--swapping to disk. TODO: fixme
|
--swapping to disk. TODO: fixme
|
||||||
|
--[=[
|
||||||
assert(cache:exec([[
|
assert(cache:exec([[
|
||||||
CREATE TABLE IF NOT EXISTS cache (
|
CREATE TABLE IF NOT EXISTS cache (
|
||||||
path TEXT PRIMARY KEY,
|
path TEXT PRIMARY KEY,
|
||||||
|
@ -147,62 +167,71 @@ function configure()
|
||||||
dirty INTEGER
|
dirty INTEGER
|
||||||
);
|
);
|
||||||
]]))
|
]]))
|
||||||
|
]=]
|
||||||
|
|
||||||
--Select the data we need to display the on the front page
|
--Select the data we need to display the on the front page
|
||||||
stmnt_index = assert(db:prepare(queries.select_site_index))
|
--stmnt_index = assert(db:prepare(queries.select_site_index))
|
||||||
--Select the data we need to read a story (and maybe display an edit
|
--Select the data we need to read a story (and maybe display an edit
|
||||||
--button
|
--button
|
||||||
stmnt_read = assert(db:prepare(queries.select_post))
|
--stmnt_read = assert(db:prepare(queries.select_post))
|
||||||
--Update the view counter when someone reads a story
|
--Update the view counter when someone reads a story
|
||||||
stmnt_update_views = assert(db:prepare(queries.update_views))
|
--stmnt_update_views = assert(db:prepare(queries.update_views))
|
||||||
--Retreive comments on a story
|
--Retreive comments on a story
|
||||||
stmnt_comments = assert(db:prepare(queries.select_comments))
|
--stmnt_comments = assert(db:prepare(queries.select_comments))
|
||||||
--Add a new comment to a story
|
--Add a new comment to a story
|
||||||
stmnt_comment_insert = assert(db:prepare(queries.insert_comment))
|
--stmnt_comment_insert = assert(db:prepare(queries.insert_comment))
|
||||||
--TODO: actually let authors edit their bio
|
--TODO: actually let authors edit their bio
|
||||||
stmnt_author_bio = assert(db:prepare([[
|
--[=[stmnt_author_bio = assert(db.conn:prepare([[
|
||||||
SELECT authors.biography FROM authors WHERE authors.name = :author;
|
SELECT authors.biography FROM authors WHERE authors.name = :author;
|
||||||
]]))
|
]]))
|
||||||
|
]=]
|
||||||
--Get the author of a story, used to check when editing that the
|
--Get the author of a story, used to check when editing that the
|
||||||
--author really owns the story they're trying to edit
|
--author really owns the story they're trying to edit
|
||||||
stmnt_author_of = assert(db:prepare(queries.select_author_of_post))
|
--stmnt_author_of = assert(db:prepare(queries.select_author_of_post))
|
||||||
--Get the data we need to display a particular author's latest
|
--Get the data we need to display a particular author's latest
|
||||||
--stories
|
--stories
|
||||||
stmnt_author = assert(db:prepare(queries.select_author_index))
|
--stmnt_author = assert(db:prepare(queries.select_author_index))
|
||||||
--Get the data we need to check if someone can log in
|
--Get the data we need to check if someone can log in
|
||||||
|
--[=[
|
||||||
stmnt_author_acct = assert(db:prepare([[
|
stmnt_author_acct = assert(db:prepare([[
|
||||||
SELECT id, salt, passhash FROM authors WHERE name = :name;
|
SELECT id, salt, passhash FROM authors WHERE name = :name;
|
||||||
]]))
|
]]))
|
||||||
|
]=]
|
||||||
--Create a new author on the site
|
--Create a new author on the site
|
||||||
stmnt_author_create = assert(db:prepare(queries.insert_author))
|
--stmnt_author_create = assert(db:prepare(queries.insert_author))
|
||||||
|
--[=[
|
||||||
stmnt_author_login = assert(db:prepare([[
|
stmnt_author_login = assert(db:prepare([[
|
||||||
SELECT name, passhash FROM authors WHERE name = :name;
|
SELECT name, passhash FROM authors WHERE name = :name;
|
||||||
]]))
|
]]))
|
||||||
|
]=]
|
||||||
--Create a new post
|
--Create a new post
|
||||||
stmnt_paste = assert(db:prepare(queries.insert_post))
|
stmnt_paste = assert(db:prepare(queries.insert_post))
|
||||||
--Keep a copy of the plain text of a post so we can edit it later
|
--Keep a copy of the plain text of a post so we can edit it later
|
||||||
--It might also be useful for migrations, if that ever needs to happen
|
--It might also be useful for migrations, if that ever needs to happen
|
||||||
stmnt_raw = assert(db:prepare(queries.insert_raw))
|
stmnt_raw = assert(db:prepare(queries.insert_raw))
|
||||||
--Tags for a story
|
--Tags for a story
|
||||||
|
--[[
|
||||||
stmnt_ins_tag = assert(db:prepare(queries.insert_tag))
|
stmnt_ins_tag = assert(db:prepare(queries.insert_tag))
|
||||||
stmnt_get_tags = assert(db:prepare(queries.select_tags))
|
stmnt_get_tags = assert(db:prepare(queries.select_tags))
|
||||||
stmnt_drop_tags = assert(db:prepare(queries.delete_tags))
|
stmnt_drop_tags = assert(db:prepare(queries.delete_tags))
|
||||||
|
]]
|
||||||
--Get the data we need to display the edit screen
|
--Get the data we need to display the edit screen
|
||||||
stmnt_edit = assert(db:prepare(queries.select_edit))
|
stmnt_edit = assert(db:prepare(queries.select_edit))
|
||||||
--Get the data we need when someone wants to download a paste
|
--Get the data we need when someone wants to download a paste
|
||||||
stmnt_download = assert(db:prepare(queries.select_download))
|
stmnt_download = assert(db:prepare(queries.select_download))
|
||||||
--When we update a post, store the plaintext again
|
--When we update a post, store the plaintext again
|
||||||
stmnt_update_raw = assert(db:prepare(queries.update_raw))
|
--stmnt_update_raw = assert(db:prepare(queries.update_raw))
|
||||||
--Should we really reset the update time every time someone makes a post?
|
--Should we really reset the update time every time someone makes a post?
|
||||||
--Someone could keep their story on the front page by just editing it a lot.
|
--Someone could keep their story on the front page by just editing it a lot.
|
||||||
--If it gets abused I can disable it I guess.
|
--If it gets abused I can disable it I guess.
|
||||||
stmnt_update = assert(db:prepare(queries.update_post))
|
--stmnt_update = assert(db:prepare(queries.update_post))
|
||||||
--Check sessions for login support
|
--Check sessions for login support
|
||||||
stmnt_insert_session = assert(db:prepare(queries.insert_session))
|
stmnt_insert_session = assert(db:prepare(queries.insert_session))
|
||||||
stmnt_get_session = assert(db:prepare(queries.select_valid_sessions))
|
stmnt_get_session = assert(db:prepare(queries.select_valid_sessions))
|
||||||
--Search by tag name
|
--Search by tag name
|
||||||
stmnt_search = assert(db:prepare(queries.select_post_tags))
|
stmnt_search = assert(db:prepare(queries.select_post_tags))
|
||||||
--only refresh pages at most once every 10 seconds
|
--only refresh pages at most once every 10 seconds
|
||||||
|
--[=[
|
||||||
stmnt_cache = cache:prepare([[
|
stmnt_cache = cache:prepare([[
|
||||||
SELECT data
|
SELECT data
|
||||||
FROM cache
|
FROM cache
|
||||||
|
@ -223,6 +252,7 @@ function configure()
|
||||||
SET dirty = 1
|
SET dirty = 1
|
||||||
WHERE path = :path;
|
WHERE path = :path;
|
||||||
]])
|
]])
|
||||||
|
]=]
|
||||||
--[=[
|
--[=[
|
||||||
]=]
|
]=]
|
||||||
print("finished running configure()")
|
print("finished running configure()")
|
||||||
|
@ -296,6 +326,7 @@ local function get_tags(id)
|
||||||
until false
|
until false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
local function dirty_cache(url)
|
local function dirty_cache(url)
|
||||||
print("Dirtying cache:",url)
|
print("Dirtying cache:",url)
|
||||||
stmnt_dirty_cache:bind_names{
|
stmnt_dirty_cache:bind_names{
|
||||||
|
@ -304,6 +335,7 @@ local function dirty_cache(url)
|
||||||
err = do_sql(stmnt_dirty_cache)
|
err = do_sql(stmnt_dirty_cache)
|
||||||
stmnt_dirty_cache:reset()
|
stmnt_dirty_cache:reset()
|
||||||
end
|
end
|
||||||
|
]=]
|
||||||
|
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
@ -356,6 +388,7 @@ local function get_session(req)
|
||||||
return author,authorid
|
return author,authorid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
--Render a page, with cacheing. If you need to dirty a cache, call dirty_cache()
|
--Render a page, with cacheing. If you need to dirty a cache, call dirty_cache()
|
||||||
local function render(pagename,callback)
|
local function render(pagename,callback)
|
||||||
print("Running render...")
|
print("Running render...")
|
||||||
|
@ -388,7 +421,9 @@ local function render(pagename,callback)
|
||||||
stmnt_insert_cache:reset()
|
stmnt_insert_cache:reset()
|
||||||
return text
|
return text
|
||||||
end
|
end
|
||||||
|
]=]
|
||||||
|
|
||||||
|
--[=[
|
||||||
--[[Parses a semicolon seperated string into it's parts, trims whitespace, lowercases, and capitalizes the first letter. Tags will not be empty. Returns an array of tags]]
|
--[[Parses a semicolon seperated string into it's parts, trims whitespace, lowercases, and capitalizes the first letter. Tags will not be empty. Returns an array of tags]]
|
||||||
local function parse_tags(str)
|
local function parse_tags(str)
|
||||||
local tags = {}
|
local tags = {}
|
||||||
|
@ -404,6 +439,7 @@ local function parse_tags(str)
|
||||||
end
|
end
|
||||||
return tags
|
return tags
|
||||||
end
|
end
|
||||||
|
]=]
|
||||||
|
|
||||||
function home(req)
|
function home(req)
|
||||||
print("Hello from lua!")
|
print("Hello from lua!")
|
||||||
|
@ -414,7 +450,7 @@ function home(req)
|
||||||
local text
|
local text
|
||||||
if host == domain then
|
if host == domain then
|
||||||
--Default home page
|
--Default home page
|
||||||
text = render(string.format("%s",domain),function()
|
text = cache.render(string.format("%s",domain),function()
|
||||||
print("Cache miss, rendering index")
|
print("Cache miss, rendering index")
|
||||||
stmnt_index:bind_names{}
|
stmnt_index:bind_names{}
|
||||||
local err = do_sql(stmnt_index)
|
local err = do_sql(stmnt_index)
|
||||||
|
@ -442,7 +478,7 @@ function home(req)
|
||||||
else
|
else
|
||||||
--Home page for an author
|
--Home page for an author
|
||||||
local subdomain = host:match("([^\\.]+)")
|
local subdomain = host:match("([^\\.]+)")
|
||||||
text = render(string.format("%s.%s",subdomain,domain),function()
|
text = cache.render(string.format("%s.%s",subdomain,domain),function()
|
||||||
print("Cache miss, rendering author:" .. subdomain)
|
print("Cache miss, rendering author:" .. subdomain)
|
||||||
stmnt_author_bio:bind_names{author=subdomain}
|
stmnt_author_bio:bind_names{author=subdomain}
|
||||||
local err = do_sql(stmnt_author_bio)
|
local err = do_sql(stmnt_author_bio)
|
||||||
|
@ -505,7 +541,7 @@ function claim(req)
|
||||||
local text
|
local text
|
||||||
if method == "GET" then
|
if method == "GET" then
|
||||||
--Get the page to claim a name
|
--Get the page to claim a name
|
||||||
text = render(string.format("%s/_claim",domain),function()
|
text = cache.render(string.format("%s/_claim",domain),function()
|
||||||
print("cache miss, rendering claim page")
|
print("cache miss, rendering claim page")
|
||||||
return pages.claim{err=""}
|
return pages.claim{err=""}
|
||||||
end)
|
end)
|
||||||
|
@ -578,7 +614,7 @@ function paste(req)
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
--For an anonymous user
|
--For an anonymous user
|
||||||
ret = render(string.format("%s/_paste",host),function()
|
ret = cache.render(string.format("%s/_paste",host),function()
|
||||||
print("Cache missing, rendering post page")
|
print("Cache missing, rendering post page")
|
||||||
return pages.paste{
|
return pages.paste{
|
||||||
domain = domain,
|
domain = domain,
|
||||||
|
@ -686,8 +722,8 @@ function paste(req)
|
||||||
local loc = string.format("https://%s/%s",domain,url)
|
local loc = string.format("https://%s/%s",domain,url)
|
||||||
http_response_header(req,"Location",loc)
|
http_response_header(req,"Location",loc)
|
||||||
http_response(req,303,"")
|
http_response(req,303,"")
|
||||||
dirty_cache(string.format("%s/%s",domain,url))
|
cache.dirty(string.format("%s/%s",domain,url))
|
||||||
dirty_cache(string.format("%s",domain))
|
cache.dirty(string.format("%s",domain))
|
||||||
return
|
return
|
||||||
elseif err == sql.ERROR or err == sql.MISUSE then
|
elseif err == sql.ERROR or err == sql.MISUSE then
|
||||||
ret = "Failed to paste: " .. tostring(err)
|
ret = "Failed to paste: " .. tostring(err)
|
||||||
|
@ -748,9 +784,9 @@ function paste(req)
|
||||||
end
|
end
|
||||||
http_response_header(req,"Location",loc)
|
http_response_header(req,"Location",loc)
|
||||||
http_response(req,303,"")
|
http_response(req,303,"")
|
||||||
dirty_cache(string.format("%s.%s",author,domain))
|
cache.dirty(string.format("%s.%s",author,domain))
|
||||||
dirty_cache(string.format("%s/%s",domain,url))
|
cache.dirty(string.format("%s/%s",domain,url))
|
||||||
dirty_cache(string.format("%s",domain))
|
cache.dirty(string.format("%s",domain))
|
||||||
return
|
return
|
||||||
elseif err == sql.ERROR or err == sql.MISUSE then
|
elseif err == sql.ERROR or err == sql.MISUSE then
|
||||||
ret = "Failed to paste: " .. tostring(err)
|
ret = "Failed to paste: " .. tostring(err)
|
||||||
|
@ -778,7 +814,7 @@ local function read_story(host,path,idp,show_comments,iam)
|
||||||
}
|
}
|
||||||
print("update:",do_sql(stmnt_update_views))
|
print("update:",do_sql(stmnt_update_views))
|
||||||
stmnt_update_views:reset()
|
stmnt_update_views:reset()
|
||||||
dirty_cache(cachestr)
|
cache.dirty(cachestr)
|
||||||
print("cachestr was:",cachestr)
|
print("cachestr was:",cachestr)
|
||||||
local readstoryf = function()
|
local readstoryf = function()
|
||||||
stmnt_read:bind_names{
|
stmnt_read:bind_names{
|
||||||
|
@ -830,7 +866,7 @@ local function read_story(host,path,idp,show_comments,iam)
|
||||||
--which is not the user's, from whoever loaded the cache last) to fix this bug, don't cache
|
--which is not the user's, from whoever loaded the cache last) to fix this bug, don't cache
|
||||||
--pages when the user is logged in. All non-logged-in users can see the same page no problem.
|
--pages when the user is logged in. All non-logged-in users can see the same page no problem.
|
||||||
if not iam then
|
if not iam then
|
||||||
return render(cachestr,readstoryf)
|
return cache.render(cachestr,readstoryf)
|
||||||
else
|
else
|
||||||
return readstoryf()
|
return readstoryf()
|
||||||
end
|
end
|
||||||
|
@ -841,6 +877,8 @@ function read(req)
|
||||||
local path = http_request_get_path(req)
|
local path = http_request_get_path(req)
|
||||||
local method = http_method_text(req)
|
local method = http_method_text(req)
|
||||||
if method == "GET" then
|
if method == "GET" then
|
||||||
|
read_get(req)
|
||||||
|
--[=[
|
||||||
local idp = string.sub(path,2)--remove leading "/"
|
local idp = string.sub(path,2)--remove leading "/"
|
||||||
assert(string.len(path) > 0,"Tried to read 0-length story id")
|
assert(string.len(path) > 0,"Tried to read 0-length story id")
|
||||||
local author, authorid = get_session(req)
|
local author, authorid = get_session(req)
|
||||||
|
@ -902,7 +940,10 @@ function read(req)
|
||||||
assert(text)
|
assert(text)
|
||||||
http_response(req,200,text)
|
http_response(req,200,text)
|
||||||
return
|
return
|
||||||
|
]=]
|
||||||
elseif method == "POST" then
|
elseif method == "POST" then
|
||||||
|
read_post(req)
|
||||||
|
--[=[
|
||||||
--We're posting a comment
|
--We're posting a comment
|
||||||
http_request_populate_post(req)
|
http_request_populate_post(req)
|
||||||
http_populate_cookies(req)
|
http_populate_cookies(req)
|
||||||
|
@ -928,11 +969,12 @@ function read(req)
|
||||||
http_response(req,500,"Internal error, failed to post comment. Go back and try again.")
|
http_response(req,500,"Internal error, failed to post comment. Go back and try again.")
|
||||||
else
|
else
|
||||||
--When we post a comment, we need to dirty the cache for the "comments displayed" page.
|
--When we post a comment, we need to dirty the cache for the "comments displayed" page.
|
||||||
dirty_cache(string.format("%s%s?comments=1",host,path))
|
cache.dirty(string.format("%s%s?comments=1",host,path))
|
||||||
local redir = string.format("https://%s%s?comments=1", domain, path)
|
local redir = string.format("https://%s%s?comments=1", domain, path)
|
||||||
http_response_header(req,"Location",redir)
|
http_response_header(req,"Location",redir)
|
||||||
http_response(req,303,"")
|
http_response(req,303,"")
|
||||||
end
|
end
|
||||||
|
]=]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -949,7 +991,7 @@ function login(req)
|
||||||
local text
|
local text
|
||||||
if method == "GET" then
|
if method == "GET" then
|
||||||
--Just give them the login page
|
--Just give them the login page
|
||||||
text = render(string.format("%s/_login",domain),function()
|
text = cache.render(string.format("%s/_login",domain),function()
|
||||||
return pages.login{
|
return pages.login{
|
||||||
err = "",
|
err = "",
|
||||||
}
|
}
|
||||||
|
@ -1093,9 +1135,9 @@ function edit(req)
|
||||||
end
|
end
|
||||||
local id_enc = encode_id(storyid)
|
local id_enc = encode_id(storyid)
|
||||||
local loc = string.format("https://%s/%s",domain,id_enc)
|
local loc = string.format("https://%s/%s",domain,id_enc)
|
||||||
dirty_cache(string.format("%s/%s",domain,id_enc)) -- This place to read this post
|
cache.dirty(string.format("%s/%s",domain,id_enc)) -- This place to read this post
|
||||||
dirty_cache(string.format("%s",domain)) -- The site index (ex, if the author changed the paste from their's to "Anonymous", the cache should reflect that).
|
cache.dirty(string.format("%s",domain)) -- The site index (ex, if the author changed the paste from their's to "Anonymous", the cache should reflect that).
|
||||||
dirty_cache(string.format("%s.%s",author,domain)) -- The author's index, same reasoning as above.
|
cache.dirty(string.format("%s.%s",author,domain)) -- The author's index, same reasoning as above.
|
||||||
http_response_header(req,"Location",loc)
|
http_response_header(req,"Location",loc)
|
||||||
http_response(req,303,"")
|
http_response(req,303,"")
|
||||||
return
|
return
|
||||||
|
@ -1112,10 +1154,10 @@ end
|
||||||
function teardown()
|
function teardown()
|
||||||
print("Exiting...")
|
print("Exiting...")
|
||||||
if db then
|
if db then
|
||||||
db:close()
|
db.close()
|
||||||
end
|
end
|
||||||
if cache then
|
if cache then
|
||||||
cache:close()
|
cache.close()
|
||||||
end
|
end
|
||||||
print("Finished lua teardown")
|
print("Finished lua teardown")
|
||||||
end
|
end
|
||||||
|
@ -1127,6 +1169,7 @@ function download(req)
|
||||||
http_request_populate_qs(req)
|
http_request_populate_qs(req)
|
||||||
local story = assert(http_argument_get_string(req,"story"))
|
local story = assert(http_argument_get_string(req,"story"))
|
||||||
local story_id = decode_id(story)
|
local story_id = decode_id(story)
|
||||||
|
print("Downloading", story_id)
|
||||||
stmnt_download:bind_names{
|
stmnt_download:bind_names{
|
||||||
postid = story_id
|
postid = story_id
|
||||||
}
|
}
|
||||||
|
@ -1137,6 +1180,7 @@ function download(req)
|
||||||
stmnt_download:reset()
|
stmnt_download:reset()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
assert(err == sql.ROW, "after doing download sql, result was not a row, was:" .. tostring(err))
|
||||||
local txt_compressed, title = unpack(stmnt_download:get_values())
|
local txt_compressed, title = unpack(stmnt_download:get_values())
|
||||||
local text = zlib.decompress(txt_compressed)
|
local text = zlib.decompress(txt_compressed)
|
||||||
stmnt_download:reset()
|
stmnt_download:reset()
|
||||||
|
@ -1147,6 +1191,8 @@ function download(req)
|
||||||
end
|
end
|
||||||
|
|
||||||
function preview(req)
|
function preview(req)
|
||||||
|
preview_post(req)
|
||||||
|
--[[
|
||||||
print("We want to preview a paste!")
|
print("We want to preview a paste!")
|
||||||
local host = http_request_get_host(req)
|
local host = http_request_get_host(req)
|
||||||
local path = http_request_get_path(req)
|
local path = http_request_get_path(req)
|
||||||
|
@ -1170,6 +1216,7 @@ function preview(req)
|
||||||
tags = tags,
|
tags = tags,
|
||||||
}
|
}
|
||||||
http_response(req,200,ret)
|
http_response(req,200,ret)
|
||||||
|
]]
|
||||||
end
|
end
|
||||||
|
|
||||||
function search(req)
|
function search(req)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
local config = require("config")
|
||||||
|
|
||||||
|
|
||||||
|
local function login_get(req)
|
||||||
|
--Just give them the login page
|
||||||
|
return cache.render(string.format("%s/_login",domain),function()
|
||||||
|
return pages.login{
|
||||||
|
err = "",
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
|
@ -0,0 +1,58 @@
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
|
local db = require("db")
|
||||||
|
local util = require("util")
|
||||||
|
|
||||||
|
local stmnt_author_acct
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
--Get the data we need to check if someone can log in
|
||||||
|
stmnt_author_acct = assert(db.conn:prepare([[
|
||||||
|
SELECT id, salt, passhash FROM authors WHERE name = :name;
|
||||||
|
]]))
|
||||||
|
|
||||||
|
return configure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function login_post(req)
|
||||||
|
--Try to log in
|
||||||
|
http_populate_multipart_form(req)
|
||||||
|
local name = assert(http_argument_get_string(req,"user"))
|
||||||
|
local pass = assert(http_file_get(req,"pass"))
|
||||||
|
stmnt_author_acct:bind_names{
|
||||||
|
name = name
|
||||||
|
}
|
||||||
|
local text
|
||||||
|
local err = util.do_sql(stmnt_author_acct)
|
||||||
|
if err == sql.ROW then
|
||||||
|
local id, salt, passhash = unpack(stmnt_author_acct:get_values())
|
||||||
|
stmnt_author_acct:reset()
|
||||||
|
local todigest = salt .. pass
|
||||||
|
local hash = sha3(todigest)
|
||||||
|
if hash == passhash then
|
||||||
|
local session = start_session(id)
|
||||||
|
http_response_cookie(req,"session",session,"/",0,0)
|
||||||
|
local loc = string.format("https://%s.%s",name,domain)
|
||||||
|
http_response_header(req,"Location",loc)
|
||||||
|
http_response(req,303,"")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
text = pages.login{
|
||||||
|
err = "Incorrect username or password"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
elseif err == sql.DONE then --Allows user enumeration, do we want this?
|
||||||
|
--Probably not a problem since all passwords are forced to be "good"
|
||||||
|
stmnt_author_acct:reset()
|
||||||
|
text = pages.login{
|
||||||
|
err = "Failed to find user:" .. name
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stmnt_author_acct:reset()
|
||||||
|
error("Other sql error during login")
|
||||||
|
end
|
||||||
|
http_response(req,200,text)
|
||||||
|
end
|
||||||
|
|
||||||
|
return login_post
|
|
@ -0,0 +1,25 @@
|
||||||
|
local et = require("etlua")
|
||||||
|
local pagenames = {
|
||||||
|
"index",
|
||||||
|
"author_index",
|
||||||
|
"claim",
|
||||||
|
"paste",
|
||||||
|
"edit",
|
||||||
|
"read",
|
||||||
|
"nostory",
|
||||||
|
"cantedit",
|
||||||
|
"noauthor",
|
||||||
|
"login",
|
||||||
|
"author_paste",
|
||||||
|
"author_edit",
|
||||||
|
"search",
|
||||||
|
}
|
||||||
|
local pages = {}
|
||||||
|
for k,v in pairs(pagenames) do
|
||||||
|
print("Compiling page:",v)
|
||||||
|
local f = assert(io.open("pages/" .. v .. ".etlua","r"))
|
||||||
|
pages[v] = assert(et.compile(f:read("*a")))
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
return pages
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
local parser_names = {"plain","imageboard"}
|
||||||
|
local parsers = {}
|
||||||
|
for _,v in pairs(parser_names) do
|
||||||
|
parsers[v] = require("parser_" .. v)
|
||||||
|
end
|
||||||
|
return parsers
|
|
@ -0,0 +1,81 @@
|
||||||
|
local config = require("config")
|
||||||
|
|
||||||
|
local function paste_get(req)
|
||||||
|
--Get the paste page
|
||||||
|
local host = http_request_get_host(req)
|
||||||
|
local text
|
||||||
|
local author,_ = get_session(req)
|
||||||
|
if host == config.domain and author then
|
||||||
|
http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,config.domain))
|
||||||
|
http_response(req,303,"")
|
||||||
|
return
|
||||||
|
elseif host == config.domain and author == nil then
|
||||||
|
text = cache.render(string.format("%s/_paste",host),function()
|
||||||
|
print("Cache missing, rendering post page")
|
||||||
|
return pages.paste{
|
||||||
|
domain = domain,
|
||||||
|
err = "",
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
http_response(req,200,text)
|
||||||
|
elseif host ~= config.domain and author then
|
||||||
|
|
||||||
|
elseif host ~= config.domain and author == nil then
|
||||||
|
|
||||||
|
else
|
||||||
|
error(string.format(
|
||||||
|
"Unable to find a good case for paste:%s,%s,%s",
|
||||||
|
host,
|
||||||
|
config.domain,
|
||||||
|
author
|
||||||
|
))
|
||||||
|
end
|
||||||
|
if host == config.domain then
|
||||||
|
local author,_ = get_session(req)
|
||||||
|
if author then
|
||||||
|
http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,domain))
|
||||||
|
http_response(req,303,"")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
--For an anonymous user
|
||||||
|
ret = cache.render(string.format("%s/_paste",host),function()
|
||||||
|
print("Cache missing, rendering post page")
|
||||||
|
return pages.paste{
|
||||||
|
domain = domain,
|
||||||
|
err = "",
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
--Or for someone that's logged in
|
||||||
|
print("Looks like a logged in user wants to paste!")
|
||||||
|
local subdomain = host:match("([^%.]+)")
|
||||||
|
local author,_ = get_session(req)
|
||||||
|
print("subdomain:",subdomain,"author:",author)
|
||||||
|
--If they try to paste as an author, but are on the
|
||||||
|
--wrong subdomain, or or not logged in, redirect them
|
||||||
|
--to the right place. Their own subdomain for authors
|
||||||
|
--or the anonymous paste page for not logged in users.
|
||||||
|
if author == nil then
|
||||||
|
http_response_header(req,"Location","https://"..domain.."/_paste")
|
||||||
|
http_response(req,303,"")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if author ~= subdomain then
|
||||||
|
http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,domain))
|
||||||
|
http_response(req,303,"")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
assert(author == subdomain,"someone wants to paste as someone else")
|
||||||
|
--We're where we want to be, serve up this users's
|
||||||
|
--paste page. No cache, because how often is a user
|
||||||
|
--going to paste?
|
||||||
|
ret = pages.author_paste{
|
||||||
|
domain = domain,
|
||||||
|
user = author,
|
||||||
|
text = "",
|
||||||
|
err = "",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,32 @@
|
||||||
|
local parsers = require("parsers")
|
||||||
|
local tags = require("tags")
|
||||||
|
local util = require("util")
|
||||||
|
local pages = require("pages")
|
||||||
|
|
||||||
|
local function preview_post(req)
|
||||||
|
print("We want to preview a paste!")
|
||||||
|
local host = http_request_get_host(req)
|
||||||
|
local path = http_request_get_path(req)
|
||||||
|
http_request_populate_post(req)
|
||||||
|
local title = assert(http_argument_get_string(req,"title"))
|
||||||
|
local text = assert(http_argument_get_string(req,"text"))
|
||||||
|
local markup = assert(http_argument_get_string(req,"markup"))
|
||||||
|
local tag_str = http_argument_get_string(req,"tags")
|
||||||
|
local tags = {}
|
||||||
|
if tag_str then
|
||||||
|
tags = util.parse_tags(tag_str)
|
||||||
|
end
|
||||||
|
print("title:",title,"text:",text,"markup:",markup)
|
||||||
|
local parsed = parsers[markup](text)
|
||||||
|
local ret = pages.read{
|
||||||
|
domain = domain,
|
||||||
|
title = title,
|
||||||
|
author = "preview",
|
||||||
|
idp = "preview",
|
||||||
|
text = parsed,
|
||||||
|
tags = tags,
|
||||||
|
}
|
||||||
|
http_response(req,200,ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
return preview_post
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
local queries = {}
|
||||||
|
|
||||||
|
setmetatable(queries,{
|
||||||
|
__index = function(self,key)
|
||||||
|
local f = assert(io.open("sql/" .. key .. ".sql","r"))
|
||||||
|
local ret = f:read("*a")
|
||||||
|
f:close()
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
return queries
|
|
@ -0,0 +1,140 @@
|
||||||
|
local sql = require("sqlite3")
|
||||||
|
|
||||||
|
local session = require("session")
|
||||||
|
local tags = require("tags")
|
||||||
|
local db = require("db")
|
||||||
|
local queries = require("queries")
|
||||||
|
|
||||||
|
local stmnt_read, stmnt_update_views, stmnt_comments
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
stmnt_read = assert(db.conn:prepare(queries.select_post))
|
||||||
|
stmnt_update_views = assert(db.conn:prepare(queries.update_views))
|
||||||
|
stmnt_comments = assert(db.conn:prepare(queries.select_comments))
|
||||||
|
return configure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Increases a story's hit counter by 1
|
||||||
|
]]
|
||||||
|
local function add_view(storyid)
|
||||||
|
stmnt_update_views:bind_names{
|
||||||
|
id = storyid
|
||||||
|
}
|
||||||
|
local err = do_sql(stmnt_update_views)
|
||||||
|
assert(err == sql.DONE, "Failed to update view counter:"..tostring(err))
|
||||||
|
stmnt_update_views:reset()
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Populates ps with story settings, returns true if story was found,
|
||||||
|
or nil if it wasn't
|
||||||
|
]]
|
||||||
|
local function populate_ps_story(req,ps)
|
||||||
|
--Make sure our story exists
|
||||||
|
stmnt_read:bind_names{
|
||||||
|
id = ps.storyid
|
||||||
|
}
|
||||||
|
local err = do_sql(stmnt_read)
|
||||||
|
if err == sql.DONE then
|
||||||
|
--We got no story
|
||||||
|
stmnt_read:reset()
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
--If we've made it here, we have a story. Populate our settings
|
||||||
|
--with title, text, ect.
|
||||||
|
assert(err == sql.ROW)
|
||||||
|
add_view(storyid)
|
||||||
|
local title, storytext, tauthor, isanon, authorname, views = unpack(
|
||||||
|
stmnt_read:get_values()
|
||||||
|
)
|
||||||
|
ps.title = title
|
||||||
|
ps.storytext = zlib.decompress(storytext)
|
||||||
|
ps.tauthor = tauthor
|
||||||
|
ps.isanon = isanon == 1
|
||||||
|
ps.authorname = authorname
|
||||||
|
ps.views = views
|
||||||
|
stmnt_read:reset()
|
||||||
|
--Tags
|
||||||
|
ps.tags = tags.get(id)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Get the comments for a story
|
||||||
|
]]
|
||||||
|
local function get_comments(req,ps)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
The author is viewing their own story, give them an edit button
|
||||||
|
]]
|
||||||
|
local function read_get_author(req,storyid,author,authorid,comments)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
An author is viewing a story, allow them to post comments as themselves
|
||||||
|
]]
|
||||||
|
local function read_get_loggedin(req,ps)
|
||||||
|
if ps.tauthor == ps.authorid then
|
||||||
|
--The story exists and we're logged in as the
|
||||||
|
--owner, display the edit button
|
||||||
|
return read_get_author(req,ps)
|
||||||
|
end
|
||||||
|
return pages.read(ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function read_get(req)
|
||||||
|
--Pages settings
|
||||||
|
local ps = {
|
||||||
|
host = http_request_get_host(req),
|
||||||
|
path = http_request_get_path(req),
|
||||||
|
method = http_method_text(req),
|
||||||
|
}
|
||||||
|
|
||||||
|
--Get our story id
|
||||||
|
assert(string.len(ps.path) > 0,"Tried to read 0-length story id")
|
||||||
|
local idp = string.sub(ps.path,2)--remove leading "/"
|
||||||
|
ps.storyid = util.decode_id(idp)
|
||||||
|
|
||||||
|
--If we're logged in, set author and authorid
|
||||||
|
local author, authorid = session.get(req)
|
||||||
|
if author and authorid then
|
||||||
|
ps.author = author
|
||||||
|
ps.authorid = authorid
|
||||||
|
end
|
||||||
|
|
||||||
|
--If we need to show comments
|
||||||
|
http_request_populate_qs(req)
|
||||||
|
ps.show_comments = http_argument_get_string(req,"comments")
|
||||||
|
if ps.show_comments then
|
||||||
|
ps.comments = get_comments(req,ps)
|
||||||
|
end
|
||||||
|
|
||||||
|
--normal story display
|
||||||
|
if (not ps.author) then
|
||||||
|
local cachestr = string.format("%s%s%s",
|
||||||
|
ps.host,
|
||||||
|
ps.path,
|
||||||
|
ps.show_comments and "?comments=1" or ""
|
||||||
|
)
|
||||||
|
local text = cache.render(cachestr,function()
|
||||||
|
populate_ps_story(req,ps)
|
||||||
|
return pages.read(ps)
|
||||||
|
end)
|
||||||
|
else --we are logged in, don't cache
|
||||||
|
populate_ps_story(req,ps)
|
||||||
|
ps.owner = (ps.author == ps.tauthor)
|
||||||
|
text = pages.read(ps)
|
||||||
|
end
|
||||||
|
assert(text)
|
||||||
|
http_response(req,200,text)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
return read_get
|
|
@ -0,0 +1,53 @@
|
||||||
|
local sql = require("sqlite3")
|
||||||
|
|
||||||
|
local cache = require("cache")
|
||||||
|
local session = require("session")
|
||||||
|
local util = require("util")
|
||||||
|
local db = require("db")
|
||||||
|
local queries = require("queries")
|
||||||
|
local config = require("config")
|
||||||
|
|
||||||
|
local stmnt_comment_insert
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
stmnt_comment_insert = assert(db.conn:prepare(queries.insert_comment))
|
||||||
|
return oldconfigure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function read_post(req)
|
||||||
|
local host = http_request_get_host(req)
|
||||||
|
local path = http_request_get_path(req)
|
||||||
|
--We're posting a comment
|
||||||
|
http_request_populate_post(req)
|
||||||
|
http_populate_cookies(req)
|
||||||
|
local author, authorid = session.get(req)
|
||||||
|
local comment_text = assert(http_argument_get_string(req,"text"))
|
||||||
|
local pasteas = assert(http_argument_get_string(req,"postas"))
|
||||||
|
local idp = string.sub(path,2)--remove leading "/"
|
||||||
|
local id = util.decode_id(idp)
|
||||||
|
local isanon = 1
|
||||||
|
--Even if an author is logged in, they may post their comment anonymously
|
||||||
|
if author and pasteas ~= "Anonymous" then
|
||||||
|
isanon = 0
|
||||||
|
end
|
||||||
|
stmnt_comment_insert:bind_names{
|
||||||
|
postid=id,
|
||||||
|
authorid = author and authorid or -1,
|
||||||
|
isanon = isanon,
|
||||||
|
comment_text = comment_text,
|
||||||
|
}
|
||||||
|
local err = util.do_sql(stmnt_comment_insert)
|
||||||
|
stmnt_comment_insert:reset()
|
||||||
|
if err ~= sql.DONE then
|
||||||
|
http_response(req,500,"Internal error, failed to post comment. Go back and try again.")
|
||||||
|
else
|
||||||
|
--When we post a comment, we need to dirty the cache for the "comments displayed" page.
|
||||||
|
cache.dirty(string.format("%s%s?comments=1",host,path))
|
||||||
|
local redir = string.format("https://%s%s?comments=1", domain, path)
|
||||||
|
http_response_header(req,"Location",redir)
|
||||||
|
http_response(req,303,"")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
return read_post
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
|
local db = require("db")
|
||||||
|
local util = require("util")
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
local stmnt_get_session, stmnt_insert_session
|
||||||
|
function configure(...)
|
||||||
|
stmnt_get_session = assert(db:prepare(queries.select_valid_sessions))
|
||||||
|
|
||||||
|
return oldconfigure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local session = {}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Retreive the name and authorid of the logged in person,
|
||||||
|
or nil+error message if not logged in
|
||||||
|
]]
|
||||||
|
function session.get(req)
|
||||||
|
http_populate_cookies(req)
|
||||||
|
local sessionid = http_request_cookie(req,"session")
|
||||||
|
if sessionid == nil then
|
||||||
|
return nil, "No session cookie passed by client"
|
||||||
|
end
|
||||||
|
stmnt_get_session:bind_names{
|
||||||
|
key = sessionid
|
||||||
|
}
|
||||||
|
local err = util.do_sql(stmnt_get_session)
|
||||||
|
if err ~= sql.ROW then
|
||||||
|
return nil, "No such session by logged in users"
|
||||||
|
end
|
||||||
|
print("get session err:",err)
|
||||||
|
local data = stmnt_get_session:get_values()
|
||||||
|
stmnt_get_session:reset()
|
||||||
|
local author = data[1]
|
||||||
|
local authorid = data[2]
|
||||||
|
return author,authorid
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Start a session for someone who logged in
|
||||||
|
]]
|
||||||
|
local function start_session(who)
|
||||||
|
local rngf = assert(io.open("/dev/urandom","rb"))
|
||||||
|
local session_t = {}
|
||||||
|
for i = 1,64 do
|
||||||
|
local r = string.byte(rngf:read(1))
|
||||||
|
local s = string.char((r % 26) + 65)
|
||||||
|
table.insert(session_t,s)
|
||||||
|
end
|
||||||
|
local session = table.concat(session_t)
|
||||||
|
rngf:close()
|
||||||
|
print("sessionid:",session)
|
||||||
|
print("authorid:",who)
|
||||||
|
stmnt_insert_session:bind_names{
|
||||||
|
sessionid = session,
|
||||||
|
authorid = who
|
||||||
|
}
|
||||||
|
local err = util.do_sql(stmnt_insert_session)
|
||||||
|
stmnt_insert_session:reset()
|
||||||
|
print("Err:",err)
|
||||||
|
assert(err == sql.DONE)
|
||||||
|
return session
|
||||||
|
end
|
||||||
|
|
||||||
|
return session
|
|
@ -0,0 +1,54 @@
|
||||||
|
local db = require("db")
|
||||||
|
local queries = require("queries")
|
||||||
|
local util = require("util")
|
||||||
|
local tags = {}
|
||||||
|
|
||||||
|
local stmnt_get_tags, stmnt_ins_tag, stmnt_drop_tags
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
|
function configure(...)
|
||||||
|
--Tags for a story
|
||||||
|
stmnt_ins_tag = assert(db.conn:prepare(queries.insert_tag))
|
||||||
|
stmnt_get_tags = assert(db.conn:prepare(queries.select_tags))
|
||||||
|
stmnt_drop_tags = assert(db.conn:prepare(queries.delete_tags))
|
||||||
|
|
||||||
|
return configure(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function tags.get(id)
|
||||||
|
local ret = {}
|
||||||
|
stmnt_get_tags:bind_names{
|
||||||
|
id = id
|
||||||
|
}
|
||||||
|
local err
|
||||||
|
repeat
|
||||||
|
err = stmnt_get_tags:step()
|
||||||
|
if err == sql.BUSY then
|
||||||
|
coroutine.yield()
|
||||||
|
elseif err == sql.ROW then
|
||||||
|
table.insert(ret,stmnt_get_tags:get_value(0))
|
||||||
|
elseif err == sql.DONE then
|
||||||
|
stmnt_get_tags:reset()
|
||||||
|
return ret
|
||||||
|
else
|
||||||
|
error(string.format("Failed to get tags for story %d : %d", id, err))
|
||||||
|
end
|
||||||
|
until false
|
||||||
|
end
|
||||||
|
|
||||||
|
function tags.set(storyid,tags)
|
||||||
|
assert(stmnt_drop_tags:bind_names{postid = storyid} == sql.OK)
|
||||||
|
util.do_sql(stmnt_drop_tags)
|
||||||
|
stmnt_drop_tags:reset()
|
||||||
|
for _,tag in pairs(tags) do
|
||||||
|
print("Looking at tag",tag)
|
||||||
|
assert(stmnt_ins_tag:bind(1,storyid) == sql.OK)
|
||||||
|
assert(stmnt_ins_tag:bind(2,tag) == sql.OK)
|
||||||
|
err = util.do_sql(stmnt_ins_tag)
|
||||||
|
stmnt_ins_tag:reset()
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
return tags
|
|
@ -0,0 +1,103 @@
|
||||||
|
|
||||||
|
local util = {}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Runs an sql query and receives the 3 arguments back, prints a nice error
|
||||||
|
message on fail, and returns true on success.
|
||||||
|
]]
|
||||||
|
function util.sqlassert(...)
|
||||||
|
local r,errcode,err = ...
|
||||||
|
if not r then
|
||||||
|
error(string.format("%d: %s",errcode, err))
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Continuously tries to perform an sql statement until it goes through
|
||||||
|
]]
|
||||||
|
function util.do_sql(stmnt)
|
||||||
|
if not stmnt then error("No statement",2) end
|
||||||
|
local err
|
||||||
|
repeat
|
||||||
|
err = stmnt:step()
|
||||||
|
print("After stepping, err is", err)
|
||||||
|
if err == sql.BUSY then
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
until(err ~= sql.BUSY)
|
||||||
|
return err
|
||||||
|
end
|
||||||
|
|
||||||
|
--see https://perishablepress.com/stop-using-unsafe-characters-in-urls/
|
||||||
|
--no underscore because we use that for our operative pages
|
||||||
|
local url_characters =
|
||||||
|
[[abcdefghijklmnopqrstuvwxyz]]..
|
||||||
|
[[ABCDEFGHIJKLMNOPQRSTUVWXYZ]]..
|
||||||
|
[[0123456789]]..
|
||||||
|
[[$-+!*'(),]]
|
||||||
|
local url_characters_rev = {}
|
||||||
|
for i = 1,string.len(url_characters) do
|
||||||
|
url_characters_rev[string.sub(url_characters,i,i)] = i
|
||||||
|
end
|
||||||
|
--[[
|
||||||
|
Encode a number to a shorter HTML-safe url path
|
||||||
|
]]
|
||||||
|
function util.encode_id(number)
|
||||||
|
local result = {}
|
||||||
|
local charlen = string.len(url_characters)
|
||||||
|
repeat
|
||||||
|
local pos = (number % charlen) + 1
|
||||||
|
number = math.floor(number / charlen)
|
||||||
|
table.insert(result,string.sub(url_characters,pos,pos))
|
||||||
|
until number == 0
|
||||||
|
return table.concat(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Given a short HTML-safe url path, convert it to a storyid
|
||||||
|
]]
|
||||||
|
function util.decode_id(s)
|
||||||
|
local res, id = pcall(function()
|
||||||
|
local n = 0
|
||||||
|
local charlen = string.len(url_characters)
|
||||||
|
for i = 1,string.len(s) do
|
||||||
|
local char = string.sub(s,i,i)
|
||||||
|
local pos = url_characters_rev[char] - 1
|
||||||
|
n = n + (pos*math.pow(charlen,i-1))
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end)
|
||||||
|
if res then
|
||||||
|
return id
|
||||||
|
else
|
||||||
|
error("Failed to decode id:" .. s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Parses a semicolon seperated string into it's parts:
|
||||||
|
1. seperates by semicolon
|
||||||
|
2. trims whitespace
|
||||||
|
3. lowercases
|
||||||
|
4. capitalizes the first letter.
|
||||||
|
Returns an array of zero or more strings.
|
||||||
|
There is no blank tag, parsing "one;two;;three" will yield
|
||||||
|
{"one","two","three"}
|
||||||
|
]]
|
||||||
|
function util.parse_tags(str)
|
||||||
|
local tags = {}
|
||||||
|
for tag in string.gmatch(str,"([^;]+)") do
|
||||||
|
assert(tag, "Found a nil or false tag in:" .. str)
|
||||||
|
local tag_trimmed = string.match(tag,"%s*(.*)%s*")
|
||||||
|
local tag_lower = string.lower(tag_trimmed)
|
||||||
|
local tag_capitalized = string.gsub(tag_lower,"^.",string.upper)
|
||||||
|
assert(tag_capitalized, "After processing tag:" .. tag .. " it was falsey.")
|
||||||
|
if string.len(tag_capitalized) > 0 then
|
||||||
|
table.insert(tags, tag_capitalized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return tags
|
||||||
|
end
|
||||||
|
|
||||||
|
return util
|
|
@ -1,51 +1,49 @@
|
||||||
<% assert(author,"No author specified") %>
|
<% assert(author,"No author specified") %> <% assert(bio,"No bio included") %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8">
|
||||||
<% assert(bio,"No bio included") %>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>🍑</title>
|
<title>🍑</title>
|
||||||
<link href="/_css/milligram.css" rel="stylesheet">
|
<link href="/_css/milligram.css" rel="stylesheet">
|
||||||
<link href="/_css/style.css" rel="stylesheet">
|
<link href="/_css/style.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="container">
|
<body class="container">
|
||||||
<h1 class="title">
|
<main class="wrapper">
|
||||||
<a href="https://<%= author %>.<%= domain %>"><%= author %></a>.<a href="https://<%= domain %>"><%= domain %></a>
|
<h1 class="title">
|
||||||
</h1>
|
<a href="https://<%= author %>.<%= domain %>"><%= author %></a>.<a href="https://<%= domain %>"><%= domain %></a>
|
||||||
|
</h1>
|
||||||
<div class="content">
|
<div class="container">
|
||||||
<%= bio %>
|
<a href="/_paste" class="button">New paste</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<% if #stories == 0 then %>
|
<%= bio %>
|
||||||
This author has not made any pastes yet.
|
</div>
|
||||||
<% else %>
|
<div class="content">
|
||||||
<table>
|
<% if #stories == 0 then %>
|
||||||
<% for k,v in pairs(stories) do %>
|
This author has not made any pastes yet.
|
||||||
<tr><td>
|
<% else %>
|
||||||
<a href="<%= v.url %>">
|
<table>
|
||||||
<%- v.title %>
|
<% for k,v in pairs(stories) do %>
|
||||||
</a>
|
<tr><td>
|
||||||
</td><td>
|
<a href="<%= v.url %>">
|
||||||
By <a href="https://<%= author %>.<%= domain %>"><%= author %></a>
|
<%- v.title %>
|
||||||
</td><td>
|
</a>
|
||||||
<ul class="row tag-list">
|
</td><td>
|
||||||
<% for i = 1,math.min(#v.tags, 5) do %>
|
By <a href="https://<%= author %>.<%= domain %>"><%= author %></a>
|
||||||
<li><a class="tag button button-outline" href="https://<%= domain %>/_search?tag=<%= v.tags[i] %>"><%= v.tags[i] %></a></li>
|
</td><td>
|
||||||
<% end %>
|
<ul class="row tag-list">
|
||||||
</ul>
|
<% for i = 1,math.min(#v.tags, 5) do %>
|
||||||
</td><td>
|
<li><a class="tag button button-outline" href="https://<%= domain %>/_search?tag=<%= v.tags[i] %>"><%= v.tags[i] %></a></li>
|
||||||
<%= v.posted %>
|
<% end %>
|
||||||
</td></tr>
|
</ul>
|
||||||
|
</td><td>
|
||||||
|
<%= v.posted %>
|
||||||
|
</td></tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
<% end %>
|
<% end %>
|
||||||
</table>
|
</div>
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
|
|
||||||
</footer>
|
</footer>
|
||||||
|
</main>
|
||||||
</body>
|
</body>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
14
src/smr.c
14
src/smr.c
|
@ -12,6 +12,7 @@
|
||||||
#include "libkore.h"
|
#include "libkore.h"
|
||||||
#include "libcrypto.h"
|
#include "libcrypto.h"
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <kore/seccomp.h>
|
||||||
|
|
||||||
int home(struct http_request *);
|
int home(struct http_request *);
|
||||||
int post_story(struct http_request *);
|
int post_story(struct http_request *);
|
||||||
|
@ -38,6 +39,15 @@ lua_State *L;
|
||||||
static / _claim claim
|
static / _claim claim
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*Allow seccomp things for luajit and sqlite*/
|
||||||
|
KORE_SECCOMP_FILTER("app",
|
||||||
|
KORE_SYSCALL_ALLOW(pread64),
|
||||||
|
KORE_SYSCALL_ALLOW(pwrite64),
|
||||||
|
KORE_SYSCALL_ALLOW(fdatasync),
|
||||||
|
KORE_SYSCALL_ALLOW(unlinkat),
|
||||||
|
KORE_SYSCALL_ALLOW(mremap)
|
||||||
|
);
|
||||||
|
|
||||||
int
|
int
|
||||||
errhandeler(lua_State *L){
|
errhandeler(lua_State *L){
|
||||||
printf("Error: %s\n",lua_tostring(L,1));
|
printf("Error: %s\n",lua_tostring(L,1));
|
||||||
|
@ -66,8 +76,10 @@ do_lua(struct http_request *req, const char *name){
|
||||||
printf("About to pcall\n");
|
printf("About to pcall\n");
|
||||||
int err = lua_pcall(L,1,0,-3);
|
int err = lua_pcall(L,1,0,-3);
|
||||||
if(err != LUA_OK){
|
if(err != LUA_OK){
|
||||||
|
size_t retlen;
|
||||||
|
const char *ret = lua_tolstring(L,-1,&retlen);
|
||||||
printf("Failed to run %s: %s\n",name,lua_tostring(L,-1));
|
printf("Failed to run %s: %s\n",name,lua_tostring(L,-1));
|
||||||
http_response(req, 500, NULL, 0);
|
http_response(req, 500, ret, retlen);
|
||||||
lua_pop(L,lua_gettop(L));
|
lua_pop(L,lua_gettop(L));
|
||||||
return (KORE_RESULT_OK);
|
return (KORE_RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
/*
|
||||||
|
If/when an author delets their account, all posts
|
||||||
|
and comments by that author are also deleted (on
|
||||||
|
delete cascade) this is intentional. This also
|
||||||
|
means that all comments by other users on a post
|
||||||
|
an author makes will also be deleted.
|
||||||
|
|
||||||
|
Post text uses zlib compression
|
||||||
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS posts (
|
CREATE TABLE IF NOT EXISTS posts (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
post_text BLOB,
|
post_text BLOB,
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
/*
|
||||||
|
Store the raw text so people can download it later, maybe
|
||||||
|
we can use it for "download as image" or "download as pdf"
|
||||||
|
in the future too. Stil stored zlib compressed
|
||||||
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS raw_text (
|
CREATE TABLE IF NOT EXISTS raw_text (
|
||||||
id INTEGER PRIMARY KEY REFERENCES posts(id) ON DELETE CASCADE,
|
id INTEGER PRIMARY KEY REFERENCES posts(id) ON DELETE CASCADE,
|
||||||
post_text BLOB,
|
post_text BLOB,
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
/*
|
||||||
|
Store a cookie for logged in users. Logged in users can edit
|
||||||
|
their own posts.
|
||||||
|
*/
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
author REFERENCES authors(id) ON DELETE CASCADE,
|
author REFERENCES authors(id) ON DELETE CASCADE,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/*
|
||||||
|
Create a fake "anonymous" user, so
|
||||||
|
that no one runs into touble being able to paste under this account.
|
||||||
|
*/
|
||||||
INSERT OR IGNORE INTO authors (
|
INSERT OR IGNORE INTO authors (
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* Add a new comment to a story */
|
||||||
INSERT INTO comments(
|
INSERT INTO comments(
|
||||||
postid,
|
postid,
|
||||||
author,
|
author,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* Get the data we need to display a particular author's latest stories */
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
posts.id,
|
posts.id,
|
||||||
posts.post_title,
|
posts.post_title,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/*
|
||||||
|
Get the author of a story, used to check when editing that the
|
||||||
|
author really owns the story they're trying to edit
|
||||||
|
*/
|
||||||
SELECT
|
SELECT
|
||||||
authors.id,
|
authors.id,
|
||||||
authors.name
|
authors.name
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* Retreive comments on a story */
|
||||||
SELECT
|
SELECT
|
||||||
authors.name,
|
authors.name,
|
||||||
comments.isanon,
|
comments.isanon,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/*
|
||||||
|
Select the data we need to read a story (and maybe display an edit button)
|
||||||
|
*/
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
post_title,
|
post_title,
|
||||||
post_text,
|
post_text,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* Select the data we need to display the on the front page */
|
||||||
SELECT
|
SELECT
|
||||||
posts.id,
|
posts.id,
|
||||||
posts.post_title,
|
posts.post_title,
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
/* Update the view counter when someone reads a story */
|
||||||
UPDATE posts SET views = views + 1 WHERE id = :id;
|
UPDATE posts SET views = views + 1 WHERE id = :id;
|
||||||
|
|
Loading…
Reference in New Issue