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
|
||||
conf_path = /etc/smr
|
||||
smr_bin_path = /usr/local/lib
|
||||
app_root=$(worker_chroot)/var/smr
|
||||
ifeq ($(DEV),true)
|
||||
worker_chroot = ./kore_chroot
|
||||
kmgr_chroot = ./kore_chroot
|
||||
parent_chroot = ./kore_chroot
|
||||
conf_path = ./kore_chroot/conf
|
||||
smr_bin_path = ./kore_chroot
|
||||
app_root=./kore_chroot/app
|
||||
endif
|
||||
|
||||
mirror=http://dl-cdn.alpinelinux.org/alpine/
|
||||
|
@ -35,11 +37,11 @@ domain=test.monster:$(port)
|
|||
server_cert=/root/cert/server.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
|
||||
#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=\
|
||||
-p luarocks5.1 \
|
||||
-p "build-base" \
|
||||
|
@ -62,21 +64,18 @@ lua_packages = \
|
|||
zlib
|
||||
|
||||
# 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_in_files:%.in=%)
|
||||
lua_files=$(shell find src/lua/*.lua -type f) $(shell find src/lua/endpoints -type f)
|
||||
src_files=$(shell find src -type f) $(shell find conf -type f)
|
||||
sql_files=$(shell find src/sql -type f)
|
||||
test_files=$(shell find spec -type f)
|
||||
built_tests=$(test_files:%=$(build_dir)%)
|
||||
built_files=$(lua_files:src/lua/%.lua=$(build_dir)%.lua)
|
||||
in_page_files=$(shell find src/pages/*.in -type f)
|
||||
page_files=$(in_page_files:%.in=%)
|
||||
test_files=$(shell find spec -type f) $(shell find spec/parser_tests -type f)
|
||||
page_files=$(shell find src/pages -type f)
|
||||
built_tests=$(test_files:%=$(app_root)/%)
|
||||
built_files=$(lua_files:src/lua/%.lua=$(app_root)/%.lua)
|
||||
part_files=$(shell find src/pages/parts/*.etlua -type f)
|
||||
built_pages=$(page_files:src/pages/%.etlua=$(build_dir)pages/%.etlua)
|
||||
built_sql=$(sql_files:src/sql/%.sql=$(build_dir)sql/%.sql)
|
||||
built_parts=$(part_files:src/%=$(app_root)/%)
|
||||
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)
|
||||
asset_in_files=$(wildcard assets/*.in -type f)
|
||||
asset_files=$(asset_in_files:%.in=%)
|
||||
initscript=/lib/systemd/system/smr.service
|
||||
config=$(conf_path)/smr.conf
|
||||
built_bin=$(smr_bin_path)/smr.so
|
||||
|
@ -88,7 +87,7 @@ apk_hash := $(APK_$(arch)_HASH)
|
|||
help: ## Print this help
|
||||
$(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:
|
||||
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) 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)
|
||||
|
||||
$(config) : conf/smr.conf
|
||||
|
@ -111,15 +110,19 @@ $(initscript) : packaging/systemd/smr.service
|
|||
$(Q)$(COPY) $< $@
|
||||
|
||||
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):
|
||||
$(Q)$(MKDIR) $(build_dir)
|
||||
$(Q)$(MKDIR) $(build_dir)/pages
|
||||
$(Q)$(MKDIR) $(build_dir)/sql
|
||||
$(Q)$(MKDIR) $(build_dir)/data
|
||||
$(Q)$(MKDIR) $(build_dir)/data/archive
|
||||
$(Q)$(MKDIR) $(build_dir)/endpoints
|
||||
$(app_root):
|
||||
$(Q)$(MKDIR) $(app_root)
|
||||
|
||||
$(app_root): $(worker_chroot)
|
||||
$(Q)$(MKDIR) $(app_root)
|
||||
$(Q)$(MKDIR) $(app_root)/pages
|
||||
$(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:
|
||||
$(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)
|
||||
|
||||
$(built_files): $(build_dir)%.lua : src/lua/%.lua $(build_dir)
|
||||
$(built_files): $(app_root)/%.lua : src/lua/%.lua $(app_root)
|
||||
$(Q)$(ECHO) "[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)$(COPY) $< $@
|
||||
|
||||
src/lua/config.lua : src/lua/config.lua.in Makefile
|
||||
$(Q)$(ECHO) "[preprocess] $@"
|
||||
$(Q)$(SPP) $(SPPFLAGS) -o $@ $<
|
||||
$(built_parts): $(app_root)/% : src/%
|
||||
$(Q)$(ECHO) "[copy] $@"
|
||||
$(Q)$(COPY) $< $@
|
||||
|
||||
$(page_files) : % : %.in $(part_files)
|
||||
$(Q)$(ECHO) "[preprocess] $@"
|
||||
$(Q)$(SPP) $(SPPFLAGS) -o $@ $<
|
||||
|
||||
$(built_sql): $(build_dir)sql/%.sql : src/sql/%.sql
|
||||
$(built_sql): $(app_root)/sql/%.sql : src/sql/%.sql
|
||||
$(Q)$(ECHO) "[copy] $@"
|
||||
$(Q)$(COPY) $^ $@
|
||||
|
||||
$(built_tests) : $(build_dir)% : src/spec/%
|
||||
$(built_tests) : $(app_root)/spec/% : spec/% $(app_root)/spec
|
||||
$(Q)$(ECHO) "[copy] $@"
|
||||
$(Q)$(COPY) $^ $@
|
||||
$(Q)$(COPY) $< $@
|
||||
|
||||
$(asset_files) : % : %.in
|
||||
$(Q)$(ECHO) "[preprocess] $@"
|
||||
$(Q)$(SPP) $(SPPFLAGS) -o $@ $<
|
||||
$(app_root)/spec: $(app_root)
|
||||
$(Q)$(MKDIR) $@
|
||||
$(Q)$(MKDIR) $@/parser_tests
|
||||
|
||||
smr.so : $(src_files) conf/build.conf $(asset_files)
|
||||
$(Q)$(ECHO) "[build] $@"
|
||||
$(Q)$(KODEV) build
|
||||
|
||||
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)
|
||||
$(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)$(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 rng = require("spec.fuzzgen")
|
||||
|
||||
describe("smr biography",function()
|
||||
describe("smr biography #todo",function()
|
||||
setup(mock_env.setup)
|
||||
teardown(mock_env.teardown)
|
||||
it("should allow users to set their biography",function()
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
_G.spy = spy
|
||||
local mock_env = require("spec.env_mock")
|
||||
local env_mock = require("spec.env_mock")
|
||||
|
||||
describe("smr cacheing",function()
|
||||
setup(mock_env.setup)
|
||||
teardown(mock_env.teardown)
|
||||
setup(env_mock.setup)
|
||||
teardown(env_mock.teardown)
|
||||
it("caches a page if the page is requested twice #working",function()
|
||||
local read_get = require("endpoints.read_get")
|
||||
local cache = require("cache")
|
||||
|
@ -29,9 +29,15 @@ describe("smr cacheing",function()
|
|||
))
|
||||
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()
|
||||
local read_get = require("endpoints.read_get")
|
||||
local cache = require("cache")
|
||||
env_mock.mockdb()
|
||||
renderspy = spy.on(cache,"render")
|
||||
configure()
|
||||
for row in cache.cache:rows("SELECT COUNT(*) FROM cache") do
|
||||
|
@ -40,7 +46,7 @@ describe("smr cacheing",function()
|
|||
"request have been made."
|
||||
))
|
||||
end
|
||||
local req = mock_env.session()
|
||||
local req = env_mock.session()
|
||||
req.method = "GET"
|
||||
req.path = "/a"
|
||||
req.args = {}
|
||||
|
|
|
@ -12,6 +12,7 @@ local ntostring
|
|||
local login_post
|
||||
local fuzzy
|
||||
local claim_post
|
||||
local session
|
||||
print_table= function(...)
|
||||
print("Print called")
|
||||
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_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_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)
|
||||
assert(req.args,"requests should have a .args table")
|
||||
assert(
|
||||
|
@ -83,7 +87,7 @@ local smr_mock_env = {
|
|||
return req.args[str]
|
||||
end),
|
||||
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
|
||||
populate method has been called. (http_populate_multipart_form())
|
||||
]])
|
||||
|
@ -156,9 +160,9 @@ local string_fmt_override = {
|
|||
setmetatable(string_fmt_override,{__index = string})
|
||||
local smr_override_env = {
|
||||
--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
|
||||
string = string_fmt_override
|
||||
--string = string_fmt_override
|
||||
}
|
||||
|
||||
mock.olds = {}
|
||||
|
@ -173,10 +177,12 @@ end
|
|||
|
||||
function mock.mockdb()
|
||||
local config = require("config")
|
||||
config.db = "data/unittest.db"
|
||||
--config.db = "data/unittest.db"
|
||||
config.db = ":memory:"
|
||||
assert(os.execute("rm " .. config.db))
|
||||
package.loaded.db = nil
|
||||
local db = require("db")
|
||||
configure()
|
||||
end
|
||||
|
||||
function mock.teardown()
|
||||
|
@ -207,7 +213,7 @@ local session_m = {__index = {
|
|||
}}
|
||||
|
||||
function mock.session(tbl)
|
||||
if post_login == nil then
|
||||
if login_post == nil then
|
||||
login_post = require("endpoints.login_post")
|
||||
fuzzy = require("spec.fuzzgen")
|
||||
claim_post = require("endpoints.claim_post")
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
|
||||
_G.spy = spy
|
||||
local mock_env = require("spec.env_mock")
|
||||
local env_mock = require("spec.env_mock")
|
||||
local rng = require("spec.fuzzgen")
|
||||
|
||||
describe("smr login",function()
|
||||
setup(mock_env.setup)
|
||||
teardown(mock_env.teardown)
|
||||
setup(env_mock.setup)
|
||||
teardown(env_mock.teardown)
|
||||
it("should allow someone to claim an account",function()
|
||||
mock_env.mockdb()
|
||||
env_mock.mockdb()
|
||||
local claim_post = require("endpoints.claim_post")
|
||||
configure()
|
||||
claim_req = {
|
||||
|
@ -186,6 +186,7 @@ describe("smr login",function()
|
|||
text = "post text",
|
||||
markup = "plain",
|
||||
tags = "",
|
||||
pasteas = username
|
||||
}
|
||||
}
|
||||
paste_post(paste_req_post)
|
||||
|
|
|
@ -10,8 +10,18 @@ borrowed sha3 implementation from https://keccak.team
|
|||
#include "libcrypto.h"
|
||||
#include "keccak.h"
|
||||
|
||||
/*
|
||||
sha3(data::string)::string
|
||||
/* rst
|
||||
@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
|
||||
lsha3(lua_State *L){
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
--[[
|
||||
--[[ md
|
||||
@name lua/addon
|
||||
|
||||
Addon loader - Addons are either:
|
||||
* 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
|
||||
* A zip file with the same
|
||||
* 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)
|
||||
desc :: string - A description for the addon.
|
||||
entry :: table[number -> string] - Describes the load order for this
|
||||
addon. Each addon's meta.lua is run, and sorted to get a load
|
||||
order for entrypoints (the strings are the names files)
|
||||
order :: number - When should we run init.lua relative to other addons?
|
||||
Each addon's meta.lua is run (in any order), addons are sorted
|
||||
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
|
||||
|
|
|
@ -17,12 +17,11 @@ local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache
|
|||
|
||||
local oldconfigure = configure
|
||||
function configure(...)
|
||||
local cache = db.sqlassert(sql.open_memory())
|
||||
ret.cache = cache -- Expose db for testing
|
||||
ret.cache = db.sqlassert(sql.open_memory())-- Expose db for testing
|
||||
--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
|
||||
assert(cache:exec([[
|
||||
assert(ret.cache:exec([[
|
||||
CREATE TABLE IF NOT EXISTS cache (
|
||||
path TEXT PRIMARY KEY,
|
||||
data BLOB,
|
||||
|
@ -30,7 +29,7 @@ function configure(...)
|
|||
dirty INTEGER
|
||||
);
|
||||
]]))
|
||||
stmnt_cache = assert(cache:prepare([[
|
||||
stmnt_cache = assert(ret.cache:prepare([[
|
||||
SELECT data
|
||||
FROM cache
|
||||
WHERE
|
||||
|
@ -38,14 +37,14 @@ function configure(...)
|
|||
((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 (
|
||||
path, data, updated, dirty
|
||||
) VALUES (
|
||||
:path, :data, strftime('%s','now'), 0
|
||||
);
|
||||
]]))
|
||||
stmnt_dirty_cache = assert(cache:prepare([[
|
||||
stmnt_dirty_cache = assert(ret.cache:prepare([[
|
||||
UPDATE OR IGNORE cache
|
||||
SET dirty = 1
|
||||
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.
|
||||
Notably, holds a connection to the open sqlite3 database in .conn
|
||||
]]
|
||||
|
@ -9,9 +10,34 @@ local config = require("config")
|
|||
|
||||
local db = {}
|
||||
|
||||
--[[
|
||||
--[[ md
|
||||
@name lua/db/sqlassert
|
||||
|
||||
Runs an sql query and receives the 3 arguments back, prints a nice error
|
||||
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)
|
||||
if not r then
|
||||
|
@ -24,8 +50,27 @@ function db.sqlassert(r, errcode, err)
|
|||
return r
|
||||
end
|
||||
|
||||
--[[
|
||||
Continuously tries to perform an sql statement until it goes through
|
||||
--[[ md
|
||||
@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)
|
||||
if not stmnt then error("No statement",2) end
|
||||
|
@ -42,11 +87,33 @@ function db.do_sql(stmnt)
|
|||
return err
|
||||
end
|
||||
|
||||
--[[
|
||||
Provides an iterator that loops over results in an sql statement
|
||||
or throws an error, then resets the statement after the loop is done.
|
||||
--[[ md
|
||||
@name lua/db/sql_rows
|
||||
|
||||
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
|
||||
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)
|
||||
if not stmnt then error("No statement",2) end
|
||||
|
@ -75,7 +142,9 @@ function db.sql_rows(stmnt)
|
|||
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
|
||||
call :: string - a string "bind" or "bind_blob"
|
||||
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)
|
||||
local f = stmnt[call](stmnt,position,data)
|
||||
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
|
||||
|
||||
|
@ -94,39 +171,25 @@ end
|
|||
local oldconfigure = configure
|
||||
db.conn = db.sqlassert(sql.open(config.db))
|
||||
function configure(...)
|
||||
|
||||
--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 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.
|
||||
--
|
||||
--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.conn:exec(queries.create_table_session))
|
||||
local statements = {
|
||||
"create_table_authors",
|
||||
"insert_anon_author",
|
||||
"create_table_posts",
|
||||
"create_table_raw_text",
|
||||
"create_table_images",
|
||||
"create_table_comments",
|
||||
"create_table_tags",
|
||||
"create_index_tags",
|
||||
"create_table_session"
|
||||
}
|
||||
-- ipairs() needed, "create table authors" must be executed before
|
||||
-- "insert anon author"
|
||||
for _, statement in ipairs(statements) do
|
||||
db.sqlassert(db.conn:exec(queries[statement]))
|
||||
end
|
||||
|
||||
return oldconfigure(...)
|
||||
end
|
||||
configure()
|
||||
|
||||
function db.close()
|
||||
db.conn:close()
|
||||
|
|
|
@ -15,7 +15,6 @@ local stmnt_author_create
|
|||
--a while, but whatever.
|
||||
local oldconfigure = configure
|
||||
function configure(...)
|
||||
|
||||
stmnt_author_create = db.sqlassert(db.conn:prepare(queries.insert_author))
|
||||
return oldconfigure(...)
|
||||
end
|
||||
|
|
|
@ -35,7 +35,7 @@ local function get_site_home(req, loggedin)
|
|||
isanon = tonumber(iar) == 1,
|
||||
posted = os.date("%B %d %Y",tonumber(dater)),
|
||||
author = author,
|
||||
tags = libtags.get(idr),
|
||||
taglist = libtags.get(idr),
|
||||
hits = hits,
|
||||
ncomments = cmts
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ local util = require("util")
|
|||
local session = require("session")
|
||||
local config = require("config")
|
||||
local pages = require("pages")
|
||||
local api = require("hooks")
|
||||
|
||||
local stmnt_author_acct
|
||||
|
||||
|
@ -18,47 +19,43 @@ function configure(...)
|
|||
return oldconfigure(...)
|
||||
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)
|
||||
--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 = 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")
|
||||
local uid, err = api.authenticate({user=name,pass=pass})
|
||||
if not uid then
|
||||
http_response(req,200,pages.login{err=err})
|
||||
return
|
||||
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
|
||||
|
||||
return login_post
|
||||
|
|
|
@ -102,7 +102,7 @@ local function author_paste(req,ps)
|
|||
text = ps.text
|
||||
}
|
||||
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))
|
||||
--No need to check if the author is posting to the
|
||||
--"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
|
||||
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))
|
||||
stmnt_read = db.sqlassert(db.conn:prepare(queries.select_post))
|
||||
stmnt_update_views = db.sqlassert(db.conn:prepare(queries.update_views))
|
||||
stmnt_comments = db.sqlassert(db.conn:prepare(queries.select_comments))
|
||||
return oldconfigure(...)
|
||||
end
|
||||
|
||||
|
|
|
@ -28,6 +28,18 @@ local pagenames = {
|
|||
"parts/story_breif",
|
||||
"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 = {}
|
||||
for k,v in pairs(pagenames) do
|
||||
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)
|
||||
pages[v] = function(env)
|
||||
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)
|
||||
if not buff then
|
||||
errorf("Failed to render %s : %s", path, err)
|
||||
|
|
|
@ -4,12 +4,13 @@ local db = require("db")
|
|||
local util = require("util")
|
||||
local queries = require("queries")
|
||||
|
||||
local oldconfigure = configure
|
||||
local stmnt_get_session, stmnt_insert_session, stmnt_delete_session
|
||||
|
||||
local oldconfigure = configure
|
||||
function configure(...)
|
||||
stmnt_get_session = assert(db.conn:prepare(queries.select_valid_sessions))
|
||||
stmnt_insert_session = assert(db.conn:prepare(queries.insert_session))
|
||||
stmnt_delete_session = assert(db.conn:prepare(queries.delete_session))
|
||||
stmnt_get_session = db.sqlassert(db.conn:prepare(queries.select_valid_sessions))
|
||||
stmnt_insert_session = db.sqlassert(db.conn:prepare(queries.insert_session))
|
||||
stmnt_delete_session = db.sqlassert(db.conn:prepare(queries.delete_session))
|
||||
return oldconfigure(...)
|
||||
end
|
||||
|
||||
|
@ -59,7 +60,7 @@ function session.start(who)
|
|||
}
|
||||
local err = db.do_sql(stmnt_insert_session)
|
||||
stmnt_insert_session:reset()
|
||||
assert(err == sql.DONE)
|
||||
assert(err == sql.DONE, "Error should have been 'DONE', was: " .. tostring(err))
|
||||
return session
|
||||
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 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 config = require("config")
|
||||
|
@ -15,8 +21,26 @@ function configure(...)
|
|||
return oldconfigure(...)
|
||||
end
|
||||
|
||||
--see https://perishablepress.com/stop-using-unsafe-characters-in-urls/
|
||||
--no underscore because we use that for our operative pages
|
||||
--[[ md
|
||||
@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 =
|
||||
[[abcdefghijklmnopqrstuvwxyz]]..
|
||||
[[ABCDEFGHIJKLMNOPQRSTUVWXYZ]]..
|
||||
|
@ -34,8 +58,11 @@ local url_characters_rev_legacy = {}
|
|||
for i = 1,string.len(url_characters_legacy) do
|
||||
url_characters_rev_legacy[string.sub(url_characters_legacy,i,i)] = i
|
||||
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)
|
||||
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,
|
||||