commit 9fe412be11c3924feace760d34f5ef65ef63a0e4 Author: Tony Klink Date: Fri Jan 12 09:35:31 2024 -0600 Initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..bbdaf13 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +DATABASE_URL="sqlite://sqlite.db" +RUST_BACKTRACE=1 \ No newline at end of file diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b5ccf1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.direnv +result +target + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3a4412b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2996 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + +[[package]] +name = "argon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes 0.11.0", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e99ff7289b20a7385f66a0feda78af2fc119d28fb56aea8886a9cd0a4abdd75" +dependencies = [ + "bech32", + "bitcoin-private", + "bitcoin_hashes 0.12.0", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + +[[package]] +name = "bitcoin_hashes" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" +dependencies = [ + "bitcoin-private", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +dependencies = [ + "serde", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +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 = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[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", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "windows-sys", +] + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flexbuffers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d14128f06405808ce75bfebe11e9b0f9da18719ede6d7bdb1702d6bfe0f7e8" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "num_enum", + "serde", + "serde_derive", +] + +[[package]] +name = "flexi_logger" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac35b454b60e1836602173e2eb7ef531173388c0212e02ec7f9fac086159ee5" +dependencies = [ + "chrono", + "crossbeam-channel", + "crossbeam-queue", + "flate2", + "glob", + "is-terminal", + "lazy_static", + "log", + "nu-ansi-term", + "regex", + "thiserror", +] + +[[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 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "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.1", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash", + "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.0", +] + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[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" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls 0.21.6", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + +[[package]] +name = "inherent" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce243b1bfa62ffc028f1cc3b6034ec63d649f3031bc8a4fbbb004e1ac17d1f68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "itoap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[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.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[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.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "version_check", +] + +[[package]] +name = "negentropy" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" + +[[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 = "nostr" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72adfa99eb5f1e5afa3f1fe6503c89989b7b0b61c4e5ec23c5b839967cd54da1" +dependencies = [ + "aes", + "base64 0.21.2", + "bip39", + "bitcoin", + "cbc", + "chacha20", + "getrandom", + "instant", + "negentropy", + "once_cell", + "reqwest", + "serde", + "serde_json", + "tracing", + "url-fork", +] + +[[package]] +name = "nu-ansi-term" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +dependencies = [ + "windows-sys", +] + +[[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", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[package]] +name = "pin-utils" +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.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.6", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.24.1", + "tokio-socks", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.22.6", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee020b1716f0a80e2ace9b03441a749e402e86712f15f16fe8a8f75afac732f" +dependencies = [ + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.2", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "sailfish" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a03fcade08eb837d7ba0d8f775f1fe6cddb00d413de0c655ab2b93e821a0eb" +dependencies = [ + "itoap", + "ryu", + "sailfish-macros", + "version_check", +] + +[[package]] +name = "sailfish-compiler" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf1deecb07747a1f7ab55745edcc406875db007602b037af5fcf59f40dd2005" +dependencies = [ + "filetime", + "home", + "memchr", + "proc-macro2", + "quote", + "serde", + "syn 2.0.28", + "toml", +] + +[[package]] +name = "sailfish-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e5946289d6daa26cde6ffb06678d7a8ba276ec6a7a55d36f64b35e7f644d22" +dependencies = [ + "proc-macro2", + "sailfish-compiler", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sea-query" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41558fa9bb5f4d73952dac0b9d9c2ce23966493fc9ee0008037b01d709838a68" +dependencies = [ + "inherent", + "sea-query-derive", +] + +[[package]] +name = "sea-query-binder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36bbb68df92e820e4d5aeb17b4acd5cc8b5d18b2c36a4dd6f4626aabfa7ab1b9" +dependencies = [ + "sea-query", + "sqlx", +] + +[[package]] +name = "sea-query-derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a82fcb49253abcb45cdcb2adf92956060ec0928635eb21b4f7a6d8f25ab0bc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.28", + "thiserror", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "bitcoin_hashes 0.12.0", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.181" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.181" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "serde_json" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "sneedstr" +version = "0.1.0" +dependencies = [ + "anyhow", + "argon2", + "async-trait", + "chrono", + "flexbuffers", + "flexi_logger", + "futures-util", + "lazy_static", + "log", + "nostr", + "regex", + "rustls 0.21.6", + "sailfish", + "sea-query", + "sea-query-binder", + "serde", + "serde_json", + "sled", + "sqlx", + "thiserror", + "tokio", + "tokio-stream", + "uuid", + "validator", + "warp", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.0.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.21.6", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.24.0", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +dependencies = [ + "atoi", + "base64 0.21.2", + "bitflags 2.3.3", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +dependencies = [ + "atoi", + "base64 0.21.2", + "bitflags 2.3.3", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +dependencies = [ + "autocfg", + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.8", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.6", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "tungstenite" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "url-fork" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956afc9d7e101f0b718a6776489cd7998d0b17fc79f4cdb6ee6761fb72d1c2ce" +dependencies = [ + "form_urlencoded", + "percent-encoding", + "serde", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom", + "rand", +] + +[[package]] +name = "validator" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd" +dependencies = [ + "idna", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.23.4", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" +dependencies = [ + "rustls-webpki", +] + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a55c57c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "sneedstr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +argon2 = "0.5.2" +async-trait = "0.1.73" +chrono = "0.4.31" +tokio = { version = "1", features = ["full"] } +warp = { varsion = "0.3.3", features = ["tls"] } +validator = { version = "0.16", features = ["derive"] } +tokio-stream = "0.1.14" +futures-util = "0.3.28" +rustls = "0.21" +anyhow = "1.0" +sled = "0.34.7" +sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls", "sqlite", "migrate", "macros"] } +flexi_logger = { version = "0.27.3", features = [ "async", "compress" ] } +lazy_static = "1.4.0" +log = "0.4" +nostr = "0.26.0" +regex = "1.9.5" +sailfish = "0.7.0" +sea-query = { version = "0.30.4", features = ["backend-sqlite", "thread-safe"] } +sea-query-binder = { version = "0.5.0", features = ["sqlx-sqlite"] } +serde = "1.0" +serde_json = "1.0" +thiserror = "1.0.48" +flexbuffers = "2.0.0" +uuid = { version = "1.3.1", features = ["v4", "fast-rng"] } + +[profile.release] +opt-level = 'z' # Optimize for size +lto = true # Enable link-time optimization +codegen-units = 1 # Reduce number of codegen units to increase optimizations +panic = 'abort' # Abort on panic +strip = true # Strip symbols from binary* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eb73ba1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +Copyright (C) 2024 Klink +This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3. + +The above copyright notice, this permission notice and the word "NIGGER" shall be included in all copies or substantial portions of the Software. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6584e0c --- /dev/null +++ b/flake.lock @@ -0,0 +1,93 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1698420672, + "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "owner": "nix-community", + "repo": "naersk", + "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1703438236, + "narHash": "sha256-aqVBq1u09yFhL7bj1/xyUeJjzr92fXVvQSSEx6AdB1M=", + "path": "/nix/store/5acdh8xyry0kdvp6xla2hw7wf3zkphkl-source", + "rev": "5f64a12a728902226210bf01d25ec6cbb9d9265b", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1703499205, + "narHash": "sha256-lF9rK5mSUfIZJgZxC3ge40tp1gmyyOXZ+lRY3P8bfbg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "naersk": "naersk", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..74f8ddd --- /dev/null +++ b/flake.nix @@ -0,0 +1,52 @@ +{ + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + naersk.url = "github:nix-community/naersk"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + }; + + outputs = { self, flake-utils, naersk, nixpkgs }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = (import nixpkgs) { inherit system; }; + + naersk' = pkgs.callPackage naersk { }; + + wwwPath = "www"; + templatesPath = "templates"; + + in rec { + # For `nix build` & `nix run`: + defaultPackage = naersk'.buildPackage { + src = ./.; + nativeBuildInputs = with pkgs; [ pkg-config openssl sqlite ]; + GIT_HASH = "000000000000000000000000000000"; + postInstall = '' + mkdir -p $out/templates + mkdir -p $out/www + cp -r ${wwwPath} $out/ + cp -r ${templatesPath} $out/ + ''; + }; + + # For `nix develop`: + devShell = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + rustc + cargo + cargo-watch + clippy + rustfmt + rust-analyzer + pkg-config + + openssl + sqlite + ]; + env = { + DATABASE_URL = "sqlite://sqlite.db"; + RUST_BACKTRACE = 1; + }; + }; + }); +} diff --git a/src/bussy/mod.rs b/src/bussy/mod.rs new file mode 100644 index 0000000..d0c9c55 --- /dev/null +++ b/src/bussy/mod.rs @@ -0,0 +1,143 @@ +use crate::{ + noose::user::{User, UserRow}, + utils::{error::Error, structs::Subscription}, +}; +use nostr::secp256k1::XOnlyPublicKey; +use std::{collections::HashMap, fmt::Debug}; +use tokio::sync::{broadcast, Mutex}; + +pub mod channels { + pub static MSG_NOOSE: &str = "MSG_NOOSE"; + pub static MSG_NIP05: &str = "MSG_NIP05"; + pub static MSG_RELAY: &str = "MSG_RELAY"; + pub static MSG_PIPELINE: &str = "MSG_PIPELINE"; +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Command { + // DbRequest + DbReqWriteEvent(Box), + DbReqFindEvent(/* client_id*/ uuid::Uuid, Subscription), + DbReqDeleteEvents(/* event ids */ Vec), + + // Old messages + DbReqInsertUser(UserRow), + DbReqGetUser(User), + DbReqCreateAccount(XOnlyPublicKey, String, String), + DbReqGetAccount(String), + DbReqClear, + // DbResponse + DbResRelayMessage( + /* client_id*/ uuid::Uuid, + /* Vec */ Vec, + ), + DbResInfo, + DbResOk, + DbResOkWithStatus(String), + DbResAccount, // TODO: Add Account DTO as a param + DbResUser(UserRow), + // Event Pipeline + PipelineReqEvent(/* client_id */ uuid::Uuid, Box), + PipelineResRelayMessageOk(/* client_id */ uuid::Uuid, nostr::RelayMessage), + PipelineResStreamOutEvent(Box), + PipelineResOk, + // Other + Str(String), + ServiceError(Error), + Noop, +} + +#[derive(Debug, Clone)] +pub struct Message { + pub source: &'static str, + pub content: Command, +} + +#[derive(Debug)] +pub struct PubSub { + subscribers: Mutex>>>, +} + +impl Default for PubSub { + fn default() -> Self { + panic!("Use PubSub::new() to initialize PubSub"); + } +} + +impl PubSub { + pub fn new() -> Self { + PubSub { + subscribers: Mutex::new(HashMap::new()), + } + } + + pub async fn subscribe(&self, topic: &str) -> broadcast::Receiver { + let (tx, _rx) = broadcast::channel(32); // 32 is the channel capacity + let mut subscribers = self.subscribers.lock().await; + subscribers + .entry(topic.to_string()) + .or_insert_with(Vec::new) + .push(tx.clone()); + tx.subscribe() + } + + pub async fn publish(&self, topic: &str, message: Message) { + let mut subscribers = self.subscribers.lock().await; + if let Some(queue) = subscribers.get_mut(topic) { + for sender in queue.iter() { + sender.send(message.clone()).ok(); + } + } + } +} + +// #[cfg(test)] +// mod tests { +// use super::channels; +// use crate::bussy::{Command, Message, PubSub}; +// use std::sync::Arc; + +// #[tokio::test] +// async fn create_bus() { +// let pubsub = Arc::new(PubSub::new()); + +// let mut subscriber1 = pubsub.subscribe(channels::MSG_NIP05).await; +// let mut subscriber2 = pubsub.subscribe(channels::MSG_NOOSE).await; + +// tokio::spawn(async move { +// while let Ok(message) = subscriber1.recv().await { +// println!("Subscriber1 received: {:?}", message); +// } +// }); + +// tokio::spawn(async move { +// while let Ok(message) = subscriber2.recv().await { +// println!("Subscriber2 received: {:?}", message); +// } +// }); + +// pubsub +// .publish( +// channels::MSG_NIP05, +// Message { +// source: "test", +// content: Command::Str("Hello S1".to_string()), +// }, +// ) +// .await; +// pubsub +// .publish( +// channels::MSG_NOOSE, +// Message { +// source: "test", +// content: Command::Str("Hello S2".to_string()), +// }, +// ) +// .await; + +// dbg!(pubsub); + +// // Sleep to keep the main thread alive while messages are processed in the background. +// tokio::time::sleep(std::time::Duration::from_secs(1)).await; +// } +// } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b14652a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,51 @@ +#![allow(dead_code)] +#![allow(unused_variables)] + +use std::thread; +pub mod bussy; +mod noose; +mod relay; +mod usernames; +pub mod utils; + +use bussy::PubSub; +use flexi_logger::Logger; + +use crate::utils::structs::Context; + +#[macro_use] +extern crate lazy_static; + +fn main() { + // Logger init + let _logger = Logger::try_with_env_or_str("info") + .unwrap() + .log_to_stdout() + .duplicate_to_stderr(flexi_logger::Duplicate::Warn) + .write_mode(flexi_logger::WriteMode::Async) + .start() + .unwrap(); + + // Services + let ctx_db = Context::new(); + let ctx_relay = ctx_db.clone(); + let ctx_usernames = ctx_relay.clone(); + + let db_handle = thread::spawn(move || { + noose::start(ctx_db); + }); + + let relay_handle = thread::spawn(move || { + relay::start(ctx_relay); + }); + + let nip05_handle = thread::spawn(move || { + usernames::start(ctx_usernames); + }); + + db_handle.join().unwrap(); + relay_handle.join().unwrap(); + nip05_handle.join().unwrap(); + + println!("Done"); +} diff --git a/src/noose/db.rs b/src/noose/db.rs new file mode 100644 index 0000000..ce3b3f8 --- /dev/null +++ b/src/noose/db.rs @@ -0,0 +1,18 @@ +use crate::{ + bussy::PubSub, + utils::{error::Error, structs::Subscription}, +}; +use async_trait::async_trait; +use nostr::Event; +use std::sync::Arc; + +#[async_trait] +pub trait Noose: Send + Sync { + async fn start(&mut self, pubsub: Arc) -> Result<(), Error>; + + async fn migration_up(&self); + + async fn write_event(&self, event: Box) -> Result; + + async fn find_event(&self, subscription: Subscription) -> Result, Error>; +} diff --git a/src/noose/migrations/1697409647688_create_events.sql b/src/noose/migrations/1697409647688_create_events.sql new file mode 100644 index 0000000..ea9ca33 --- /dev/null +++ b/src/noose/migrations/1697409647688_create_events.sql @@ -0,0 +1,23 @@ +CREATE TABLE events ( + id TEXT PRIMARY KEY, + kind INTEGER NOT NULL, + pubkey TEXT NOT NULL, + content TEXT NOT NULL, + created_at INTEGER NOT NULL, + tags TEXT NOT NULL, + sig TEXT NOT NULL +); + +CREATE INDEX idx_events_kind ON events (kind); +CREATE INDEX idx_events_pubkey ON events (pubkey); + + +CREATE TABLE tags ( + tag TEXT NOT NULL, + value TEXT NOT NULL, + event_id TEXT REFERENCES events(id) ON DELETE CASCADE +); + +CREATE INDEX idx_tags_tag ON tags (tag); +CREATE INDEX idx_tags_value ON tags (value); +CREATE INDEX idx_tags_event_id ON tags (event_id); diff --git a/src/noose/migrations/1697410161900_add_relays.sql b/src/noose/migrations/1697410161900_add_relays.sql new file mode 100644 index 0000000..feca351 --- /dev/null +++ b/src/noose/migrations/1697410161900_add_relays.sql @@ -0,0 +1,5 @@ +CREATE TABLE relays ( + url TEXT PRIMARY KEY, + domain TEXT NOT NULL, + active BOOLEAN NOT NULL +); diff --git a/src/noose/migrations/1697410223576_events_fts.sql b/src/noose/migrations/1697410223576_events_fts.sql new file mode 100644 index 0000000..85737f3 --- /dev/null +++ b/src/noose/migrations/1697410223576_events_fts.sql @@ -0,0 +1 @@ +CREATE VIRTUAL TABLE events_fts USING fts5(id, content); diff --git a/src/noose/migrations/1697410294265_users.sql b/src/noose/migrations/1697410294265_users.sql new file mode 100644 index 0000000..16af3e6 --- /dev/null +++ b/src/noose/migrations/1697410294265_users.sql @@ -0,0 +1,10 @@ +CREATE TABLE users ( + pubkey TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + inserted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + admin BOOLEAN DEFAULT false +); + +CREATE INDEX idx_users_pubkey ON users (pubkey); +CREATE INDEX idx_users_username ON users (username); + diff --git a/src/noose/migrations/1697410424624_pragma.sql b/src/noose/migrations/1697410424624_pragma.sql new file mode 100644 index 0000000..610bd0b --- /dev/null +++ b/src/noose/migrations/1697410424624_pragma.sql @@ -0,0 +1,2 @@ +PRAGMA foreign_keys = ON; +PRAGMA auto_vacuum = FULL; diff --git a/src/noose/migrations/1697410480767_unattached_media.sql b/src/noose/migrations/1697410480767_unattached_media.sql new file mode 100644 index 0000000..3c510c7 --- /dev/null +++ b/src/noose/migrations/1697410480767_unattached_media.sql @@ -0,0 +1,11 @@ +CREATE TABLE unattached_media ( + id TEXT PRIMARY KEY, + pubkey TEXT NOT NULL, + url TEXT NOT NULL, + data TEXT NOT NULL, + uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX unattached_media_id ON unattached_media (id); +CREATE INDEX unattached_media_pubkey ON unattached_media (pubkey); +CREATE INDEX unattached_media_url ON unattached_media (url); diff --git a/src/noose/mod.rs b/src/noose/mod.rs new file mode 100644 index 0000000..e9bcc7a --- /dev/null +++ b/src/noose/mod.rs @@ -0,0 +1,33 @@ +use crate::utils::structs::Context; +use tokio::runtime; + +use db::Noose; +use pipeline::Pipeline; + +pub mod db; +pub mod pipeline; +// mod sled; +mod sqlite; +pub mod user; + +pub fn start(context: Context) { + let rt = runtime::Runtime::new().unwrap(); + + rt.block_on(async move { + let pipeline_pubsub = context.pubsub.clone(); + let db_pubsub = context.pubsub.clone(); + + let pipeline_handle = tokio::task::spawn(async move { + let mut pipeline = Pipeline::new(pipeline_pubsub); + pipeline.start().await.unwrap(); + }); + + let sqlite_writer_handle = tokio::task::spawn(async move { + let mut db_writer = sqlite::SqliteDb::new().await; + db_writer.start(db_pubsub).await.unwrap(); + }); + + sqlite_writer_handle.await.unwrap(); + pipeline_handle.await.unwrap(); + }); +} diff --git a/src/noose/pipeline.rs b/src/noose/pipeline.rs new file mode 100644 index 0000000..9fbffe1 --- /dev/null +++ b/src/noose/pipeline.rs @@ -0,0 +1,166 @@ +use crate::bussy::{channels, Command, Message, PubSub}; +use crate::utils::error::Error; +use nostr::Event; +use std::sync::Arc; + +pub struct Pipeline { + pubsub: Arc, +} + +impl Pipeline { + pub fn new(pubsub: Arc) -> Self { + Self { pubsub } + } + + pub async fn start(&mut self) -> Result<(), Error> { + let mut subscriber = self.pubsub.subscribe(channels::MSG_PIPELINE).await; + + while let Ok(message) = subscriber.recv().await { + log::debug!("[Pipeline] received message: {:?}", message); + let command = match message.content { + Command::PipelineReqEvent(client_id, event) => { + match self.handle_event(client_id, event.clone()).await { + Ok(_) => { + let message = + nostr::RelayMessage::new_ok(event.id, true, "".to_string()); + Command::PipelineResRelayMessageOk(client_id, message) + } + Err(e) => Command::ServiceError(e), + } + } + _ => Command::Noop, + }; + if command != Command::Noop { + let channel = message.source; + let message = Message { + source: channels::MSG_PIPELINE, + content: command, + }; + + log::info!( + "[Pipeline] channel: {} - publishing new message: {:?}", + channel, + message + ); + + self.pubsub.publish(channel, message).await; + } + } + + Ok(()) + } + + pub async fn handle_event( + &self, + client_id: uuid::Uuid, + event: Box, + ) -> Result<(), Error> { + let store_event_task = self.store_event(event.clone()); + let process_deletions_task = self.process_deletions(event.clone()); + let track_hashtags_task = self.track_hashtags(event.clone()); + let process_media_task = self.process_media(event.clone()); + let stream_out_task = self.stream_out(event.clone()); + let broadcast_task = self.broadcast(event.clone()); + + let ( + store_event_result, + process_deletions_result, + track_hashtags_result, + process_media_result, + stream_out_result, + broadcast_result, + ) = tokio::join!( + store_event_task, + process_deletions_task, + track_hashtags_task, + process_media_task, + stream_out_task, + broadcast_task + ); + + match ( + store_event_result, + process_deletions_result, + track_hashtags_result, + process_media_result, + stream_out_result, + broadcast_result, + ) { + (Ok(_), Ok(_), Ok(_), Ok(_), Ok(_), Ok(_)) => { + log::info!("[Pipeline] Tasks finished successfully"); + Ok(()) + } + _ => { + log::error!("[Pipeline] One or more futures returned an error."); + Err(Error::internal_with_message( + "[Pipeline] One or more futures returned an error.", + )) + } + } + } + + async fn store_event(&self, event: Box) -> Result<(), Error> { + if event.kind.is_ephemeral() { + return Ok(()); + } + + self.pubsub + .publish( + channels::MSG_NOOSE, + Message { + source: channels::MSG_PIPELINE, + content: Command::DbReqWriteEvent(event), + }, + ) + .await; + + Ok(()) + } + + async fn process_deletions(&self, event: Box) -> Result<(), Error> { + // if event.kind.as_u32() == 5 { + // let events_for_deletion: Vec = event + // .tags + // .iter() + // .filter_map(|tag| match tag { + // nostr::Tag::Event(event_id, _, _) => Some(event_id.to_string()), + // _ => None, + // }) + // .collect(); + + // self.pubsub + // .publish( + // channels::MSG_NOOSE, + // Message { + // source: channels::MSG_PIPELINE, + // content: Command::DbReqDeleteEvents(events_for_deletion), + // }, + // ) + // .await; + // } + + Ok(()) + } + + async fn track_hashtags(&self, event: Box) -> Result<(), Error> { + Ok(()) + } + + async fn process_media(&self, event: Box) -> Result<(), Error> { + Ok(()) + } + + async fn stream_out(&self, event: Box) -> Result<(), Error> { + let message = Message { + source: channels::MSG_PIPELINE, + content: Command::PipelineResStreamOutEvent(event), + }; + self.pubsub.publish(channels::MSG_RELAY, message).await; + + Ok(()) + } + + async fn broadcast(&self, event: Box) -> Result<(), Error> { + Ok(()) + } +} diff --git a/src/noose/sled.rs b/src/noose/sled.rs new file mode 100644 index 0000000..d48785f --- /dev/null +++ b/src/noose/sled.rs @@ -0,0 +1,234 @@ +use super::db::Noose; +use crate::bussy::{channels, Command, Message, PubSub}; +use crate::utils::error::Error; +use crate::utils::structs::Subscription; +use async_trait::async_trait; +use nostr::Event; +use serde::Serialize; +use std::sync::Arc; + +use super::user::{User, UserRow}; + +// Db Interface +pub struct SledDb { + db: sled::Db, + events: sled::Tree, + nip05s: sled::Tree, + pub users: sled::Tree, + + index: sled::Db, +} + +impl SledDb { + pub fn new() -> Self { + let db = sled::open("/tmp/sled_db").unwrap(); + let events = db.open_tree("events").unwrap(); + let nip05s = db.open_tree("identifiers").unwrap(); + let accounts = db.open_tree("accounts").unwrap(); + + let index = sled::open("/tmp/sled_index").unwrap(); + + Self { + db, + events, + nip05s, + users: accounts, + index, + } + } + + fn clear_db(&self) -> Result<(), sled::Error> { + self.db.clear() + } + + fn clear_index(&self) -> Result<(), sled::Error> { + self.index.clear() + } + + async fn insert_user(&self, user: UserRow) -> Result<(), Error> { + let pubkey = user.pubkey.clone(); + let username = user.username.clone(); + + if let Ok(Some(_)) = self.nip05s.get(&username) { + return Err(Error::internal_with_message("User already exists")); + } + + let mut user_buff = flexbuffers::FlexbufferSerializer::new(); + user.serialize(&mut user_buff).unwrap(); + + self.nip05s.insert(&username, user_buff.view()).unwrap(); + + let prefix = "nip05:"; + let key = format!("{}{}", prefix, pubkey); + self.index.insert(key, username.as_bytes()).unwrap(); + + Ok(()) + } + + async fn get_user(&self, user: User) -> Result { + let mut user_row = None; + if let Some(username) = user.name { + if let Ok(Some(buff)) = self.nip05s.get(username) { + let b = flexbuffers::from_slice::(&buff).unwrap(); + user_row = Some(b); + } + } else if let Some(pubkey) = user.pubkey { + let prefix = "nip05:"; + let reference = format!("{}{}", prefix, pubkey); + if let Ok(Some(row)) = self.index.get(reference) { + let key = String::from_utf8(row.to_vec()).unwrap(); + + if let Ok(Some(buff)) = self.nip05s.get(key) { + let b = flexbuffers::from_slice::(&buff).unwrap(); + + user_row = Some(b); + } + } + } + match user_row { + Some(user) => Ok(user), + None => Err(Error::internal_with_message("User not found")), + } + } +} + +#[async_trait] +impl Noose for SledDb { + async fn start(&mut self, pubsub: Arc) -> Result<(), Error> { + let mut subscriber = pubsub.subscribe(channels::MSG_NOOSE).await; + + while let Ok(message) = subscriber.recv().await { + log::info!("noose subscriber received: {:?}", message); + let command = match message.content { + Command::DbReqInsertUser(user) => match self.insert_user(user).await { + Ok(_) => Command::DbResOk, + Err(e) => Command::ServiceError(e), + }, + Command::DbReqGetUser(user) => match self.get_user(user).await { + Ok(user) => Command::DbResUser(user), + Err(e) => Command::ServiceError(e), + }, + Command::DbReqWriteEvent(event) => match self.write_event(event).await { + Ok(_) => Command::DbResOk, + Err(e) => Command::ServiceError(e), + }, + _ => Command::Noop, + }; + if command != Command::Noop { + log::info!("Publishing new message"); + let channel = message.source; + + pubsub + .publish( + channel, + Message { + source: channels::MSG_NOOSE, + content: command, + }, + ) + .await; + } + } + + Ok(()) + } + + async fn migration_up(&self) {} + + async fn write_event(&self, event: Box) -> Result { + let mut event_buff = flexbuffers::FlexbufferSerializer::new(); + event.serialize(&mut event_buff).unwrap(); + + self.events.insert(event.id, event_buff.view()).unwrap(); + { + // Timestamp + let key = format!("created_at:{}|#e:{}", event.created_at, event.id); + self.index.insert(key, event.id.as_bytes()).unwrap(); + } + + { + // Author, pubkeys #p + let key = format!("#author:{}|#e:{}", event.pubkey, event.id); + self.index.insert(key, event.id.as_bytes()).unwrap(); + // self.index.scan_prefix( + } + + { + // Kinds + let key = format!("#k:{}|#e:{}", event.kind, event.id); + self.index.insert(key, event.id.as_bytes()).unwrap(); + // self.index.scan_prefix( + } + + { + // Tags + event.tags.iter().for_each(|tag| { + if let Some(key) = match tag { + // #e tag + nostr::Tag::Event(event_id, _, _) => Some(format!("#e:{}", event_id)), + // #p tag + nostr::Tag::PubKey(pubkey, _) => Some(format!("#p:{}|#e:{}", pubkey, event.id)), + // #t tag + nostr::Tag::Hashtag(hashtag) => Some(format!("#t:{}|#e:{}", hashtag, event.id)), + // #a tag + nostr::Tag::A { + kind, + public_key, + identifier, + relay_url, + } => Some(format!( + "#a:kind:{}|#a:pubkey:{}#a:identifier:{}|#e:{}", + kind, public_key, identifier, event.id + )), + _ => None, + } { + self.index.insert(key, event.id.as_bytes()).unwrap(); + } + }); + + // let key = format!("#t:{}|#e:{}", event.kind, event.id); + // self.index.insert(key, event.id.as_bytes()).unwrap(); + // self.index.scan_prefix( + } + + let message = format!("[\"OK\", \"{}\", true, \"\"]", event.id.to_string()); + Ok(message) + } + + async fn find_event(&self, subscription: Subscription) -> Result, Error> { + todo!() + } +} + +#[cfg(test)] +mod tests { + use super::SledDb; + use crate::{ + bussy::PubSub, + noose::user::{User, UserRow}, + }; + use std::sync::Arc; + + #[tokio::test] + async fn get_db_names() { + let pubsub = Arc::new(PubSub::new()); + + let db = SledDb::new(); + + let pk = "npub1p3ya99jfdafnqlk87p6wfd36d2nme5mkld769rhd9pkht6hmqlaq6mzxdu".to_string(); + let username = "klink".to_string(); + let user = UserRow::new(pk, username, false); + let result = db.insert_user(user).await; + + let pubkey = "npub1p3ya99jfdafnqlk87p6wfd36d2nme5mkld769rhd9pkht6hmqlaq6mzxdu".to_string(); + let username = "klink".to_string(); + let user = User { + name: None, + pubkey: Some(pubkey), + }; + let user = db.get_user(user).await; + + db.clear_db().unwrap(); + db.clear_index().unwrap(); + } +} diff --git a/src/noose/sqlite.rs b/src/noose/sqlite.rs new file mode 100644 index 0000000..110c6d3 --- /dev/null +++ b/src/noose/sqlite.rs @@ -0,0 +1,889 @@ +use async_trait::async_trait; +use nostr::{Event, JsonUtil}; +use sea_query::{extension::sqlite::SqliteExpr, Query}; +use sea_query_binder::SqlxBinder; +use sqlx::sqlite::{Sqlite, SqlitePoolOptions}; +use sqlx::FromRow; +use sqlx::{migrate::MigrateDatabase, Pool}; +use std::sync::Arc; + +use super::db::Noose; +use crate::bussy::{channels, Command, Message, PubSub}; +use crate::utils::{error::Error, structs::Subscription}; + +enum EventsTable { + Table, + EventId, + Kind, + Pubkey, + Content, + CreatedAt, + Tags, + Sig, +} + +impl sea_query::Iden for EventsTable { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!( + s, + "{}", + match self { + Self::Table => "events", + Self::EventId => "id", + Self::Kind => "kind", + Self::Pubkey => "pubkey", + Self::Content => "content", + Self::CreatedAt => "created_at", + Self::Tags => "tags", + Self::Sig => "sig", + } + ) + .unwrap() + } +} + +enum EventsFTSTable { + Table, + EventId, + Content, +} + +impl sea_query::Iden for EventsFTSTable { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!( + s, + "{}", + match self { + Self::Table => "events_fts", + Self::EventId => "id", + Self::Content => "content", + } + ) + .unwrap() + } +} + +enum TagsTable { + Table, + Tag, + Value, + EventId, +} + +impl sea_query::Iden for TagsTable { + fn unquoted(&self, s: &mut dyn std::fmt::Write) { + write!( + s, + "{}", + match self { + Self::Table => "tags", + Self::Tag => "tag", + Self::Value => "value", + Self::EventId => "event_id", + } + ) + .unwrap() + } +} + +#[derive(FromRow, Debug)] +struct EventsCountRow(i32); + +#[derive(FromRow, Debug)] +struct EventRow { + id: String, + pubkey: String, + created_at: i64, + kind: i64, + tags: String, + sig: String, + content: String, +} + +impl EventRow { + pub fn to_string(&self, subscription_id: nostr::SubscriptionId) -> String { + let tags: Vec> = serde_json::from_str(&self.tags).unwrap(); + + let message = serde_json::json!([ + "EVENT", + subscription_id, + { + "id": self.id, + "content": self.content, + "created_at": self.created_at, + "kind": self.kind, + "pubkey": self.pubkey, + "sig": self.sig, + "tags": tags + } + ]); + + message.to_string() + } +} + +pub struct SqliteDb { + pool: Pool, +} + +impl SqliteDb { + pub async fn new() -> Self { + let pool = SqliteDb::build_pool("noose_pool", 42).await; + + Self { pool } + } + + pub fn info(&self) { + dbg!(self.pool.options()); + } + + async fn migrate(pool: &Pool) { + sqlx::migrate!("src/noose/migrations") + .run(pool) + .await + .unwrap() + } + + async fn build_pool(name: &str, max_size: u32) -> Pool { + let pool_options = SqlitePoolOptions::new() + .test_before_acquire(true) + // .idle_timeout(Some(Duration::from_secs(10))) + // .max_lifetime(Some(Duration::from_secs(30))) + .max_lifetime(None) + .idle_timeout(None) + .max_connections(max_size); + + let db_url = "sqlite://sqlite.db"; + if !Sqlite::database_exists(db_url).await.unwrap_or(false) { + log::info!("Creating database {}", db_url); + match Sqlite::create_database(db_url).await { + Ok(_) => log::info!("Db {} created", db_url), + Err(_) => panic!("Failed to create database {}", db_url), + } + } + if let Ok(pool) = pool_options.connect(db_url).await { + log::info!("Connected to sqlite pool {}", name); + + pool + } else { + panic!("Connection to sqlite pool {} failed", name); + } + } + + async fn add_event(&self, event: Box) -> Result { + let id = event.id.to_string(); + let kind = event.kind.to_string(); + let pubkey = event.pubkey.to_string(); + let content = event.content.to_string(); + let created_at = event.created_at.as_i64(); + let tags = serde_json::to_string(&event.tags).unwrap(); + let sig = event.sig.to_string(); + + let message = format!("[\"OK\", \"{}\", true, \"\"]", id.clone()); + + if event.is_ephemeral() { + return Ok(message); + } + + let tx = self.pool.begin().await.unwrap(); + { + if event.is_replaceable() { + dbg!("new event is replaceable - searching for previously stored event"); + let (sql, values) = Query::select() + .from(EventsTable::Table) + .columns([EventsTable::EventId]) + .and_where( + sea_query::Expr::col(EventsTable::Pubkey).eq(event.pubkey.to_string()), + ) + .and_where(sea_query::Expr::col(EventsTable::Kind).eq(event.kind.as_u32())) + .and_where( + sea_query::Expr::col(EventsTable::CreatedAt).gte(event.created_at.as_i64()), + ) + .limit(1) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let repl_count = sqlx::query_with(&sql, values).fetch_one(&self.pool).await; + + if repl_count.ok().is_some() { + return Ok(message); + } + } + } + + { + if event.is_parameterized_replaceable() { + dbg!( + "new event is parametrized replaceable - searching for previously stored event" + ); + let d_tags: Vec = event + .tags + .iter() + .filter(|tag| tag.kind() == nostr::TagKind::D) + .map(|tag| tag.clone().to_vec()[1].clone()) + .collect(); + let (sql, values) = Query::select() + .from(EventsTable::Table) + .column((EventsTable::Table, EventsTable::EventId)) + .left_join( + TagsTable::Table, + sea_query::Expr::col((TagsTable::Table, TagsTable::EventId)) + .equals((EventsTable::Table, EventsTable::EventId)), + ) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::Pubkey)) + .eq(event.pubkey.to_string()), + ) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::Kind)) + .eq(event.kind.as_u32()), + ) + .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)).eq("d")) + .and_where( + sea_query::Expr::col((TagsTable::Table, TagsTable::Value)) + .eq(d_tags[0].to_string()), + ) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt)) + .gte(event.created_at.as_i64()), + ) + .limit(1) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let repl_count = sqlx::query_with(&sql, values).fetch_one(&self.pool).await; + + if repl_count.ok().is_some() { + return Ok(message); + } + } + } + + // Insert replaceble event + { + if event.is_replaceable() { + dbg!("deleting older replaceable event from events table"); + let (sql, values) = Query::delete() + .from_table(EventsTable::Table) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::Kind)) + .eq(event.kind.as_u32()), + ) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::Pubkey)) + .eq(event.pubkey.to_string()), + ) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::EventId)) + .not_in_subquery( + Query::select() + .from(EventsTable::Table) + .column(EventsTable::EventId) + .and_where( + sea_query::Expr::col(EventsTable::Kind) + .eq(event.kind.as_u32()), + ) + .and_where( + sea_query::Expr::col(EventsTable::Pubkey) + .eq(event.pubkey.to_string()), + ) + .order_by(EventsTable::CreatedAt, sea_query::Order::Desc) + .limit(1) + .to_owned(), + ), + ) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let results = sqlx::query_with(&sql, values) + .execute(&self.pool) + .await + .unwrap(); + + if results.rows_affected() > 0 { + log::info!( + "removed {} older replaceable kind {} events for author: {:?}", + results.rows_affected(), + event.kind.as_u32(), + event.pubkey.to_string() + ); + } + } + } + + // Insert parametrized replaceble event + { + if event.is_parameterized_replaceable() { + dbg!("deleting older parametrized replaceable event from events table"); + let d_tag = event.identifier(); + let (sql, values) = Query::delete() + .from_table(EventsTable::Table) + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::EventId)) + .in_subquery( + Query::select() + .from(EventsTable::Table) + .column((EventsTable::Table, EventsTable::EventId)) + .left_join( + TagsTable::Table, + sea_query::Expr::col(( + TagsTable::Table, + TagsTable::EventId, + )) + .equals((EventsTable::Table, EventsTable::EventId)), + ) + .and_where( + sea_query::Expr::col(( + EventsTable::Table, + EventsTable::Kind, + )) + .eq(event.kind.as_u32()), + ) + .and_where( + sea_query::Expr::col(( + EventsTable::Table, + EventsTable::Pubkey, + )) + .eq(event.pubkey.to_string()), + ) + .and_where( + sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)) + .eq("d"), + ) + .and_where( + sea_query::Expr::col((TagsTable::Table, TagsTable::Value)) + .eq(d_tag), + ) + .to_owned(), + ), + ) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let results = sqlx::query_with(&sql, values) + .execute(&self.pool) + .await + .unwrap(); + + if results.rows_affected() > 0 { + log::info!("removed {} older parameterized replaceable kind {} events for author: {:?}", results.rows_affected(), event.kind, event.pubkey); + } + } + } + + // Process deletion events + dbg!(event.as_json()); + if event.kind.as_u32() == 5 { + dbg!("deleting event"); + let ids: Vec = event.event_ids().map(|eid| eid.to_string()).collect(); + let (sql, values) = Query::delete() + .from_table(EventsTable::Table) + .and_where(sea_query::Expr::col(EventsTable::Kind).ne(5)) + .and_where(sea_query::Expr::col(EventsTable::Pubkey).eq(event.pubkey.to_string())) + .and_where(sea_query::Expr::col(EventsTable::EventId).is_in(&ids)) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let results = sqlx::query_with(&sql, values) + .execute(&self.pool) + .await + .unwrap(); + + if results.rows_affected() > 0 { + log::info!( + "removed {} events for author {:?}", + results.rows_affected(), + event.pubkey + ); + } + + // Delete from EventsFTS + let (sql, values) = Query::delete() + .from_table(EventsFTSTable::Table) + .and_where(sea_query::Expr::col(EventsFTSTable::EventId).is_in(&ids)) + .build_sqlx(sea_query::SqliteQueryBuilder); + let _ = sqlx::query_with(&sql, values) + .execute(&self.pool) + .await + .unwrap(); + } else { + dbg!("inserting new event in events"); + // Insert into Events table + let (sql, values) = Query::insert() + .into_table(EventsTable::Table) + .columns([ + EventsTable::EventId, + EventsTable::Content, + EventsTable::Kind, + EventsTable::Pubkey, + EventsTable::CreatedAt, + EventsTable::Tags, + EventsTable::Sig, + ]) + .values_panic([ + id.clone().into(), + content.clone().into(), + kind.into(), + pubkey.into(), + created_at.into(), + tags.into(), + sig.into(), + ]) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let results = sqlx::query_with(&sql, values) + .execute(&self.pool) + .await + .unwrap(); + + // Insert into EventsFTS table + dbg!("inserting new event into eventsFTS"); + let (sql, values) = Query::insert() + .into_table(EventsFTSTable::Table) + .columns([EventsFTSTable::EventId, EventsFTSTable::Content]) + .values_panic([id.clone().into(), content.into()]) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let results = sqlx::query_with(&sql, values) + .execute(&self.pool) + .await + .unwrap(); + + // Insert into Tags table + dbg!("inserting new event into tags"); + for tag in event.tags.clone() { + let tag = tag.to_vec(); + if tag.len() >= 2 { + let tag_name = &tag[0]; + let tag_value = &tag[1]; + if tag_name.len() == 1 { + let (sql, values) = Query::insert() + .into_table(TagsTable::Table) + .columns([TagsTable::Tag, TagsTable::Value, TagsTable::EventId]) + .values_panic([tag_name.into(), tag_value.into(), id.clone().into()]) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let results = sqlx::query_with(&sql, values) + .execute(&self.pool) + .await + .unwrap(); + } + } + } + } + + tx.commit().await.unwrap(); + + Ok(message) + } + + async fn index_search(&self, event: Box) -> Result<(), Error> { + let id = event.id.to_string(); + let content = event.content.to_string(); + let (sql, values) = Query::insert() + .into_table(EventsFTSTable::Table) + .columns([EventsFTSTable::EventId, EventsFTSTable::Content]) + .values_panic([id.into(), content.into()]) + .build_sqlx(sea_query::SqliteQueryBuilder); + + let results = sqlx::query_with(&sql, values).execute(&self.pool).await; + if results.is_ok() { + Ok(()) + } else { + Err(Error::internal_with_message( + "Unable to write event to events_fts index", + )) + } + } + + async fn index_tags(&self, event: Box) -> Result<(), Error> { + // let t: Vec = Vec::new(); + // for tag in event.tags { + // tag.kind() + // } + + Ok(()) + } + + fn get_filter_query(&self, filter: &nostr::Filter) -> sea_query::SelectStatement { + let mut query = Query::select() + .column((EventsTable::Table, EventsTable::EventId)) + .column((EventsTable::Table, EventsTable::Content)) + .columns([ + EventsTable::Kind, + EventsTable::Pubkey, + EventsTable::CreatedAt, + EventsTable::Tags, + EventsTable::Sig, + ]) + .from(EventsTable::Table) + .order_by(EventsTable::CreatedAt, sea_query::Order::Desc) + .to_owned(); + + if !filter.ids.is_empty() { + let ids = filter.ids.iter().map(|id| id.to_string()); + query = query + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::EventId)).is_in(ids), + ) + .to_owned(); + } + + if !filter.kinds.is_empty() { + let kinds: Vec = filter.kinds.iter().map(|kind| kind.as_u32()).collect(); + query = query + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::Kind)).is_in(kinds), + ) + .to_owned(); + } + + if !filter.authors.is_empty() { + let authors = filter.authors.iter().map(|author| author.to_string()); + query = query + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::Pubkey)).is_in(authors), + ) + .to_owned(); + } + + if let Some(since) = filter.since { + query = query + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt)) + .gte(since.as_u64()), + ) + .to_owned(); + } + + if let Some(until) = filter.until { + query = query + .and_where( + sea_query::Expr::col((EventsTable::Table, EventsTable::CreatedAt)) + .lte(until.as_u64()), + ) + .to_owned(); + } + + if let Some(limit) = filter.limit { + query = query.limit(limit as u64).to_owned(); + } + + filter.generic_tags.iter().for_each(|(tag, values)| { + let values = values.iter().map(|val| val.to_string()); + query = query + .left_join( + TagsTable::Table, + sea_query::Expr::col((TagsTable::Table, TagsTable::EventId)) + .equals((EventsTable::Table, EventsTable::EventId)), + ) + .and_where( + sea_query::Expr::col((TagsTable::Table, TagsTable::Tag)).eq(tag.to_string()), + ) + .and_where(sea_query::Expr::col((TagsTable::Table, TagsTable::Value)).is_in(values)) + .to_owned(); + }); + + if let Some(search) = &filter.search { + query = query + .inner_join( + EventsFTSTable::Table, + sea_query::Expr::col((EventsTable::Table, EventsTable::EventId)) + .equals((EventsFTSTable::Table, EventsFTSTable::EventId)), + ) + .and_where( + sea_query::Expr::col((EventsFTSTable::Table, EventsFTSTable::Content)) + .matches(search), + ) + .to_owned(); + } + + query + } + + fn get_filters_query(&self, subscription: Subscription) -> Option { + subscription + .filters + .iter() + .map(|filter| { + Query::select() + .column((EventsTable::Table, EventsTable::EventId)) + .column((EventsTable::Table, EventsTable::Content)) + .columns([ + EventsTable::Kind, + EventsTable::Pubkey, + EventsTable::CreatedAt, + EventsTable::Tags, + EventsTable::Sig, + ]) + .from_subquery( + self.get_filter_query(filter), + sea_query::Alias::new("events"), + ) + .to_owned() + }) + .reduce(|mut result, query| result.union(sea_query::UnionType::All, query).to_owned()) + } + + async fn delete_filters(&self, subscription: Subscription) -> Vec { + todo!() + } + + async fn count_events_by_filters(&self, subscription: Subscription) -> i32 { + if subscription.filters.is_empty() { + return 0; + } + + let (sql, values) = self + .get_filters_query(subscription) + .unwrap() + .clear_selects() + .expr_as( + sea_query::Func::count(sea_query::Expr::col(( + EventsTable::Table, + EventsTable::EventId, + ))), + sea_query::Alias::new("count"), + ) + .build_sqlx(sea_query::SqliteQueryBuilder); + + println!("count_filters SEA_QUERY built SQL: {}", sql.clone()); + + let counts = sqlx::query_as_with::<_, EventsCountRow, _>(&sql, values) + .fetch_one(&self.pool) + .await + .unwrap(); + + dbg!(counts); + + 1 + } +} + +#[async_trait] +impl Noose for SqliteDb { + async fn start(&mut self, pubsub: Arc) -> Result<(), Error> { + let mut subscriber = pubsub.subscribe(channels::MSG_NOOSE).await; + + while let Ok(message) = subscriber.recv().await { + log::info!("[Noose] received message: {:?}", message); + let command = match message.content { + Command::DbReqWriteEvent(event) => match self.write_event(event).await { + Ok(status) => Command::DbResOkWithStatus(status), + Err(e) => Command::ServiceError(e), + }, + Command::DbReqFindEvent(client_id, subscriptioin) => { + match self.find_event(subscriptioin).await { + Ok(events) => Command::DbResRelayMessage(client_id, events), + Err(e) => Command::ServiceError(e), + } + } + _ => Command::Noop, + }; + if command != Command::Noop { + let channel = message.source; + let message = Message { + source: channels::MSG_NOOSE, + content: command, + }; + + log::debug!("[Noose] publishing new message: {:?}", message); + + pubsub.publish(channel, message).await; + } + } + + Ok(()) + } + + async fn migration_up(&self) { + SqliteDb::migrate(&self.pool).await; + } + + async fn write_event(&self, event: Box) -> Result { + log::debug!("[Noose] write_event triggered"); + + let status = self.add_event(event).await.unwrap(); + + return Ok(status); + } + + async fn find_event(&self, subscription: Subscription) -> Result, Error> { + log::debug!("making query from filters..."); + + let eose_message = + vec![nostr::RelayMessage::EndOfStoredEvents(subscription.id.clone()).as_json()]; + + if let Some(sql_statement) = self.get_filters_query(subscription.clone()) { + let (sql, values) = sql_statement.build_sqlx(sea_query::SqliteQueryBuilder); + log::info!("SEA_QUERY built SQL: {}", sql.clone()); + + match sqlx::query_as_with::<_, EventRow, _>(&sql, values) + .fetch_all(&self.pool) + .await + { + Ok(rows) => { + if rows.is_empty() { + return Ok(eose_message); + } else { + let relay_messages: Vec = rows + .iter() + .map(|row| row.to_string(subscription.id.clone())) + .collect(); + return Ok(relay_messages); + } + } + Err(e) => { + log::error!("{}", e); + return Err(Error::internal(e.into())); + } + } + } + + return Ok(eose_message); + } +} + +#[cfg(test)] +mod tests { + use super::Noose; + use super::SqliteDb; + use crate::utils::structs::Subscription; + + use nostr::util::JsonUtil; + + #[tokio::test] + async fn find_event() { + let db = SqliteDb::new().await; + + let t = std::time::Instant::now(); + let client_id = "test_id".to_string(); + + let cm = nostr::ClientMessage::from_json( + r#"["REQ","7b9bc4b6-701c-40b6-898f-4e7c6b5b1510",{"authors":["04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"],"kinds":[0]}]"#, + ).unwrap(); + let (sub_id, filters) = match cm { + nostr::ClientMessage::Req { + subscription_id, + filters, + } => (subscription_id, filters), + _ => panic!("sneed :("), + }; + let sub = Subscription::new(sub_id, filters); + db.find_event(sub).await.unwrap(); + println!( + "Time passed: {}", + (std::time::Instant::now() - t).as_millis() + ); + } + + #[tokio::test] + async fn delete_events() { + let db = SqliteDb::new().await; + + let t = std::time::Instant::now(); + let client_id = "test_id".to_string(); + + let my_keys = nostr::Keys::generate(); + + let eid = nostr::EventId::all_zeros(); + let tag_event = nostr::Tag::Event { + event_id: eid, + relay_url: None, + marker: None, + }; + let tag_url = nostr::Tag::AbsoluteURL(nostr::types::UncheckedUrl::new( + "http://foo.net".to_string(), + )); + let tag_hashtag = nostr::Tag::Hashtag("farm".to_string()); + + let event = nostr::EventBuilder::new_text_note( + "sneed feed and seed", + vec![tag_event, tag_url, tag_hashtag], + ) + .to_event(&my_keys) + .unwrap(); + + dbg!(&event.as_json()); + let resp = db.add_event(Box::new(event.clone())).await.unwrap(); + dbg!(resp); + + let delete_event = nostr::EventBuilder::delete(vec![event.id]) + .to_event(&my_keys) + .unwrap(); + + dbg!(&delete_event); + let resp = db.add_event(Box::new(delete_event.clone())).await.unwrap(); + dbg!(resp); + + // let sub_id = nostr::SubscriptionId::new("test".to_string()); + // let mut subscription = Subscription::new(sub_id, vec![]); + + // if delete_event.kind == nostr::Kind::EventDeletion { + // delete_event + // .tags + // .iter() + // .filter(|tag| { + // matches!( + // tag, + // nostr::Tag::Event { + // event_id, + // relay_url, + // marker, + // } + // ) + // }) + // .for_each(|tag| { + // if let nostr::Tag::Event { + // event_id, + // relay_url, + // marker, + // } = tag + // { + // let filter = nostr::Filter::new(); + // let filter = &filter.event(*event_id); + + // subscription.filters.push(filter.clone()); + // } + // }); + + // dbg!(&subscription); + // } + + // let res = db.delete_filters(subscription).await; + + // dbg!(res); + + // let sub = Subscription::new(sub_id, filters); + // let num = db.delete_filters(sub).await.len(); + // println!( + // "Time passed: {}", + // (std::time::Instant::now() - t).as_millis() + // ); + + // assert_eq!(num, 1); + } + + #[tokio::test] + async fn count_events() { + let db = SqliteDb::new().await; + + let t = std::time::Instant::now(); + let client_id = "test_id".to_string(); + + let cm = nostr::ClientMessage::from_json( + r#"["COUNT","7b9bc4b6-701c-40b6-898f-4e7c6b5b1510",{"authors":["6be3c1446231fe6d117d72e29b60094bbb3eec029100c34f627dc4ebe8369a64"],"kinds":[1]}]"#, + ).unwrap(); + let (sub_id, filters) = match cm { + nostr::ClientMessage::Count { + subscription_id, + filters, + } => (subscription_id, filters), + _ => panic!("sneed :("), + }; + + let sub = Subscription::new(sub_id, filters); + let num = db.count_events_by_filters(sub).await; + println!( + "Time passed: {}", + (std::time::Instant::now() - t).as_millis() + ); + + assert_eq!(num, 1); + } +} diff --git a/src/noose/user.rs b/src/noose/user.rs new file mode 100644 index 0000000..fe31020 --- /dev/null +++ b/src/noose/user.rs @@ -0,0 +1,43 @@ +use chrono::Utc; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use validator::{Validate, ValidationError}; + +lazy_static! { + static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap(); +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)] +pub struct UserRow { + pub pubkey: String, + pub username: String, + inserted_at: i64, + admin: bool, +} + +impl UserRow { + pub fn new(pubkey: String, username: String, admin: bool) -> Self { + Self { + pubkey, + username, + inserted_at: Utc::now().timestamp(), + admin, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)] +pub struct User { + #[validate(custom = "validate_pubkey")] + pub pubkey: Option, + #[validate(length(min = 1), regex = "VALID_CHARACTERS")] + pub name: Option, +} + +pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> { + use nostr::prelude::FromPkStr; + match nostr::Keys::from_pk_str(value) { + Ok(_) => Ok(()), + Err(_) => Err(ValidationError::new("Unable to parse pubkey")), + } +} diff --git a/src/relay/handler.rs b/src/relay/handler.rs new file mode 100644 index 0000000..26f8e81 --- /dev/null +++ b/src/relay/handler.rs @@ -0,0 +1,12 @@ +use crate::relay::ws; +use crate::utils::structs::Context; +use std::net::SocketAddr; +use warp::{Rejection, Reply}; + +pub async fn ws_handler( + ws: warp::ws::Ws, + context: Context, + client_addr: Option, +) -> Result { + Ok(ws.on_upgrade(move |socket| ws::client_connection(socket, context, client_addr))) +} diff --git a/src/relay/mod.rs b/src/relay/mod.rs new file mode 100644 index 0000000..9c46b59 --- /dev/null +++ b/src/relay/mod.rs @@ -0,0 +1,24 @@ +mod handler; +mod routes; +mod ws; + +use crate::utils::rejection_handler::handle_rejection; +use crate::utils::structs::Context; +use tokio::runtime; +use warp::Filter; + +pub fn start(context: Context) { + let rt = runtime::Runtime::new().unwrap(); + rt.block_on(async { + log::info!("Starting Relay on wss://127.0.0.1:8080"); + + let routes = routes::routes(context).recover(handle_rejection); + + warp::serve(routes) + // .tls() + // .cert(CERT) + // .key(KEY) + .run(([127, 0, 0, 1], 8080)) + .await; + }); +} diff --git a/src/relay/routes.rs b/src/relay/routes.rs new file mode 100644 index 0000000..0187217 --- /dev/null +++ b/src/relay/routes.rs @@ -0,0 +1,31 @@ +use super::handler; +use crate::utils::filter::with_context; +use crate::utils::structs::Context; +use warp::{Filter, Rejection, Reply}; + +pub fn routes(context: Context) -> impl Filter + Clone { + let cors = warp::cors().allow_any_origin(); + + static_files().or(index(context)).with(cors) +} + +fn index(context: Context) -> impl Filter + Clone { + let client_addr = warp::addr::remote(); + + warp::path::end() + .and(warp::ws()) + .and(with_context(context)) + .and(client_addr) + .and_then(handler::ws_handler) +} + +fn static_files() -> impl Filter + Clone { + let mut foo = std::env::current_exe().unwrap(); + foo.pop(); + + let mut www = foo.clone(); + www.pop(); + www.push(std::path::Path::new("www/static")); + + warp::get().and(warp::fs::dir(www)) +} diff --git a/src/relay/ws.rs b/src/relay/ws.rs new file mode 100644 index 0000000..c642847 --- /dev/null +++ b/src/relay/ws.rs @@ -0,0 +1,264 @@ +use crate::{ + bussy::channels, + utils::structs::{Client, Context, Subscription}, +}; + +use futures_util::StreamExt; +use nostr::{ClientMessage, Event, Filter, JsonUtil, SubscriptionId}; +use serde_json::from_str; +use std::net::SocketAddr; +use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; +use warp::ws::{Message, WebSocket}; + +use futures_util::SinkExt; + +pub async fn client_connection(ws: WebSocket, context: Context, client_addr: Option) { + let (mut ws_sender, mut ws_receiver) = ws.split(); + let (client_sender, client_receiver) = mpsc::unbounded_channel(); + + let mut client_receiver = UnboundedReceiverStream::new(client_receiver); + + // Create and Add to the Context new Client and set its sender + let ip = client_addr.unwrap().ip().to_string(); + let mut client = Client::new(ip); + + client.client_connection = Some(client_sender); + + let mut subscriber = context.pubsub.subscribe(channels::MSG_RELAY).await; + + loop { + tokio::select! { + Ok(message) = subscriber.recv() => { + match message.content { + crate::bussy::Command::PipelineResRelayMessageOk(client_id, relay_message) => { + if client.client_id == client_id { + if let Some(sender) = &client.client_connection { + if !sender.is_closed() { + log::info!("[Relay] sending back the status of the processed event: {}", relay_message.as_json()); + sender.send(Ok(Message::text(relay_message.as_json()))).unwrap(); + } + + } + } + }, + crate::bussy::Command::PipelineResStreamOutEvent(event) => { + // if client.client_id == client_id { + if let Some(sender) = &client.client_connection { + if let Some(relay_message) = get_relay_message(&client, event) { + log::info!("[Relay] sending processed event to subscribed client: {}", relay_message.as_json()); + if !sender.is_closed() {sender.send(Ok(Message::text(relay_message.as_json()))).unwrap()}; + } + } + // } + } + crate::bussy::Command::DbResRelayMessage(client_id, events) => { + if client.client_id == client_id { + if let Some(sender) = &client.client_connection { + if !sender.is_closed() { + for event in events { + sender.send(Ok(Message::text(event))).unwrap(); + } + } + } + } + } + crate::bussy::Command::DbResOkWithStatus(status) => { + if let Some(sender) = &client.client_connection { + sender.send(Ok(Message::text(status))).unwrap(); + } + }, + _ => () + } + + }, + Some(message) = client_receiver.next() => { + match message { + Ok(message) => { + // ws_sender + // .send(message) + // .unwrap_or_else(|e| { + // log::error!("websocket send error: {}", e); + // }) + // .await; + + match ws_sender.send(message).await { + Ok(_) => (), + Err(e) => { + log::error!("websocket send error: {}", e); + break; + } + } + } + Err(e) => { + log::error!("websocket send error: {}", e); + break; + } + } + }, + Some(result) = ws_receiver.next() => { + let msg = match result { + Ok(msg) => msg, + Err(e) => { + log::error!( + "error receiving ws message for id: {}: {}", + client.client_id.clone(), + e + ); + break; + } + }; + socket_on_message(&context, &mut client, msg).await; + } + } + } + + // Handle proper disconnects + socket_on_close(&client).await; +} + +/// Checking if client with id needs the event +fn get_relay_message(client: &Client, event: Box) -> Option { + let mut id = &"".to_string(); + log::info!( + "Checking if client with id {} needs the event", + client.client_id + ); + if client.subscriptions.iter().any(|(sub_id, sub)| { + if sub.interested_in_event(&event) { + id = sub_id; + return true; + } + false + }) { + return Some(nostr::RelayMessage::Event { + subscription_id: nostr::SubscriptionId::new(id), + event, + }); + } + + None +} + +async fn socket_on_close(client: &Client) { + // clients.write().await.remove(id); + log::info!("{} disconnected", client.client_id); +} + +async fn socket_on_message(context: &Context, client: &mut Client, msg: Message) { + let message = match msg.to_str() { + Ok(raw_message) => raw_message, + Err(_) => return, + }; + + if message == "ping" || message == "ping\n" { + return; + } + + let client_message: ClientMessage = match from_str(message) { + Ok(parsed_message) => parsed_message, + Err(e) => { + log::error!("error while parsing client message request: {}", e); + + let response = nostr::RelayMessage::new_notice("Invalid message"); + let message = Message::text(response.as_json()); + send(client, message); + + return; + } + }; + + log::info!( + "[client {} - {}] message: {}", + client.ip(), + client.client_id, + client_message.as_json() + ); + handle_msg(context, client, client_message).await; +} + +fn send(client: &Client, message: Message) { + if let Some(sender) = &client.client_connection { + if !sender.is_closed() { + sender.send(Ok(message)).unwrap(); + } + } +} + +async fn handle_msg(context: &Context, client: &mut Client, client_message: ClientMessage) { + match client_message { + ClientMessage::Event(event) => handle_event(context, client, event).await, + ClientMessage::Req { + subscription_id, + filters, + } => handle_req(context, client, subscription_id, filters).await, + ClientMessage::Count { + subscription_id, + filters, + } => handle_count(client, subscription_id, filters).await, + ClientMessage::Close(subscription_id) => handle_close(client, subscription_id).await, + ClientMessage::Auth(event) => handle_auth(client, event).await, + _ => (), + } +} + +async fn handle_event(context: &Context, client: &Client, event: Box) { + log::debug!("handle_event is processing new event"); + + context + .pubsub + .publish( + channels::MSG_PIPELINE, + crate::bussy::Message { + source: channels::MSG_RELAY, + content: crate::bussy::Command::PipelineReqEvent(client.client_id, event), + }, + ) + .await; +} + +async fn handle_req( + context: &Context, + client: &mut Client, + subscription_id: SubscriptionId, + filters: Vec, +) { + let subscription = Subscription::new(subscription_id.clone(), filters); + let needs_historical_events = subscription.needs_historical_events(); + + client.subscribe(subscription.clone()).unwrap(); + + if needs_historical_events { + context + .pubsub + .publish( + channels::MSG_NOOSE, + crate::bussy::Message { + source: channels::MSG_RELAY, + content: crate::bussy::Command::DbReqFindEvent(client.client_id, subscription), + }, + ) + .await + } +} + +async fn handle_count(client: &Client, subscription_id: SubscriptionId, filters: Vec) { + // context.pubsub.send(new nostr event) then handle possible errors + let subscription = Subscription::new(subscription_id, filters); + + let message = Message::text("COUNT not implemented"); + send(client, message); +} + +async fn handle_close(client: &mut Client, subscription_id: SubscriptionId) { + // context.pubsub.send(new nostr event) then handle possible errors + client.unsubscribe(subscription_id); + + // let message = Message::text("CLOSE not implemented"); + // send(client, message); +} + +async fn handle_auth(client: &Client, event: Box) { + let message = Message::text("AUTH not implemented"); + send(client, message); +} diff --git a/src/usernames/accounts.rs b/src/usernames/accounts.rs new file mode 100644 index 0000000..d03aeb8 --- /dev/null +++ b/src/usernames/accounts.rs @@ -0,0 +1,62 @@ +use crate::noose::user::{User, UserRow}; + +use crate::bussy::{channels, Command, Message}; +use crate::usernames::util::InvalidParameter; +use crate::utils::error::Error; + +use warp::{Rejection, Reply}; + +use super::Context; + +pub async fn create_account(user: User, context: Context) -> Result { + let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await; + + let pubkey = match user.pubkey { + Some(pk) => { + use nostr::prelude::FromPkStr; + let keys = nostr::Keys::from_pk_str(&pk).unwrap(); + keys.public_key().to_string() + } + None => { + return Err(warp::reject::custom(Error::bad_request( + "Pubkey is required", + ))) + } + }; + + let name = match user.name { + Some(n) => n, + None => return Err(warp::reject::custom(Error::bad_request("Name is required"))), + }; + + let user_row = UserRow::new(pubkey, name, false); + + let command = Command::DbReqInsertUser(user_row); + context + .pubsub + .publish( + channels::MSG_NOOSE, + Message { + source: channels::MSG_NIP05, + content: command, + }, + ) + .await; + + if let Ok(result) = subscriber.recv().await { + match result.content { + Command::DbResOk => Ok(warp::reply::with_status( + "ACCOUNT CREATED", + warp::http::StatusCode::CREATED, + )), + Command::ServiceError(e) => Err(warp::reject::custom(e)), + // _ => Err(warp::reject::custom(InvalidParameter)), + _ => Ok(warp::reply::with_status( + "something else", + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + )), + } + } else { + Err(warp::reject::custom(InvalidParameter)) + } +} diff --git a/src/usernames/dto/mod.rs b/src/usernames/dto/mod.rs new file mode 100644 index 0000000..eb8bc55 --- /dev/null +++ b/src/usernames/dto/mod.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use crate::usernames::validators::validate_pubkey; +use crate::utils::error::Error; +use nostr::prelude::*; +use nostr::{key::XOnlyPublicKey, Keys}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use validator::Validate; + +lazy_static! { + static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap(); +} + +#[derive(Serialize, Deserialize, Debug, Validate)] +pub struct UserBody { + #[validate(length(min = 1), regex = "VALID_CHARACTERS")] + pub name: String, + #[validate(custom(function = "validate_pubkey"))] + pub pubkey: String, +} + +impl UserBody { + pub fn get_pubkey(&self) -> XOnlyPublicKey { + let keys = Keys::from_pk_str(&self.pubkey).unwrap(); + + keys.public_key() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Nip05 { + names: HashMap, +} + +#[derive(Serialize, Deserialize, Debug, Validate, Clone)] +pub struct UserQuery { + #[validate(length(min = 1))] + pub user: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Validate)] +pub struct Account { + #[validate(custom(function = "validate_pubkey"))] + pub pubkey: String, + #[validate(length(min = 1), regex = "VALID_CHARACTERS")] + pub name: String, + #[validate(length(min = 1))] + pub password: String, +} + +impl Account { + pub fn get_pubkey(&self) -> Result { + match nostr::Keys::from_pk_str(&self.pubkey) { + Ok(pk) => Ok(pk.public_key()), + Err(e) => Err(Error::invalid_param("pubkey", self.pubkey.clone())), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Validate)] +pub struct AccountPubkey { + #[validate(custom(function = "validate_pubkey"))] + pub pubkey: String, +} diff --git a/src/usernames/filter.rs b/src/usernames/filter.rs new file mode 100644 index 0000000..b74c5a8 --- /dev/null +++ b/src/usernames/filter.rs @@ -0,0 +1,27 @@ +use crate::utils::error::Error; +use validator::Validate; +use warp::{Filter, Rejection}; + +pub fn with_client_ip() {} + +pub fn with_user_body() {} + +pub fn validate_body_filter( +) -> impl Filter + Copy { + warp::body::json::().and_then(|query: T| async move { + match query.validate() { + Ok(_) => Ok(query), + Err(e) => Err(warp::reject::custom(Error::validation_error(e))), + } + }) +} + +pub fn validate_query_filter( +) -> impl Filter + Copy { + warp::query::query::().and_then(|query: T| async move { + match query.validate() { + Ok(_) => Ok(query), + Err(e) => Err(warp::reject::custom(Error::validation_error(e))), + } + }) +} diff --git a/src/usernames/handler.rs b/src/usernames/handler.rs new file mode 100644 index 0000000..b281d2e --- /dev/null +++ b/src/usernames/handler.rs @@ -0,0 +1,89 @@ +use crate::bussy::{channels, Command, Message}; +use crate::noose::user::User; +use crate::usernames::dto::{Account, UserQuery}; +use crate::utils::error::Error; +use crate::utils::structs::Context; +use serde_json::json; +use warp::{Rejection, Reply}; + +pub async fn get_account( + // account: Result, + account: Account, + context: Context, +) -> Result { + let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await; + + let command = Command::DbReqGetAccount(account.pubkey); + + context + .pubsub + .publish( + channels::MSG_NOOSE, + Message { + source: channels::MSG_NIP05, + content: command, + }, + ) + .await; + + if let Ok(result) = subscriber.recv().await { + match result.content { + Command::DbResAccount => Ok(warp::reply::with_status( + "ACCOUNT CREATED", + warp::http::StatusCode::CREATED, + )), + Command::ServiceError(e) => Err(warp::reject::custom(e)), + _ => Err(warp::reject::custom(Error::internal_with_message( + "Unhandled message type", + ))), + } + } else { + Err(warp::reject::custom(Error::internal_with_message( + "Unhandeled message type", + ))) + } +} + +pub async fn get_user(user_query: UserQuery, context: Context) -> Result { + let name = user_query.user; + let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await; + + let user = User { + name: Some(name), + pubkey: None, + }; + let command = Command::DbReqGetUser(user); + context + .pubsub + .publish( + channels::MSG_NOOSE, + Message { + source: channels::MSG_NIP05, + content: command, + }, + ) + .await; + + if let Ok(message) = subscriber.recv().await { + let mut response = json!({"names": {}, "relays": {}}); + match message.content { + Command::DbResUser(user) => { + response = json!({ + "names": { + user.username: user.pubkey + }, + "relays": {} + }); + Ok(warp::reply::json(&response)) + } + Command::ServiceError(e) => Ok(warp::reply::json(&response)), + _ => Err(warp::reject::custom(Error::internal_with_message( + "Unhandeled message type", + ))), + } + } else { + Err(warp::reject::custom(Error::internal_with_message( + "Unhandeled message type", + ))) + } +} diff --git a/src/usernames/mod.rs b/src/usernames/mod.rs new file mode 100644 index 0000000..2236d9d --- /dev/null +++ b/src/usernames/mod.rs @@ -0,0 +1,22 @@ +mod accounts; +pub mod dto; +mod filter; +mod handler; +mod routes; +mod util; +mod validators; + +use super::utils::structs::Context; +use crate::utils::rejection_handler::handle_rejection; +use tokio::runtime; +use warp::Filter; + +pub fn start(context: Context) { + let rt = runtime::Runtime::new().unwrap(); + rt.block_on(async { + log::info!("Starting NIP-05 on http://127.0.0.1:8085"); + let routes = routes::routes(context).recover(handle_rejection); + + warp::serve(routes).run(([127, 0, 0, 1], 8085)).await; + }) +} diff --git a/src/usernames/routes.rs b/src/usernames/routes.rs new file mode 100644 index 0000000..96d8ad3 --- /dev/null +++ b/src/usernames/routes.rs @@ -0,0 +1,57 @@ +use crate::noose::user::User; + +use super::accounts::create_account; +use super::dto::{Account, UserQuery}; +use super::filter::{validate_body_filter, validate_query_filter}; +use super::handler::{get_account, get_user}; +use crate::utils::filter::with_context; +use crate::utils::structs::Context; +use warp::{Filter, Rejection, Reply}; + +pub fn routes(context: Context) -> impl Filter + Clone { + let index = warp::path::end().map(|| warp::reply::html("

SNEED!

")); + + index + .or(nip05_get(context.clone())) + .or(account_create(context.clone())) +} + +pub fn nip05_get(context: Context) -> impl Filter + Clone { + warp::get() + .and(warp::path(".well-known")) + .and(warp::path("nostr.json")) + .and(validate_query_filter::()) + .and(with_context(context)) + .and_then(get_user) +} + +pub fn account_create( + context: Context, +) -> impl Filter + Clone { + warp::path("account") + .and(warp::post()) + .and(validate_body_filter::()) + .and(with_context(context)) + .and_then(create_account) +} + +pub fn account_get( + context: Context, +) -> impl Filter + Clone { + warp::path("account") + .and(warp::get()) + .and(validate_body_filter::()) + .and(with_context(context)) + .and_then(get_account) +} + +// pub fn account_update( +// context: Context, +// ) -> impl Filter + Clone { +// warp::path("account") +// .and(warp::put()) +// .and(warp::body::json::()) +// .then(validate_body) +// .and(with_context(context)) +// .and_then(update_account) +// } diff --git a/src/usernames/util.rs b/src/usernames/util.rs new file mode 100644 index 0000000..ca2aec1 --- /dev/null +++ b/src/usernames/util.rs @@ -0,0 +1,3 @@ +#[derive(Debug)] +pub struct InvalidParameter; +impl warp::reject::Reject for InvalidParameter {} diff --git a/src/usernames/validators.rs b/src/usernames/validators.rs new file mode 100644 index 0000000..48f6cb9 --- /dev/null +++ b/src/usernames/validators.rs @@ -0,0 +1,30 @@ +use super::dto::AccountPubkey; +use crate::utils::error::Error; +use nostr::prelude::FromPkStr; +use validator::{Validate, ValidationError}; + +pub async fn validate_account_pubkey_query( + account_pubkey: AccountPubkey, +) -> Result +where + AccountPubkey: Validate, +{ + match account_pubkey.validate() { + Ok(_) => Ok(account_pubkey), + Err(e) => { + log::error!("AccountPubkey validation errors: {}", e); + Err(Error::validation_error(e)) + } + } +} + +pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> { + if value.is_empty() { + return Err(ValidationError::new("Value is empty")); + } + + match nostr::Keys::from_pk_str(value) { + Ok(_) => Ok(()), + Err(_) => Err(ValidationError::new("Unable to parse pk_str")), + } +} diff --git a/src/utils/crypto.rs b/src/utils/crypto.rs new file mode 100644 index 0000000..2aa9104 --- /dev/null +++ b/src/utils/crypto.rs @@ -0,0 +1,52 @@ +use argon2::{ + password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Argon2, +}; + +pub fn password_hash(password: String) -> String { + let password = password.as_bytes(); + let salt = SaltString::generate(&mut OsRng); + + // Argon2 with default params (Argon2id v19) + let argon2 = Argon2::default(); + + // Hash password to PHC string ($argon2id$v=19$...) + let password_hash = argon2.hash_password(password, &salt).unwrap().to_string(); + + password_hash +} + +pub fn password_verify(password: String, password_hash: String) -> bool { + // Verify password against PHC string. + // + // NOTE: hash params from `parsed_hash` are used instead of what is configured in the + // `Argon2` instance. + let password = password.as_bytes(); + let parsed_hash = PasswordHash::new(&password_hash).unwrap(); + Argon2::default() + .verify_password(password, &parsed_hash) + .is_ok() +} + +#[cfg(test)] +mod tests { + use super::{password_hash, password_verify}; + + #[test] + fn hash() { + let pass = "mysecretpassword".to_string(); + let hash = password_hash(pass); + + assert_ne!(hash, "".to_string()); + } + + #[test] + fn verify() { + let pass = "mysecretpassword".to_string(); + let hash = password_hash(pass.clone()); + + let verification = password_verify(pass, hash); + + assert!(verification); + } +} diff --git a/src/utils/error.rs b/src/utils/error.rs new file mode 100644 index 0000000..4f34cde --- /dev/null +++ b/src/utils/error.rs @@ -0,0 +1,132 @@ +use serde::{Deserialize, Serialize}; +use serde_json; +use std::{ + convert::From, + fmt::{self, Display}, +}; +use validator::ValidationErrors; +use warp::{http::StatusCode, reject::Reject}; + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct Error { + pub code: u16, + pub message: String, + /// Sneedstr version. + #[serde(skip_serializing_if = "Option::is_none")] + pub sneedstr_version: Option, +} + +impl Error { + pub fn new(code: StatusCode, message: String) -> Self { + Self { + code: code.as_u16(), + message, + sneedstr_version: None, + } + } + + pub fn from_anyhow_error(code: StatusCode, err: anyhow::Error) -> Self { + Self::new(code, err.to_string()) + } + + pub fn bad_request(msg: S) -> Self { + Self::new(StatusCode::BAD_REQUEST, msg.to_string()) + } + + pub fn internal_with_message(msg: S) -> Self { + Self::new(StatusCode::INTERNAL_SERVER_ERROR, msg.to_string()) + } + + pub fn validation_error(msg: ValidationErrors) -> Self { + let message = + serde_json::to_string(&msg.field_errors()).unwrap_or("Validation Error".to_string()); + Self::new(StatusCode::BAD_REQUEST, message) + } + + pub fn not_found(resource: &str, identifier: S, service_version: u16) -> Self { + Self::new( + StatusCode::NOT_FOUND, + format!("{} not found by {}", resource, identifier), + ) + .sneedstr_version(service_version) + } + + pub fn invalid_param(name: &str, value: S) -> Self { + Self::bad_request(format!("invalid parameter {}: {}", name, value)) + } + + pub fn invalid_request_body(msg: S) -> Self { + Self::bad_request(format!("invalid request body: {}", msg)) + } + + pub fn internal(err: anyhow::Error) -> Self { + Self::from_anyhow_error(StatusCode::INTERNAL_SERVER_ERROR, err) + } + + pub fn status_code(&self) -> StatusCode { + StatusCode::from_u16(self.code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) + } + + pub fn sneedstr_version(mut self, service_version: u16) -> Self { + self.sneedstr_version = Some(service_version); + self + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.status_code(), &self.message)?; + if let Some(val) = &self.sneedstr_version { + write!(f, "\ndiem ledger version: {}", val)?; + } + Ok(()) + } +} + +impl Reject for Error {} + +impl From for Error { + fn from(e: anyhow::Error) -> Self { + Self::internal(e) + } +} + +impl From for Error { + fn from(err: serde_json::error::Error) -> Self { + Self::internal(err.into()) + } +} + +#[cfg(test)] +mod tests { + use super::Error; + use warp::http::StatusCode; + + #[test] + fn test_to_string() { + let err = Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()); + assert_eq!(err.to_string(), "400 Bad Request: invalid address") + } + + #[test] + fn test_from_anyhow_error_as_internal_error() { + let err = Error::from(anyhow::format_err!("hello")); + assert_eq!(err.to_string(), "500 Internal Server Error: hello") + } + + #[test] + fn test_to_string_with_sneedstr_version() { + let err = + Error::new(StatusCode::BAD_REQUEST, "invalid address".to_owned()).sneedstr_version(123); + assert_eq!( + err.to_string(), + "400 Bad Request: invalid address\ndiem ledger version: 123" + ) + } + + #[test] + fn test_internal_error() { + let err = Error::internal(anyhow::format_err!("hello")); + assert_eq!(err.to_string(), "500 Internal Server Error: hello") + } +} diff --git a/src/utils/filter.rs b/src/utils/filter.rs new file mode 100644 index 0000000..16b7d83 --- /dev/null +++ b/src/utils/filter.rs @@ -0,0 +1,8 @@ +use super::structs::Context; +use warp::Filter; + +pub fn with_context( + context: Context, +) -> impl Filter + Clone { + warp::any().map(move || context.clone()) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..e0c3436 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,7 @@ +pub mod crypto; +pub mod error; +pub mod filter; +// mod nostr_filter_helpers; +pub mod rejection_handler; +pub mod response; +pub mod structs; diff --git a/src/utils/nostr_filter_helpers.rs b/src/utils/nostr_filter_helpers.rs new file mode 100644 index 0000000..3b4a2de --- /dev/null +++ b/src/utils/nostr_filter_helpers.rs @@ -0,0 +1,156 @@ +use nostr::{Event, Filter, Kind, Tag}; + +fn ids_match(filter: &Filter, event: &Event) -> bool { + if filter.ids.is_empty() { + println!("[FILTER][IDS] skipped"); + return true; + } + + println!( + "[FILTER][IDS] matched: {:?}", + filter.ids.iter().any(|id| id == &event.id.to_string()) + ); + + filter.ids.iter().any(|id| id == &event.id.to_string()) +} + +fn kind_match(filter: &Filter, kind: Kind) -> bool { + if filter.kinds.is_empty() { + println!("[FILTER][KINDS] skipped"); + return true; + } + + println!( + "[FILTER][KIND] matched: {:?}", + filter.kinds.iter().any(|k| k == &kind) + ); + + filter.kinds.iter().any(|k| k == &kind) +} + +fn pubkeys_match(filter: &Filter, event: &Event) -> bool { + if filter.pubkeys.is_empty() { + println!("[FILTER][PUBKEYS] skipped"); + return true; + } + + println!( + "[FILTER][PUBKEYS] matched: {:?}", + filter.pubkeys.iter().any(|pk| pk == &event.pubkey) + ); + filter.pubkeys.iter().any(|pk| pk == &event.pubkey) +} + +fn authors_match(filter: &Filter, event: &Event) -> bool { + dbg!(filter); + if filter.authors.is_empty() { + println!("[FILTER][AUTHORS] skipped"); + return true; + } + + println!( + "[FILTER][AUTHORS] matched: {:?}", + filter + .authors + .iter() + .any(|author| author == &event.pubkey.to_string()) + ); + filter + .authors + .iter() + .any(|author| author == &event.pubkey.to_string()) +} + +fn delegated_authors_match(filter: &Filter, event: &Event) -> bool { + // Optional implementation + + // let delegated_authors_match = filter.authors.iter().any(|author| { + // event.tags.iter().any(|tag| match tag { + // Tag::Delegation { + // delegator_pk, + // conditions, + // sig, + // } => filter + // .authors + // .iter() + // .any(|author| author == &delegator_pk.to_string()), + // _ => false, + // }) + // }); + println!( + "[FILTER][DELEGATED_AUTHORS] matched: {:?}", + event.tags.iter().any(|tag| match tag { + Tag::Delegation { + delegator_pk, + conditions, + sig, + } => filter + .authors + .iter() + .any(|author| author == &delegator_pk.to_string()), + _ => false, + }) + ); + + event.tags.iter().any(|tag| match tag { + Tag::Delegation { + delegator_pk, + conditions, + sig, + } => filter + .authors + .iter() + .any(|author| author == &delegator_pk.to_string()), + _ => true, + }) +} + +fn tag_match(filter: &Filter, event: &Event) -> bool { + println!( + "[FILTER][TAG] matched: {:?}", + filter.generic_tags.iter().any(|(key, value)| { + event.tags.iter().any(|tag| { + let kv = tag.as_vec(); + key.to_string() == kv[0] && value.iter().any(|vv| vv == &kv[1]) + }) + }) + ); + + filter.generic_tags.iter().any(|(key, value)| { + event.tags.iter().any(|tag| { + let kv = tag.as_vec(); + key.to_string() == kv[0] && value.iter().any(|vv| vv == &kv[1]) + }) + }); + + true // TODO: Fix delegated authors check +} + +pub fn interested_in_event(filter: &Filter, event: &Event) -> bool { + ids_match(filter, event) + && filter.since.map_or( + { + println!("[FILTER][SINCE][default] matched: {:?}", true); + true + }, + |t| { + println!("[FILTER][SINCE] matched: {:?}", event.created_at >= t); + event.created_at >= t + }, + ) + && filter.until.map_or( + { + println!("[FILTER][UNTIL][default] matched: {:?}", true); + true + }, + |t| { + println!("[FILTER][UNTIL] matched: {:?}", event.created_at <= t); + event.created_at <= t + }, + ) + && kind_match(filter, event.kind) + && (pubkeys_match(filter, event) + || authors_match(filter, event) + || delegated_authors_match(filter, event)) + && tag_match(filter, event) +} diff --git a/src/utils/rejection_handler.rs b/src/utils/rejection_handler.rs new file mode 100644 index 0000000..dd92d21 --- /dev/null +++ b/src/utils/rejection_handler.rs @@ -0,0 +1,45 @@ +use super::error::Error; + +use std::convert::Infallible; +use warp::{ + body::BodyDeserializeError, + cors::CorsForbidden, + http::StatusCode, + reject::{LengthRequired, MethodNotAllowed, PayloadTooLarge, UnsupportedMediaType}, + reply, Rejection, Reply, +}; + +pub async fn handle_rejection(err: Rejection) -> Result { + let code; + let body; + + if err.is_not_found() { + code = StatusCode::NOT_FOUND; + body = reply::json(&Error::new(code, "Not Found".to_owned())); + } else if let Some(error) = err.find::() { + code = error.status_code(); + body = reply::json(error); + } else if let Some(cause) = err.find::() { + code = StatusCode::FORBIDDEN; + body = reply::json(&Error::new(code, cause.to_string())); + } else if let Some(cause) = err.find::() { + code = StatusCode::BAD_REQUEST; + body = reply::json(&Error::new(code, cause.to_string())); + } else if let Some(cause) = err.find::() { + code = StatusCode::LENGTH_REQUIRED; + body = reply::json(&Error::new(code, cause.to_string())); + } else if let Some(cause) = err.find::() { + code = StatusCode::PAYLOAD_TOO_LARGE; + body = reply::json(&Error::new(code, cause.to_string())); + } else if let Some(cause) = err.find::() { + code = StatusCode::UNSUPPORTED_MEDIA_TYPE; + body = reply::json(&Error::new(code, cause.to_string())); + } else if let Some(cause) = err.find::() { + code = StatusCode::METHOD_NOT_ALLOWED; + body = reply::json(&Error::new(code, cause.to_string())); + } else { + code = StatusCode::INTERNAL_SERVER_ERROR; + body = reply::json(&Error::new(code, format!("unexpected error: {:?}", err))); + } + Ok(reply::with_status(body, code)) +} diff --git a/src/utils/response.rs b/src/utils/response.rs new file mode 100644 index 0000000..2f01b7a --- /dev/null +++ b/src/utils/response.rs @@ -0,0 +1,28 @@ +use super::error::Error; + +use anyhow::Result; +use serde::Serialize; +use warp::http::header::{HeaderValue, CONTENT_TYPE}; + +pub struct Response { + pub body: Vec, +} + +impl Response { + pub fn new(body: &T) -> Result { + Ok(Self { + body: serde_json::to_vec(body)?, + }) + } +} + +impl warp::Reply for Response { + fn into_response(self) -> warp::reply::Response { + let mut res = warp::reply::Response::new(self.body.into()); + let headers = res.headers_mut(); + + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + + res + } +} diff --git a/src/utils/structs.rs b/src/utils/structs.rs new file mode 100644 index 0000000..10b711e --- /dev/null +++ b/src/utils/structs.rs @@ -0,0 +1,135 @@ +use super::error::Error; +// use super::nostr_filter_helpers; +use crate::PubSub; +use nostr::{Event, Filter, SubscriptionId}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::mpsc; +use uuid::Uuid; +use warp::ws::Message; + +const MAX_SUBSCRIPTIONS: usize = 256; +const MAX_SUBSCRIPTION_ID_LEN: usize = 256; + +#[derive(serde::Serialize, PartialEq, Eq, Debug, Clone)] +pub struct Subscription { + pub id: SubscriptionId, + pub filters: Vec, +} + +impl Subscription { + pub fn new(id: SubscriptionId, filters: Vec) -> Self { + Self { id, filters } + } + + pub fn get_id(&self) -> String { + self.id.to_string() + } + + pub fn needs_historical_events(&self) -> bool { + self.filters.iter().any(|f| f.limit != Some(0)) + } + + pub fn interested_in_event(&self, event: &Event) -> bool { + log::info!("[Subscription] Checking if client is interested in the new event"); + for filter in &self.filters { + if filter.match_event(event) { + log::info!("[Subscription] found filter that matches the event"); + return true; + } + } + false + } +} + +#[derive(Debug, Clone)] +pub struct Client { + client_ip_addr: String, + pub client_id: Uuid, + pub client_connection: Option>>, + pub subscriptions: HashMap, + max_subs: usize, +} + +impl Client { + pub fn new(client_ip_addr: String) -> Self { + Self { + client_ip_addr, + client_id: Uuid::new_v4(), + client_connection: None, + subscriptions: HashMap::new(), + max_subs: MAX_SUBSCRIPTIONS, + } + } + + pub fn ip(&self) -> &str { + &self.client_ip_addr + } + + pub fn subscribe(&mut self, subscription: Subscription) -> Result<(), Error> { + let k = subscription.get_id(); + let sub_id_len = k.len(); + if sub_id_len > MAX_SUBSCRIPTION_ID_LEN { + log::debug!( + "Ignoring sub request with excessive length: ({})", + sub_id_len + ); + + return Err(Error::bad_request("sub request is too long")); + } + + if self.subscriptions.contains_key(&k) { + self.subscriptions.remove(&k); + self.subscriptions.insert(k, subscription.clone()); + + log::debug!( + "replaced existing subscription (cid: {}, sub: {:?})", + self.client_id, + subscription.get_id() + ); + + return Ok(()); + } + + if self.subscriptions.len() >= self.max_subs { + return Err(Error::bad_request("max subs exceeded")); + } + + // Insert subscription + self.subscriptions.insert(k, subscription); + log::debug!( + "registered new subscription, currently have {} active subs (cid: {})", + self.subscriptions.len(), + self.client_id + ); + Ok(()) + } + + pub fn unsubscribe(&mut self, subscription_id: SubscriptionId) { + self.subscriptions.remove(&subscription_id.to_string()); + log::debug!( + "removed subscription, currently have {} active subs (cid: {})", + self.subscriptions.len(), + self.client_id + ); + } +} + +#[derive(Debug, Clone)] +pub struct Context { + pub pubsub: Arc, +} + +impl Default for Context { + fn default() -> Self { + panic!("Use Context::new() to initialize Contexr"); + } +} + +impl Context { + pub fn new() -> Self { + let pubsub = Arc::new(PubSub::new()); + + Self { pubsub } + } +} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f7ada7e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,66 @@ + + + + + + + Registration + + +
+

Create new user

+
+

Sign Up

+
+ + + +
+
+ + + +
+ +
+
+ + + diff --git a/templates/index.stpl b/templates/index.stpl new file mode 100644 index 0000000..d044eaa --- /dev/null +++ b/templates/index.stpl @@ -0,0 +1,70 @@ + + + + + + + + + + + Registration + + +
+

Create new user

+
+

Sign Up

+
+ + + +
+
+ + + +
+ +
+
+ + + diff --git a/templates/static/android-chrome-192x192.png b/templates/static/android-chrome-192x192.png new file mode 100644 index 0000000..b87bdf7 Binary files /dev/null and b/templates/static/android-chrome-192x192.png differ diff --git a/templates/static/android-chrome-512x512.png b/templates/static/android-chrome-512x512.png new file mode 100644 index 0000000..407a549 Binary files /dev/null and b/templates/static/android-chrome-512x512.png differ diff --git a/templates/static/apple-touch-icon.png b/templates/static/apple-touch-icon.png new file mode 100644 index 0000000..075ba41 Binary files /dev/null and b/templates/static/apple-touch-icon.png differ diff --git a/templates/static/favicon-16x16.png b/templates/static/favicon-16x16.png new file mode 100644 index 0000000..2dd3e01 Binary files /dev/null and b/templates/static/favicon-16x16.png differ diff --git a/templates/static/favicon-32x32.png b/templates/static/favicon-32x32.png new file mode 100644 index 0000000..1027bdd Binary files /dev/null and b/templates/static/favicon-32x32.png differ diff --git a/templates/static/favicon.ico b/templates/static/favicon.ico new file mode 100644 index 0000000..896232f Binary files /dev/null and b/templates/static/favicon.ico differ diff --git a/templates/static/site.webmanifest b/templates/static/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/templates/static/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/www/static/android-chrome-192x192.png b/www/static/android-chrome-192x192.png new file mode 100644 index 0000000..ea8a7cf Binary files /dev/null and b/www/static/android-chrome-192x192.png differ diff --git a/www/static/android-chrome-512x512.png b/www/static/android-chrome-512x512.png new file mode 100644 index 0000000..a958b02 Binary files /dev/null and b/www/static/android-chrome-512x512.png differ diff --git a/www/static/apple-touch-icon.png b/www/static/apple-touch-icon.png new file mode 100644 index 0000000..b504b8f Binary files /dev/null and b/www/static/apple-touch-icon.png differ diff --git a/www/static/favicon-16x16.png b/www/static/favicon-16x16.png new file mode 100644 index 0000000..cc9cab2 Binary files /dev/null and b/www/static/favicon-16x16.png differ diff --git a/www/static/favicon-32x32.png b/www/static/favicon-32x32.png new file mode 100644 index 0000000..682574d Binary files /dev/null and b/www/static/favicon-32x32.png differ diff --git a/www/static/favicon.ico b/www/static/favicon.ico new file mode 100644 index 0000000..0ab01d3 Binary files /dev/null and b/www/static/favicon.ico differ diff --git a/www/static/site.webmanifest b/www/static/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/www/static/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file