feat: edit approval

This commit is contained in:
Pascal Engélibert 2022-12-29 22:46:21 +01:00
parent c13e172938
commit 0c3ea546fd
Signed by: tuxmain
GPG Key ID: 3504BC6D362F7DCA
9 changed files with 750 additions and 185 deletions

197
Cargo.lock generated
View File

@ -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",
]

View File

@ -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]

View File

@ -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 }.

View File

@ -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)?

View File

@ -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);

View File

@ -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,

View File

@ -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,