feat: edit approval
This commit is contained in:
parent
c13e172938
commit
0c3ea546fd
9 changed files with 750 additions and 185 deletions
197
Cargo.lock
generated
197
Cargo.lock
generated
|
@ -87,9 +87,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.66"
|
||||
version = "1.0.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
||||
|
||||
[[package]]
|
||||
name = "anymap2"
|
||||
|
@ -304,9 +304,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.59"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
|
||||
checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -351,6 +351,12 @@ version = "0.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.3"
|
||||
|
@ -374,9 +380,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
@ -442,9 +448,9 @@ checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.77"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
|
||||
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -498,9 +504,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.0.29"
|
||||
version = "4.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
|
||||
checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
|
@ -540,16 +546,6 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.0.0"
|
||||
|
@ -572,7 +568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64",
|
||||
"base64 0.13.1",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"percent-encoding",
|
||||
|
@ -685,9 +681,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.83"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
|
||||
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
|
@ -697,9 +693,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.83"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
|
||||
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
|
@ -712,15 +708,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.83"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
|
||||
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.83"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
|
||||
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -886,9 +882,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.23"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11"
|
||||
checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -1223,9 +1219,9 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
|||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -1293,7 +1289,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-channel",
|
||||
"async-std",
|
||||
"base64",
|
||||
"base64 0.13.1",
|
||||
"cookie",
|
||||
"futures-lite",
|
||||
"infer",
|
||||
|
@ -1350,9 +1346,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.23.1"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d"
|
||||
checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
|
@ -1466,9 +1462,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.5.1"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
|
||||
checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
|
@ -1481,9 +1477,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
|
@ -1529,15 +1525,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.138"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
|
||||
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
@ -1685,6 +1681,15 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom8"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
|
@ -1706,9 +1711,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
|
@ -1746,14 +1751,14 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
|||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core 0.8.5",
|
||||
"parking_lot_core 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
|
@ -1804,9 +1809,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
|||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0"
|
||||
checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
|
@ -1814,9 +1819,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdc078600d06ff90d4ed238f0119d84ab5d43dbaad278b0e33a8820293b32344"
|
||||
checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
|
@ -1824,9 +1829,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a1af60b1c4148bb269006a750cff8e2ea36aff34d2d96cf7be0b14d1bed23c"
|
||||
checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
|
@ -1837,9 +1842,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fec8605d59fc2ae0c6c1aefc0c7c7a9769732017c0ce07f7a9cfffa7b4404f20"
|
||||
checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
|
@ -1935,9 +1940,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748"
|
||||
checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
|
@ -2001,15 +2006,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
version = "1.0.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -2022,9 +2027,9 @@ checksum = "b45c49fc4f91f35bae654f85ebb3a44d60ac64f11b3166ffa609def390c732d8"
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -2143,7 +2148,7 @@ version = "0.11.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
|
@ -2255,7 +2260,7 @@ version = "0.10.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "716889595f4edc3cfeb94d9f122e413f73e37d7d80ea1c14196e1004241a3889"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.1",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"http",
|
||||
|
@ -2348,14 +2353,14 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
|
@ -2374,9 +2379,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
||||
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
|
@ -2411,18 +2416,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.149"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.149"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2440,9 +2445,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.89"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
|
||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -2693,9 +2698,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.105"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
|
||||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2747,18 +2752,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.37"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2914,9 +2919,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -2932,13 +2937,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.15.0"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1541ba70885967e662f69d31ab3aeca7b1aaecfcd58679590b893e9239c3646"
|
||||
checksum = "dd30deba9a1cd7153c22aecf93e86df639e7b81c622b0af8d9255e989991a7b7"
|
||||
dependencies = [
|
||||
"combine",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"nom8",
|
||||
"serde",
|
||||
"toml_datetime",
|
||||
]
|
||||
|
@ -2998,9 +3003,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "typed-sled"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1362aa15e9df65c77830762d81cbd92bc49f1a315bf17ef4b595539c99ee87c2"
|
||||
checksum = "32b903ab728fcd56542a5c538dfc3d196aeffcdb1dc37e34c49f61782adc5cfa"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"pin-project",
|
||||
|
@ -3131,9 +3136,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
|
@ -3350,7 +3355,7 @@ name = "webcomment"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"base64",
|
||||
"base64 0.20.0",
|
||||
"clap",
|
||||
"crossbeam-channel",
|
||||
"directories",
|
||||
|
@ -3387,9 +3392,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.5"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
|
||||
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -9,8 +9,8 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
argon2 = "0.4.1"
|
||||
base64 = "0.13.1"
|
||||
clap = { version = "4.0.29", default-features = false, features = ["derive", "error-context", "help", "std", "usage"] }
|
||||
base64 = "0.20.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"
|
||||
fluent-bundle = "0.15.2"
|
||||
|
@ -23,14 +23,14 @@ petname = { version = "1.1.3", optional = true, default-features = false, featur
|
|||
rand = "0.8.5"
|
||||
rand_core = { version = "0.6.4", features = ["std"] }
|
||||
rpassword = "7.2.0"
|
||||
serde = { version = "1.0.149", features = ["derive", "rc"] }
|
||||
serde = { version = "1.0.152", features = ["derive", "rc"] }
|
||||
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"] }
|
||||
toml_edit = { version = "0.15.0", features = ["easy"] }
|
||||
typed-sled = "0.2.0"
|
||||
toml_edit = { version = "0.16.2", features = ["easy"] }
|
||||
typed-sled = "0.2.1"
|
||||
unic-langid = { version = "0.9.1", features = ["macros"] }
|
||||
|
||||
[features]
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
admin-comment-approve = Approve
|
||||
admin-comment-approve-edit = Approve
|
||||
admin-comment-edit = Edit
|
||||
admin-comment-edit-edit = Edit
|
||||
admin-comment-remove = Remove
|
||||
admin-comment-remove-edit = Remove
|
||||
admin_login-password_prompt = Password:
|
||||
admin_login-submit_button = Login
|
||||
admin_login-title = Admin login | Comments
|
||||
edit_comment-success = Your edit has been published. <a href="{ $edit_link }#edit_comment-form">You can edit it with this link.</a>
|
||||
edit_comment-success_pending = Your edit has been saved. It will be reviewed by an administrator before being published. <a href="{ $edit_link }#edit_comment-form">You can edit it with this link.</a>
|
||||
error-antispam = The edition quota from your IP is reached. You will be unblocked in { $antispam_timeout }s.
|
||||
error-comment-author_name_too_long = Author name length is { $len } but maximum is { $max_len }.
|
||||
error-comment-email_too_long = E-mail length is { $len } but maximum is { $max_len }.
|
||||
|
|
42
src/db.rs
42
src/db.rs
|
@ -1,3 +1,4 @@
|
|||
use base64::engine::Engine;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{net::IpAddr, path::Path};
|
||||
|
@ -7,11 +8,18 @@ const DB_DIR: &str = "db";
|
|||
|
||||
pub type Time = u64;
|
||||
|
||||
pub const BASE64: base64::engine::fast_portable::FastPortable =
|
||||
base64::engine::fast_portable::FastPortable::from(
|
||||
&base64::alphabet::URL_SAFE,
|
||||
base64::engine::fast_portable::NO_PAD,
|
||||
);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Dbs {
|
||||
pub comment: Tree<CommentId, Comment>,
|
||||
pub comment: Tree<CommentId, (Comment, CommentStatus)>,
|
||||
pub comment_approved: Tree<(TopicHash, Time, CommentId), ()>,
|
||||
pub comment_pending: Tree<(TopicHash, Time, CommentId), Option<IpAddr>>,
|
||||
/// -> (client_addr, is_edit)
|
||||
pub comment_pending: Tree<(TopicHash, Time, CommentId), (Option<IpAddr>, bool)>,
|
||||
/// client_addr -> (last_mutation, mutation_count)
|
||||
pub client_mutation: Tree<IpAddr, (Time, u32)>,
|
||||
}
|
||||
|
@ -34,6 +42,14 @@ pub fn load_dbs(path: Option<&Path>) -> Dbs {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum CommentStatus {
|
||||
Pending = 0,
|
||||
Approved = 1,
|
||||
ApprovedEdited(Comment) = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct Comment {
|
||||
pub author: String,
|
||||
|
@ -54,13 +70,20 @@ impl MutationToken {
|
|||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
base64::encode_config(self.0, base64::URL_SAFE_NO_PAD)
|
||||
let mut buf = vec![0; 24];
|
||||
let size = BASE64.encode(&self.0, &mut buf);
|
||||
buf.truncate(size);
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Result<Self, base64::DecodeError> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let mut buf = [0; 16];
|
||||
base64::decode_config_slice(s, base64::URL_SAFE_NO_PAD, &mut buf)?;
|
||||
BASE64.decode(
|
||||
s.as_bytes(),
|
||||
&mut buf,
|
||||
BASE64.decoded_length_estimate(s.len()),
|
||||
)?;
|
||||
Ok(Self(buf))
|
||||
})
|
||||
.map_err(|_| base64::DecodeError::InvalidLength)?
|
||||
|
@ -107,13 +130,20 @@ impl CommentId {
|
|||
}
|
||||
|
||||
pub fn to_base64(&self) -> String {
|
||||
base64::encode_config(self.0, base64::URL_SAFE_NO_PAD)
|
||||
let mut buf = vec![0; 24];
|
||||
let size = BASE64.encode(&self.0, &mut buf);
|
||||
buf.truncate(size);
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
|
||||
pub fn from_base64(s: &str) -> Result<Self, base64::DecodeError> {
|
||||
std::panic::catch_unwind(|| {
|
||||
let mut buf = [0; 16];
|
||||
base64::decode_config_slice(s, base64::URL_SAFE_NO_PAD, &mut buf)?;
|
||||
BASE64.decode(
|
||||
s.as_bytes(),
|
||||
&mut buf,
|
||||
BASE64.decoded_length_estimate(s.len()),
|
||||
)?;
|
||||
Ok(Self(buf))
|
||||
})
|
||||
.map_err(|_| base64::DecodeError::InvalidLength)?
|
||||
|
|
422
src/helpers.rs
422
src/helpers.rs
|
@ -11,43 +11,145 @@ pub fn new_pending_comment(
|
|||
dbs: &Dbs,
|
||||
) -> Result<CommentId, sled::Error> {
|
||||
let comment_id = CommentId::new();
|
||||
dbs.comment.insert(&comment_id, comment)?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(comment.clone(), CommentStatus::Pending))?;
|
||||
dbs.comment_pending.insert(
|
||||
&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
),
|
||||
&addr,
|
||||
&(addr, false),
|
||||
)?;
|
||||
Ok(comment_id)
|
||||
}
|
||||
|
||||
pub fn approve_comment(comment_id: CommentId, dbs: &Dbs) -> Result<(), sled::Error> {
|
||||
if let Some(comment) = dbs.comment.get(&comment_id)? {
|
||||
dbs.comment_pending.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment_approved
|
||||
.insert(&(comment.topic_hash, comment.post_time, comment_id), &())?;
|
||||
// TODO when approval disabled
|
||||
pub fn edit_comment(
|
||||
comment_id: CommentId,
|
||||
old_comment: Comment,
|
||||
comment_status: CommentStatus,
|
||||
edited_comment: Comment,
|
||||
addr: Option<IpAddr>,
|
||||
dbs: &Dbs,
|
||||
) -> Result<(), sled::Error> {
|
||||
match comment_status {
|
||||
CommentStatus::Pending => {
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(edited_comment, CommentStatus::Pending))?;
|
||||
// TODO should we update ip address in comment_pending?
|
||||
}
|
||||
CommentStatus::Approved => {
|
||||
dbs.comment_pending.insert(
|
||||
&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
),
|
||||
&(addr, true),
|
||||
)?;
|
||||
dbs.comment.insert(
|
||||
&comment_id,
|
||||
&(old_comment, CommentStatus::ApprovedEdited(edited_comment)),
|
||||
)?;
|
||||
}
|
||||
CommentStatus::ApprovedEdited(_old_edited_comment) => {
|
||||
dbs.comment.insert(
|
||||
&comment_id,
|
||||
&(old_comment, CommentStatus::ApprovedEdited(edited_comment)),
|
||||
)?;
|
||||
// TODO should we update ip address in comment_pending?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_comment(comment_id: CommentId, dbs: &Dbs) -> Result<Option<Comment>, sled::Error> {
|
||||
if let Some(comment) = dbs.comment.remove(&comment_id)? {
|
||||
pub fn approve_comment(comment_id: CommentId, dbs: &Dbs) -> Result<(), sled::Error> {
|
||||
if let Some((comment, CommentStatus::Pending)) = dbs.comment.get(&comment_id)? {
|
||||
dbs.comment_pending.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment_approved.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
dbs.comment_approved.insert(
|
||||
&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone(),
|
||||
),
|
||||
&(),
|
||||
)?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(comment, CommentStatus::Approved))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn approve_edit(comment_id: CommentId, dbs: &Dbs) -> Result<Option<Comment>, sled::Error> {
|
||||
if let Some((comment, CommentStatus::ApprovedEdited(edited_comment))) =
|
||||
dbs.comment.get(&comment_id)?
|
||||
{
|
||||
dbs.comment_pending.remove(&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(edited_comment, CommentStatus::Approved))?;
|
||||
return Ok(Some(comment));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn remove_comment(
|
||||
comment_id: CommentId,
|
||||
dbs: &Dbs,
|
||||
) -> Result<Option<(Comment, CommentStatus)>, sled::Error> {
|
||||
if let Some((comment, edited_comment)) = dbs.comment.remove(&comment_id)? {
|
||||
match &edited_comment {
|
||||
CommentStatus::Pending => {
|
||||
dbs.comment_pending.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
))?;
|
||||
}
|
||||
CommentStatus::Approved => {
|
||||
dbs.comment_approved.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
))?;
|
||||
}
|
||||
CommentStatus::ApprovedEdited(edited_comment) => {
|
||||
dbs.comment_pending.remove(&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment_approved.remove(&(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id,
|
||||
))?;
|
||||
}
|
||||
}
|
||||
return Ok(Some((comment, edited_comment)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn remove_edit(comment_id: CommentId, dbs: &Dbs) -> Result<Option<Comment>, sled::Error> {
|
||||
if let Some((comment, CommentStatus::ApprovedEdited(edited_comment))) =
|
||||
dbs.comment.get(&comment_id)?
|
||||
{
|
||||
dbs.comment_pending.remove(&(
|
||||
edited_comment.topic_hash.clone(),
|
||||
edited_comment.post_time,
|
||||
comment_id.clone(),
|
||||
))?;
|
||||
dbs.comment
|
||||
.insert(&comment_id, &(comment.clone(), CommentStatus::Approved))?;
|
||||
return Ok(Some(comment));
|
||||
}
|
||||
Ok(None)
|
||||
|
@ -57,15 +159,15 @@ pub fn iter_comments_by_topic<'a, V: typed_sled::KV>(
|
|||
topic_hash: TopicHash,
|
||||
tree: &'a Tree<(TopicHash, Time, CommentId), V>,
|
||||
dbs: &'a Dbs,
|
||||
) -> impl Iterator<Item = (CommentId, Comment, V)> + 'a {
|
||||
) -> impl Iterator<Item = (CommentId, Comment, CommentStatus, V)> + 'a {
|
||||
tree.range(
|
||||
(topic_hash.clone(), 0, CommentId::zero())..=(topic_hash, Time::MAX, CommentId::max()),
|
||||
)
|
||||
.filter_map(|entry| {
|
||||
let ((_topic_hash, _time, comment_id), val) = entry
|
||||
.map_err(|e| error!("Reading comment_by_topic_and_time: {:?}", e))
|
||||
.map_err(|e| error!("Reading comment index: {:?}", e))
|
||||
.ok()?;
|
||||
let comment = dbs
|
||||
let (comment, comment_status) = dbs
|
||||
.comment
|
||||
.get(&comment_id)
|
||||
.map_err(|e| error!("Reading comment: {:?}", e))
|
||||
|
@ -74,23 +176,27 @@ pub fn iter_comments_by_topic<'a, V: typed_sled::KV>(
|
|||
error!("Comment not found");
|
||||
None
|
||||
})?;
|
||||
Some((comment_id, comment, val))
|
||||
Some((comment_id, comment, comment_status, val))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter_approved_comments_by_topic(
|
||||
topic_hash: TopicHash,
|
||||
dbs: &Dbs,
|
||||
) -> impl Iterator<Item = (CommentId, Comment)> + '_ {
|
||||
) -> impl Iterator<Item = (CommentId, Comment, CommentStatus)> + '_ {
|
||||
iter_comments_by_topic(topic_hash, &dbs.comment_approved, dbs)
|
||||
.map(|(comment_id, comment, ())| (comment_id, comment))
|
||||
.map(|(comment_id, comment, comment_status, ())| (comment_id, comment, comment_status))
|
||||
}
|
||||
|
||||
pub fn iter_pending_comments_by_topic(
|
||||
topic_hash: TopicHash,
|
||||
dbs: &Dbs,
|
||||
) -> impl Iterator<Item = (CommentId, Comment, Option<IpAddr>)> + '_ {
|
||||
iter_comments_by_topic(topic_hash, &dbs.comment_pending, dbs)
|
||||
) -> impl Iterator<Item = (CommentId, Comment, Option<IpAddr>, CommentStatus)> + '_ {
|
||||
iter_comments_by_topic(topic_hash, &dbs.comment_pending, dbs).map(
|
||||
|(comment_id, comment, comment_status, (addr, _is_edit))| {
|
||||
(comment_id, comment, addr, comment_status)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns Some(time_left) if the client is banned.
|
||||
|
@ -248,9 +354,11 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn test_comment() {
|
||||
// Post a comment
|
||||
|
||||
let comment = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Emmanuel Goldstein"),
|
||||
author: String::from("Jerry"),
|
||||
email: None,
|
||||
last_edit_time: None,
|
||||
mutation_token: MutationToken::new(),
|
||||
|
@ -262,7 +370,13 @@ mod test {
|
|||
let comment_id = new_pending_comment(&comment, None, &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(iter.next(), Some(Ok((comment_id.clone(), comment.clone()))));
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment.clone(), CommentStatus::Pending)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
|
@ -274,13 +388,265 @@ mod test {
|
|||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
None
|
||||
(None, false)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit the comment
|
||||
|
||||
let comment2 = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Jerry Smith"),
|
||||
email: Some(String::from("jerry.smith@example.tld")),
|
||||
last_edit_time: Some(137),
|
||||
mutation_token: comment.mutation_token.clone(),
|
||||
post_time: 42,
|
||||
text: String::from("Good bye world!"),
|
||||
};
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment.clone(),
|
||||
CommentStatus::Pending,
|
||||
comment2.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment2.clone(), CommentStatus::Pending)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
(None, false)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Approve the comment
|
||||
|
||||
approve_comment(comment_id.clone(), &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment2.clone(), CommentStatus::Approved)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit the approved comment
|
||||
|
||||
let comment3 = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Jerry Smith is back"),
|
||||
email: Some(String::from("jerry.smith@example.tld")),
|
||||
last_edit_time: Some(666),
|
||||
mutation_token: comment.mutation_token.clone(),
|
||||
post_time: 42,
|
||||
text: String::from("Hello again!"),
|
||||
};
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment2.clone(),
|
||||
CommentStatus::Approved,
|
||||
comment3.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(
|
||||
comment2.clone(),
|
||||
CommentStatus::ApprovedEdited(comment3.clone())
|
||||
)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
(None, true)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit the edited approved comment
|
||||
|
||||
let comment4 = Comment {
|
||||
topic_hash: TopicHash::from_topic("test"),
|
||||
author: String::from("Jerry Smith is still back"),
|
||||
email: Some(String::from("jerry.smith@example.tld")),
|
||||
last_edit_time: Some(1337),
|
||||
mutation_token: comment.mutation_token.clone(),
|
||||
post_time: 42,
|
||||
text: String::from("Hello again one more time!"),
|
||||
};
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment2.clone(),
|
||||
CommentStatus::ApprovedEdited(comment3.clone()),
|
||||
comment4.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment2, CommentStatus::ApprovedEdited(comment4.clone()))
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
(None, true)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Approve the edit
|
||||
|
||||
approve_edit(comment_id.clone(), &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment4.clone(), CommentStatus::Approved)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_approved.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
(
|
||||
comment.topic_hash.clone(),
|
||||
comment.post_time,
|
||||
comment_id.clone()
|
||||
),
|
||||
()
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
// Edit and remove the edit
|
||||
|
||||
edit_comment(
|
||||
comment_id.clone(),
|
||||
comment4.clone(),
|
||||
CommentStatus::Approved,
|
||||
comment.clone(),
|
||||
None,
|
||||
&dbs,
|
||||
)
|
||||
.unwrap();
|
||||
remove_edit(comment_id.clone(), &dbs).unwrap();
|
||||
|
||||
let mut iter = dbs.comment.iter();
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(Ok((
|
||||
comment_id.clone(),
|
||||
(comment4.clone(), CommentStatus::Approved)
|
||||
)))
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
let mut iter = dbs.comment_pending.iter();
|
||||
assert_eq!(iter.next(), None);
|
||||
|
||||
|
|
|
@ -65,11 +65,21 @@ pub struct ApproveQuery {
|
|||
pub approve: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ApproveEditQuery {
|
||||
pub approve_edit: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct RemoveQuery {
|
||||
pub remove: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct RemoveEditQuery {
|
||||
pub remove_edit: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct EditQuery {
|
||||
pub edit: String,
|
||||
|
|
156
src/server.rs
156
src/server.rs
|
@ -119,7 +119,7 @@ async fn serve_edit_comment<'a>(
|
|||
return serve_comments(req, config, templates, dbs, client_langs, context, 400).await;
|
||||
};
|
||||
|
||||
let Some(comment) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
let Some((comment, _edited_comment)) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
context.insert("log", &["not found comment"]);
|
||||
return serve_comments(req, config, templates, dbs, client_langs, context, 404).await;
|
||||
};
|
||||
|
@ -189,6 +189,13 @@ async fn serve_comments<'a>(
|
|||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<ApproveEditQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.approve_edit) {
|
||||
helpers::approve_edit(comment_id, &dbs)
|
||||
.map_err(|e| error!("Approving edit: {:?}", e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<RemoveQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.remove) {
|
||||
helpers::remove_comment(comment_id, &dbs)
|
||||
|
@ -196,9 +203,16 @@ async fn serve_comments<'a>(
|
|||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<RemoveEditQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.remove_edit) {
|
||||
helpers::remove_edit(comment_id, &dbs)
|
||||
.map_err(|e| error!("Removing edit: {:?}", e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
if let Ok(query) = req.query::<EditQuery>() {
|
||||
if let Ok(comment_id) = CommentId::from_base64(&query.edit) {
|
||||
if let Some(comment) = dbs.comment.get(&comment_id).unwrap() {
|
||||
if let Some((comment, _comment_status)) = dbs.comment.get(&comment_id).unwrap() {
|
||||
context.insert("edit_comment", &comment_id.to_base64());
|
||||
context.insert("edit_comment_author", &comment.author);
|
||||
context.insert("edit_comment_email", &comment.email);
|
||||
|
@ -210,14 +224,38 @@ async fn serve_comments<'a>(
|
|||
context.insert(
|
||||
"comments_pending",
|
||||
&helpers::iter_pending_comments_by_topic(topic_hash.clone(), &dbs)
|
||||
.map(|(comment_id, comment, addr)| CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: admin,
|
||||
id: comment_id.to_base64(),
|
||||
needs_approval: true,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
.map(|(comment_id, comment, addr, comment_status)| {
|
||||
if let CommentStatus::ApprovedEdited(edited_comment) = comment_status {
|
||||
CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: edited_comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: edited_comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: Some(OriginalComment {
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
last_edit_time: comment.last_edit_time,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
}),
|
||||
post_time: edited_comment.post_time,
|
||||
text: edited_comment.text,
|
||||
}
|
||||
} else {
|
||||
CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: None,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<CommentWithId>>(),
|
||||
);
|
||||
|
@ -226,12 +264,14 @@ async fn serve_comments<'a>(
|
|||
context.insert(
|
||||
"comments",
|
||||
&helpers::iter_approved_comments_by_topic(topic_hash, &dbs)
|
||||
.map(|(comment_id, comment)| CommentWithId {
|
||||
.map(|(comment_id, comment, _comment_status)| CommentWithId {
|
||||
addr: None,
|
||||
author: comment.author,
|
||||
editable: admin,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
needs_approval: false,
|
||||
original: None,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
})
|
||||
|
@ -272,10 +312,10 @@ async fn serve_admin<'a>(
|
|||
&dbs.comment_pending
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let ((_topic_hash, _time, comment_id), addr) = entry
|
||||
let ((_topic_hash, _time, comment_id), (addr, _is_edit)) = entry
|
||||
.map_err(|e| error!("Reading comment_pending: {:?}", e))
|
||||
.ok()?;
|
||||
let comment = dbs
|
||||
let (comment, comment_status) = dbs
|
||||
.comment
|
||||
.get(&comment_id)
|
||||
.map_err(|e| error!("Reading comment: {:?}", e))
|
||||
|
@ -284,15 +324,37 @@ async fn serve_admin<'a>(
|
|||
error!("Comment not found");
|
||||
None
|
||||
})?;
|
||||
Some(CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
needs_approval: true,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
})
|
||||
if let CommentStatus::ApprovedEdited(edited_comment) = comment_status {
|
||||
Some(CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: edited_comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: edited_comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: Some(OriginalComment {
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
last_edit_time: comment.last_edit_time,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
}),
|
||||
post_time: edited_comment.post_time,
|
||||
text: edited_comment.text,
|
||||
})
|
||||
} else {
|
||||
Some(CommentWithId {
|
||||
addr: addr.map(|addr| addr.to_string()),
|
||||
author: comment.author,
|
||||
editable: true,
|
||||
id: comment_id.to_base64(),
|
||||
last_edit_time: comment.last_edit_time,
|
||||
needs_approval: true,
|
||||
original: None,
|
||||
post_time: comment.post_time,
|
||||
text: comment.text,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<CommentWithId>>(),
|
||||
);
|
||||
|
@ -456,6 +518,7 @@ async fn handle_post_comments(
|
|||
.unwrap()],
|
||||
);
|
||||
}
|
||||
// TODO add message to client log and change http code
|
||||
Err(e) => error!("Adding pending comment: {:?}", e),
|
||||
}
|
||||
} else {
|
||||
|
@ -466,16 +529,20 @@ async fn handle_post_comments(
|
|||
context.insert("new_comment_errors", &errors);
|
||||
}
|
||||
CommentQuery::EditComment(query) => {
|
||||
helpers::check_comment(config, locales, &client_langs, &query.comment, &mut errors);
|
||||
let Ok(topic) = req.param("topic") else {
|
||||
return Err(tide::Error::from_str(404, "No topic"))
|
||||
};
|
||||
|
||||
let Ok(comment_id) = CommentId::from_base64(&query.id) else {
|
||||
return Err(tide::Error::from_str(400, "Invalid comment id"));
|
||||
};
|
||||
|
||||
let Some(mut comment) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
let Some((old_comment, old_edited_comment)) = dbs.comment.get(&comment_id).unwrap() else {
|
||||
return Err(tide::Error::from_str(404, "Not found"));
|
||||
};
|
||||
|
||||
helpers::check_comment(config, locales, &client_langs, &query.comment, &mut errors);
|
||||
|
||||
let mutation_token = if admin {
|
||||
None
|
||||
} else {
|
||||
|
@ -491,7 +558,7 @@ async fn handle_post_comments(
|
|||
};
|
||||
|
||||
if let Err(e) =
|
||||
helpers::check_can_edit_comment(config, &comment, &mutation_token)
|
||||
helpers::check_can_edit_comment(config, &old_comment, &mutation_token)
|
||||
{
|
||||
errors.push(e.to_string());
|
||||
}
|
||||
|
@ -535,6 +602,8 @@ async fn handle_post_comments(
|
|||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let mut comment = old_comment.clone();
|
||||
|
||||
comment.author = if query.comment.author.is_empty() {
|
||||
petname::Petnames::large().generate_one(2, " ")
|
||||
} else {
|
||||
|
@ -548,7 +617,42 @@ async fn handle_post_comments(
|
|||
comment.text = query.comment.text;
|
||||
comment.last_edit_time = Some(time);
|
||||
|
||||
dbs.comment.insert(&comment_id, &comment).unwrap();
|
||||
match helpers::edit_comment(
|
||||
comment_id.clone(),
|
||||
old_comment,
|
||||
old_edited_comment,
|
||||
comment.clone(),
|
||||
client_addr,
|
||||
&dbs,
|
||||
) {
|
||||
Ok(()) => {
|
||||
context.insert(
|
||||
"log",
|
||||
&[locales
|
||||
.tr(
|
||||
&client_langs,
|
||||
if config.comment_approve {
|
||||
"edit_comment-success_pending"
|
||||
} else {
|
||||
"edit_comment-success"
|
||||
},
|
||||
Some(&FluentArgs::from_iter([(
|
||||
"edit_link",
|
||||
format!(
|
||||
"{}t/{}/edit/{}/{}",
|
||||
&config.root_url,
|
||||
topic,
|
||||
comment_id.to_base64(),
|
||||
comment.mutation_token.to_base64(),
|
||||
),
|
||||
)])),
|
||||
)
|
||||
.unwrap()],
|
||||
);
|
||||
}
|
||||
// TODO add message to client log and change http code
|
||||
Err(e) => error!("Editing comment: {:?}", e),
|
||||
}
|
||||
} else {
|
||||
context.insert("edit_comment", &comment_id.to_base64());
|
||||
if let Some(mutation_token) = &mutation_token {
|
||||
|
|
|
@ -41,7 +41,18 @@ pub struct CommentWithId {
|
|||
pub author: String,
|
||||
pub editable: bool,
|
||||
pub id: String,
|
||||
pub last_edit_time: Option<Time>,
|
||||
pub needs_approval: bool,
|
||||
pub original: 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,
|
||||
}
|
||||
|
|
|
@ -16,42 +16,76 @@
|
|||
{% if comments_pending %}
|
||||
<div id="comments_pending">
|
||||
{% for comment in comments_pending %}
|
||||
<div class="comment{% if comment.needs_approval %} comment_pending{% endif %}" id="comment-{{ comment.id | safe }}">
|
||||
<span class="comment-author">{{ comment.author }}</span>
|
||||
{% if comment.addr %}
|
||||
<span class="comment-addr">{{ comment.addr }}</span>
|
||||
{% endif %}
|
||||
<span class="comment-date">{{ comment.post_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% if comment.editable %}
|
||||
<div class="comment{% if comment.needs_approval %} comment_pending{% endif %}" id="comment-{{ comment.id | safe }}">
|
||||
<span class="comment-author">{{ comment.author }}</span>
|
||||
{% if comment.addr %}
|
||||
<span class="comment-addr">{{ comment.addr }}</span>
|
||||
{% endif %}
|
||||
<span class="comment-date">{{ comment.post_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% if comment.last_edit_time %}
|
||||
<span class="comment-date">{{ comment.last_edit_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% endif %}
|
||||
{% if comment.editable %}
|
||||
{% if comment.original %}
|
||||
<a href="?edit_edit={{ comment.id | safe }}#edit_comment-form">{{ tr(l=l,k="admin-comment-edit-edit")|safe }}</a>
|
||||
{% else %}
|
||||
<a href="?edit={{ comment.id | safe }}#edit_comment-form">{{ tr(l=l,k="admin-comment-edit")|safe }}</a>
|
||||
{% endif %}
|
||||
{% if admin and comment.needs_approval %}
|
||||
<a href="?approve={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-approve")|safe }}</a>
|
||||
{% endif %}
|
||||
{% if admin %}
|
||||
{% if comment.needs_approval %}
|
||||
{% if comment.original %}
|
||||
<a href="?approve_edit={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-approve-edit")|safe }}</a>
|
||||
{% else %}
|
||||
<a href="?approve={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-approve")|safe }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if comment.original %}
|
||||
<a href="?remove_edit={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-remove-edit")|safe }}</a>
|
||||
{% else %}
|
||||
<a href="?remove={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-remove")|safe }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<p class="comment-text">{{ comment.text }}</p>
|
||||
{% if admin and comment.original %}
|
||||
<div class="comment comment_original" id="comment-{{ comment.id | safe }}-original">
|
||||
<span class="comment-author">{{ comment.original.author }}</span>
|
||||
<span class="comment-date">{{ comment.original.post_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% if comment.original.last_edit_time %}
|
||||
<span class="comment-date">{{ comment.original.last_edit_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% endif %}
|
||||
{% if comment.editable %}
|
||||
<a href="?edit={{ comment.id | safe }}#edit_comment-form">{{ tr(l=l,k="admin-comment-edit")|safe }}</a>
|
||||
{% endif %}
|
||||
{% if admin %}
|
||||
<a href="?remove={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-remove")|safe }}</a>
|
||||
{% endif %}
|
||||
<p class="comment-text">{{ comment.text }}</p>
|
||||
<p class="comment-text">{{ comment.original.text }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="comments">
|
||||
{% for comment in comments %}
|
||||
<div class="comment{% if comment.needs_approval %} comment_pending{% endif %}" id="comment-{{ comment.id | safe }}">
|
||||
<span class="comment-author">{{ comment.author }}</span>
|
||||
<span class="comment-date">{{ comment.post_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% if comment.editable %}
|
||||
<a href="?edit={{ comment.id | safe }}#edit_comment-form">{{ tr(l=l,k="admin-comment-edit")|safe }}</a>
|
||||
{% endif %}
|
||||
{% if admin and comment.needs_approval %}
|
||||
<a href="?approve={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-approve")|safe }}</a>
|
||||
{% endif %}
|
||||
{% if admin %}
|
||||
<a href="?remove={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-remove")|safe }}</a>
|
||||
{% endif %}
|
||||
<p class="comment-text">{{ comment.text }}</p>
|
||||
</div>
|
||||
<div class="comment{% if comment.needs_approval %} comment_pending{% endif %}" id="comment-{{ comment.id | safe }}">
|
||||
<span class="comment-author">{{ comment.author }}</span>
|
||||
<span class="comment-date">{{ comment.post_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% if comment.last_edit_time %}
|
||||
<span class="comment-date">{{ comment.last_edit_time | date(format="%F %R", locale=time_lang) }}</span>
|
||||
{% endif %}
|
||||
{% if comment.editable %}
|
||||
<a href="?edit={{ comment.id | safe }}#edit_comment-form">{{ tr(l=l,k="admin-comment-edit")|safe }}</a>
|
||||
{% endif %}
|
||||
{% if admin and comment.needs_approval %}
|
||||
<a href="?approve={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-approve")|safe }}</a>
|
||||
{% endif %}
|
||||
{% if admin %}
|
||||
<a href="?remove={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-remove")|safe }}</a>
|
||||
{% endif %}
|
||||
<p class="comment-text">{{ comment.text }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<form id="new_comment-form" action="#new_comment-form" method="post">
|
||||
|
|
Loading…
Reference in a new issue