diff --git a/src/lua/cache.lua b/src/lua/cache.lua
new file mode 100644
index 0000000..6290cef
--- /dev/null
+++ b/src/lua/cache.lua
@@ -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
diff --git a/src/lua/claim_get.lua b/src/lua/claim_get.lua
new file mode 100644
index 0000000..cd13079
--- /dev/null
+++ b/src/lua/claim_get.lua
@@ -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
diff --git a/src/lua/claim_post.lua b/src/lua/claim_post.lua
new file mode 100644
index 0000000..a950fe8
--- /dev/null
+++ b/src/lua/claim_post.lua
@@ -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
diff --git a/src/lua/config.lua b/src/lua/config.lua
new file mode 100644
index 0000000..804038f
--- /dev/null
+++ b/src/lua/config.lua
@@ -0,0 +1,5 @@
+
+return {
+ domain = "test.monster:8888",
+ production = false,
+}
diff --git a/src/lua/db.lua b/src/lua/db.lua
new file mode 100644
index 0000000..f8dee8c
--- /dev/null
+++ b/src/lua/db.lua
@@ -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
diff --git a/src/lua/edit_get.lua b/src/lua/edit_get.lua
new file mode 100644
index 0000000..600e0e1
--- /dev/null
+++ b/src/lua/edit_get.lua
@@ -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
diff --git a/src/lua/edit_post.lua b/src/lua/edit_post.lua
new file mode 100644
index 0000000..7bb093e
--- /dev/null
+++ b/src/lua/edit_post.lua
@@ -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
diff --git a/src/lua/index_get.lua b/src/lua/index_get.lua
new file mode 100644
index 0000000..7e65fe0
--- /dev/null
+++ b/src/lua/index_get.lua
@@ -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
diff --git a/src/lua/init.lua b/src/lua/init.lua
index 2a971f7..9bb7161 100644
--- a/src/lua/init.lua
+++ b/src/lua/init.lua
@@ -1,19 +1,34 @@
+
print("Really fast print from init.lua")
+--Luarocks libraries
local et = require("etlua")
local sql = require("lsqlite3")
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
end
+--[[
local parser_names = {"plain","imageboard"}
local parsers = {}
for _,v in pairs(parser_names) do
parsers[v] = require("parser_" .. v)
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 = {
"index",
"author_index",
@@ -36,7 +51,9 @@ for k,v in pairs(pagenames) do
pages[v] = assert(et.compile(f:read("*a")))
f:close()
end
+]]
+--[=[
local queries = {}
--These are all loaded during startup, won't affect ongoing performance.
setmetatable(queries,{
@@ -47,13 +64,13 @@ setmetatable(queries,{
return ret
end
})
-
+]=]
---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_ins_tag, stmnt_drop_tags, stmnt_get_tags
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_edit, stmnt_update, stmnt_update_raw, stmnt_author_of
local stmnt_comments, stmnt_comment_insert
@@ -79,6 +96,7 @@ local function decodeentities(capture)
end
end
+--[[
local function sqlassert(...)
local r,errcode,err = ...
if not r then
@@ -86,6 +104,7 @@ local function sqlassert(...)
end
return r
end
+]]
local function sqlbind(stmnt,call,position,data)
assert(call == "bind" or call == "bind_blob","Bad bind call, call was:" .. call)
@@ -99,22 +118,22 @@ end
print("Hello from init.lua")
function configure()
- db = sqlassert(sql.open("data/posts.db"))
- --db = sqlassert(sql.open_memory())
- cache = sqlassert(sql.open_memory())
- print("Compiled pages...")
- --Test that compression works
+ --db = sqlassert(sql.open("data/posts.db"))
+ ----db = sqlassert(sql.open_memory())
+ --cache = sqlassert(sql.open_memory())
+
+ --Test that compression works. For some reason, the zlib library
+ --fails if this is done as a one-liner
local msg = "test message"
local one = zlib.compress(msg)
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")
--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
--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
--and comments by that author are also deleted (on
--delete cascade) this is intentional. This also
@@ -122,23 +141,24 @@ function configure()
--an author makes will also be deleted.
--
--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
--we can use it for "download as image" or "download as pdf"
--in the future too. Stil stored zlib compressed
- assert(db:exec(queries.create_table_raw_text))
- assert(db:exec(queries.create_table_images))
- assert(db:exec(queries.create_table_comments))
- assert(db:exec(queries.create_table_tags))
- assert(db:exec(queries.create_index_tags))
+ --assert(db:exec(queries.create_table_raw_text))
+ --assert(db:exec(queries.create_table_images))
+ --assert(db:exec(queries.create_table_comments))
+ --assert(db:exec(queries.create_table_tags))
+ --assert(db:exec(queries.create_index_tags))
--Store a cookie for logged in users. Logged in users can edit
--their own posts.
- assert(db:exec(queries.create_table_session))
- print("Created db tables")
+ --assert(db:exec(queries.create_table_session))
+ --print("Created db tables")
--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,
@@ -147,62 +167,71 @@ function configure()
dirty INTEGER
);
]]))
+ ]=]
--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
--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
- stmnt_update_views = assert(db:prepare(queries.update_views))
+ --stmnt_update_views = assert(db:prepare(queries.update_views))
--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
- 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
- stmnt_author_bio = assert(db:prepare([[
+ --[=[stmnt_author_bio = assert(db.conn:prepare([[
SELECT authors.biography FROM authors WHERE authors.name = :author;
]]))
+ ]=]
--Get the author of a story, used to check when editing that the
--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
--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
+ --[=[
stmnt_author_acct = assert(db:prepare([[
SELECT id, salt, passhash FROM authors WHERE name = :name;
]]))
+ ]=]
--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([[
SELECT name, passhash FROM authors WHERE name = :name;
]]))
+ ]=]
--Create a new 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
--It might also be useful for migrations, if that ever needs to happen
stmnt_raw = assert(db:prepare(queries.insert_raw))
--Tags for a story
+ --[[
stmnt_ins_tag = assert(db:prepare(queries.insert_tag))
stmnt_get_tags = assert(db:prepare(queries.select_tags))
stmnt_drop_tags = assert(db:prepare(queries.delete_tags))
+ ]]
--Get the data we need to display the edit screen
stmnt_edit = assert(db:prepare(queries.select_edit))
--Get the data we need when someone wants to download a paste
stmnt_download = assert(db:prepare(queries.select_download))
--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?
--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.
- stmnt_update = assert(db:prepare(queries.update_post))
+ --stmnt_update = assert(db:prepare(queries.update_post))
--Check sessions for login support
stmnt_insert_session = assert(db:prepare(queries.insert_session))
stmnt_get_session = assert(db:prepare(queries.select_valid_sessions))
--Search by tag name
stmnt_search = assert(db:prepare(queries.select_post_tags))
--only refresh pages at most once every 10 seconds
+ --[=[
stmnt_cache = cache:prepare([[
SELECT data
FROM cache
@@ -223,6 +252,7 @@ function configure()
SET dirty = 1
WHERE path = :path;
]])
+ ]=]
--[=[
]=]
print("finished running configure()")
@@ -296,6 +326,7 @@ local function get_tags(id)
until false
end
+--[=[
local function dirty_cache(url)
print("Dirtying cache:",url)
stmnt_dirty_cache:bind_names{
@@ -304,6 +335,7 @@ local function dirty_cache(url)
err = do_sql(stmnt_dirty_cache)
stmnt_dirty_cache:reset()
end
+]=]
--[[
@@ -356,6 +388,7 @@ local function get_session(req)
return author,authorid
end
+--[=[
--Render a page, with cacheing. If you need to dirty a cache, call dirty_cache()
local function render(pagename,callback)
print("Running render...")
@@ -388,7 +421,9 @@ local function render(pagename,callback)
stmnt_insert_cache:reset()
return text
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]]
local function parse_tags(str)
local tags = {}
@@ -404,6 +439,7 @@ local function parse_tags(str)
end
return tags
end
+]=]
function home(req)
print("Hello from lua!")
@@ -414,7 +450,7 @@ function home(req)
local text
if host == domain then
--Default home page
- text = render(string.format("%s",domain),function()
+ text = cache.render(string.format("%s",domain),function()
print("Cache miss, rendering index")
stmnt_index:bind_names{}
local err = do_sql(stmnt_index)
@@ -442,7 +478,7 @@ function home(req)
else
--Home page for an author
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)
stmnt_author_bio:bind_names{author=subdomain}
local err = do_sql(stmnt_author_bio)
@@ -505,7 +541,7 @@ function claim(req)
local text
if method == "GET" then
--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")
return pages.claim{err=""}
end)
@@ -578,7 +614,7 @@ function paste(req)
return
else
--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")
return pages.paste{
domain = domain,
@@ -686,8 +722,8 @@ function paste(req)
local loc = string.format("https://%s/%s",domain,url)
http_response_header(req,"Location",loc)
http_response(req,303,"")
- dirty_cache(string.format("%s/%s",domain,url))
- dirty_cache(string.format("%s",domain))
+ cache.dirty(string.format("%s/%s",domain,url))
+ cache.dirty(string.format("%s",domain))
return
elseif err == sql.ERROR or err == sql.MISUSE then
ret = "Failed to paste: " .. tostring(err)
@@ -748,9 +784,9 @@ function paste(req)
end
http_response_header(req,"Location",loc)
http_response(req,303,"")
- dirty_cache(string.format("%s.%s",author,domain))
- dirty_cache(string.format("%s/%s",domain,url))
- dirty_cache(string.format("%s",domain))
+ cache.dirty(string.format("%s.%s",author,domain))
+ cache.dirty(string.format("%s/%s",domain,url))
+ cache.dirty(string.format("%s",domain))
return
elseif err == sql.ERROR or err == sql.MISUSE then
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))
stmnt_update_views:reset()
- dirty_cache(cachestr)
+ cache.dirty(cachestr)
print("cachestr was:",cachestr)
local readstoryf = function()
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
--pages when the user is logged in. All non-logged-in users can see the same page no problem.
if not iam then
- return render(cachestr,readstoryf)
+ return cache.render(cachestr,readstoryf)
else
return readstoryf()
end
@@ -841,6 +877,8 @@ function read(req)
local path = http_request_get_path(req)
local method = http_method_text(req)
if method == "GET" then
+ read_get(req)
+ --[=[
local idp = string.sub(path,2)--remove leading "/"
assert(string.len(path) > 0,"Tried to read 0-length story id")
local author, authorid = get_session(req)
@@ -902,7 +940,10 @@ function read(req)
assert(text)
http_response(req,200,text)
return
+ ]=]
elseif method == "POST" then
+ read_post(req)
+ --[=[
--We're posting a comment
http_request_populate_post(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.")
else
--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)
http_response_header(req,"Location",redir)
http_response(req,303,"")
end
+ ]=]
end
end
@@ -949,7 +991,7 @@ function login(req)
local text
if method == "GET" then
--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{
err = "",
}
@@ -1093,9 +1135,9 @@ function edit(req)
end
local id_enc = encode_id(storyid)
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
- 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).
- dirty_cache(string.format("%s.%s",author,domain)) -- The author's index, same reasoning as above.
+ cache.dirty(string.format("%s/%s",domain,id_enc)) -- This place to read this post
+ 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).
+ cache.dirty(string.format("%s.%s",author,domain)) -- The author's index, same reasoning as above.
http_response_header(req,"Location",loc)
http_response(req,303,"")
return
@@ -1112,10 +1154,10 @@ end
function teardown()
print("Exiting...")
if db then
- db:close()
+ db.close()
end
if cache then
- cache:close()
+ cache.close()
end
print("Finished lua teardown")
end
@@ -1127,6 +1169,7 @@ function download(req)
http_request_populate_qs(req)
local story = assert(http_argument_get_string(req,"story"))
local story_id = decode_id(story)
+ print("Downloading", story_id)
stmnt_download:bind_names{
postid = story_id
}
@@ -1137,6 +1180,7 @@ function download(req)
stmnt_download:reset()
return
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 text = zlib.decompress(txt_compressed)
stmnt_download:reset()
@@ -1147,6 +1191,8 @@ function download(req)
end
function preview(req)
+ preview_post(req)
+ --[[
print("We want to preview a paste!")
local host = http_request_get_host(req)
local path = http_request_get_path(req)
@@ -1170,6 +1216,7 @@ function preview(req)
tags = tags,
}
http_response(req,200,ret)
+ ]]
end
function search(req)
diff --git a/src/lua/login_get.lua b/src/lua/login_get.lua
new file mode 100644
index 0000000..4ad9c90
--- /dev/null
+++ b/src/lua/login_get.lua
@@ -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
diff --git a/src/lua/login_post.lua b/src/lua/login_post.lua
new file mode 100644
index 0000000..d8cc902
--- /dev/null
+++ b/src/lua/login_post.lua
@@ -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
diff --git a/src/lua/pages.lua b/src/lua/pages.lua
new file mode 100644
index 0000000..e492cad
--- /dev/null
+++ b/src/lua/pages.lua
@@ -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
diff --git a/src/lua/parsers.lua b/src/lua/parsers.lua
new file mode 100644
index 0000000..1f679c9
--- /dev/null
+++ b/src/lua/parsers.lua
@@ -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
diff --git a/src/lua/paste_get.lua b/src/lua/paste_get.lua
new file mode 100644
index 0000000..440f722
--- /dev/null
+++ b/src/lua/paste_get.lua
@@ -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
diff --git a/src/lua/preview_post.lua b/src/lua/preview_post.lua
new file mode 100644
index 0000000..b7886be
--- /dev/null
+++ b/src/lua/preview_post.lua
@@ -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
diff --git a/src/lua/queries.lua b/src/lua/queries.lua
new file mode 100644
index 0000000..3d4e500
--- /dev/null
+++ b/src/lua/queries.lua
@@ -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
diff --git a/src/lua/read_get.lua b/src/lua/read_get.lua
new file mode 100644
index 0000000..2fbed09
--- /dev/null
+++ b/src/lua/read_get.lua
@@ -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
diff --git a/src/lua/read_post.lua b/src/lua/read_post.lua
new file mode 100644
index 0000000..81907da
--- /dev/null
+++ b/src/lua/read_post.lua
@@ -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
diff --git a/src/lua/render.lua b/src/lua/render.lua
new file mode 100644
index 0000000..139597f
--- /dev/null
+++ b/src/lua/render.lua
@@ -0,0 +1,2 @@
+
+
diff --git a/src/lua/session.lua b/src/lua/session.lua
new file mode 100644
index 0000000..7912d01
--- /dev/null
+++ b/src/lua/session.lua
@@ -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
diff --git a/src/lua/tags.lua b/src/lua/tags.lua
new file mode 100644
index 0000000..9612c66
--- /dev/null
+++ b/src/lua/tags.lua
@@ -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
diff --git a/src/lua/util.lua b/src/lua/util.lua
new file mode 100644
index 0000000..a798141
--- /dev/null
+++ b/src/lua/util.lua
@@ -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
diff --git a/src/pages/author_index.etlua b/src/pages/author_index.etlua
index 8654483..98b8f76 100644
--- a/src/pages/author_index.etlua
+++ b/src/pages/author_index.etlua
@@ -1,51 +1,49 @@
-<% assert(author,"No author specified") %>
-<% assert(bio,"No bio included") %>
-
-
-
-
+
-
+
diff --git a/src/smr.c b/src/smr.c
index 6030602..3e14a5c 100644
--- a/src/smr.c
+++ b/src/smr.c
@@ -12,6 +12,7 @@
#include "libkore.h"
#include "libcrypto.h"
#include
+#include
int home(struct http_request *);
int post_story(struct http_request *);
@@ -38,6 +39,15 @@ lua_State *L;
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
errhandeler(lua_State *L){
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");
int err = lua_pcall(L,1,0,-3);
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));
- http_response(req, 500, NULL, 0);
+ http_response(req, 500, ret, retlen);
lua_pop(L,lua_gettop(L));
return (KORE_RESULT_OK);
}
diff --git a/src/sql/create_table_posts.sql b/src/sql/create_table_posts.sql
index 76cc858..1f6f5dc 100644
--- a/src/sql/create_table_posts.sql
+++ b/src/sql/create_table_posts.sql
@@ -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 (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
post_text BLOB,
diff --git a/src/sql/create_table_raw_text.sql b/src/sql/create_table_raw_text.sql
index a424f9a..5ca8c51 100644
--- a/src/sql/create_table_raw_text.sql
+++ b/src/sql/create_table_raw_text.sql
@@ -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 (
id INTEGER PRIMARY KEY REFERENCES posts(id) ON DELETE CASCADE,
post_text BLOB,
diff --git a/src/sql/create_table_session.sql b/src/sql/create_table_session.sql
index 28e5f11..e4f4c8c 100644
--- a/src/sql/create_table_session.sql
+++ b/src/sql/create_table_session.sql
@@ -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 (
key TEXT PRIMARY KEY,
author REFERENCES authors(id) ON DELETE CASCADE,
diff --git a/src/sql/insert_anon_author.sql b/src/sql/insert_anon_author.sql
index b2ff0e6..0036f83 100644
--- a/src/sql/insert_anon_author.sql
+++ b/src/sql/insert_anon_author.sql
@@ -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 (
id,
name,
diff --git a/src/sql/insert_comment.sql b/src/sql/insert_comment.sql
index 0adf012..3986b31 100644
--- a/src/sql/insert_comment.sql
+++ b/src/sql/insert_comment.sql
@@ -1,3 +1,4 @@
+/* Add a new comment to a story */
INSERT INTO comments(
postid,
author,
diff --git a/src/sql/select_author_index.sql b/src/sql/select_author_index.sql
index ac536f8..319da48 100644
--- a/src/sql/select_author_index.sql
+++ b/src/sql/select_author_index.sql
@@ -1,3 +1,5 @@
+/* Get the data we need to display a particular author's latest stories */
+
SELECT
posts.id,
posts.post_title,
diff --git a/src/sql/select_author_of_post.sql b/src/sql/select_author_of_post.sql
index 8527b90..e875211 100644
--- a/src/sql/select_author_of_post.sql
+++ b/src/sql/select_author_of_post.sql
@@ -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
authors.id,
authors.name
diff --git a/src/sql/select_comments.sql b/src/sql/select_comments.sql
index 05b5419..d147f79 100644
--- a/src/sql/select_comments.sql
+++ b/src/sql/select_comments.sql
@@ -1,3 +1,4 @@
+/* Retreive comments on a story */
SELECT
authors.name,
comments.isanon,
diff --git a/src/sql/select_post.sql b/src/sql/select_post.sql
index c614596..9e36fdc 100644
--- a/src/sql/select_post.sql
+++ b/src/sql/select_post.sql
@@ -1,3 +1,7 @@
+/*
+Select the data we need to read a story (and maybe display an edit button)
+*/
+
SELECT
post_title,
post_text,
diff --git a/src/sql/select_site_index.sql b/src/sql/select_site_index.sql
index 024b692..99d8abc 100644
--- a/src/sql/select_site_index.sql
+++ b/src/sql/select_site_index.sql
@@ -1,3 +1,4 @@
+/* Select the data we need to display the on the front page */
SELECT
posts.id,
posts.post_title,
diff --git a/src/sql/update_views.sql b/src/sql/update_views.sql
index 3af22f7..685f6fe 100644
--- a/src/sql/update_views.sql
+++ b/src/sql/update_views.sql
@@ -1 +1,2 @@
+/* Update the view counter when someone reads a story */
UPDATE posts SET views = views + 1 WHERE id = :id;