diff --git a/Cargo.lock b/Cargo.lock index b697fa8..cc82983 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 64ea791..a36724b 100644 --- a/Cargo.toml +++ b/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] diff --git a/locales/en.ftl b/locales/en.ftl index 8ce2d08..0c8b35d 100644 --- a/locales/en.ftl +++ b/locales/en.ftl @@ -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. You can edit it with this link. +edit_comment-success_pending = Your edit has been saved. It will be reviewed by an administrator before being published. You can edit it with this link. 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 }. diff --git a/src/db.rs b/src/db.rs index 544137e..c4b08f7 100644 --- a/src/db.rs +++ b/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, + pub comment: Tree, pub comment_approved: Tree<(TopicHash, Time, CommentId), ()>, - pub comment_pending: Tree<(TopicHash, Time, CommentId), Option>, + /// -> (client_addr, is_edit) + pub comment_pending: Tree<(TopicHash, Time, CommentId), (Option, bool)>, /// client_addr -> (last_mutation, mutation_count) pub client_mutation: Tree, } @@ -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 { 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 { 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)? diff --git a/src/helpers.rs b/src/helpers.rs index 7ccd231..8ca1b6a 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -11,43 +11,145 @@ pub fn new_pending_comment( dbs: &Dbs, ) -> Result { 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, + 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, 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, 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, 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, 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 + 'a { +) -> impl Iterator + '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 + '_ { +) -> impl Iterator + '_ { 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)> + '_ { - iter_comments_by_topic(topic_hash, &dbs.comment_pending, dbs) +) -> impl Iterator, 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); diff --git a/src/queries.rs b/src/queries.rs index f59a70e..1ea18b8 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -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, diff --git a/src/server.rs b/src/server.rs index b9acac4..06a19fe 100644 --- a/src/server.rs +++ b/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::() { + 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::() { 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::() { + 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::() { 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::>(), ); @@ -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::>(), ); @@ -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 { diff --git a/src/templates.rs b/src/templates.rs index 987fe7d..cdd1667 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -41,7 +41,18 @@ pub struct CommentWithId { pub author: String, pub editable: bool, pub id: String, + pub last_edit_time: Option