Work on ripping out spp
This commit is contained in:
parent
60213086dc
commit
e430d9b512
79
Makefile
79
Makefile
|
@ -16,12 +16,14 @@ kmgr_chroot = $(smr_var)/kore_kmgr
|
||||||
parent_chroot = $(smr_var)/kore_parent
|
parent_chroot = $(smr_var)/kore_parent
|
||||||
conf_path = /etc/smr
|
conf_path = /etc/smr
|
||||||
smr_bin_path = /usr/local/lib
|
smr_bin_path = /usr/local/lib
|
||||||
|
app_root=$(worker_chroot)/var/smr
|
||||||
ifeq ($(DEV),true)
|
ifeq ($(DEV),true)
|
||||||
worker_chroot = ./kore_chroot
|
worker_chroot = ./kore_chroot
|
||||||
kmgr_chroot = ./kore_chroot
|
kmgr_chroot = ./kore_chroot
|
||||||
parent_chroot = ./kore_chroot
|
parent_chroot = ./kore_chroot
|
||||||
conf_path = ./kore_chroot/conf
|
conf_path = ./kore_chroot/conf
|
||||||
smr_bin_path = ./kore_chroot
|
smr_bin_path = ./kore_chroot
|
||||||
|
app_root=./kore_chroot/app
|
||||||
endif
|
endif
|
||||||
|
|
||||||
mirror=http://dl-cdn.alpinelinux.org/alpine/
|
mirror=http://dl-cdn.alpinelinux.org/alpine/
|
||||||
|
@ -35,11 +37,11 @@ domain=test.monster:$(port)
|
||||||
server_cert=/root/cert/server.pem
|
server_cert=/root/cert/server.pem
|
||||||
server_key=/root/cert/key.pem
|
server_key=/root/cert/key.pem
|
||||||
|
|
||||||
SPPFLAGS=-D port=$(port) -D kore_chroot=$(build_dir) -D chuser=$(user) -D domain=$(domain) -D bin_path="$(bin_path)" -D server_cert="$(server_cert)" -D server_key="$(server_key)" -D worker_chroot="$(worker_chroot)" -D kmgr_chroot="$(kmgr_chroot)"
|
SPPFLAGS=-D port=$(port) -D kore_chroot=$(worker_chroot) -D chuser=$(user) -D domain=$(domain) -D bin_path="$(bin_path)" -D server_cert="$(server_cert)" -D server_key="$(server_key)" -D worker_chroot="$(worker_chroot)" -D kmgr_chroot="$(kmgr_chroot)"
|
||||||
# squelch prints, flip to print verbose information
|
# squelch prints, flip to print verbose information
|
||||||
#Q=@
|
#Q=@
|
||||||
Q=
|
Q=
|
||||||
LUAROCKS_FLAGS=--tree $(build_dir)/usr/lib/luarocks --lua-version 5.1
|
LUAROCKS_FLAGS=--tree $(worker_chroot)/usr/lib/luarocks --lua-version 5.1
|
||||||
chroot_packages=\
|
chroot_packages=\
|
||||||
-p luarocks5.1 \
|
-p luarocks5.1 \
|
||||||
-p "build-base" \
|
-p "build-base" \
|
||||||
|
@ -62,21 +64,18 @@ lua_packages = \
|
||||||
zlib
|
zlib
|
||||||
|
|
||||||
# Probably don't change stuff past here if you're just using smr
|
# Probably don't change stuff past here if you're just using smr
|
||||||
lua_in_files=$(shell find src/lua/*.in -type f)
|
lua_files=$(shell find src/lua/*.lua -type f) $(shell find src/lua/endpoints -type f)
|
||||||
lua_files=$(shell find src/lua/*.lua -type f) $(shell find src/lua/endpoints -type f) $(lua_in_files:%.in=%)
|
|
||||||
src_files=$(shell find src -type f) $(shell find conf -type f)
|
src_files=$(shell find src -type f) $(shell find conf -type f)
|
||||||
sql_files=$(shell find src/sql -type f)
|
sql_files=$(shell find src/sql -type f)
|
||||||
test_files=$(shell find spec -type f)
|
test_files=$(shell find spec -type f) $(shell find spec/parser_tests -type f)
|
||||||
built_tests=$(test_files:%=$(build_dir)%)
|
page_files=$(shell find src/pages -type f)
|
||||||
built_files=$(lua_files:src/lua/%.lua=$(build_dir)%.lua)
|
built_tests=$(test_files:%=$(app_root)/%)
|
||||||
in_page_files=$(shell find src/pages/*.in -type f)
|
built_files=$(lua_files:src/lua/%.lua=$(app_root)/%.lua)
|
||||||
page_files=$(in_page_files:%.in=%)
|
|
||||||
part_files=$(shell find src/pages/parts/*.etlua -type f)
|
part_files=$(shell find src/pages/parts/*.etlua -type f)
|
||||||
built_pages=$(page_files:src/pages/%.etlua=$(build_dir)pages/%.etlua)
|
built_parts=$(part_files:src/%=$(app_root)/%)
|
||||||
built_sql=$(sql_files:src/sql/%.sql=$(build_dir)sql/%.sql)
|
built_pages=$(page_files:src/pages/%.etlua=$(app_root)/pages/%.etlua)
|
||||||
|
built_sql=$(sql_files:src/sql/%.sql=$(app_root)/sql/%.sql)
|
||||||
built=$(built_files) $(built_sql) $(built_pages) $(built_tests)
|
built=$(built_files) $(built_sql) $(built_pages) $(built_tests)
|
||||||
asset_in_files=$(wildcard assets/*.in -type f)
|
|
||||||
asset_files=$(asset_in_files:%.in=%)
|
|
||||||
initscript=/lib/systemd/system/smr.service
|
initscript=/lib/systemd/system/smr.service
|
||||||
config=$(conf_path)/smr.conf
|
config=$(conf_path)/smr.conf
|
||||||
built_bin=$(smr_bin_path)/smr.so
|
built_bin=$(smr_bin_path)/smr.so
|
||||||
|
@ -88,7 +87,7 @@ apk_hash := $(APK_$(arch)_HASH)
|
||||||
help: ## Print this help
|
help: ## Print this help
|
||||||
$(Q)$(GREP) -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | $(SORT) | $(AWK) 'BEGIN {FS = ":.*?## "}; {printf "%-10s %s\n", $$1, $$2}'
|
$(Q)$(GREP) -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | $(SORT) | $(AWK) 'BEGIN {FS = ":.*?## "}; {printf "%-10s %s\n", $$1, $$2}'
|
||||||
|
|
||||||
all: $(build_dir) smr.so $(built_files) $(built_pages) $(built_sql) ## Build and run smr in a chroot
|
all: $(app_root) smr.so $(built_files) $(built_pages) $(built_sql) ## Build and run smr in a chroot
|
||||||
|
|
||||||
apk-tools-static-$(version).apk:
|
apk-tools-static-$(version).apk:
|
||||||
wget -q $(mirror)latest-stable/main/$(arch)/apk-tools-static-$(version).apk
|
wget -q $(mirror)latest-stable/main/$(arch)/apk-tools-static-$(version).apk
|
||||||
|
@ -100,7 +99,7 @@ clean: ## clean up all the files generated by this makefile
|
||||||
$(Q)$(RM) $(asset_files)
|
$(Q)$(RM) $(asset_files)
|
||||||
$(Q)$(RM) smr.so
|
$(Q)$(RM) smr.so
|
||||||
|
|
||||||
install: $(worker_chroot) $(kmgr_chroot) $(parent_chroot) $(initscript) $(config) smr.so $(built_files) $(built_pages) $(built_sql) ## Install smr into a new host system
|
install: $(app_root) $(kmgr_chroot) $(parent_chroot) $(initscript) $(config) smr.so $(built_files) $(built_pages) $(built_sql) ## Install smr into a new host system
|
||||||
$(Q)$(COPY) smr.so $(built_bin)
|
$(Q)$(COPY) smr.so $(built_bin)
|
||||||
|
|
||||||
$(config) : conf/smr.conf
|
$(config) : conf/smr.conf
|
||||||
|
@ -111,15 +110,19 @@ $(initscript) : packaging/systemd/smr.service
|
||||||
$(Q)$(COPY) $< $@
|
$(Q)$(COPY) $< $@
|
||||||
|
|
||||||
cloc: ## calculate source lines of code in smr
|
cloc: ## calculate source lines of code in smr
|
||||||
cloc --force-lang="html",etlua.in --force-lang="html",etlua --force-lang="lua",lua.in src assets Makefile
|
cloc --force-lang="html",etlua --force-lang="lua",luasrc assets Makefile
|
||||||
|
|
||||||
$(build_dir):
|
$(app_root):
|
||||||
$(Q)$(MKDIR) $(build_dir)
|
$(Q)$(MKDIR) $(app_root)
|
||||||
$(Q)$(MKDIR) $(build_dir)/pages
|
|
||||||
$(Q)$(MKDIR) $(build_dir)/sql
|
$(app_root): $(worker_chroot)
|
||||||
$(Q)$(MKDIR) $(build_dir)/data
|
$(Q)$(MKDIR) $(app_root)
|
||||||
$(Q)$(MKDIR) $(build_dir)/data/archive
|
$(Q)$(MKDIR) $(app_root)/pages
|
||||||
$(Q)$(MKDIR) $(build_dir)/endpoints
|
$(Q)$(MKDIR) $(app_root)/pages/parts
|
||||||
|
$(Q)$(MKDIR) $(app_root)/sql
|
||||||
|
$(Q)$(MKDIR) $(app_root)/data
|
||||||
|
$(Q)$(MKDIR) $(app_root)/data/archive
|
||||||
|
$(Q)$(MKDIR) $(app_root)/endpoints
|
||||||
|
|
||||||
alpine-chroot-install:
|
alpine-chroot-install:
|
||||||
$(Q)wget https://raw.githubusercontent.com/alpinelinux/alpine-chroot-install/v0.14.0/alpine-chroot-install \
|
$(Q)wget https://raw.githubusercontent.com/alpinelinux/alpine-chroot-install/v0.14.0/alpine-chroot-install \
|
||||||
|
@ -134,43 +137,39 @@ $(worker_chroot) $(kmgr_chroot) $(parent_chroot): alpine-chroot-install
|
||||||
|
|
||||||
code : $(built_files)
|
code : $(built_files)
|
||||||
|
|
||||||
$(built_files): $(build_dir)%.lua : src/lua/%.lua $(build_dir)
|
$(built_files): $(app_root)/%.lua : src/lua/%.lua $(app_root)
|
||||||
$(Q)$(ECHO) "[copy] $@"
|
$(Q)$(ECHO) "[copy] $@"
|
||||||
$(Q)$(COPY) $< $@
|
$(Q)$(COPY) $< $@
|
||||||
|
|
||||||
$(built_pages): $(build_dir)pages/%.etlua : src/pages/%.etlua $(build_dir)
|
$(built_pages): $(app_root)/pages/%.etlua : src/pages/%.etlua $(app_root)
|
||||||
$(Q)$(ECHO) "[copy] $@"
|
$(Q)$(ECHO) "[copy] $@"
|
||||||
$(Q)$(COPY) $< $@
|
$(Q)$(COPY) $< $@
|
||||||
|
|
||||||
src/lua/config.lua : src/lua/config.lua.in Makefile
|
$(built_parts): $(app_root)/% : src/%
|
||||||
$(Q)$(ECHO) "[preprocess] $@"
|
$(Q)$(ECHO) "[copy] $@"
|
||||||
$(Q)$(SPP) $(SPPFLAGS) -o $@ $<
|
$(Q)$(COPY) $< $@
|
||||||
|
|
||||||
$(page_files) : % : %.in $(part_files)
|
$(built_sql): $(app_root)/sql/%.sql : src/sql/%.sql
|
||||||
$(Q)$(ECHO) "[preprocess] $@"
|
|
||||||
$(Q)$(SPP) $(SPPFLAGS) -o $@ $<
|
|
||||||
|
|
||||||
$(built_sql): $(build_dir)sql/%.sql : src/sql/%.sql
|
|
||||||
$(Q)$(ECHO) "[copy] $@"
|
$(Q)$(ECHO) "[copy] $@"
|
||||||
$(Q)$(COPY) $^ $@
|
$(Q)$(COPY) $^ $@
|
||||||
|
|
||||||
$(built_tests) : $(build_dir)% : src/spec/%
|
$(built_tests) : $(app_root)/spec/% : spec/% $(app_root)/spec
|
||||||
$(Q)$(ECHO) "[copy] $@"
|
$(Q)$(ECHO) "[copy] $@"
|
||||||
$(Q)$(COPY) $^ $@
|
$(Q)$(COPY) $< $@
|
||||||
|
|
||||||
$(asset_files) : % : %.in
|
$(app_root)/spec: $(app_root)
|
||||||
$(Q)$(ECHO) "[preprocess] $@"
|
$(Q)$(MKDIR) $@
|
||||||
$(Q)$(SPP) $(SPPFLAGS) -o $@ $<
|
$(Q)$(MKDIR) $@/parser_tests
|
||||||
|
|
||||||
smr.so : $(src_files) conf/build.conf $(asset_files)
|
smr.so : $(src_files) conf/build.conf $(asset_files)
|
||||||
$(Q)$(ECHO) "[build] $@"
|
$(Q)$(ECHO) "[build] $@"
|
||||||
$(Q)$(KODEV) build
|
$(Q)$(KODEV) build
|
||||||
|
|
||||||
test : $(built) ## run the unit tests
|
test : $(built) ## run the unit tests
|
||||||
$(Q)$(CD) kore_chroot && busted -v --no-keep-going #--exclude-tags slow
|
$(Q)$(CD) $(app_root) && busted -v --no-keep-going --exclude-tags "slow,todo,working"
|
||||||
|
|
||||||
cov : $(built) ## code coverage (based on unit tests)
|
cov : $(built) ## code coverage (based on unit tests)
|
||||||
$(Q)$(RM) $(kore_chroot)/luacov.stats.out
|
$(Q)$(RM) $(kore_chroot)/luacov.stats.out
|
||||||
$(Q)$(CD) $(kore_chroot) && busted -v -c --no-keep-going #--exclude-tags slow
|
$(Q)$(CD) $(kore_chroot) && busted -v -c --no-keep-going --exclude-tags slow
|
||||||
$(Q)$(CD) $(kore_chroot) && luacov endpoints/
|
$(Q)$(CD) $(kore_chroot) && luacov endpoints/
|
||||||
$(Q)$(ECHO) "open kore_chroot/luacov.report.out to view coverage results."
|
$(Q)$(ECHO) "open kore_chroot/luacov.report.out to view coverage results."
|
||||||
|
|
|
@ -2,7 +2,7 @@ _G.spy = spy
|
||||||
local mock_env = require("spec.env_mock")
|
local mock_env = require("spec.env_mock")
|
||||||
local rng = require("spec.fuzzgen")
|
local rng = require("spec.fuzzgen")
|
||||||
|
|
||||||
describe("smr biography",function()
|
describe("smr biography #todo",function()
|
||||||
setup(mock_env.setup)
|
setup(mock_env.setup)
|
||||||
teardown(mock_env.teardown)
|
teardown(mock_env.teardown)
|
||||||
it("should allow users to set their biography",function()
|
it("should allow users to set their biography",function()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
|
||||||
_G.spy = spy
|
_G.spy = spy
|
||||||
local mock_env = require("spec.env_mock")
|
local env_mock = require("spec.env_mock")
|
||||||
|
|
||||||
describe("smr cacheing",function()
|
describe("smr cacheing",function()
|
||||||
setup(mock_env.setup)
|
setup(env_mock.setup)
|
||||||
teardown(mock_env.teardown)
|
teardown(env_mock.teardown)
|
||||||
it("caches a page if the page is requested twice #working",function()
|
it("caches a page if the page is requested twice #working",function()
|
||||||
local read_get = require("endpoints.read_get")
|
local read_get = require("endpoints.read_get")
|
||||||
local cache = require("cache")
|
local cache = require("cache")
|
||||||
|
@ -29,9 +29,15 @@ describe("smr cacheing",function()
|
||||||
))
|
))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
it("should expose the database connection", function()
|
||||||
|
local cache = require("cache")
|
||||||
|
configure()
|
||||||
|
assert(cache.cache, "Not exposed under .cache")
|
||||||
|
end)
|
||||||
it("does not cache the page if the user is logged in", function()
|
it("does not cache the page if the user is logged in", function()
|
||||||
local read_get = require("endpoints.read_get")
|
local read_get = require("endpoints.read_get")
|
||||||
local cache = require("cache")
|
local cache = require("cache")
|
||||||
|
env_mock.mockdb()
|
||||||
renderspy = spy.on(cache,"render")
|
renderspy = spy.on(cache,"render")
|
||||||
configure()
|
configure()
|
||||||
for row in cache.cache:rows("SELECT COUNT(*) FROM cache") do
|
for row in cache.cache:rows("SELECT COUNT(*) FROM cache") do
|
||||||
|
@ -40,7 +46,7 @@ describe("smr cacheing",function()
|
||||||
"request have been made."
|
"request have been made."
|
||||||
))
|
))
|
||||||
end
|
end
|
||||||
local req = mock_env.session()
|
local req = env_mock.session()
|
||||||
req.method = "GET"
|
req.method = "GET"
|
||||||
req.path = "/a"
|
req.path = "/a"
|
||||||
req.args = {}
|
req.args = {}
|
||||||
|
|
|
@ -12,6 +12,7 @@ local ntostring
|
||||||
local login_post
|
local login_post
|
||||||
local fuzzy
|
local fuzzy
|
||||||
local claim_post
|
local claim_post
|
||||||
|
local session
|
||||||
print_table= function(...)
|
print_table= function(...)
|
||||||
print("Print called")
|
print("Print called")
|
||||||
local args = {...}
|
local args = {...}
|
||||||
|
@ -69,7 +70,10 @@ local smr_mock_env = {
|
||||||
http_request_get_path = spy.new(function(req) return req.path or "/" end),
|
http_request_get_path = spy.new(function(req) return req.path or "/" end),
|
||||||
http_request_populate_qs = spy.new(function(req) req.qs_populated = true end),
|
http_request_populate_qs = spy.new(function(req) req.qs_populated = true end),
|
||||||
http_request_populate_post = spy.new(function(req) req.post_populated = true end),
|
http_request_populate_post = spy.new(function(req) req.post_populated = true end),
|
||||||
http_populate_multipart_form = spy.new(function(req) req.post_populated = true end),
|
http_populate_multipart_form = spy.new(function(req)
|
||||||
|
req.post_populated = true
|
||||||
|
req.multipart_form_populated = true
|
||||||
|
end),
|
||||||
http_argument_get_string = spy.new(function(req,str)
|
http_argument_get_string = spy.new(function(req,str)
|
||||||
assert(req.args,"requests should have a .args table")
|
assert(req.args,"requests should have a .args table")
|
||||||
assert(
|
assert(
|
||||||
|
@ -83,7 +87,7 @@ local smr_mock_env = {
|
||||||
return req.args[str]
|
return req.args[str]
|
||||||
end),
|
end),
|
||||||
http_file_get = spy.new(function(req,filename)
|
http_file_get = spy.new(function(req,filename)
|
||||||
assert(req.multipart_forum_populated,[[
|
assert(req.multipart_form_populated,[[
|
||||||
http_file_get() can only be called after the approriate
|
http_file_get() can only be called after the approriate
|
||||||
populate method has been called. (http_populate_multipart_form())
|
populate method has been called. (http_populate_multipart_form())
|
||||||
]])
|
]])
|
||||||
|
@ -156,9 +160,9 @@ local string_fmt_override = {
|
||||||
setmetatable(string_fmt_override,{__index = string})
|
setmetatable(string_fmt_override,{__index = string})
|
||||||
local smr_override_env = {
|
local smr_override_env = {
|
||||||
--Detour assert so we don't actually perform any checks
|
--Detour assert so we don't actually perform any checks
|
||||||
assert = spy.new(function(bool,msg,level) return bool end),
|
--assert = spy.new(function(bool,msg,level) return bool end),
|
||||||
--Allow string.format to accept nil as arguments
|
--Allow string.format to accept nil as arguments
|
||||||
string = string_fmt_override
|
--string = string_fmt_override
|
||||||
}
|
}
|
||||||
|
|
||||||
mock.olds = {}
|
mock.olds = {}
|
||||||
|
@ -173,10 +177,12 @@ end
|
||||||
|
|
||||||
function mock.mockdb()
|
function mock.mockdb()
|
||||||
local config = require("config")
|
local config = require("config")
|
||||||
config.db = "data/unittest.db"
|
--config.db = "data/unittest.db"
|
||||||
|
config.db = ":memory:"
|
||||||
assert(os.execute("rm " .. config.db))
|
assert(os.execute("rm " .. config.db))
|
||||||
package.loaded.db = nil
|
package.loaded.db = nil
|
||||||
local db = require("db")
|
local db = require("db")
|
||||||
|
configure()
|
||||||
end
|
end
|
||||||
|
|
||||||
function mock.teardown()
|
function mock.teardown()
|
||||||
|
@ -207,7 +213,7 @@ local session_m = {__index = {
|
||||||
}}
|
}}
|
||||||
|
|
||||||
function mock.session(tbl)
|
function mock.session(tbl)
|
||||||
if post_login == nil then
|
if login_post == nil then
|
||||||
login_post = require("endpoints.login_post")
|
login_post = require("endpoints.login_post")
|
||||||
fuzzy = require("spec.fuzzgen")
|
fuzzy = require("spec.fuzzgen")
|
||||||
claim_post = require("endpoints.claim_post")
|
claim_post = require("endpoints.claim_post")
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
|
||||||
_G.spy = spy
|
_G.spy = spy
|
||||||
local mock_env = require("spec.env_mock")
|
local env_mock = require("spec.env_mock")
|
||||||
local rng = require("spec.fuzzgen")
|
local rng = require("spec.fuzzgen")
|
||||||
|
|
||||||
describe("smr login",function()
|
describe("smr login",function()
|
||||||
setup(mock_env.setup)
|
setup(env_mock.setup)
|
||||||
teardown(mock_env.teardown)
|
teardown(env_mock.teardown)
|
||||||
it("should allow someone to claim an account",function()
|
it("should allow someone to claim an account",function()
|
||||||
mock_env.mockdb()
|
env_mock.mockdb()
|
||||||
local claim_post = require("endpoints.claim_post")
|
local claim_post = require("endpoints.claim_post")
|
||||||
configure()
|
configure()
|
||||||
claim_req = {
|
claim_req = {
|
||||||
|
@ -186,6 +186,7 @@ describe("smr login",function()
|
||||||
text = "post text",
|
text = "post text",
|
||||||
markup = "plain",
|
markup = "plain",
|
||||||
tags = "",
|
tags = "",
|
||||||
|
pasteas = username
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
paste_post(paste_req_post)
|
paste_post(paste_req_post)
|
||||||
|
|
|
@ -10,8 +10,18 @@ borrowed sha3 implementation from https://keccak.team
|
||||||
#include "libcrypto.h"
|
#include "libcrypto.h"
|
||||||
#include "keccak.h"
|
#include "keccak.h"
|
||||||
|
|
||||||
/*
|
/* rst
|
||||||
sha3(data::string)::string
|
@name lua/sha3
|
||||||
|
|
||||||
|
Provides a sha3 implementation.
|
||||||
|
|
||||||
|
::signature
|
||||||
|
sha3(data :: {{lua/string}}) :: {{lua/string}}
|
||||||
|
::params
|
||||||
|
{{lua/string}} data - The data to hash
|
||||||
|
::returns
|
||||||
|
{{lua/string}} - The hash of the input string
|
||||||
|
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
lsha3(lua_State *L){
|
lsha3(lua_State *L){
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
--[[
|
--[[ md
|
||||||
|
@name lua/addon
|
||||||
|
|
||||||
Addon loader - Addons are either:
|
Addon loader - Addons are either:
|
||||||
* A folder with at least two files:
|
* A folder with at least two files:
|
||||||
- meta.lua - contains load order information
|
- meta.lua - contains addon information
|
||||||
- init.lua - entrypoint that gets run to load the addon
|
- init.lua - entrypoint that gets run to load the addon
|
||||||
* A zip file with the same
|
* A zip file with the same
|
||||||
* A sqlite3 database with a table "files" that has at least the columns
|
* A sqlite3 database with a table "files" that has at least the columns
|
||||||
|
@ -15,9 +17,15 @@ each worker), and should return a table with at least the following information
|
||||||
{
|
{
|
||||||
name :: string - A name for the addon (all addons must have unique names)
|
name :: string - A name for the addon (all addons must have unique names)
|
||||||
desc :: string - A description for the addon.
|
desc :: string - A description for the addon.
|
||||||
entry :: table[number -> string] - Describes the load order for this
|
order :: number - When should we run init.lua relative to other addons?
|
||||||
addon. Each addon's meta.lua is run, and sorted to get a load
|
Each addon's meta.lua is run (in any order), addons are sorted
|
||||||
order for entrypoints (the strings are the names files)
|
according to their order, and finally each addon's init.lua is
|
||||||
|
called according to this order.
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.lua may include additional information that can be read and used by other
|
||||||
|
addons. meta.lua is run in a restricted environment with almost no functions
|
||||||
|
available.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local oldconfigure = configure
|
local oldconfigure = configure
|
||||||
|
|
|
@ -17,12 +17,11 @@ local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache
|
||||||
|
|
||||||
local oldconfigure = configure
|
local oldconfigure = configure
|
||||||
function configure(...)
|
function configure(...)
|
||||||
local cache = db.sqlassert(sql.open_memory())
|
ret.cache = db.sqlassert(sql.open_memory())-- Expose db for testing
|
||||||
ret.cache = cache -- Expose db for testing
|
|
||||||
--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
|
--swapping to disk. TODO
|
||||||
assert(cache:exec([[
|
assert(ret.cache:exec([[
|
||||||
CREATE TABLE IF NOT EXISTS cache (
|
CREATE TABLE IF NOT EXISTS cache (
|
||||||
path TEXT PRIMARY KEY,
|
path TEXT PRIMARY KEY,
|
||||||
data BLOB,
|
data BLOB,
|
||||||
|
@ -30,7 +29,7 @@ function configure(...)
|
||||||
dirty INTEGER
|
dirty INTEGER
|
||||||
);
|
);
|
||||||
]]))
|
]]))
|
||||||
stmnt_cache = assert(cache:prepare([[
|
stmnt_cache = assert(ret.cache:prepare([[
|
||||||
SELECT data
|
SELECT data
|
||||||
FROM cache
|
FROM cache
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -38,14 +37,14 @@ function configure(...)
|
||||||
((dirty = 0) OR (strftime('%s','now') - updated) > 20)
|
((dirty = 0) OR (strftime('%s','now') - updated) > 20)
|
||||||
;
|
;
|
||||||
]]))
|
]]))
|
||||||
stmnt_insert_cache = assert(cache:prepare([[
|
stmnt_insert_cache = assert(ret.cache:prepare([[
|
||||||
INSERT OR REPLACE INTO cache (
|
INSERT OR REPLACE INTO cache (
|
||||||
path, data, updated, dirty
|
path, data, updated, dirty
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:path, :data, strftime('%s','now'), 0
|
:path, :data, strftime('%s','now'), 0
|
||||||
);
|
);
|
||||||
]]))
|
]]))
|
||||||
stmnt_dirty_cache = assert(cache:prepare([[
|
stmnt_dirty_cache = assert(ret.cache:prepare([[
|
||||||
UPDATE OR IGNORE cache
|
UPDATE OR IGNORE cache
|
||||||
SET dirty = 1
|
SET dirty = 1
|
||||||
WHERE path = :path;
|
WHERE path = :path;
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
--[[
|
|
||||||
Holds configuration.
|
|
||||||
A one-stop-shop for runtime configuration
|
|
||||||
]]
|
|
||||||
local config = {
|
|
||||||
domain = "<{get domain}>",
|
|
||||||
production = false,
|
|
||||||
legacy_url_cutoff = 144,
|
|
||||||
approot = "<{get approot}>"
|
|
||||||
}
|
|
||||||
config.db = config.approot .. "data/posts.db"
|
|
||||||
|
|
||||||
return config
|
|
143
src/lua/db.lua
143
src/lua/db.lua
|
@ -1,4 +1,5 @@
|
||||||
--[[
|
--[[ md
|
||||||
|
@name lua/db
|
||||||
Does most of the database interaction.
|
Does most of the database interaction.
|
||||||
Notably, holds a connection to the open sqlite3 database in .conn
|
Notably, holds a connection to the open sqlite3 database in .conn
|
||||||
]]
|
]]
|
||||||
|
@ -9,9 +10,34 @@ local config = require("config")
|
||||||
|
|
||||||
local db = {}
|
local db = {}
|
||||||
|
|
||||||
--[[
|
--[[ md
|
||||||
|
@name lua/db/sqlassert
|
||||||
|
|
||||||
Runs an sql query and receives the 3 arguments back, prints a nice error
|
Runs an sql query and receives the 3 arguments back, prints a nice error
|
||||||
message on fail, and returns true on success.
|
message on fail, and returns true on success.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
0. r - {{lsqlite/stmnt}} | {{lua/nil}} - The userdata returned from
|
||||||
|
{{lsqlite/db/prepare}}
|
||||||
|
0. errcode - {{lua/nil}} | {{lua/number}} - If the first argument back from
|
||||||
|
{{lsqlite/db/prepare}} is nil, this second argument is a numeric errorcode,
|
||||||
|
see {{lsqlite/errcodes}}
|
||||||
|
0. err - {{lua/nil}} | {{lua/string}} - The string error returned from
|
||||||
|
{{lsqlite/db/prepare}}. Only non-nil if the first return value was nil. A
|
||||||
|
string message describing what went wrong in the statment. If this argument is
|
||||||
|
also {{lua/nil}}, this function retrives the error mssage from
|
||||||
|
{{lsqlite/db/errmsg}}.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
0. r - {{lua/userdata}} | {{lua/nil}} - The first argument passed in. Used so
|
||||||
|
that error checking and assignment can all be done on a single line.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
> db = require("db")
|
||||||
|
> query = db.sqlassert(db.conn:parepare("SELECT 'Hello, world!'"))
|
||||||
]]
|
]]
|
||||||
function db.sqlassert(r, errcode, err)
|
function db.sqlassert(r, errcode, err)
|
||||||
if not r then
|
if not r then
|
||||||
|
@ -24,8 +50,27 @@ function db.sqlassert(r, errcode, err)
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[ md
|
||||||
Continuously tries to perform an sql statement until it goes through
|
@name lua/db/do_sql
|
||||||
|
|
||||||
|
Continuously tries to perform an sql statement until it goes through. This function may call {{lua/coroutine/yield}}
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
0. stmnt - {{lsqlite/stmnt}} - The userdata returned form {{lsqlite/db/prepare}}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
0. err - {{lua/number}} - The error code returned from running the statement. Will be `lsqlite.OK` on success, see {{lsqlite/errcodes}}
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
> sql = require("lsqlite3")
|
||||||
|
> configure = function(...) end -- Mock smr environment
|
||||||
|
> db = require("db")
|
||||||
|
> configure()
|
||||||
|
> query = db.conn:prepare("SELECT 'Hello, world!';")
|
||||||
|
> assert(db.do_sql(query))
|
||||||
]]
|
]]
|
||||||
function db.do_sql(stmnt)
|
function db.do_sql(stmnt)
|
||||||
if not stmnt then error("No statement",2) end
|
if not stmnt then error("No statement",2) end
|
||||||
|
@ -42,11 +87,33 @@ function db.do_sql(stmnt)
|
||||||
return err
|
return err
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[ md
|
||||||
Provides an iterator that loops over results in an sql statement
|
@name lua/db/sql_rows
|
||||||
or throws an error, then resets the statement after the loop is done.
|
|
||||||
|
Provides an iterator that loops over results in an sql statement or throws an
|
||||||
|
error, then resets the statement after the loop is done.
|
||||||
Returned iterator returns varargs, so the values can be unpacked in-line in the
|
Returned iterator returns varargs, so the values can be unpacked in-line in the
|
||||||
for loop.
|
for loop. This statement is approximately the same as {{sqlite/stmt/rows}}, but
|
||||||
|
may yield when the db connection is busy, and continue execution when the
|
||||||
|
connection is free again.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
0. stmnt - {{lsqlite/stmnt}} - The userdata returned from {{sqlite/db/prepare}}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
0. iterator - {{lua/iterator}} - The iterator function that returns varargs of the returns from the sql statement.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
> db = require("db")
|
||||||
|
> query = db.conn:prepare("SELECT 'Hello, world!';")
|
||||||
|
> for row in db.sql_rows(query) do
|
||||||
|
> print(row)
|
||||||
|
> end
|
||||||
|
Hello, world!
|
||||||
|
|
||||||
]]
|
]]
|
||||||
function db.sql_rows(stmnt)
|
function db.sql_rows(stmnt)
|
||||||
if not stmnt then error("No statement",2) end
|
if not stmnt then error("No statement",2) end
|
||||||
|
@ -75,7 +142,9 @@ function db.sql_rows(stmnt)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Binds an argument to as statement with nice error reporting on failure
|
@name lua/db/sqlbind
|
||||||
|
|
||||||
|
Binds an argument to as statement with nice error reporting on failure. Approximatly the same as {{lsqlite/stmt/bind_names}}, but with better error reporting.
|
||||||
stmnt :: sql.stmnt - the prepared sql statemnet
|
stmnt :: sql.stmnt - the prepared sql statemnet
|
||||||
call :: string - a string "bind" or "bind_blob"
|
call :: string - a string "bind" or "bind_blob"
|
||||||
position :: number - the argument position to bind to
|
position :: number - the argument position to bind to
|
||||||
|
@ -85,7 +154,15 @@ function db.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)
|
||||||
local f = stmnt[call](stmnt,position,data)
|
local f = stmnt[call](stmnt,position,data)
|
||||||
if f ~= sql.OK then
|
if f ~= sql.OK then
|
||||||
error(string.format("Failed call %s(%d,%q): %s", call, position, data, db.conn:errmsg()),2)
|
local errs = string.format(
|
||||||
|
"Failed call %s(%d,%q): %s",
|
||||||
|
call,
|
||||||
|
position,
|
||||||
|
data,
|
||||||
|
db.conn:errmsg()
|
||||||
|
)
|
||||||
|
log(LOG_ERR,errs)
|
||||||
|
error(errs,2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -94,39 +171,25 @@ end
|
||||||
local oldconfigure = configure
|
local oldconfigure = configure
|
||||||
db.conn = db.sqlassert(sql.open(config.db))
|
db.conn = db.sqlassert(sql.open(config.db))
|
||||||
function configure(...)
|
function configure(...)
|
||||||
|
local statements = {
|
||||||
--Create sql tables
|
"create_table_authors",
|
||||||
assert(db.conn:exec(queries.create_table_authors))
|
"insert_anon_author",
|
||||||
--Create a fake "anonymous" user, so we don't run into trouble
|
"create_table_posts",
|
||||||
--so that no one runs into trouble being able to paste under this account.
|
"create_table_raw_text",
|
||||||
assert(db.conn:exec(queries.insert_anon_author))
|
"create_table_images",
|
||||||
--If/when an author deletes their account, all posts
|
"create_table_comments",
|
||||||
--and comments by that author are also deleted (on
|
"create_table_tags",
|
||||||
--delete cascade) this is intentional. This also
|
"create_index_tags",
|
||||||
--means that all comments by other users on a post
|
"create_table_session"
|
||||||
--an author makes will also be deleted.
|
}
|
||||||
--
|
-- ipairs() needed, "create table authors" must be executed before
|
||||||
--Post text uses zlib compression
|
-- "insert anon author"
|
||||||
assert(db.conn:exec(queries.create_table_posts))
|
for _, statement in ipairs(statements) do
|
||||||
--Store the raw text so people can download it later, maybe
|
db.sqlassert(db.conn:exec(queries[statement]))
|
||||||
--we can use it for "download as image" or "download as pdf"
|
end
|
||||||
--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.conn:exec(queries.create_table_session))
|
|
||||||
|
|
||||||
return oldconfigure(...)
|
return oldconfigure(...)
|
||||||
end
|
end
|
||||||
configure()
|
|
||||||
|
|
||||||
function db.close()
|
function db.close()
|
||||||
db.conn:close()
|
db.conn:close()
|
||||||
|
|
|
@ -15,7 +15,6 @@ local stmnt_author_create
|
||||||
--a while, but whatever.
|
--a while, but whatever.
|
||||||
local oldconfigure = configure
|
local oldconfigure = configure
|
||||||
function configure(...)
|
function configure(...)
|
||||||
|
|
||||||
stmnt_author_create = db.sqlassert(db.conn:prepare(queries.insert_author))
|
stmnt_author_create = db.sqlassert(db.conn:prepare(queries.insert_author))
|
||||||
return oldconfigure(...)
|
return oldconfigure(...)
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,7 +35,7 @@ local function get_site_home(req, loggedin)
|
||||||
isanon = tonumber(iar) == 1,
|
isanon = tonumber(iar) == 1,
|
||||||
posted = os.date("%B %d %Y",tonumber(dater)),
|
posted = os.date("%B %d %Y",tonumber(dater)),
|
||||||
author = author,
|
author = author,
|
||||||
tags = libtags.get(idr),
|
taglist = libtags.get(idr),
|
||||||
hits = hits,
|
hits = hits,
|
||||||
ncomments = cmts
|
ncomments = cmts
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ local util = require("util")
|
||||||
local session = require("session")
|
local session = require("session")
|
||||||
local config = require("config")
|
local config = require("config")
|
||||||
local pages = require("pages")
|
local pages = require("pages")
|
||||||
|
local api = require("hooks")
|
||||||
|
|
||||||
local stmnt_author_acct
|
local stmnt_author_acct
|
||||||
|
|
||||||
|
@ -18,47 +19,43 @@ function configure(...)
|
||||||
return oldconfigure(...)
|
return oldconfigure(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local old_authenticate = api.authenticate
|
||||||
|
function api.authenticate(data)
|
||||||
|
stmnt_author_acct:bind_names{name=data.user}
|
||||||
|
local err = db.do_sql(stmnt_author_acct)
|
||||||
|
if err ~= sql.ROW then
|
||||||
|
stmnt_author_acct:reset()
|
||||||
|
log(LOG_NOTICE,string.format("User %q failed to log in",data.user))
|
||||||
|
end
|
||||||
|
local id, salt, passhash = unpack(stmnt_author_acct:get_values())
|
||||||
|
stmnt_author_acct:reset()
|
||||||
|
local hash = sha3(salt .. data.pass)
|
||||||
|
if hash == passhash then
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function login_post(req)
|
local function login_post(req)
|
||||||
--Try to log in
|
--Try to log in
|
||||||
http_populate_multipart_form(req)
|
http_populate_multipart_form(req)
|
||||||
local name = assert(http_argument_get_string(req,"user"))
|
local name = assert(http_argument_get_string(req,"user"))
|
||||||
local pass = assert(http_file_get(req,"pass"))
|
local pass = assert(http_file_get(req,"pass"))
|
||||||
stmnt_author_acct:bind_names{
|
local uid, err = api.authenticate({user=name,pass=pass})
|
||||||
name = name
|
if not uid then
|
||||||
}
|
http_response(req,200,pages.login{err=err})
|
||||||
local text
|
return
|
||||||
local err = db.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 mysession = session.start(id)
|
|
||||||
local domain_no_port = config.domain:match("(.*):.*") or config.domain
|
|
||||||
http_response_header(req,"set-cookie",string.format(
|
|
||||||
[[session=%s; SameSite=Lax; Path=/; Domain=%s; HttpOnly; Secure]],mysession,domain_no_port
|
|
||||||
))
|
|
||||||
local loc = string.format("https://%s.%s",name,config.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
|
end
|
||||||
http_response(req,200,text)
|
local user_session = session.start(uid)
|
||||||
|
local domain_no_port = config.domain:match("(.*):.*") or config.domain
|
||||||
|
local cookie_string = string.format(
|
||||||
|
[[session=%s; SameSite=Lax; Path=/; Domain=%s; HttpOnly; Secure]],
|
||||||
|
user_session,
|
||||||
|
domain_no_port
|
||||||
|
)
|
||||||
|
http_response_header(req,"set-cookie",cookie_string)
|
||||||
|
local loc = string.format("https://%s.%s",name,config.domain)
|
||||||
|
http_response_header(req,"Location",loc)
|
||||||
|
http_response(req,303,"")
|
||||||
end
|
end
|
||||||
|
|
||||||
return login_post
|
return login_post
|
||||||
|
|
|
@ -102,7 +102,7 @@ local function author_paste(req,ps)
|
||||||
text = ps.text
|
text = ps.text
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
local asanon = assert(http_argument_get_string(req,"pasteas"))
|
local asanon = assert(http_argument_get_string(req,"pasteas") or "anonymous")
|
||||||
local textsha3 = sha3(ps.text .. get_random_bytes(32))
|
local textsha3 = sha3(ps.text .. get_random_bytes(32))
|
||||||
--No need to check if the author is posting to the
|
--No need to check if the author is posting to the
|
||||||
--"right" sudomain, just post it to the one they have
|
--"right" sudomain, just post it to the one they have
|
||||||
|
|
|
@ -14,9 +14,9 @@ local stmnt_read, stmnt_update_views, stmnt_comments
|
||||||
|
|
||||||
local oldconfigure = configure
|
local oldconfigure = configure
|
||||||
function configure(...)
|
function configure(...)
|
||||||
stmnt_read = assert(db.conn:prepare(queries.select_post))
|
stmnt_read = db.sqlassert(db.conn:prepare(queries.select_post))
|
||||||
stmnt_update_views = assert(db.conn:prepare(queries.update_views))
|
stmnt_update_views = db.sqlassert(db.conn:prepare(queries.update_views))
|
||||||
stmnt_comments = assert(db.conn:prepare(queries.select_comments))
|
stmnt_comments = db.sqlassert(db.conn:prepare(queries.select_comments))
|
||||||
return oldconfigure(...)
|
return oldconfigure(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,18 @@ local pagenames = {
|
||||||
"parts/story_breif",
|
"parts/story_breif",
|
||||||
"parts/taglist"
|
"parts/taglist"
|
||||||
}
|
}
|
||||||
|
--Functions available to all templates
|
||||||
|
local global_env = {
|
||||||
|
include = function(filename)
|
||||||
|
local fp = assert(io.open(filename,"r"))
|
||||||
|
local data = assert(fp:read("*a"))
|
||||||
|
fp:close()
|
||||||
|
return data
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
local global_env_m = {
|
||||||
|
__index=global_env
|
||||||
|
}
|
||||||
local pages = {}
|
local pages = {}
|
||||||
for k,v in pairs(pagenames) do
|
for k,v in pairs(pagenames) do
|
||||||
local path = string.format(config.approot .. "pages/%s.etlua",v)
|
local path = string.format(config.approot .. "pages/%s.etlua",v)
|
||||||
|
@ -46,6 +58,12 @@ for k,v in pairs(pagenames) do
|
||||||
assert(func, "Failed to load " .. path)
|
assert(func, "Failed to load " .. path)
|
||||||
pages[v] = function(env)
|
pages[v] = function(env)
|
||||||
assert(type(env) == "table","env must be a table")
|
assert(type(env) == "table","env must be a table")
|
||||||
|
-- Add our global metatable functions at the bottom metatable's __index
|
||||||
|
local cursor = env
|
||||||
|
while cursor ~= nil and getmetatable(cursor) and getmetatable(cursor).__index do
|
||||||
|
cursor = getmetatable(cursor)
|
||||||
|
end
|
||||||
|
setmetatable(cursor,global_env_m)
|
||||||
local buff, err = parser:run(func,env)
|
local buff, err = parser:run(func,env)
|
||||||
if not buff then
|
if not buff then
|
||||||
errorf("Failed to render %s : %s", path, err)
|
errorf("Failed to render %s : %s", path, err)
|
||||||
|
|
|
@ -4,12 +4,13 @@ local db = require("db")
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
local queries = require("queries")
|
local queries = require("queries")
|
||||||
|
|
||||||
local oldconfigure = configure
|
|
||||||
local stmnt_get_session, stmnt_insert_session, stmnt_delete_session
|
local stmnt_get_session, stmnt_insert_session, stmnt_delete_session
|
||||||
|
|
||||||
|
local oldconfigure = configure
|
||||||
function configure(...)
|
function configure(...)
|
||||||
stmnt_get_session = assert(db.conn:prepare(queries.select_valid_sessions))
|
stmnt_get_session = db.sqlassert(db.conn:prepare(queries.select_valid_sessions))
|
||||||
stmnt_insert_session = assert(db.conn:prepare(queries.insert_session))
|
stmnt_insert_session = db.sqlassert(db.conn:prepare(queries.insert_session))
|
||||||
stmnt_delete_session = assert(db.conn:prepare(queries.delete_session))
|
stmnt_delete_session = db.sqlassert(db.conn:prepare(queries.delete_session))
|
||||||
return oldconfigure(...)
|
return oldconfigure(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ function session.start(who)
|
||||||
}
|
}
|
||||||
local err = db.do_sql(stmnt_insert_session)
|
local err = db.do_sql(stmnt_insert_session)
|
||||||
stmnt_insert_session:reset()
|
stmnt_insert_session:reset()
|
||||||
assert(err == sql.DONE)
|
assert(err == sql.DONE, "Error should have been 'DONE', was: " .. tostring(err))
|
||||||
return session
|
return session
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
--[[ md
|
||||||
|
@name lua/tags
|
||||||
|
|
||||||
|
Helper methods for cleaning story tags.
|
||||||
|
Tags are the main way to search smr, a simple `+<tag>` or `-<tag>` will show all
|
||||||
|
stories that include (+) or do not include (-) a particular tag.
|
||||||
|
|
||||||
|
Tags are stored in the {{table_tags}} and are deleted if the story they are
|
||||||
|
attached to is deleted. If an author is deleted, all their stories are deleted,
|
||||||
|
and this will cascade to deleting tags on their stories too.
|
||||||
|
]]
|
||||||
local sql = require("lsqlite3")
|
local sql = require("lsqlite3")
|
||||||
|
|
||||||
local db = require("db")
|
local db = require("db")
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
--[[ md
|
||||||
|
@name lua/util
|
||||||
|
Various utilities that aren't big enough for their own module, but are still
|
||||||
|
used in more than one place.
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
local sql = require("lsqlite3")
|
local sql = require("lsqlite3")
|
||||||
local config = require("config")
|
local config = require("config")
|
||||||
|
@ -15,8 +21,26 @@ function configure(...)
|
||||||
return oldconfigure(...)
|
return oldconfigure(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
--see https://perishablepress.com/stop-using-unsafe-characters-in-urls/
|
--[[ md
|
||||||
--no underscore because we use that for our operative pages
|
@name doc/url_spec
|
||||||
|
|
||||||
|
URLs generated from smr use letters and numbers to encode a monotonically
|
||||||
|
increasing post id into a url that can easily be shared (and ends up
|
||||||
|
considerably shorter). The characters used in url generation are:
|
||||||
|
[a-z][A-Z][0-9], and numbers are encoded to use the second available 1-character
|
||||||
|
permuation, then the first available 2-character permutation, and so on.
|
||||||
|
|
||||||
|
For example, the first post is encoded as 'b', the second as 'c', the thrid
|
||||||
|
as 'd', and so on. The off-by-one nature is to simplify implementation of
|
||||||
|
2-character and 3-character combinations with Lua's 1-indexed arrays.
|
||||||
|
|
||||||
|
see https://perishablepress.com/stop-using-unsafe-characters-in-urls/
|
||||||
|
no underscore because we use that for our operative pages
|
||||||
|
|
||||||
|
A set of legacy characters that are no longer in use (because they were invalid
|
||||||
|
to use in URL's) is also defined, but unused as long as
|
||||||
|
{{config/legacy_url_cutoff}} is set to 0.
|
||||||
|
]]
|
||||||
local url_characters =
|
local url_characters =
|
||||||
[[abcdefghijklmnopqrstuvwxyz]]..
|
[[abcdefghijklmnopqrstuvwxyz]]..
|
||||||
[[ABCDEFGHIJKLMNOPQRSTUVWXYZ]]..
|
[[ABCDEFGHIJKLMNOPQRSTUVWXYZ]]..
|
||||||
|
@ -34,8 +58,11 @@ local url_characters_rev_legacy = {}
|
||||||
for i = 1,string.len(url_characters_legacy) do
|
for i = 1,string.len(url_characters_legacy) do
|
||||||
url_characters_rev_legacy[string.sub(url_characters_legacy,i,i)] = i
|
url_characters_rev_legacy[string.sub(url_characters_legacy,i,i)] = i
|
||||||
end
|
end
|
||||||
--[[
|
|
||||||
Encode a number to a shorter HTML-safe url path
|
--[[ md
|
||||||
|
@name lua/util/encode_id
|
||||||
|
Encode a number to a shorter HTML-safe url path. Url paths are generated
|
||||||
|
according to the {{doc/url_spec}
|
||||||
]]
|
]]
|
||||||
function util.encode_id(number)
|
function util.encode_id(number)
|
||||||
local result = {}
|
local result = {}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<% assert(author,"No author specified") %>
|
|
||||||
<% assert(bio,"No bio included") %>
|
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
<a href="https://<%= author %>.<%= domain %>"><%= author %></a>.<a href="https://<%= domain %>"><%= domain %></a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<form action="https://<%= author %>.<%= domain %>/" method="post" class="container">
|
|
||||||
<textarea name="" cols=80 rows=24 class="column">
|
|
||||||
<%= bio %>
|
|
||||||
</textarea><br/>
|
|
||||||
<input type="submit">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<% if #stories == 0 then %>
|
|
||||||
This author has not made any pastes yet.
|
|
||||||
<% else %>
|
|
||||||
<table>
|
|
||||||
<% for k,story in pairs(stories) do %>
|
|
||||||
<{system cat src/pages/parts/story_breif.etlua}>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<% assert(author,"No author specified") %>
|
|
||||||
<% assert(bio,"No bio included") %>
|
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
<a href="https://<%= author %>.<%= domain %>"><%= author %></a>.<a href="https://<%= domain %>"><%= domain %></a>
|
|
||||||
</h1>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<a href="/_paste" class="button column column-0">New paste</a>
|
|
||||||
<% if not loggedin then %>
|
|
||||||
<a href="/_login" class="button column column-0">Log in</a>
|
|
||||||
<% else %>
|
|
||||||
<a href="/_logout" class="button column column-0">Log out</a>
|
|
||||||
<a href="/_bio" class="button column column-0">Edit bio</a>
|
|
||||||
<% end %>
|
|
||||||
<span class="column column-0"></span>
|
|
||||||
<{system cat src/pages/parts/search.etlua}>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% if bio ~= "" then %>
|
|
||||||
<div class="container">
|
|
||||||
<blockquote class="biography">
|
|
||||||
<%- bio %>
|
|
||||||
</blockquote>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="content">
|
|
||||||
<% if #stories == 0 then %>
|
|
||||||
This author has not made any pastes yet.
|
|
||||||
<% else %>
|
|
||||||
<table>
|
|
||||||
<% for k,story in pairs(stories) do %>
|
|
||||||
<{system cat src/pages/parts/story_breif.etlua}>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
Paste
|
|
||||||
</h1>
|
|
||||||
<% if err then %><em class="error"><%= err %></em><% end %>
|
|
||||||
<form action="https://<%= user %>.<%= domain %>/_paste" method="post" class="container">
|
|
||||||
<fieldset>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" name="title" placeholder="Title" class="column column-70"></input>
|
|
||||||
<select id="pasteas" name="pasteas" class="column column-10">
|
|
||||||
<option value="<%= user %>"><%= user %></option>
|
|
||||||
<option value="anonymous">Anonymous</option>
|
|
||||||
</select>
|
|
||||||
<select id="markup" name="markup" class="column column-10">
|
|
||||||
<option value="plain">Plain</option>
|
|
||||||
<option value="imageboard">Imageboard</option>
|
|
||||||
</select>
|
|
||||||
<div class="column column-10">
|
|
||||||
<label for="unlisted" class="label-inline">Unlisted</label>
|
|
||||||
<input type="checkbox" name="unlisted" id="unlisted"></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" name="tags" placeholder="Tags (semicolon;seperated)" class="column"></input>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<textarea name="text" cols=80 rows=24 class="column"><%= text %></textarea><br/>
|
|
||||||
</div>
|
|
||||||
<input type="submit">
|
|
||||||
<input type="submit" formtarget="_blank" value="Preview" formaction="https://<%= domain %>/_preview">
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
🙁
|
|
||||||
</h1>
|
|
||||||
<div class="container">
|
|
||||||
<p>
|
|
||||||
You don't have permission to edit: <%= path %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
Register
|
|
||||||
</h1>
|
|
||||||
Once you press submit, you will be prompted to download a file.<br/>
|
|
||||||
slash.monster uses this file in place of a password, keep it safe.<br/>
|
|
||||||
Consider keeping a copy on a USB in case your hard drive fails.<br/>
|
|
||||||
The admin cannot recover your passfile, and will not reset accounts.<br/>
|
|
||||||
<b>Names may be up to 30 characters, alphanumeric, no symbols, all lower case.</b><br/>
|
|
||||||
<% if err then %><em class="error"><%= err %></em><% end %>
|
|
||||||
<form action="/_claim" method="post">
|
|
||||||
<fieldset>
|
|
||||||
<label for="user">Name:</label>
|
|
||||||
<input type="text" name="user" id="user" placeholder="name">
|
|
||||||
<input type="submit">
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
Once you have your file, you can <a href="/_login">log in</a>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
Paste
|
|
||||||
</h1>
|
|
||||||
<% if err then %><em class="error"><%= err %></em><% end %>
|
|
||||||
<form action="https://<%= user %>.<%= domain %>/_edit" method="post" class="container">
|
|
||||||
<fieldset>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" name="title" placeholder="Title" class="column column-60" value="<%= title %>"></input>
|
|
||||||
<input type="hidden" name="story" value="<%= story %>">
|
|
||||||
<select id="pasteas" name="pasteas" class="column column-10">
|
|
||||||
<% if isanon then %>
|
|
||||||
<option value="<%= user %>"><%= user %></option>
|
|
||||||
<option value="anonymous" selected>Anonymous</option>
|
|
||||||
<% else %>
|
|
||||||
<option value="<%= user %>" selected><%= user %></option>
|
|
||||||
<option value="anonymous">Anonymous</option>
|
|
||||||
<% end %>
|
|
||||||
</select>
|
|
||||||
<select id="markup" name="markup" class="column column-10">
|
|
||||||
<option value="plain">Plain</option>
|
|
||||||
<option value="imageboard">Imageboard</option>
|
|
||||||
</select>
|
|
||||||
<div class="column column-20">
|
|
||||||
<label for="unlisted" class="label-inline">Unlisted</label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="unlisted"
|
|
||||||
id="unlisted"
|
|
||||||
<% if unlisted then %>
|
|
||||||
checked
|
|
||||||
<% end %>
|
|
||||||
>
|
|
||||||
</input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" name="tags" value="<%= tags %>" placeholder="Tags (semicolon;seperated)" class="column"></input>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<textarea name="text" cols=80 rows=24 class="column"><%= text %></textarea><br/>
|
|
||||||
</div>
|
|
||||||
<input type="submit">
|
|
||||||
<input type="submit" formtarget="_blank" value="Preview" formaction="https://<%= domain %>/_preview">
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
Edit Biography for <%= user %>
|
|
||||||
</h1>
|
|
||||||
<% if err then %><em class="error"><%= err %></em><% end %>
|
|
||||||
<form action="https://<%= user %>.<%= domain %>/_bio" method="post" class="container">
|
|
||||||
<fieldset>
|
|
||||||
<input type="hidden" name="author" value="<%= user %>">
|
|
||||||
<div class="row">
|
|
||||||
<textarea name="text" cols=80 rows=24 class="column"><%= text %></textarea><br/>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit">
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
<% if errcode then -%><%= errcode -%><% end -%>:
|
|
||||||
<% if errcodemsg then -%><%= errcodemsg -%><% end %>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<% if explanation then -%><%= explanation -%><% end -%>
|
|
||||||
|
|
||||||
<% if should_traceback then %>
|
|
||||||
<code><pre>
|
|
||||||
<%- debug.traceback() %>
|
|
||||||
</pre></code>
|
|
||||||
<% end %>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
|
@ -1,36 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title column">
|
|
||||||
<a href="https://<%= domain %>">
|
|
||||||
<%= domain %>
|
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<a href="/_paste" class="button column column-0">New paste</a>
|
|
||||||
<% if not loggedin then %>
|
|
||||||
<a href="/_login" class="button column column-0">Log in</a>
|
|
||||||
<a href="/_claim" class="button column column-0">Register</a>
|
|
||||||
<% else %>
|
|
||||||
<a href="/_logout" class="button column column-0">Log out</a>
|
|
||||||
<span class="column column-0"></span>
|
|
||||||
<% end %>
|
|
||||||
<{system cat src/pages/parts/search.etlua}>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<{ system cat src/pages/parts/motd.etlua }>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<% if #stories == 0 then %>
|
|
||||||
No stories available.
|
|
||||||
<% else %>
|
|
||||||
<table id="story_list">
|
|
||||||
<% for k,story in pairs(stories) do %>
|
|
||||||
<{system cat src/pages/parts/story_breif.etlua}>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
Login
|
|
||||||
</h1>
|
|
||||||
<% if err then %><em class="error"><%= err %></em><% end %>
|
|
||||||
<form action="/_login" method="post" enctype="multipart/form-data">
|
|
||||||
<fieldset>
|
|
||||||
<label for="user">Name:</label>
|
|
||||||
<input type="text" name="user" id="user" placeholder="name" autocorrect="off" autocapitalize="none">
|
|
||||||
<label for="pass">Passfile:</label>
|
|
||||||
<input type="file" name="pass" id="pass">
|
|
||||||
<input type="submit" value="Log In"/>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
🙁
|
|
||||||
</h1>
|
|
||||||
<div class="container">
|
|
||||||
<p>
|
|
||||||
No author found: <%= author %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
🙁
|
|
||||||
</h1>
|
|
||||||
<div class="container">
|
|
||||||
<p>
|
|
||||||
No story found: <%= path %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
|
@ -1,27 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
Paste
|
|
||||||
</h1>
|
|
||||||
<% if err then %><em class="error"><%= err %></em><% end %>
|
|
||||||
<form action="https://<%= domain %>/_paste" method="post" class="container"><fieldset>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" name="title" placeholder="Title" class="column column-60"></input>
|
|
||||||
<select id="markup" name="markup" class="column column-20">
|
|
||||||
<option value="plain">Plain</option>
|
|
||||||
<option value="imageboard">Imageboard</option>
|
|
||||||
</select>
|
|
||||||
<div class="column column-20">
|
|
||||||
<label for="unlisted" class="label-inline">Unlisted</label>
|
|
||||||
<input type="checkbox" name="unlisted" id="unlisted"></input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" name="tags" placeholder="Tags (semicolon;seperated)" class="column"></input>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<textarea name="text" cols=80 rows=24 class="column"></textarea><br/>
|
|
||||||
</div>
|
|
||||||
<input type="submit">
|
|
||||||
<input type="submit" formtarget="_blank" value="Preview" formaction="https://<%= domain %>/_preview">
|
|
||||||
</fieldset></form>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
|
@ -1,84 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<% local api = require("hooks") %>
|
|
||||||
<nav>
|
|
||||||
<a href="https://<%= domain %>"><%= domain %></a>/<a href="https://<%= domain %>/<%= short %>"><%= short %></a>
|
|
||||||
</nav>
|
|
||||||
<% if owner then -%>
|
|
||||||
<div class="row">
|
|
||||||
<% for _, spec in ipairs(api.get.page_owner(getfenv(1))) do %>
|
|
||||||
<form action="<%= spec.endpoint %>" method="<%= spec.method %>"><fieldset>
|
|
||||||
<% for key, value in pairs(spec.fields) do %>
|
|
||||||
<input type="hidden" name="<%= key %>" value="<%= value %>"/>
|
|
||||||
<% end %>
|
|
||||||
<input type="submit" value="<%= spec.text %>" class="button column column-0"/>
|
|
||||||
</fieldset></form>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end -%>
|
|
||||||
<article>
|
|
||||||
<h2 class="title"> <%- title %> </h2>
|
|
||||||
<h3>
|
|
||||||
<% if isanon or author == nil then -%>
|
|
||||||
By Anonymous
|
|
||||||
<% else -%>
|
|
||||||
By <a href="https://<%= author %>.<%= domain %>"><%= author %></a>
|
|
||||||
<% end -%>
|
|
||||||
</h3>
|
|
||||||
<ul class="tag-list">
|
|
||||||
<% for _,tag in pairs(tags) do -%>
|
|
||||||
<{system cat src/pages/parts/taglist.etlua}>
|
|
||||||
<% end -%>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<%- text %>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<p><%= views %> Hits, <%= #comments %> Comments</p>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<% for _, spec in ipairs(api.get.page_reader(getfenv(1))) do %>
|
|
||||||
<form action="<%= spec.endpoint %>" method="<%= spec.method %>"><fieldset>
|
|
||||||
<% for key, value in pairs(spec.fields) do %>
|
|
||||||
<input type="hidden" name="<%= key %>" value="<%= value %>"/>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<input type="submit" value="<%= spec.text %>" class="button">
|
|
||||||
</fieldset></form>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form action="https://<%= domain %>/<%= short %>" method="POST">
|
|
||||||
<% if unlisted then %>
|
|
||||||
<input type="hidden" name="pwd" value="<%= hashstr %>"/>
|
|
||||||
<% end %>
|
|
||||||
<textarea name="text" cols=60 rows=10 class="column"></textarea>
|
|
||||||
</div><% if iam then %>
|
|
||||||
<select id="postas" name="postas">
|
|
||||||
<option value="Anonymous">Anonymous</option>
|
|
||||||
<option value="<%= iam %>"><%= iam %></option>
|
|
||||||
</select>
|
|
||||||
<input type="submit" value="post" class="button">
|
|
||||||
<% else %>
|
|
||||||
<input type="hidden" name="postas" value="Anonymous">
|
|
||||||
<input type="submit" value="post" class="button">
|
|
||||||
<% end %>
|
|
||||||
</form>
|
|
||||||
<% if comments and #comments == 0 then %>
|
|
||||||
<p><i>No comments yet</i></p>
|
|
||||||
<% else %>
|
|
||||||
<section>
|
|
||||||
<% for _,comment in pairs(comments) do %>
|
|
||||||
<article>
|
|
||||||
<% if comment.isanon then %>
|
|
||||||
<p><b>Anonymous</b></p>
|
|
||||||
<% else %>
|
|
||||||
<p><b><%= comment.author %></b></p>
|
|
||||||
<% end %>
|
|
||||||
<p><%= comment.text %></p>
|
|
||||||
</article>
|
|
||||||
<% end %>
|
|
||||||
</section>
|
|
||||||
<% end %>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<{system cat src/pages/parts/header.etlua}>
|
|
||||||
<h1 class="title">
|
|
||||||
<a href="https://<%= domain %>"><%= domain %></a>/
|
|
||||||
</h1>
|
|
||||||
<div class="row">
|
|
||||||
<{system cat src/pages/parts/search.etlua}>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<% if #results == 0 then %>
|
|
||||||
No stories matched your search.
|
|
||||||
<% else %>
|
|
||||||
<table>
|
|
||||||
<% for k,story in pairs(results) do %>
|
|
||||||
<{system cat src/pages/parts/story_breif.etlua}>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<{system cat src/pages/parts/footer.etlua}>
|
|
|
@ -1,56 +0,0 @@
|
||||||
SELECT
|
|
||||||
posts.id,
|
|
||||||
posts.post_title,
|
|
||||||
posts.isanon,
|
|
||||||
authors.name,
|
|
||||||
posts.post_time,
|
|
||||||
posts.views,
|
|
||||||
COUNT(comments.id)
|
|
||||||
FROM
|
|
||||||
posts,authors
|
|
||||||
LEFT JOIN comments ON comments.postid = posts.id
|
|
||||||
WHERE
|
|
||||||
authors.id = posts.authorid
|
|
||||||
AND posts.unlisted = 0
|
|
||||||
<% for field, values in pairs(result) do -%>
|
|
||||||
<% for _,value in pairs(values) do -%>
|
|
||||||
<% local pn,expr,value = unpack(value) -%>
|
|
||||||
<% local n = (pn == "+" and "" or "NOT") -%>
|
|
||||||
<% if field == "title" then -%>
|
|
||||||
AND <%= n %> posts.post_title LIKE ?
|
|
||||||
<% elseif field == "author" then -%>
|
|
||||||
AND <%= n %> authors.name LIKE ?
|
|
||||||
<% elseif field == "date" then -%>
|
|
||||||
AND <%= n %> posts.post_time <%- expr %> ?
|
|
||||||
<% elseif field == "hits" then -%>
|
|
||||||
AND posts.views <%- expr -%> ?
|
|
||||||
<% end -%>
|
|
||||||
<% end -%>
|
|
||||||
<% end -%>
|
|
||||||
GROUP BY
|
|
||||||
posts.id
|
|
||||||
<% for _,tag in pairs(result.tags) do -%>
|
|
||||||
INTERSECT
|
|
||||||
SELECT
|
|
||||||
posts.id,
|
|
||||||
posts.post_title,
|
|
||||||
posts.isanon,
|
|
||||||
authors.name,
|
|
||||||
posts.post_time,
|
|
||||||
posts.views,
|
|
||||||
COUNT(comments.id)
|
|
||||||
FROM
|
|
||||||
posts,authors,tags
|
|
||||||
LEFT JOIN comments ON comments.postid = posts.id
|
|
||||||
WHERE
|
|
||||||
posts.authorid = authors.id
|
|
||||||
AND tags.postid = posts.id
|
|
||||||
<% local n,v,t = unpack(tag) -%>
|
|
||||||
<% n = (n == "-" and "NOT" or "") -%>
|
|
||||||
AND <%= n %> tags.tag = ?
|
|
||||||
GROUP BY
|
|
||||||
posts.id
|
|
||||||
<% end -%>
|
|
||||||
ORDER BY
|
|
||||||
posts.post_time DESC
|
|
||||||
;
|
|
21
src/smr.c
21
src/smr.c
|
@ -33,7 +33,9 @@ int delete(struct http_request *);
|
||||||
int do_lua(struct http_request *req, const char *name);
|
int do_lua(struct http_request *req, const char *name);
|
||||||
int errhandeler(lua_State *);
|
int errhandeler(lua_State *);
|
||||||
lua_State *L;
|
lua_State *L;
|
||||||
/* These should be defined in in kore somewhere and included here */
|
/*
|
||||||
|
These should be defined in in kore somewhere and included here
|
||||||
|
*/
|
||||||
void kore_worker_configure(void);
|
void kore_worker_configure(void);
|
||||||
void kore_worker_teardown(void);
|
void kore_worker_teardown(void);
|
||||||
/*
|
/*
|
||||||
|
@ -111,7 +113,8 @@ do_lua(struct http_request *req, const char *name){
|
||||||
return do_lua(req,#lua_method);\
|
return do_lua(req,#lua_method);\
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/* md
|
||||||
|
@name http/_paste
|
||||||
Called at the endpoint <domain>/_paste.
|
Called at the endpoint <domain>/_paste.
|
||||||
This method doesn't need any parameters for GET requests.
|
This method doesn't need any parameters for GET requests.
|
||||||
This method expects the following for POST requests:
|
This method expects the following for POST requests:
|
||||||
|
@ -121,10 +124,14 @@ This method expects the following for POST requests:
|
||||||
In addition to the normal assets, this page includes
|
In addition to the normal assets, this page includes
|
||||||
suggest_tags.js, which suggests tags that have been
|
suggest_tags.js, which suggests tags that have been
|
||||||
submitted to the site before.
|
submitted to the site before.
|
||||||
@function _G.paste
|
|
||||||
@custom http_method GET POST
|
@custom http_method GET POST
|
||||||
|
*/
|
||||||
|
/* md
|
||||||
|
@name lua/paste
|
||||||
|
This function is called automatically with the request submitted at
|
||||||
|
<endpoint>/_paste
|
||||||
@param http_request req The request to service
|
@param http_request req The request to service
|
||||||
***/
|
*/
|
||||||
route(post_story,"paste");
|
route(post_story,"paste");
|
||||||
|
|
||||||
/***
|
/***
|
||||||
|
@ -180,10 +187,14 @@ route(read_story, "read");
|
||||||
|
|
||||||
/***
|
/***
|
||||||
Called at the endpoint <domain>/_login
|
Called at the endpoint <domain>/_login
|
||||||
This method does not requirei any parameters for GET requests.
|
This method does not require any parameters for GET requests.
|
||||||
This method requiries the following for POST requests:
|
This method requiries the following for POST requests:
|
||||||
* user :: [a-z0-9]{1,30} - The username to log in as
|
* user :: [a-z0-9]{1,30} - The username to log in as
|
||||||
* pass :: any - The passfile for this user
|
* pass :: any - The passfile for this user
|
||||||
|
To overload login functionality in an addon, see @{api.authenticate}
|
||||||
|
@function _G.login
|
||||||
|
@custom http_method GET POST
|
||||||
|
@param http_request req The request to service.
|
||||||
***/
|
***/
|
||||||
int
|
int
|
||||||
login(struct http_request *req){
|
login(struct http_request *req){
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
/*
|
||||||
|
The tags table is indexed on tag, so that search is fast
|
||||||
|
*/
|
||||||
CREATE INDEX tag_index ON tags(tag);
|
CREATE INDEX tag_index ON tags(tag);
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
/* md
|
||||||
|
@name sql/table/authors
|
||||||
|
If/when an author deletes 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.
|
||||||
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS authors (
|
CREATE TABLE IF NOT EXISTS authors (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
name TEXT UNIQUE ON CONFLICT FAIL,
|
name TEXT UNIQUE ON CONFLICT FAIL,
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
Comments on a post.
|
||||||
|
|
||||||
|
When an author deletes their account or the posts this comment
|
||||||
|
is posted on is deleted, this comment will also be deleted.
|
||||||
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS comments (
|
CREATE TABLE IF NOT EXISTS comments (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
postid REFERENCES posts(id) ON DELETE CASCADE,
|
postid REFERENCES posts(id) ON DELETE CASCADE,
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/*
|
||||||
|
We may want to store images one day. This is unused for now
|
||||||
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS images (
|
CREATE TABLE IF NOT EXISTS images (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
/*
|
/*
|
||||||
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, edit their biographies, and post stories and comment under their own name.
|
||||||
|
TODO: WE can hash the "key" so that even if the database gets
|
||||||
|
dumped, a hacker can't cookie-steal with only read access
|
||||||
|
to the db.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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 @@
|
||||||
|
/*
|
||||||
|
Tags on a post
|
||||||
|
A post's tags are deleted if the post is deleted.
|
||||||
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS tags (
|
CREATE TABLE IF NOT EXISTS tags (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
postid REFERENCES posts(id) ON DELETE CASCADE,
|
postid REFERENCES posts(id) ON DELETE CASCADE,
|
||||||
|
|
Loading…
Reference in New Issue