A HTTP server that uses wlambda scripting for handling requests. Main application domain: quick HTTP API prototypes.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

392 lines
14 KiB

!@import u util;
!@import a auth;
!:global auth_realm = \ "wctor_journal" ;
!:global local_endpoint = \ "0.0.0.0:19099" ;
!:global file_prefix = { || "/journal/files" };
!:global file_path = { || "webdata/" };
!:global need_auth = { ||
((_1 0 16) == "/journal/public/" &or
(_1 0 14) == "/journal/files") { $f } { $t }
};
!:global auth = { a:auth[[@]] };
!parse_tags = {
!tags = _;
$@v tags | std:re:map $q/\s*("(.*?)"|[^,]+)\s*/ {
!m = _;
$+ ~ (is_none m.2) { m.1 } { m.2 };
}
};
!save_search = {
db:exec $q"DELETE FROM searches WHERE search=?" _;
db:exec $q"INSERT INTO searches (search) VALUES(?)" _;
};
!get_search = {
!r = db:exec $q"SELECT search FROM searches ORDER BY id DESC LIMIT 1";
r.0
};
!:global req = {
!(method, path, data, url, qp) = @;
!data = block :from_req {
!t = std:str:cat method ":" path;
u:regex_match t $[
$q"^GET:/$", {||
return :from_req ${
status = 302,
redirect = "/journal/files/main.html"
};
},
$q"^GET:/journal/search/last", {||
return :from_req get_search[];
},
$q"^GET:/journal/(?:public/)?attachments/(\d+)", {||
return :from_req ~
db:exec "SELECT * FROM attachments WHERE entry_id=?" _.1;
},
$q"^GET:/journal/public/search/entries/recent", {||
return :from_req ~
db:exec $q"SELECT * FROM entries e
WHERE deleted=0
AND is_public=1
ORDER BY mtime DESC, id DESC
LIMIT 25";
},
$q"^GET:/journal/search/entries/recent", {||
return :from_req ~
db:exec $q"SELECT * FROM entries e
WHERE deleted=0
ORDER BY mtime DESC, id DESC
LIMIT 25";
},
$q"^GET:/journal/trigger_attachment_thumb/(\d+)", {||
!at = db:exec "SELECT * FROM attachments WHERE id=?" _.1
| _? :from_req;
!local_filename_thumb =
std:str:cat at.0.entry_id "_tb_" at.0.id "_" at.0.name;
std:displayln "triggered thumb creation: " at;
std:re:match "^image/" at.0.type {||
std:displayln "creating thumbnail... " local_filename_thumb;
make_webdata_thumbnail
(std:str:cat "attachments/" at.0.local_filename)
(std:str:cat "attachments/" local_filename_thumb)
| _? :from_req;
db:exec
"UPDATE attachments SET local_thumb_filename=? WHERE id=?"
local_filename_thumb at.0.id
| _? :from_req;
};
return :from_req $["ok", at];
},
$q"^GET:/journal/deleteupload/(\d+)", {||
db:exec "DELETE FROM attachments WHERE id=?" _.1 | _? :from_req;
return :from_req $["ok"];
},
$q"^POST:/journal/sliceupload/(\d+)", {||
!at = _? :from_req ~ db:exec
"SELECT local_filename FROM attachments WHERE id=?" _.1;
(is_some at.0.local_filename) {
!d = data.data;
std:re:match $q$;base64,(.*)$ d {
_? :from_req ~
append_webdata
(std:str:cat "attachments/" at.0.local_filename)
(b64:decode _.1);
};
};
return :from_req $[_.1];
},
$q"^POST:/journal/fileupload/(\d+)", {||
!entry_id = _.1;
_? :from_req ~ db:exec
"INSERT INTO attachments (entry_id, name, type) VALUES(?, ?, ?)"
entry_id data.name data.type;
!at_id = _? :from_req ~
db:exec "SELECT MAX(id) AS new_at_id FROM attachments";
!local_filename =
std:str:cat entry_id "_" at_id.0.new_at_id "_" data.name;
_? :from_req ~ db:exec
"UPDATE attachments SET local_filename=? WHERE id=?"
local_filename at_id.0.new_at_id;
!d = data.data;
std:re:match $q$;base64,(.*)$ d {
_? :from_req ~
write_webdata
(std:str:cat "attachments/" local_filename)
(b64:decode _.1);
};
return :from_req ~ $[at_id.0.new_at_id];
},
$q"^POST:/journal/search/entries", {||
if data.save {
save_search data.search;
};
!args = $[];
(not ~ is_none data.search) {
!s = u:search_to_sql ~ _? :from_req ~ u:parse_search data.search;
std:displayln :SQL_SEARCH " " s;
std:push args s.sql;
std:append args s.binds;
} {
std:push args
$q"SELECT * FROM entries WHERE deleted=0 ORDER BY mtime DESC LIMIT 40";
};
return :from_req ~ db:exec[[args]];
},
$q"^GET:/journal/public/data/entries/(\d+)", {||
return :from_req ~
0 ~ db:exec $q"SELECT * FROM entries WHERE id=? AND is_public=1" _.1;
},
$q"^GET:/journal/data/entries/(\d+)", {||
return :from_req ~
0 ~ db:exec $q"SELECT * FROM entries WHERE id=?" _.1;
},
$q"^GET:/journal/public/data/entries", {||
return :from_req ~
db:exec $q"SELECT * FROM entries
WHERE deleted=0
AND is_public=1
ORDER BY id DESC
LIMIT 50";
},
$q"^GET:/journal/data/entries", {||
return :from_req ~
db:exec $q"SELECT * FROM entries
WHERE deleted=0
ORDER BY id DESC
LIMIT 50";
},
$q"^POST:/journal/data/entries/(\d+)", {||
!entry_id = _.1;
std:displayln "DATA SAVE:" _ data;
# check whether the to be saved entry is out of date:
(not ~ is_none data.mtime) {
!mt =_? :from_req ~
db:exec $q"SELECT id, mtime FROM entries WHERE
id = ? AND mtime > ?" data.id data.mtime;
(not ~ is_none mt.0.mtime) {
std:displayln "OUT OF DATE: " mt.0 data;
return :from_req $e ${
status = 403,
data = $["outofdate", std:ser:json data]
};
}
};
# save diff to history:
!old = db:exec "SELECT * FROM entries WHERE id=?" entry_id
| _? :from_req;
!diff = text_diff old.0.body data.body;
!hist_num =
db:exec
"SELECT MAX(hist_num) AS hist_num FROM history WHERE entry_id=?" entry_id
| _? :from_req;
!out_hist_num = $n;
(is_some hist_num)
{ .out_hist_num = hist_num.0.hist_num + 1 }
{ .out_hist_num = 1 };
db:exec
$q$
INSERT INTO history (entry_id, hist_num, tags, body, mtime)
VALUES(?, ?, ?, ?, ?)
$ entry_id out_hist_num old.0.tags (u:diff2txt diff) old.0.mtime
| _? :from_req;
# update entry:
std:displayln "UUUUUU " data;
_? :from_req ~
db:exec
"UPDATE entries SET tags=?,body=?,deleted=?,mtime=datetime('now'),is_public=? WHERE id=?"
data.tags
data.body
(is_none data.deleted)[{ 0 }, { data.deleted }]
(bool[data.is_public] { 1 } { 0 })
entry_id;
# recreate tag structure:
!tag_vec = parse_tags data.tags;
!tag_ids = $@v tag_vec {
_? :from_req ~
db:exec "INSERT OR IGNORE INTO tags (name) VALUES(?)" _;
!r = _? :from_req ~
db:exec "SELECT id FROM tags WHERE name=?" _;
$+ r.(0).id;
};
_? :from_req ~ db:exec
$q"DELETE FROM tag_entries WHERE entry_id=?"
entry_id;
tag_ids {
_? :from_req ~
db:exec
$q"INSERT INTO tag_entries (tag_id, entry_id)
VALUES(?,?)" _ entry_id;
};
!e = _? :from_req ~
db:exec "SELECT * FROM entries WHERE id=?" entry_id;
std:displayln "SAVE ENTRY" e;
return :from_req $[ "ok", entry_id, e.0 ];
},
$q"^POST:/journal/data/entries", {||
std:displayln "POST NEW" data;
_? :from_req ~
db:exec
"INSERT INTO entries (tags, body) VALUES(?,?)"
data.tags data.body;
!e = _? :from_req ~
db:exec "SELECT MAX(id) AS new_entry_id FROM entries";
!new_entry_id = e.(0).new_entry_id;
!tag_vec = parse_tags data.tags;
!tag_ids = $@v tag_vec {
_? :from_req ~
db:exec "INSERT OR IGNORE INTO tags (name) VALUES(?)" _;
!r = _? :from_req ~
db:exec "SELECT id FROM tags WHERE name=?" _;
$+ r.(0).id
};
tag_ids {
_? :from_req ~
db:exec
$q"INSERT INTO tag_entries (tag_id, entry_id)
VALUES(?,?)" _ new_entry_id;
};
return :from_req e;
},
$q"^GET:/journal/data/entries/(\d+)", {
return :from_req ~
db:exec "SELECT * FROM entries WHERE id=?" _.1;
},
];
$e $["No URL Handler!", t]
};
std:displayln "RET[" data "]";
(is_err data) {
std:displayln :ERROR " " (unwrap_err data | str);
(is_map ~ unwrap_err data) { unwrap_err data } {
${
status = 500,
content_type = "text/plain",
body = unwrap_err data,
}
};
} {
if is_some[data.redirect] {
data
} {
${ data = data }
}
};
};
!setup_db = {
db:connect_sqlite "j.sqlite";
db:exec $q"
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY,
tags TEXT NOT NULL DEFAULT '',
ctime TEXT DEFAULT (datetime('now')),
mtime TEXT DEFAULT (datetime('now')),
body TEXT NOT NULL DEFAULT '',
deleted INTEGER NOT NULL DEFAULT 0
);
";
db:exec $q"
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE
);
";
db:exec $q"
CREATE TABLE IF NOT EXISTS tag_entries (
tag_id INTEGER,
entry_id INTEGER,
FOREIGN KEY(tag_id) REFERENCES tags(id),
FOREIGN KEY(entry_id) REFERENCES entries(id)
);
";
db:exec $q"
CREATE TABLE IF NOT EXISTS system (
key TEXT PRIMARY KEY,
value TEXT
);
";
db:exec $q"
CREATE TABLE IF NOT EXISTS searches (
id INTEGER PRIMARY KEY,
search TEXT
);
";
!r = unwrap ~ db:exec "SELECT value FROM system WHERE key=?" :version;
!version = r.0.value;
std:displayln "* db version = " version;
(not r) {
unwrap ~ db:exec "INSERT INTO system (key, value) VALUES(?, ?)" :version "1";
} {
!new_version = $&$n;
(version == "1") {
.new_version = "2";
unwrap ~ db:exec $q"
CREATE TABLE IF NOT EXISTS attachments (
id INTEGER PRIMARY KEY,
entry_id INTEGER,
upload_time TEXT NOT NULL DEFAULT (datetime('now')),
type TEXT,
name TEXT,
local_filename TEXT,
local_thumb_filename TEXT,
FOREIGN KEY (entry_id) REFERENCES entries(id)
);
";
};
(version == "2") {
.new_version = "3";
unwrap ~ db:exec $q"
CREATE TABLE IF NOT EXISTS history (
entry_id INTEGER,
hist_num INTEGER,
mtime TEXT NOT NULL DEFAULT (datetime('now')),
tags TEXT,
body TEXT,
FOREIGN KEY (entry_id) REFERENCES entries(id),
PRIMARY KEY (entry_id, hist_num)
);
";
};
(version == "3") {
.new_version = "4";
unwrap ~ db:exec $q"
ALTER TABLE entries ADD is_public INTEGER DEFAULT 0
";
};
(is_some $*new_version) {
db:exec "UPDATE system SET value=? WHERE key=?" new_version :version;
std:displayln "UPDATED DATABASE FROM " version " to " new_version;
};
};
};
setup_db[];