diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..444059a --- /dev/null +++ b/.envrc @@ -0,0 +1,10 @@ +source_up + +export DATABASE_USER="newsreader"; +export DATABASE_NAME="newsreader"; +export DATABASE_HOST="nixos-07.h.xinu.tv"; +export DATABASE_URL="postgres://${DATABASE_USER}@${DATABASE_HOST}/${DATABASE_NAME}"; +export PROD_DATABASE_USER="newsreader"; +export PROD_DATABASE_NAME="newsreader"; +export PROD_DATABASE_HOST="postgres.h.xinu.tv"; +export PROD_DATABASE_URL="postgres://${PROD_DATABASE_USER}@${PROD_DATABASE_HOST}/${PROD_DATABASE_NAME}"; diff --git a/Cargo.lock b/Cargo.lock index b82df9e..75308c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -59,6 +60,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "ammonia" version = "3.3.0" @@ -241,6 +248,15 @@ dependencies = [ "syn 2.0.69", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic" version = "0.5.3" @@ -312,6 +328,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bimap" version = "0.6.3" @@ -335,6 +357,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -441,6 +466,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-sha1" version = "0.2.0" @@ -489,6 +520,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -508,6 +554,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -658,6 +713,17 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -726,9 +792,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -769,6 +843,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "enclose" @@ -801,6 +878,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fast_chemail" version = "0.9.6" @@ -830,6 +924,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -897,6 +1002,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.3", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -1249,12 +1365,28 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "hermit-abi" @@ -1262,6 +1394,39 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "html5ever" version = "0.26.0" @@ -1523,6 +1688,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -1559,6 +1727,23 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linkify" version = "0.10.0" @@ -1676,6 +1861,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1707,6 +1902,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1789,6 +1990,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notmuch" version = "0.1.0" @@ -1802,12 +2013,49 @@ dependencies = [ "thiserror", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1815,6 +2063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1928,6 +2177,15 @@ dependencies = [ "syn 2.0.69", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2151,6 +2409,33 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "powerfmt" version = "0.2.0" @@ -2610,6 +2895,26 @@ dependencies = [ "uncased", ] +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2875,6 +3180,7 @@ dependencies = [ "serde", "serde_json", "shared", + "sqlx", "thiserror", "tokio", "urlencoding", @@ -2899,6 +3205,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2936,6 +3253,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -2981,6 +3308,223 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash 0.8.11", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.2.6", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.6.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa 1.0.11", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.6.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa 1.0.11", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", +] [[package]] name = "stable-pattern" @@ -3038,6 +3582,17 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.9.3" @@ -3072,6 +3627,12 @@ dependencies = [ "syn 2.0.69", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -3378,6 +3939,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3510,12 +4072,30 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "unreachable" version = "1.0.0" @@ -3569,6 +4149,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -4003,3 +4589,9 @@ dependencies = [ "quote", "syn 2.0.69", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/server/Cargo.toml b/server/Cargo.toml index 80a6ace..ef20f4b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -28,4 +28,5 @@ css-inline = "0.13.0" anyhow = "1.0.79" maplit = "1.0.2" linkify = "0.10.0" +sqlx = { version = "0.7.4", features = ["postgres", "runtime-tokio"] } diff --git a/server/Rocket.toml b/server/Rocket.toml index 42e280c..fc64944 100644 --- a/server/Rocket.toml +++ b/server/Rocket.toml @@ -7,3 +7,4 @@ address = "0.0.0.0" port = 9345 # Uncomment to make it production like. #log_level = "critical" +newsreader_database_url = "postgres://newsreader@nixos-07.h.xinu.tv/newsreader" diff --git a/server/sql/tags.sql b/server/sql/tags.sql new file mode 100644 index 0000000..43520ee --- /dev/null +++ b/server/sql/tags.sql @@ -0,0 +1,13 @@ +SELECT + site, + name, + count ( + NOT is_read + OR NULL + ) unread +FROM + post AS p + JOIN feed AS f ON p.site = f.slug +GROUP BY + 1, + 2; diff --git a/server/src/bin/server.rs b/server/src/bin/server.rs index 0de3fcc..9edbf61 100644 --- a/server/src/bin/server.rs +++ b/server/src/bin/server.rs @@ -7,6 +7,7 @@ use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse}; use glog::Flags; use notmuch::{Notmuch, NotmuchError, ThreadSet}; use rocket::{ + fairing::AdHoc, http::{ContentType, Header}, request::Request, response::{content, Debug, Responder}, @@ -14,12 +15,19 @@ use rocket::{ Response, State, }; use rocket_cors::{AllowedHeaders, AllowedOrigins}; +use serde::Deserialize; use server::{ error::ServerError, graphql::{ attachment_bytes, cid_attachment_bytes, Attachment, GraphqlSchema, Mutation, QueryRoot, }, }; +use sqlx::postgres::PgPool; + +#[derive(Deserialize)] +struct Config { + newsreader_database_url: String, +} #[get("/refresh")] async fn refresh(nm: &State) -> Result, Debug> { @@ -224,12 +232,7 @@ async fn main() -> Result<(), Box> { } .to_cors()?; - let schema = Schema::build(QueryRoot, Mutation, EmptySubscription) - .data(Notmuch::default()) - .extension(async_graphql::extensions::Logger) - .finish(); - - let _ = rocket::build() + let rkt = rocket::build() .mount( shared::urls::MOUNT_POINT, routes![ @@ -248,11 +251,19 @@ async fn main() -> Result<(), Box> { ], ) .attach(cors) - .manage(schema) - .manage(Notmuch::default()) - //.manage(Notmuch::with_config("../notmuch/testdata/notmuch.config")) - .launch() - .await?; + .attach(AdHoc::config::()); + let config: Config = rkt.figment().extract()?; + let pool = PgPool::connect(&config.newsreader_database_url).await?; + let schema = Schema::build(QueryRoot, Mutation, EmptySubscription) + .data(Notmuch::default()) + .data(pool.clone()) + .extension(async_graphql::extensions::Logger) + .finish(); + + let rkt = rkt.manage(schema).manage(pool).manage(Notmuch::default()); + //.manage(Notmuch::with_config("../notmuch/testdata/notmuch.config")) + + rkt.launch().await?; Ok(()) } diff --git a/server/src/error.rs b/server/src/error.rs index f9552c3..a0d15f8 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -13,4 +13,6 @@ pub enum ServerError { IoError(#[from] std::io::Error), #[error("attachement not found")] PartNotFound, + #[error("sqlx error")] + SQLXError(#[from] sqlx::Error), } diff --git a/server/src/graphql.rs b/server/src/graphql.rs index 993b334..3c0763c 100644 --- a/server/src/graphql.rs +++ b/server/src/graphql.rs @@ -1,10 +1,5 @@ const MAX_RAW_MESSAGE_SIZE: usize = 100_000; -use std::{ - collections::HashMap, - fs::File, - hash::{DefaultHasher, Hash, Hasher}, - time::Instant, -}; +use std::fs::File; use async_graphql::{ connection::{self, Connection, Edge}, @@ -14,8 +9,9 @@ use log::{error, info, warn}; use mailparse::{parse_mail, MailHeader, MailHeaderMap, ParsedMail}; use memmap::MmapOptions; use notmuch::Notmuch; +use sqlx::postgres::PgPool; -use crate::{error::ServerError, linkify_html, sanitize_html}; +use crate::{error::ServerError, linkify_html, newsreader, nm, sanitize_html}; /// # Number of seconds since the Epoch pub type UnixTime = isize; @@ -181,11 +177,11 @@ pub struct Email { } #[derive(SimpleObject)] -struct Tag { - name: String, - fg_color: String, - bg_color: String, - unread: usize, +pub(crate) struct Tag { + pub name: String, + pub fg_color: String, + pub bg_color: String, + pub unread: usize, } pub struct QueryRoot; @@ -261,44 +257,10 @@ impl QueryRoot { async fn tags<'ctx>(&self, ctx: &Context<'ctx>) -> FieldResult> { let nm = ctx.data_unchecked::(); - let now = Instant::now(); + let pool = ctx.data_unchecked::(); let needs_unread = ctx.look_ahead().field("unread").exists(); - let unread_msg_cnt: HashMap = if needs_unread { - // 10000 is an arbitrary number, if there's more than 10k unread messages, we'll - // get an inaccurate count. - nm.search("is:unread", 0, 10000)? - .0 - .iter() - .fold(HashMap::new(), |mut m, ts| { - ts.tags.iter().for_each(|t| { - m.entry(t.clone()).and_modify(|c| *c += 1).or_insert(1); - }); - m - }) - } else { - HashMap::new() - }; - let tags = nm - .tags()? - .into_iter() - .map(|tag| { - let mut hasher = DefaultHasher::new(); - tag.hash(&mut hasher); - let hex = format!("#{:06x}", hasher.finish() % (1 << 24)); - let unread = if needs_unread { - *unread_msg_cnt.get(&tag).unwrap_or(&0) - } else { - 0 - }; - Tag { - name: tag, - fg_color: "white".to_string(), - bg_color: hex, - unread, - } - }) - .collect(); - info!("Fetching tags took {} seconds", now.elapsed().as_secs_f32()); + let mut tags = nm::tags(nm, needs_unread)?; + tags.append(&mut newsreader::tags(pool, needs_unread).await?); Ok(tags) } async fn thread<'ctx>(&self, ctx: &Context<'ctx>, thread_id: String) -> Result { diff --git a/server/src/lib.rs b/server/src/lib.rs index 1925deb..538b840 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,6 +1,7 @@ pub mod error; pub mod graphql; -pub mod nm; +mod newsreader; +mod nm; use css_inline::{CSSInliner, InlineError, InlineOptions}; use linkify::{LinkFinder, LinkKind}; diff --git a/server/src/newsreader.rs b/server/src/newsreader.rs new file mode 100644 index 0000000..29dfe97 --- /dev/null +++ b/server/src/newsreader.rs @@ -0,0 +1,30 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; + +use log::info; +use sqlx::postgres::PgPool; + +const TAG_PREFIX: &'static str = "News"; + +use crate::{error, graphql::Tag}; +pub async fn tags(pool: &PgPool, needs_unread: bool) -> Result, error::ServerError> { + // TODO: write separate query for needs_unread. + let tags = sqlx::query_file!("sql/tags.sql").fetch_all(pool).await?; + info!("sqlx tags {tags:#?}"); + let tags = tags + .into_iter() + .map(|tag| { + let mut hasher = DefaultHasher::new(); + tag.site.hash(&mut hasher); + let hex = format!("#{:06x}", hasher.finish() % (1 << 24)); + let unread = tag.unread.unwrap_or(0).try_into().unwrap_or(0); + let name = format!("{TAG_PREFIX}/{}", tag.site.expect("tag must have site")); + Tag { + name, + fg_color: "white".to_string(), + bg_color: hex, + unread, + } + }) + .collect(); + Ok(tags) +} diff --git a/server/src/nm.rs b/server/src/nm.rs index e91172c..13c00c5 100644 --- a/server/src/nm.rs +++ b/server/src/nm.rs @@ -1,6 +1,14 @@ +use std::{ + collections::HashMap, + hash::{DefaultHasher, Hash, Hasher}, + time::Instant, +}; + +use log::info; +use notmuch::Notmuch; use shared::Message; -use crate::error; +use crate::{error, graphql::Tag}; // TODO(wathiede): decide good error type pub fn threadset_to_messages( @@ -11,3 +19,44 @@ pub fn threadset_to_messages( } Ok(Vec::new()) } + +pub fn tags(nm: &Notmuch, needs_unread: bool) -> Result, error::ServerError> { + let now = Instant::now(); + let unread_msg_cnt: HashMap = if needs_unread { + // 10000 is an arbitrary number, if there's more than 10k unread messages, we'll + // get an inaccurate count. + nm.search("is:unread", 0, 10000)? + .0 + .iter() + .fold(HashMap::new(), |mut m, ts| { + ts.tags.iter().for_each(|t| { + m.entry(t.clone()).and_modify(|c| *c += 1).or_insert(1); + }); + m + }) + } else { + HashMap::new() + }; + let tags = nm + .tags()? + .into_iter() + .map(|tag| { + let mut hasher = DefaultHasher::new(); + tag.hash(&mut hasher); + let hex = format!("#{:06x}", hasher.finish() % (1 << 24)); + let unread = if needs_unread { + *unread_msg_cnt.get(&tag).unwrap_or(&0) + } else { + 0 + }; + Tag { + name: tag, + fg_color: "white".to_string(), + bg_color: hex, + unread, + } + }) + .collect(); + info!("Fetching tags took {} seconds", now.elapsed().as_secs_f32()); + Ok(tags) +}