Compare commits

..

2 commits

Author SHA1 Message Date
7ee817de46
feat: locales 2022-12-04 10:12:34 +01:00
0d7356ec1f
opti: Replace Arc with &'static 2022-10-29 11:03:37 +02:00
13 changed files with 637 additions and 239 deletions

416
Cargo.lock generated
View file

@ -69,9 +69,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.19"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
@ -116,9 +116,9 @@ checksum = "5f093eed78becd229346bf859eec0aa4dd7ddde0757287b2b4107a1f09c80002"
[[package]]
name = "async-channel"
version = "1.7.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28"
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
"concurrent-queue",
"event-listener",
@ -137,23 +137,23 @@ dependencies = [
[[package]]
name = "async-executor"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
dependencies = [
"async-lock",
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca"
checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
dependencies = [
"async-channel",
"async-executor",
@ -182,31 +182,32 @@ dependencies = [
[[package]]
name = "async-io"
version = "1.9.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7"
checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794"
dependencies = [
"async-lock",
"autocfg",
"concurrent-queue",
"futures-lite",
"libc",
"log",
"once_cell",
"parking",
"polling",
"slab",
"socket2",
"waker-fn",
"winapi",
"windows-sys",
]
[[package]]
name = "async-lock"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
dependencies = [
"event-listener",
"futures-lite",
]
[[package]]
@ -217,20 +218,20 @@ checksum = "72faff1fdc615a0199d7bf71e6f389af54d46a66e9beb5d76c39e48eda93ecce"
[[package]]
name = "async-process"
version = "1.5.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c"
checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4"
dependencies = [
"async-io",
"async-lock",
"autocfg",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"libc",
"once_cell",
"signal-hook",
"winapi",
"windows-sys",
]
[[package]]
@ -303,9 +304,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
[[package]]
name = "async-trait"
version = "0.1.58"
version = "0.1.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c"
checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
dependencies = [
"proc-macro2",
"quote",
@ -373,11 +374,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2"
version = "0.10.4"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388"
checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e"
dependencies = [
"digest 0.10.5",
"digest 0.10.6",
]
[[package]]
@ -400,16 +401,16 @@ dependencies = [
[[package]]
name = "blocking"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc"
checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8"
dependencies = [
"async-channel",
"async-lock",
"async-task",
"atomic-waker",
"fastrand",
"futures-lite",
"once_cell",
]
[[package]]
@ -435,21 +436,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cc"
version = "1.0.73"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]]
name = "cfg-if"
@ -459,9 +454,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"num-integer",
@ -503,9 +498,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.0.18"
version = "4.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b"
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
dependencies = [
"bitflags",
"clap_derive",
@ -515,9 +510,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.0.18"
version = "4.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3"
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
dependencies = [
"heck",
"proc-macro-error",
@ -557,11 +552,11 @@ dependencies = [
[[package]]
name = "concurrent-queue"
version = "1.2.4"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c"
checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b"
dependencies = [
"cache-padded",
"crossbeam-utils",
]
[[package]]
@ -629,9 +624,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.11"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
@ -642,9 +637,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.12"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
@ -690,9 +685,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.80"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a"
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
dependencies = [
"cc",
"cxxbridge-flags",
@ -702,9 +697,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.80"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827"
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
dependencies = [
"cc",
"codespan-reporting",
@ -717,15 +712,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.80"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a"
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
[[package]]
name = "cxxbridge-macro"
version = "1.0.80"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7"
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
dependencies = [
"proc-macro2",
"quote",
@ -734,9 +729,9 @@ dependencies = [
[[package]]
name = "darling"
version = "0.14.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
dependencies = [
"darling_core",
"darling_macro",
@ -744,9 +739,9 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.14.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
dependencies = [
"fnv",
"ident_case",
@ -758,9 +753,9 @@ dependencies = [
[[package]]
name = "darling_macro"
version = "0.14.1"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
dependencies = [
"darling_core",
"quote",
@ -777,7 +772,7 @@ dependencies = [
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core 0.9.4",
"parking_lot_core 0.9.5",
]
[[package]]
@ -828,9 +823,9 @@ dependencies = [
[[package]]
name = "digest"
version = "0.10.5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer 0.10.3",
"crypto-common",
@ -863,6 +858,17 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "displaydoc"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.8.0"
@ -918,6 +924,40 @@ dependencies = [
"web-sys",
]
[[package]]
name = "fluent-bundle"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd"
dependencies = [
"fluent-langneg",
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash",
"self_cell",
"smallvec",
"unic-langid",
]
[[package]]
name = "fluent-langneg"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
dependencies = [
"unic-langid",
]
[[package]]
name = "fluent-syntax"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78"
dependencies = [
"thiserror",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -1137,9 +1177,9 @@ dependencies = [
[[package]]
name = "gloo-timers"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9"
checksum = "98c4a8d6391675c6b2ee1a6c8d06e8e2d03605c44cec1270675985a4c2a5500b"
dependencies = [
"futures-channel",
"futures-core",
@ -1286,9 +1326,9 @@ checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
[[package]]
name = "hyper"
version = "0.14.20"
version = "0.14.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
dependencies = [
"bytes",
"futures-channel",
@ -1310,9 +1350,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
version = "0.23.0"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d"
dependencies = [
"http",
"hyper",
@ -1323,9 +1363,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.51"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@ -1381,9 +1421,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.9.1"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
@ -1406,10 +1446,29 @@ dependencies = [
]
[[package]]
name = "ipnet"
version = "2.5.0"
name = "intl-memoizer"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f"
dependencies = [
"type-map",
"unic-langid",
]
[[package]]
name = "intl_pluralrules"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
dependencies = [
"unic-langid",
]
[[package]]
name = "ipnet"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
[[package]]
name = "itertools"
@ -1470,9 +1529,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.136"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "link-cplusplus"
@ -1601,9 +1660,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
@ -1647,9 +1706,9 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.13.1"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
dependencies = [
"hermit-abi",
"libc",
@ -1657,9 +1716,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "opaque-debug"
@ -1669,9 +1728,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "parking"
@ -1706,9 +1765,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
dependencies = [
"cfg-if",
"libc",
@ -1745,9 +1804,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
version = "2.4.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a"
checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0"
dependencies = [
"thiserror",
"ucd-trie",
@ -1755,9 +1814,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.4.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2"
checksum = "cdc078600d06ff90d4ed238f0119d84ab5d43dbaad278b0e33a8820293b32344"
dependencies = [
"pest",
"pest_generator",
@ -1765,9 +1824,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.4.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db"
checksum = "28a1af60b1c4148bb269006a750cff8e2ea36aff34d2d96cf7be0b14d1bed23c"
dependencies = [
"pest",
"pest_meta",
@ -1778,9 +1837,9 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.4.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d"
checksum = "fec8605d59fc2ae0c6c1aefc0c7c7a9769732017c0ce07f7a9cfffa7b4404f20"
dependencies = [
"once_cell",
"pest",
@ -1866,16 +1925,16 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polling"
version = "2.4.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2"
checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748"
dependencies = [
"autocfg",
"cfg-if",
"libc",
"log",
"wepoll-ffi",
"winapi",
"windows-sys",
]
[[package]]
@ -1891,9 +1950,9 @@ dependencies = [
[[package]]
name = "ppv-lite86"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-crate"
@ -2053,9 +2112,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
@ -2064,15 +2123,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.27"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "reqwest"
version = "0.11.12"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
dependencies = [
"base64",
"bytes",
@ -2130,9 +2189,20 @@ checksum = "56770675ebc04927ded3e60633437841581c285dc6236109ea25fbf3beb7b59e"
[[package]]
name = "rpassword"
version = "7.1.0"
version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c9f5d2a0c3e2ea729ab3706d22217177770654c3ef5056b68b69d07332d3f5"
checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
dependencies = [
"libc",
"rtoolbox",
"winapi",
]
[[package]]
name = "rtoolbox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
dependencies = [
"libc",
"winapi",
@ -2235,6 +2305,12 @@ dependencies = [
"toml",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -2302,6 +2378,12 @@ dependencies = [
"untrusted",
]
[[package]]
name = "self_cell"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af"
[[package]]
name = "semver"
version = "0.9.0"
@ -2319,18 +2401,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.147"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.147"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
dependencies = [
"proc-macro2",
"quote",
@ -2348,9 +2430,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.87"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [
"itoa",
"ryu",
@ -2397,7 +2479,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.5",
"digest 0.10.6",
]
[[package]]
@ -2427,7 +2509,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.5",
"digest 0.10.6",
]
[[package]]
@ -2529,6 +2611,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "static-rc"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91d0104a7b28aeda24b30919f83222570111ac0bf1aab23aaffb8f59330e654"
[[package]]
name = "stdweb"
version = "0.4.20"
@ -2601,9 +2689,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.103"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [
"proc-macro2",
"quote",
@ -2742,6 +2830,15 @@ dependencies = [
"syn",
]
[[package]]
name = "tinystr"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aeafdfd935e4a7fe16a91ab711fa52d54df84f9c8f7ca5837a9d1d902ef4c2"
dependencies = [
"displaydoc",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -2759,9 +2856,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.21.2"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3"
dependencies = [
"autocfg",
"bytes",
@ -2777,9 +2874,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "1.8.0"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
@ -2886,6 +2983,15 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "type-map"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46"
dependencies = [
"rustc-hash",
]
[[package]]
name = "typed-sled"
version = "0.2.0"
@ -2941,6 +3047,49 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-langid"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "398f9ad7239db44fd0f80fe068d12ff22d78354080332a5077dc6f52f14dcf2f"
dependencies = [
"unic-langid-impl",
"unic-langid-macros",
]
[[package]]
name = "unic-langid-impl"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35bfd2f2b8796545b55d7d3fd3e89a0613f68a0d1c8bc28cb7ff96b411a35ff"
dependencies = [
"tinystr",
]
[[package]]
name = "unic-langid-macros"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "055e618bf694161ffff0466d95cef3e1a5edc59f6ba1888e97801f2b4ebdc4fe"
dependencies = [
"proc-macro-hack",
"tinystr",
"unic-langid-impl",
"unic-langid-macros-impl",
]
[[package]]
name = "unic-langid-macros-impl"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f5cdec05b907f4e2f6843f4354f4ce6a5bebe1a56df320a49134944477ce4d8"
dependencies = [
"proc-macro-hack",
"quote",
"syn",
"unic-langid-impl",
]
[[package]]
name = "unic-segment"
version = "0.9.0"
@ -3033,9 +3182,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
dependencies = [
"getrandom 0.2.8",
]
@ -3201,6 +3350,9 @@ dependencies = [
"clap",
"crossbeam-channel",
"directories",
"fluent-bundle",
"fluent-langneg",
"intl-memoizer",
"log",
"matrix-sdk",
"rand 0.8.5",
@ -3209,11 +3361,13 @@ dependencies = [
"serde",
"sha2 0.10.6",
"sled",
"static-rc",
"tera",
"tide",
"tokio",
"toml_edit",
"typed-sled",
"unic-langid",
]
[[package]]
@ -3358,9 +3512,9 @@ dependencies = [
[[package]]
name = "zeroize_derive"
version = "1.3.2"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17"
checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
dependencies = [
"proc-macro2",
"quote",

View file

@ -10,19 +10,24 @@ edition = "2021"
[dependencies]
argon2 = "0.4.1"
base64 = "0.13.1"
clap = { version = "4.0.18", default-features = false, features = ["derive", "std"] }
clap = { version = "4.0.29", default-features = false, features = ["derive", "error-context", "help", "std", "usage"] }
crossbeam-channel = "0.5.6"
directories = "4.0.1"
fluent-bundle = "0.15.2"
fluent-langneg = "0.13.0"
intl-memoizer = "0.5.1"
log = "0.4.17"
matrix-sdk = { version = "0.6.2", default-features = false, features = ["rustls-tls"] }
rand = "0.8.5"
rand_core = { version = "0.6.4", features = ["std"] }
rpassword = "7.1.0"
serde = { version = "1.0.147", features = ["derive", "rc"] }
rpassword = "7.2.0"
serde = { version = "1.0.148", features = ["derive", "rc"] }
sha2 = "0.10.6"
sled = "0.34.7"
static-rc = "0.6.1"
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.21.2", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] }
toml_edit = { version = "0.15.0", features = ["easy"] }
typed-sled = "0.2.0"
unic-langid = { version = "0.9.1", features = ["macros"] }

View file

@ -10,8 +10,9 @@ Rust webserver for comments, that you can easily embed in a website.
* Admin approval
* Admin notification on new comment via Matrix
* Embedded one-file webserver
* [Tera](https://github.com/Keats/tera) templates
* Customizable [Tera](https://github.com/Keats/tera) templates
* Comment frequency limit per IP
* i18n
## Use

14
locales/en.ftl Normal file
View file

@ -0,0 +1,14 @@
admin-comment-approve = Approve
admin-comment-edit = Edit
admin-comment-remove = Remove
admin_login-password_prompt = Password:
admin_login-submit_button = Login
admin_login-title = Admin login | Comments
error-antispam = The edition quota from your IP is reached. You will be unblocked in { $antispam_timeout }s.
error-list = Whoops, the following error occurred:
comment_form-author = Your name:
comment_form-email = Your email:
comment_form-edit_button = Edit comment
comment_form-new_button = Post comment
comment_form-text = Your comment:
title = Comments

14
locales/fr.ftl Normal file
View file

@ -0,0 +1,14 @@
admin-comment-approve = Approuver
admin-comment-edit = Modifier
admin-comment-remove = Supprimer
admin_login-password_prompt = Mot de passe :
admin_login-submit_button = S'authentifier
admin_login-title = Authentification admin | Commentaires
error-antispam = Le quota d'édition de votre adresse IP est atteint, elle sera débloquée dans { $antispam_timeout }s.
error-list = Oups, l'erreur suivante est survenue :
comment_form-author = Votre nom :
comment_form-email = Votre e-mail :
comment_form-edit_button = Modifier
comment_form-new_button = Envoyer
comment_form-text = Votre commentaire :
title = Commentaires

View file

@ -1,8 +1,8 @@
use crate::{config::Config, db::*};
use std::{sync::Arc, time::Duration};
use std::time::Duration;
pub async fn run_cleaner(config: Arc<Config>, dbs: Dbs) {
pub async fn run_cleaner(config: &Config, dbs: Dbs) {
let mut last_db_clean = 0;
loop {
let time = std::time::SystemTime::now()
@ -11,7 +11,7 @@ pub async fn run_cleaner(config: Arc<Config>, dbs: Dbs) {
.as_secs();
if time > last_db_clean + 3600 {
clean_antispam(config.clone(), dbs.clone(), time);
clean_antispam(config, dbs.clone(), time);
last_db_clean = time;
}
@ -19,7 +19,7 @@ pub async fn run_cleaner(config: Arc<Config>, dbs: Dbs) {
}
}
fn clean_antispam(config: Arc<Config>, dbs: Dbs, time: u64) {
fn clean_antispam(config: &Config, dbs: Dbs, time: u64) {
for (addr, (last_mutation, _mutation_count)) in
dbs.client_mutation.iter().filter_map(|o| o.ok())
{

View file

@ -36,8 +36,9 @@ pub struct Config {
pub cookies_https_only: bool,
#[serde(default = "Config::default_cookies_domain")]
pub cookies_domain: Option<String>,
#[serde(default = "Config::default_lang")]
pub lang: String,
/// Format: "language_REGION"
#[serde(default = "Config::default_default_lang")]
pub default_lang: String,
#[serde(default = "Config::default_listen")]
pub listen: SocketAddr,
/// Send a matrix message on new comment
@ -102,8 +103,8 @@ impl Config {
fn default_cookies_domain() -> Option<String> {
None
}
fn default_lang() -> String {
"en_GB".into()
fn default_default_lang() -> String {
"en_US".into()
}
fn default_listen() -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 31720)
@ -152,7 +153,7 @@ impl Default for Config {
comment_text_max_len: Self::default_comment_text_max_len(),
cookies_https_only: Self::default_cookies_https_only(),
cookies_domain: Self::default_cookies_domain(),
lang: Self::default_lang(),
default_lang: Self::default_default_lang(),
listen: Self::default_listen(),
matrix_notify: Self::default_matrix_notify(),
matrix_password: Self::default_matrix_password(),

103
src/locales.rs Normal file
View file

@ -0,0 +1,103 @@
use crate::config::Config;
use fluent_bundle::{bundle::FluentBundle, FluentArgs, FluentResource};
use fluent_langneg::{
accepted_languages, negotiate::filter_matches, negotiate_languages, NegotiationStrategy,
};
use intl_memoizer::concurrent::IntlLangMemoizer;
use log::error;
use std::{borrow::Cow, collections::HashMap, ops::Deref, str::FromStr};
use unic_langid::{langid, LanguageIdentifier};
static LOCALE_FILES: &[(LanguageIdentifier, &str)] = &[
(langid!("en"), include_str!("../locales/en.ftl")),
(langid!("fr"), include_str!("../locales/fr.ftl")),
];
pub struct Locales {
bundles: HashMap<LanguageIdentifier, FluentBundle<FluentResource, IntlLangMemoizer>>,
pub default_lang: LanguageIdentifier,
langs: Vec<LanguageIdentifier>,
}
impl Locales {
pub fn new(config: &Config) -> Self {
let mut langs = Vec::new();
Self {
bundles: LOCALE_FILES
.iter()
.map(|(lang, raw)| {
let mut bundle = FluentBundle::new_concurrent(vec![lang.clone()]);
bundle
.add_resource(
FluentResource::try_new(raw.to_string()).unwrap_or_else(|e| {
panic!("Failed parsing `{lang}` locale: {e:?}")
}),
)
.unwrap();
langs.push(lang.clone());
(lang.clone(), bundle)
})
.collect::<HashMap<LanguageIdentifier, FluentBundle<FluentResource, IntlLangMemoizer>>>(
),
default_lang: filter_matches(
&[LanguageIdentifier::from_str(&config.default_lang)
.expect("Invalid default language")],
&langs,
NegotiationStrategy::Filtering,
)
.get(0)
.expect("Unavailable default language")
.deref()
.clone(),
langs,
}
}
// TODO fix fluent-langneg's weird API
pub fn tr<'a>(
&'a self,
langs: &[LanguageIdentifier],
key: &str,
args: Option<&'a FluentArgs>,
) -> Option<Cow<str>> {
for prefered_lang in negotiate_languages(
langs,
&self.langs,
Some(&self.default_lang),
NegotiationStrategy::Filtering,
) {
if let Some(bundle) = self.bundles.get(prefered_lang.as_ref()) {
println!("got bundle");
if let Some(message) = bundle.get_message(key) {
let mut errors = Vec::new();
let ret = bundle.format_pattern(message.value().unwrap(), args, &mut errors);
for error in errors {
error!("Formatting message `{key}` in lang `{prefered_lang}`: {error}");
}
return Some(ret);
}
}
}
None
}
}
pub fn get_client_langs<State>(req: &tide::Request<State>) -> Vec<LanguageIdentifier> {
if let Some(header) = req.header("Accept-Language") {
accepted_languages::parse(header.as_str())
} else {
println!("NO HEADER");
Vec::new()
}
}
/// Get the first language that is likely to be usable with chrono
pub fn get_time_lang(langs: &[LanguageIdentifier]) -> Option<String> {
for lang in langs {
if let Some(region) = &lang.region {
return Some(format!("{}_{}", lang.language.as_str(), region.as_str()));
}
}
None
}

View file

@ -3,6 +3,7 @@ mod cli;
mod config;
mod db;
mod helpers;
mod locales;
mod notify;
mod queries;
mod server;
@ -13,7 +14,9 @@ use argon2::{
Argon2,
};
use clap::Parser;
use std::sync::Arc;
use log::warn;
use std::{collections::HashMap, str::FromStr};
use unic_langid::LanguageIdentifier;
#[tokio::main]
async fn main() {
@ -25,10 +28,42 @@ async fn main() {
}
cli::MainSubcommand::Start(subopt) => {
let (config, dbs, templates) = init_all(opt.opt, subopt);
let config = Arc::new(config);
let templates = Arc::new(templates);
tokio::spawn(cleaner::run_cleaner(config.clone(), dbs.clone()));
server::run_server(config, dbs, templates).await;
// These will never be dropped nor mutated
let templates = Box::leak(Box::new(templates));
let config = Box::leak(Box::new(config));
let locales = Box::leak(Box::new(locales::Locales::new(config)));
// TODO args
templates.tera.register_function(
"tr",
Box::new(
|args: &HashMap<String, tera::Value>| -> tera::Result<tera::Value> {
let langs = if let Some(tera::Value::Array(langs)) = args.get("l") {
langs
.iter()
.filter_map(|lang| lang.as_str())
.filter_map(|lang| LanguageIdentifier::from_str(lang).ok())
.collect()
} else {
vec![locales.default_lang.clone()]
};
let key = args
.get("k")
.ok_or_else(|| tera::Error::from("Missing argument `k`"))?
.as_str()
.ok_or_else(|| tera::Error::from("Argument `k` must be string"))?;
let res = locales.tr(&langs, key, None);
if res.is_none() {
warn!("(calling `tr` in template) translation key `{key}` not found");
}
Ok(res.into())
},
),
);
tokio::spawn(cleaner::run_cleaner(config, dbs.clone()));
server::run_server(config, dbs, templates, locales).await;
}
cli::MainSubcommand::Psw => {
let mut config = config::read_config(&opt.opt.dir.0);

View file

@ -3,10 +3,7 @@ use crate::config::Config;
use crossbeam_channel::Receiver;
use log::error;
use matrix_sdk::ruma;
use std::{
sync::Arc,
time::{Duration, SystemTime},
};
use std::time::{Duration, SystemTime};
enum OptionSince<T> {
Some(T),
@ -80,10 +77,10 @@ impl Notifier {
}
}
pub async fn run_notifier(config: Arc<Config>, recv: Receiver<()>) {
let mut notifier = Notifier::new(&config).await;
pub async fn run_notifier(config: &Config, recv: Receiver<()>) {
let mut notifier = Notifier::new(config).await;
for () in recv {
notifier.notify(&config).await;
notifier.notify(config).await;
}
}

View file

@ -1,68 +1,72 @@
use crate::{config::*, db::*, helpers, queries::*, templates::*};
#![allow(clippy::too_many_arguments)]
use crate::{config::*, db::*, helpers, locales::*, queries::*, templates::*};
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use crossbeam_channel::Sender;
use fluent_bundle::FluentArgs;
use log::{error, warn};
use std::sync::Arc;
use tera::Context;
use unic_langid::LanguageIdentifier;
pub async fn run_server(config: Arc<Config>, dbs: Dbs, templates: Arc<Templates>) {
pub async fn run_server(
config: &'static Config,
dbs: Dbs,
templates: &'static Templates,
locales: &'static Locales,
) {
tide::log::start();
let (notify_send, notify_recv) = crossbeam_channel::bounded(10);
tokio::spawn(crate::notify::run_notifier(config.clone(), notify_recv));
tokio::spawn(crate::notify::run_notifier(config, notify_recv));
let mut app = tide::new();
app.at(&format!("{}t/:topic", config.root_url)).get({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
let client_langs = get_client_langs(&req);
serve_comments(
req,
config.clone(),
templates.clone(),
config,
templates,
dbs.clone(),
client_langs,
Context::new(),
200,
)
}
});
app.at(&format!("{}t/:topic", config.root_url)).post({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_post_comments(
req,
config.clone(),
templates.clone(),
config,
templates,
dbs.clone(),
locales,
notify_send.clone(),
)
}
});
app.at(&format!("{}admin", config.root_url)).get({
let config = config.clone();
let templates = templates.clone();
move |req: tide::Request<()>| serve_admin_login(req, config.clone(), templates.clone())
});
app.at(&format!("{}admin", config.root_url))
.get(move |req: tide::Request<()>| {
let client_langs = get_client_langs(&req);
serve_admin_login(req, config, templates, client_langs)
});
app.at(&format!("{}admin", config.root_url)).post({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_post_admin(req, config.clone(), templates.clone(), dbs.clone())
}
move |req: tide::Request<()>| handle_post_admin(req, config, templates, dbs.clone())
});
app.listen(config.listen).await.unwrap();
}
async fn serve_comments<'a>(
req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
config: &Config,
templates: &Templates,
dbs: Dbs,
client_langs: Vec<LanguageIdentifier>,
mut context: Context,
status_code: u16,
) -> tide::Result<tide::Response> {
@ -71,13 +75,25 @@ async fn serve_comments<'a>(
};
let admin = req.cookie("admin").map_or(false, |psw| {
check_admin_password_hash(&config, &String::from(psw.value()))
check_admin_password_hash(config, &String::from(psw.value()))
});
let topic_hash = TopicHash::from_topic(topic);
context.insert("config", &config);
context.insert("admin", &admin);
let time_lang = get_time_lang(&client_langs);
context.insert(
"time_lang",
time_lang.as_ref().unwrap_or(&config.default_lang),
);
context.insert(
"l",
&client_langs
.iter()
.map(|lang| lang.language.as_str())
.collect::<Vec<&str>>(),
);
if admin {
if let Ok(query) = req.query::<ApproveQuery>() {
@ -142,13 +158,26 @@ async fn serve_comments<'a>(
async fn serve_admin<'a>(
_req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
config: &Config,
templates: &Templates,
dbs: Dbs,
client_langs: &[LanguageIdentifier],
) -> tide::Result<tide::Response> {
let mut context = Context::new();
context.insert("config", &config);
context.insert("admin", &true);
let time_lang = get_time_lang(client_langs);
context.insert(
"time_lang",
time_lang.as_ref().unwrap_or(&config.default_lang),
);
context.insert(
"l",
&client_langs
.iter()
.map(|lang| lang.language.as_str())
.collect::<Vec<&str>>(),
);
context.insert(
"comments",
@ -187,11 +216,24 @@ async fn serve_admin<'a>(
async fn serve_admin_login(
_req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
config: &Config,
templates: &Templates,
client_langs: Vec<LanguageIdentifier>,
) -> tide::Result<tide::Response> {
let mut context = Context::new();
context.insert("config", &config);
let time_lang = get_time_lang(&client_langs);
context.insert(
"time_lang",
time_lang.as_ref().unwrap_or(&config.default_lang),
);
context.insert(
"l",
&client_langs
.iter()
.map(|lang| lang.language.as_str())
.collect::<Vec<&str>>(),
);
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
@ -201,17 +243,20 @@ async fn serve_admin_login(
async fn handle_post_comments(
mut req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
config: &Config,
templates: &Templates,
dbs: Dbs,
locales: &Locales,
notify_send: Sender<()>,
) -> tide::Result<tide::Response> {
let admin = req.cookie("admin").map_or(false, |psw| {
check_admin_password_hash(&config, &String::from(psw.value()))
check_admin_password_hash(config, &String::from(psw.value()))
});
let client_langs = get_client_langs(&req);
let client_addr = if !admin && config.antispam_enable {
match helpers::get_client_addr(&config, &req) {
match helpers::get_client_addr(config, &req) {
Some(Ok(addr)) => {
if config.antispam_whitelist.contains(&addr) {
None
@ -241,16 +286,25 @@ async fn handle_post_comments(
return Err(tide::Error::from_str(404, "No topic"))
};
helpers::check_comment(&config, &query.comment, &mut errors);
helpers::check_comment(config, &query.comment, &mut errors);
if let Some(client_addr) = &client_addr {
if let Some(antispam_timeout) =
helpers::antispam_check_client_mutation(client_addr, &dbs, &config).unwrap()
helpers::antispam_check_client_mutation(client_addr, &dbs, config).unwrap()
{
errors.push(format!(
"The edition quota from your IP is reached. You will be unblocked in {}s.",
antispam_timeout
));
errors.push(
locales
.tr(
&client_langs,
"error-antispam",
Some(&FluentArgs::from_iter([(
"antispam_timeout",
antispam_timeout,
)])),
)
.unwrap()
.into_owned(),
);
}
}
@ -294,7 +348,7 @@ async fn handle_post_comments(
return Err(tide::Error::from_str(403, "Forbidden"));
}
helpers::check_comment(&config, &query.comment, &mut errors);
helpers::check_comment(config, &query.comment, &mut errors);
let comment_id = if let Ok(comment_id) = CommentId::from_base64(&query.id) {
comment_id
@ -308,21 +362,33 @@ async fn handle_post_comments(
return Err(tide::Error::from_str(404, "Not found"));
};
if let Some(client_addr) = &client_addr {
// We're admin
/*if let Some(client_addr) = &client_addr {
if let Some(antispam_timeout) =
helpers::antispam_check_client_mutation(client_addr, &dbs, &config).unwrap()
helpers::antispam_check_client_mutation(client_addr, &dbs, config).unwrap()
{
errors.push(format!(
"The edition quota from your IP is reached. You will be unblocked in {}s.",
antispam_timeout
));
let client_langs = get_client_langs(&req);
errors.push(
locales
.tr(
&client_langs,
"error-antispam",
Some(&FluentArgs::from_iter([(
"antispam_timeout",
antispam_timeout,
)])),
)
.unwrap()
.into_owned(),
);
}
}
}*/
if errors.is_empty() {
if let Some(client_addr) = &client_addr {
// We're admin
/*if let Some(client_addr) = &client_addr {
helpers::antispam_update_client_mutation(client_addr, &dbs).unwrap();
}
}*/
let time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
@ -353,6 +419,7 @@ async fn handle_post_comments(
config,
templates,
dbs,
client_langs,
context,
if errors.is_empty() { 200 } else { 400 },
)
@ -361,22 +428,27 @@ async fn handle_post_comments(
async fn handle_post_admin(
mut req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
config: &Config,
templates: &Templates,
dbs: Dbs,
) -> tide::Result<tide::Response> {
if let Some(psw) = req.cookie("admin") {
if check_admin_password(&config, &String::from(psw.value())).is_some() {
if check_admin_password(config, &String::from(psw.value())).is_some() {
#[allow(clippy::match_single_binding)]
match req.body_form::<AdminQuery>().await? {
_ => serve_admin(req, config, templates, dbs).await,
_ => {
let client_langs = get_client_langs(&req);
serve_admin(req, config, templates, dbs, &client_langs).await
}
}
} else {
serve_admin_login(req, config, templates).await
let client_langs = get_client_langs(&req);
serve_admin_login(req, config, templates, client_langs).await
}
} else if let AdminQuery::Login(query) = req.body_form::<AdminQuery>().await? {
if let Some(password_hash) = check_admin_password(&config, &query.psw) {
serve_admin(req, config.clone(), templates, dbs)
if let Some(password_hash) = check_admin_password(config, &query.psw) {
let client_langs = get_client_langs(&req);
serve_admin(req, config, templates, dbs, &client_langs)
.await
.map(|mut r| {
let mut cookie = tide::http::Cookie::new("admin", password_hash);
@ -392,10 +464,12 @@ async fn handle_post_admin(
r
})
} else {
serve_admin_login(req, config, templates).await
let client_langs = get_client_langs(&req);
serve_admin_login(req, config, templates, client_langs).await
}
} else {
serve_admin_login(req, config, templates).await
let client_langs = get_client_langs(&req);
serve_admin_login(req, config, templates, client_langs).await
}
}

View file

@ -1,14 +1,14 @@
<!doctype html>
<html lang="en">
<html lang="{{ time_lang }}">
<head>
<meta charset="utf-8"/>
<title>Admin login | Comments</title>
<title>{{ tr(l=l,k="admin_login-title")|safe }}</title>
</head>
<body>
<form action="#" method="post">
<label for="login-psw">Password:</label>
<label for="login-psw">{{ tr(l=l,k="admin_login-password_prompt")|safe }}</label>
<input type="password" id="login-psw" name="psw"/><br/>
<button type="submit" name="a" value="login">Login</button>
<button type="submit" name="a" value="login">{{ tr(l=l,k="admin_login-submit_button")|safe }}</button>
</form>
</body>
</html>

View file

@ -1,8 +1,8 @@
<!doctype html>
<html lang="en">
<html lang="{{ time_lang }}">
<head>
<meta charset="utf-8"/>
<title>Comments</title>
<title>{{ tr(l=l,k="title")|safe }}</title>
</head>
<body>
{% if comments_pending %}
@ -10,15 +10,15 @@
{% 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>
<span class="comment-date">{{ comment.post_time | date(format="%F %R", locale=config.lang) }}</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">Edit</a>
<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 }}">Approve</a>
<a href="?approve={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-approve")|safe }}</a>
{% endif %}
{% if admin %}
<a href="?remove={{ comment.id | safe }}">Remove</a>
<a href="?remove={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-remove")|safe }}</a>
{% endif %}
<p class="comment-text">{{ comment.text }}</p>
</div>
@ -29,15 +29,15 @@
{% 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=config.lang) }}</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">Edit</a>
<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 }}">Approve</a>
<a href="?approve={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-approve")|safe }}</a>
{% endif %}
{% if admin %}
<a href="?remove={{ comment.id | safe }}">Remove</a>
<a href="?remove={{ comment.id | safe }}">{{ tr(l=l,k="admin-comment-remove")|safe }}</a>
{% endif %}
<p class="comment-text">{{ comment.text }}</p>
</div>
@ -45,25 +45,25 @@
</div>
<form id="new_comment-form" action="#new_comment-form" method="post">
{% if new_comment_errors %}
<p>Whoops, the following error occurred:</p>
<p>{{ tr(l=l,k="error-list")|safe }}</p>
<ul id="new_comment-errors" class="errors">
{% for error in new_comment_errors %}
<li class="error">{{ error | safe }}</li>
{% endfor %}
</ul>
{% endif %}
<label for="new_comment-author">Your name:</label>
<label for="new_comment-author">{{ tr(l=l,k="comment_form-author")|safe }}</label>
<input type="text" id="new_comment-author" name="author" maxlength="{{ config.comment_author_max_len | safe }}"{% if new_comment_author %} value="{{ new_comment_author }}"{% endif %}/><br/>
<label for="new_comment-email">Your e-mail:</label>
<label for="new_comment-email">{{ tr(l=l,k="comment_form-email")|safe }}</label>
<input type="email" id="new_comment-email" name="email" maxlength="{{ config.comment_email_max_len | safe }}"{% if new_comment_email %} value="{{ new_comment_email }}"{% endif %}/><br/>
<label for="new_comment-text">Your comment:</label><br/>
<label for="new_comment-text">{{ tr(l=l,k="comment_form-text")|safe }}</label><br/>
<textarea id="new_comment-text" name="text" maxlength="{{ config.comment_text_max_len | safe }}">{% if new_comment_text %}{{ new_comment_text }}{% endif %}</textarea><br/>
<button type="submit" name="a" value="new_comment">Post comment</button>
<button type="submit" name="a" value="new_comment">{{ tr(l=l,k="comment_form-new_button")|safe }}</button>
</form>
{% if edit_comment %}
<form id="edit_comment-form" action="#edit_comment-form" method="post">
{% if edit_comment_errors %}
<p>Whoops, the following error occurred:</p>
<p>{{ tr(l=l,k="error-list")|safe }}</p>
<ul id="edit_comment-errors" class="errors">
{% for error in edit_comment_errors %}
<li class="error">{{ error | safe }}</li>
@ -71,13 +71,13 @@
</ul>
{% endif %}
<input type="hidden" name="id" value="{{ edit_comment | safe }}" autocomplete="off"/>
<label for="edit_comment-author">Your name:</label>
<label for="edit_comment-author">{{ tr(l=l,k="comment_form-author")|safe }}</label>
<input type="text" id="edit_comment-author" name="author" maxlength="{{ config.comment_author_max_len | safe }}"{% if edit_comment_author %} value="{{ edit_comment_author }}"{% endif %}/><br/>
<label for="edit_comment-email">Your e-mail:</label>
<label for="edit_comment-email">{{ tr(l=l,k="comment_form-email")|safe }}</label>
<input type="email" id="edit_comment-email" name="email" maxlength="{{ config.comment_email_max_len | safe }}"{% if edit_comment_email %} value="{{ edit_comment_email }}"{% endif %}/><br/>
<label for="edit_comment-text">Your comment:</label><br/>
<label for="edit_comment-text">{{ tr(l=l,k="comment_form-text")|safe }}</label><br/>
<textarea id="edit_comment-text" name="text" maxlength="{{ config.comment_text_max_len | safe }}">{% if edit_comment_text %}{{ edit_comment_text }}{% endif %}</textarea><br/>
<button type="submit" name="a" value="edit_comment">Edit comment</button>
<button type="submit" name="a" value="edit_comment">{{ tr(l=l,k="comment_form-edit_button")|safe }}</button>
</form>
{% endif %}
</body>