Api & JS client
This commit is contained in:
parent
8629bba07f
commit
5deba2fdb1
10 changed files with 399 additions and 144 deletions
99
Cargo.lock
generated
99
Cargo.lock
generated
|
@ -304,9 +304,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.60"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
|
||||
checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -353,9 +353,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
|||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.20.0"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
|
@ -421,11 +421,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -681,9 +682,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
|
||||
checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
|
@ -693,9 +694,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
|
||||
checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
|
@ -708,15 +709,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
|
||||
checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.85"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
|
||||
checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -768,7 +769,7 @@ dependencies = [
|
|||
"hashbrown",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core 0.9.5",
|
||||
"parking_lot_core 0.9.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1149,9 +1150,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
|
||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
|
@ -1399,11 +1400,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.18"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
|
||||
checksum = "a05705bc64e0b66a806c3740bd6578ea66051b157ec42dc219c785cbf185aef3"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"log",
|
||||
|
@ -1462,9 +1462,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.7.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e"
|
||||
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
|
@ -1770,9 +1770,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.5"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
|
||||
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
@ -1809,9 +1809,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
|||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.5.2"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4"
|
||||
checksum = "4257b4a04d91f7e9e6290be5d3da4804dd5784fafde3a497d73eb2b4a158c30a"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
|
@ -1819,9 +1819,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.5.2"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603"
|
||||
checksum = "241cda393b0cdd65e62e07e12454f1f25d57017dcc514b1514cd3c4645e3a0a6"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
|
@ -1829,9 +1829,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.5.2"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7"
|
||||
checksum = "46b53634d8c8196302953c74d5352f33d0c512a9499bd2ce468fc9f4128fa27c"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
|
@ -1842,13 +1842,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.5.2"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065"
|
||||
checksum = "0ef4f1332a8d4678b41966bb4cc1d0676880e84183a1ecc3f4b69f03e99c7a51"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha1 0.10.5",
|
||||
"sha2 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2127,9 +2127,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.0"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -2349,11 +2349,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
||||
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"base64 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2486,17 +2486,6 @@ dependencies = [
|
|||
"sha1_smol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.0"
|
||||
|
@ -2665,7 +2654,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha1 0.6.1",
|
||||
"sha1",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -2865,9 +2854,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.23.0"
|
||||
version = "1.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
|
||||
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
|
@ -2988,9 +2977,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "type-map"
|
||||
|
@ -3355,7 +3344,7 @@ name = "webcomment"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"base64 0.20.0",
|
||||
"base64 0.21.0",
|
||||
"clap",
|
||||
"crossbeam-channel",
|
||||
"directories",
|
||||
|
|
|
@ -9,7 +9,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
argon2 = "0.4.1"
|
||||
base64 = "0.20.0"
|
||||
base64 = "0.21.0"
|
||||
clap = { version = "4.0.32", default-features = false, features = ["derive", "error-context", "help", "std", "usage"] }
|
||||
crossbeam-channel = "0.5.6"
|
||||
directories = "4.0.1"
|
||||
|
@ -29,7 +29,7 @@ sha2 = "0.10.6"
|
|||
sled = "0.34.7"
|
||||
tera = { version = "1.17.1", features = ["builtins", "date-locale"] }
|
||||
tide = { version = "0.16.0", default-features = false, features = ["h1-server", "cookies", "logger"] }
|
||||
tokio = { version = "1.23.0", features = ["macros", "rt-multi-thread"] }
|
||||
tokio = { version = "1.24.1", features = ["macros", "rt-multi-thread"] }
|
||||
toml_edit = { version = "0.17.1", features = ["easy"] }
|
||||
typed-sled = "0.2.3"
|
||||
unic-langid = { version = "0.9.1", features = ["macros"] }
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<title>Webcomment</title>
|
||||
<script type="text/javascript" src="js/jquery.js"></script>
|
||||
<script type="text/javascript" src="js/webcomment.js"></script>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="comments"></div>
|
||||
|
|
|
@ -8,27 +8,68 @@ You should have received a copy of the GNU Affero General Public License along w
|
|||
var webcomments = {};
|
||||
|
||||
const MODE_TOPIC = 1;// param: {topic:str}
|
||||
const ORDER_BY_DATE_ASC = 1;
|
||||
const ORDER_BY_DATE_DESC = 2;
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
default_order: ORDER_BY_DATE_ASC,
|
||||
/*template_comment: `
|
||||
<div class="comment" id="{{root.id}}-{{comment.id}}">
|
||||
<p class="comment-meta">{{comment.author}} {{comment.post_time}}</p>
|
||||
<p class="comment-text">{{comment.text}}</p>
|
||||
</div>`,*/
|
||||
template_comment: `
|
||||
<div class="comment" id="{{root.id}}-{{comment.id}}">
|
||||
<p class="comment-meta">
|
||||
<a class="comment-post_time" aria-label="Permalink" title="Permalink" href="#{{root.id}}-{{comment.id}}">{{comment.post_time}}</a>
|
||||
<span class="comment-author"></span></p>
|
||||
<p class="comment-text"></p>
|
||||
</div>`,
|
||||
template_widget: `
|
||||
<div class="comments"></div>
|
||||
<form class="comment-new-form" action="#" onsubmit="event.preventDefault();post_new_comment('{{root.id}}')">
|
||||
<fieldset>
|
||||
<legend>New comment</legend>
|
||||
<label>
|
||||
Your name:
|
||||
<input type="text" name="author" class="comment-form-author"/>
|
||||
</label><br/>
|
||||
<label>
|
||||
Your email:
|
||||
<input type="email" name="email" class="comment-form-email"/>
|
||||
</label><br/>
|
||||
<textarea class="comment-form-text" name="text"></textarea><br/>
|
||||
<input type="submit" value="Post"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
`,
|
||||
};
|
||||
|
||||
class Webcomment {
|
||||
constructor(root, api, mode, mode_param) {
|
||||
this.root = root;
|
||||
constructor(root_id, api, mode, mode_param, config) {
|
||||
this.root_id = root_id;
|
||||
this.api = api;
|
||||
this.mode = mode;
|
||||
this.mode_param = mode_param;
|
||||
this.config = config;
|
||||
|
||||
console.log("constr");
|
||||
this.root = document.getElementById(root_id);
|
||||
this.root.innerHTML = config.template_widget.replaceAll("{{root.id}}", this.root_id);;
|
||||
this.elem_comments = this.root.getElementsByClassName("comments")[0];
|
||||
|
||||
switch(mode) {
|
||||
case MODE_TOPIC:
|
||||
this.query_comments_by_topic(mode_param.topic);
|
||||
var this_ = this;
|
||||
this.query_comments_by_topic(mode_param.topic, function(resp) {
|
||||
this_.append_comments(resp.comments);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.log("Webcomment: invalid mode");
|
||||
}
|
||||
}
|
||||
|
||||
query_comments_by_topic(topic) {
|
||||
console.log("query");
|
||||
query_comments_by_topic(topic, success) {
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: this.api+"/api/comments_by_topic",
|
||||
|
@ -36,15 +77,80 @@ class Webcomment {
|
|||
mutation_token: "",
|
||||
topic: topic,
|
||||
}),
|
||||
success: function(resp) {
|
||||
console.log(resp);
|
||||
},
|
||||
success: success,
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
});
|
||||
}
|
||||
|
||||
query_new_comment(topic, author, email, text, success) {
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: this.api+"/api/new_comment",
|
||||
data: JSON.stringify({
|
||||
author: author,
|
||||
email: email,
|
||||
text: text,
|
||||
topic: topic,
|
||||
}),
|
||||
success: success,
|
||||
dataType: "json",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
});
|
||||
}
|
||||
|
||||
post_new_comment() {
|
||||
var elem_author = $("#comments .comment-new-form [name=author]")[0];
|
||||
var elem_email = $("#comments .comment-new-form [name=email]")[0];
|
||||
var elem_text = $("#comments .comment-new-form [name=text]")[0];
|
||||
|
||||
switch(this.mode) {
|
||||
case MODE_TOPIC:
|
||||
var comment = {
|
||||
topic: this.mode_param.topic,
|
||||
author: elem_author.value,
|
||||
email: elem_email.value,
|
||||
text: elem_text.value,
|
||||
};
|
||||
var this_ = this;
|
||||
this.query_new_comment(comment.topic, comment.author, comment.email, comment.text, function(resp) {
|
||||
if(resp.id) {
|
||||
comment.id = resp.id;
|
||||
comment.post_time = resp.post_time;
|
||||
this_.append_comments([comment]);
|
||||
elem_text.value = "";
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.log("Webcomment: invalid mode");
|
||||
}
|
||||
}
|
||||
|
||||
append_comments(comments) {
|
||||
for(var i in comments) {
|
||||
var comment = comments[i];
|
||||
var post_time = new Date(comment.post_time*1000);
|
||||
|
||||
var comment_html = this.config.template_comment;
|
||||
comment_html = comment_html.replaceAll("{{root.id}}", this.root_id);
|
||||
comment_html = comment_html.replaceAll("{{comment.id}}", comment.id);
|
||||
//comment_html = comment_html.replaceAll("{{comment.author}}", comment.author);
|
||||
comment_html = comment_html.replaceAll("{{comment.post_time}}", post_time.toLocaleDateString()+" "+post_time.toLocaleTimeString());
|
||||
//comment_html = comment_html.replaceAll("{{comment.text}}", comment.text);
|
||||
$(this.elem_comments).append(comment_html);
|
||||
|
||||
var elem = document.getElementById(this.root_id+"-"+comment.id);
|
||||
elem.getElementsByClassName("comment-author")[0].innerHTML = comment.author;
|
||||
elem.getElementsByClassName("comment-text")[0].innerHTML = comment.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function webcomment_topic(root_id, api, topic) {
|
||||
webcomments[root_id] = (new Webcomment(document.getElementById(root_id), api, MODE_TOPIC, {topic: topic}));
|
||||
function webcomment_topic(root_id, api, topic, config=DEFAULT_CONFIG) {
|
||||
webcomments[root_id] = (new Webcomment(root_id, api, MODE_TOPIC, {topic: topic}, config));
|
||||
}
|
||||
|
||||
function post_new_comment(root_id) {
|
||||
webcomments[root_id].post_new_comment();
|
||||
}
|
||||
|
|
9
client/style.css
Normal file
9
client/style.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.comment {
|
||||
border-left: 2px solid #888;
|
||||
border-right: 2px solid #888;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.comment-meta, .comment-meta a, .comment-meta a:visited {
|
||||
color: #666;
|
||||
}
|
28
src/db.rs
28
src/db.rs
|
@ -12,10 +12,10 @@ const DB_DIR: &str = "db";
|
|||
|
||||
pub type Time = u64;
|
||||
|
||||
pub const BASE64: base64::engine::fast_portable::FastPortable =
|
||||
base64::engine::fast_portable::FastPortable::from(
|
||||
pub const BASE64: base64::engine::general_purpose::GeneralPurpose =
|
||||
base64::engine::general_purpose::GeneralPurpose::new(
|
||||
&base64::alphabet::URL_SAFE,
|
||||
base64::engine::fast_portable::NO_PAD,
|
||||
base64::engine::general_purpose::NO_PAD,
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -74,20 +74,13 @@ impl MutationToken {
|
|||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
let mut buf = vec![0; 24];
|
||||
let size = BASE64.encode(&self.0, &mut buf);
|
||||
buf.truncate(size);
|
||||
String::from_utf8(buf).unwrap()
|
||||
BASE64.encode(self.0)
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Result<Self, base64::DecodeError> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let mut buf = [0; 16];
|
||||
BASE64.decode(
|
||||
s.as_bytes(),
|
||||
&mut buf,
|
||||
BASE64.decoded_length_estimate(s.len()),
|
||||
)?;
|
||||
BASE64.decode_slice_unchecked(s.as_bytes(), &mut buf)?;
|
||||
Ok(Self(buf))
|
||||
})
|
||||
.map_err(|_| base64::DecodeError::InvalidLength)?
|
||||
|
@ -134,20 +127,13 @@ impl CommentId {
|
|||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
let mut buf = vec![0; 24];
|
||||
let size = BASE64.encode(&self.0, &mut buf);
|
||||
buf.truncate(size);
|
||||
String::from_utf8(buf).unwrap()
|
||||
BASE64.encode(self.0)
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Result<Self, base64::DecodeError> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let mut buf = [0; 16];
|
||||
BASE64.decode(
|
||||
s.as_bytes(),
|
||||
&mut buf,
|
||||
BASE64.decoded_length_estimate(s.len()),
|
||||
)?;
|
||||
BASE64.decode_slice_unchecked(s.as_bytes(), &mut buf)?;
|
||||
Ok(Self(buf))
|
||||
})
|
||||
.map_err(|_| base64::DecodeError::InvalidLength)?
|
||||
|
|
|
@ -29,7 +29,7 @@ pub async fn run_server(
|
|||
.build())
|
||||
});
|
||||
|
||||
api::init_routes(&mut app, config, dbs.clone()).await;
|
||||
api::init_routes(&mut app, config, dbs.clone(), notify_send.clone()).await;
|
||||
page::init_routes(&mut app, config, dbs, templates, locales, notify_send).await;
|
||||
|
||||
app.listen(config.listen).await.unwrap();
|
||||
|
|
|
@ -1,54 +1,29 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
mod queries;
|
||||
mod resps;
|
||||
|
||||
use crate::{config::*, db::*, helpers, notify::Notification};
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
use log::{error, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
enum ApiError {
|
||||
InvalidAdminPassword,
|
||||
}
|
||||
|
||||
pub async fn init_routes(app: &mut tide::Server<()>, config: &'static Config, dbs: Dbs) {
|
||||
pub async fn init_routes(
|
||||
app: &mut tide::Server<()>,
|
||||
config: &'static Config,
|
||||
dbs: Dbs,
|
||||
notify_send: Sender<Notification>,
|
||||
) {
|
||||
// TODO pagination
|
||||
app.at(&format!("{}api/comments_by_topic", config.root_url))
|
||||
.post({
|
||||
let dbs = dbs.clone();
|
||||
move |req: tide::Request<()>| query_comments_by_topic(req, config, dbs.clone())
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CommentWithId {
|
||||
pub addr: Option<String>,
|
||||
pub author: String,
|
||||
pub editable: bool,
|
||||
pub id: String,
|
||||
pub last_edit_time: Option<Time>,
|
||||
pub status: Option<OriginalComment>,
|
||||
pub post_time: Time,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct OriginalComment {
|
||||
pub author: String,
|
||||
pub editable: bool,
|
||||
pub last_edit_time: Option<Time>,
|
||||
pub post_time: Time,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CommentsByTopicQuery {
|
||||
mutation_token: Option<String>,
|
||||
topic: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CommentsByTopicResp {
|
||||
comments: Vec<CommentWithId>,
|
||||
app.at(&format!("{}api/new_comment", config.root_url))
|
||||
.post({
|
||||
move |req: tide::Request<()>| {
|
||||
query_new_comment(req, config, dbs.clone(), notify_send.clone())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn query_comments_by_topic(
|
||||
|
@ -56,11 +31,17 @@ async fn query_comments_by_topic(
|
|||
config: &Config,
|
||||
dbs: Dbs,
|
||||
) -> tide::Result<tide::Response> {
|
||||
let Ok(CommentsByTopicQuery {
|
||||
mutation_token,
|
||||
let Ok(queries::CommentsByTopic {
|
||||
mutation_token: _mutation_token,
|
||||
topic,
|
||||
}) = req.body_json().await else {
|
||||
return Err(tide::Error::from_str(400, "Invalid request"));
|
||||
return Ok(tide::Response::builder(400)
|
||||
.content_type(tide::http::mime::JSON)
|
||||
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
||||
.body(
|
||||
tide::Body::from_json(&resps::Error::InvalidRequest).unwrap(),
|
||||
)
|
||||
.build());
|
||||
};
|
||||
|
||||
let topic_hash = TopicHash::from_topic(&topic);
|
||||
|
@ -69,19 +50,21 @@ async fn query_comments_by_topic(
|
|||
.content_type(tide::http::mime::JSON)
|
||||
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
||||
.body(
|
||||
tide::Body::from_json(&CommentsByTopicResp {
|
||||
tide::Body::from_json(&resps::CommentsByTopic {
|
||||
comments: helpers::iter_approved_comments_by_topic(topic_hash, &dbs)
|
||||
.map(|(comment_id, comment, _comment_status)| CommentWithId {
|
||||
addr: None,
|
||||
author: comment.author,
|
||||
editable: false,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
post_time: comment.post_time,
|
||||
status: None,
|
||||
text: comment.text,
|
||||
})
|
||||
.collect::<Vec<CommentWithId>>(),
|
||||
.map(
|
||||
|(comment_id, comment, _comment_status)| resps::CommentWithId {
|
||||
addr: None,
|
||||
author: comment.author,
|
||||
editable: false,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
post_time: comment.post_time,
|
||||
status: None,
|
||||
text: comment.text,
|
||||
},
|
||||
)
|
||||
.collect::<Vec<resps::CommentWithId>>(),
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!("Serializing CommentsByTopicResp to json: {e:?}");
|
||||
|
@ -90,3 +73,125 @@ async fn query_comments_by_topic(
|
|||
)
|
||||
.build())
|
||||
}
|
||||
|
||||
async fn query_new_comment(
|
||||
mut req: tide::Request<()>,
|
||||
config: &Config,
|
||||
dbs: Dbs,
|
||||
notify_send: Sender<Notification>,
|
||||
) -> tide::Result<tide::Response> {
|
||||
let Ok(query) = req.body_json::<queries::NewComment>().await else {
|
||||
return Ok(tide::Response::builder(400)
|
||||
.content_type(tide::http::mime::JSON)
|
||||
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
||||
.body(
|
||||
tide::Body::from_json(&resps::Error::InvalidRequest).unwrap(),
|
||||
)
|
||||
.build());
|
||||
};
|
||||
|
||||
if query.author.len() > config.comment_author_max_len
|
||||
|| query.email.len() > config.comment_email_max_len
|
||||
|| query.text.len() > config.comment_text_max_len
|
||||
{
|
||||
return Ok(tide::Response::builder(400)
|
||||
.content_type(tide::http::mime::JSON)
|
||||
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
||||
.body(tide::Body::from_json(&resps::Error::IllegalContent).unwrap())
|
||||
.build());
|
||||
}
|
||||
|
||||
let client_addr = match helpers::get_client_addr(config, &req) {
|
||||
Some(Ok(addr)) => Some(addr),
|
||||
Some(Err(e)) => {
|
||||
warn!("Unable to parse client addr: {}", e);
|
||||
None
|
||||
}
|
||||
None => {
|
||||
warn!("No client addr");
|
||||
None
|
||||
}
|
||||
};
|
||||
let antispam_enabled = config.antispam_enable
|
||||
&& client_addr
|
||||
.as_ref()
|
||||
.map_or(false, |addr| !config.antispam_whitelist.contains(addr));
|
||||
|
||||
if let Some(client_addr) = &client_addr {
|
||||
if antispam_enabled {
|
||||
if let Some(antispam_timeout) =
|
||||
helpers::antispam_check_client_mutation(client_addr, &dbs, config).unwrap()
|
||||
{
|
||||
return Ok(tide::Response::builder(403)
|
||||
.content_type(tide::http::mime::JSON)
|
||||
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
||||
.body(
|
||||
tide::Body::from_json(&resps::Error::Antispam {
|
||||
timeout: antispam_timeout,
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It's OK
|
||||
|
||||
if let Some(client_addr) = &client_addr {
|
||||
if antispam_enabled {
|
||||
helpers::antispam_update_client_mutation(client_addr, &dbs).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let topic_hash = TopicHash::from_topic(&query.topic);
|
||||
|
||||
let time = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let comment = Comment {
|
||||
topic_hash,
|
||||
author: if query.author.is_empty() {
|
||||
petname::Petnames::large().generate_one(2, " ")
|
||||
} else {
|
||||
query.author
|
||||
},
|
||||
email: if query.email.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(query.email)
|
||||
},
|
||||
last_edit_time: None,
|
||||
mutation_token: MutationToken::new(),
|
||||
post_time: time,
|
||||
text: query.text,
|
||||
};
|
||||
match helpers::new_pending_comment(&comment, client_addr, &dbs) {
|
||||
Ok(comment_id) => {
|
||||
notify_send.send(Notification { topic: query.topic }).ok();
|
||||
Ok(tide::Response::builder(200)
|
||||
.content_type(tide::http::mime::JSON)
|
||||
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
||||
.body(
|
||||
tide::Body::from_json(&resps::NewComment {
|
||||
id: comment_id.to_base64(),
|
||||
mutation_token: comment.mutation_token.to_base64(),
|
||||
post_time: time,
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.build())
|
||||
}
|
||||
// TODO add message to client log and change http code
|
||||
Err(e) => {
|
||||
error!("Adding pending comment: {:?}", e);
|
||||
Ok(tide::Response::builder(500)
|
||||
.content_type(tide::http::mime::JSON)
|
||||
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
||||
.body(tide::Body::from_json(&resps::Error::Internal).unwrap())
|
||||
.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
src/server/api/queries.rs
Normal file
15
src/server/api/queries.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CommentsByTopic {
|
||||
pub mutation_token: Option<String>,
|
||||
pub topic: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NewComment {
|
||||
pub author: String,
|
||||
pub email: String,
|
||||
pub text: String,
|
||||
pub topic: String,
|
||||
}
|
44
src/server/api/resps.rs
Normal file
44
src/server/api/resps.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use crate::db::*;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub enum Error {
|
||||
Antispam { timeout: Time },
|
||||
IllegalContent,
|
||||
Internal,
|
||||
InvalidRequest,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CommentsByTopic {
|
||||
pub comments: Vec<CommentWithId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct OriginalComment {
|
||||
pub author: String,
|
||||
pub editable: bool,
|
||||
pub last_edit_time: Option<Time>,
|
||||
pub post_time: Time,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CommentWithId {
|
||||
pub addr: Option<String>,
|
||||
pub author: String,
|
||||
pub editable: bool,
|
||||
pub id: String,
|
||||
pub last_edit_time: Option<Time>,
|
||||
pub status: Option<OriginalComment>,
|
||||
pub post_time: Time,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct NewComment {
|
||||
pub id: String,
|
||||
pub mutation_token: String,
|
||||
pub post_time: Time,
|
||||
}
|
Loading…
Reference in a new issue