diff --git a/Cargo.lock b/Cargo.lock index af68aa8..449c722 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,16 +89,171 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote 1.0.14", + "syn 1.0.85", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab 0.4.5", +] + +[[package]] +name = "async-global-executor" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log 0.4.14", + "once_cell", + "parking", + "polling", + "slab 0.4.5", + "socket2", + "waker-fn", + "winapi 0.3.9", +] + +[[package]] +name = "async-lock" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +dependencies = [ + "async-io", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "async-std" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils 0.8.6", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log 0.4.14", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab 0.4.5", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +dependencies = [ + "async-stream-impl 0.2.1", + "futures-core", +] + [[package]] name = "async-stream" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ - "async-stream-impl", + "async-stream-impl 0.3.2", "futures-core", ] +[[package]] +name = "async-stream-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" +dependencies = [ + "proc-macro2 1.0.36", + "quote 1.0.14", + "syn 1.0.85", +] + [[package]] name = "async-stream-impl" version = "0.3.2" @@ -110,6 +265,12 @@ dependencies = [ "syn 1.0.85", ] +[[package]] +name = "async-task" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" + [[package]] name = "async-trait" version = "0.1.52" @@ -130,6 +291,12 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "atomicwrites" version = "0.2.5" @@ -195,6 +362,12 @@ dependencies = [ "safemem", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + [[package]] name = "base64" version = "0.13.0" @@ -264,6 +437,20 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "blocking" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + [[package]] name = "bstr" version = "0.2.17" @@ -320,6 +507,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + [[package]] name = "cast" version = "0.2.7" @@ -415,6 +608,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + [[package]] name = "const_fn" version = "0.4.9" @@ -438,7 +640,7 @@ dependencies = [ "hkdf", "percent-encoding 2.1.0", "rand 0.8.4", - "sha2", + "sha2 0.9.9", "subtle", "time 0.2.27", "version_check 0.9.4", @@ -538,11 +740,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array 0.14.5", + "typenum", ] [[package]] @@ -555,6 +758,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote 1.0.14", + "syn 1.0.85", +] + [[package]] name = "ctr" version = "0.6.0" @@ -646,13 +859,22 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.0", "crypto-common", - "generic-array 0.14.5", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", ] [[package]] @@ -664,6 +886,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.3.6" @@ -771,6 +1002,12 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + [[package]] name = "failure" version = "0.1.8" @@ -840,21 +1077,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "fsevent" version = "0.4.0" @@ -914,9 +1136,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -929,9 +1151,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -939,9 +1161,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-cpupool" @@ -955,9 +1177,9 @@ dependencies = [ [[package]] name = "futures-executor" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -967,15 +1189,30 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2 1.0.36", "quote 1.0.14", @@ -984,21 +1221,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures 0.1.31", "futures-channel", @@ -1141,6 +1378,35 @@ dependencies = [ "walkdir", ] +[[package]] +name = "gloo-timers" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "golgi" +version = "0.1.1" +dependencies = [ + "async-std", + "async-stream 0.3.2", + "base64 0.13.0", + "futures 0.3.21", + "hex", + "kuska-handshake", + "kuska-sodiumoxide", + "kuska-ssb", + "serde 1.0.133", + "serde_json", + "sha2 0.10.2", +] + [[package]] name = "gpio-cdev" version = "0.2.0" @@ -1214,6 +1480,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.10.0" @@ -1501,6 +1773,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "jsonrpc-client-core" version = "0.5.0" @@ -1536,7 +1817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" dependencies = [ "derive_more", - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core 18.0.0", "jsonrpc-pubsub 18.0.0", "log 0.4.14", @@ -1577,7 +1858,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "futures-executor", "futures-util", "log 0.4.14", @@ -1606,7 +1887,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-client-transports", ] @@ -1630,7 +1911,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "hyper 0.14.16", "jsonrpc-core 18.0.0", "jsonrpc-server-utils 18.0.0", @@ -1658,7 +1939,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core 18.0.0", "lazy_static", "log 0.4.14", @@ -1691,7 +1972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" dependencies = [ "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "globset", "jsonrpc-core 18.0.0", "lazy_static", @@ -1760,6 +2041,60 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "kuska-handshake" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33da4b69f23c2ece0b3e729d079cebdc2c0206e493e42f510f500ad81c631d5" +dependencies = [ + "futures 0.3.21", + "hex", + "kuska-sodiumoxide", + "log 0.4.14", + "thiserror", +] + +[[package]] +name = "kuska-sodiumoxide" +version = "0.2.5-0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0f8eafdd240b722243787b51fdaf8df6693fb8621d0f7061cdba574214cf88" +dependencies = [ + "libc", + "libsodium-sys", + "serde 1.0.133", +] + +[[package]] +name = "kuska-ssb" +version = "0.4.0" +dependencies = [ + "async-std", + "async-stream 0.2.1", + "base64 0.11.0", + "dirs 2.0.2", + "futures 0.3.21", + "get_if_addrs", + "hex", + "kuska-handshake", + "kuska-sodiumoxide", + "log 0.4.14", + "once_cell", + "regex", + "serde 1.0.133", + "serde_json", + "thiserror", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log 0.4.14", +] + [[package]] name = "language-tags" version = "0.2.2" @@ -1784,6 +2119,18 @@ version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +[[package]] +name = "libsodium-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "walkdir", +] + [[package]] name = "linked-hash-map" version = "0.3.0" @@ -1857,6 +2204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -2313,43 +2661,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags 1.3.2", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-src" -version = "111.17.0+1.1.1m" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d6a336abd10814198f66e2a91ccd7336611f30334119ca8ce300536666fcf4" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" -dependencies = [ - "autocfg 1.0.1", - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - [[package]] name = "owning_ref" version = "0.4.1" @@ -2359,6 +2670,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.7.1" @@ -2501,6 +2818,7 @@ name = "peach-lib" version = "1.3.2" dependencies = [ "chrono", + "dirs 4.0.0", "fslock", "jsonrpc-client-core", "jsonrpc-client-http", @@ -2512,6 +2830,7 @@ dependencies = [ "serde_json", "serde_yaml", "sha3", + "toml", ] [[package]] @@ -2574,7 +2893,7 @@ dependencies = [ [[package]] name = "peach-stats" -version = "0.2.0" +version = "0.3.0" dependencies = [ "log 0.4.14", "miniserde", @@ -2587,20 +2906,21 @@ dependencies = [ name = "peach-web" version = "0.5.0" dependencies = [ + "base64 0.13.0", + "dirs 4.0.0", "env_logger 0.8.4", + "golgi", + "lazy_static", "log 0.4.14", "nest", - "openssl", "peach-lib", "peach-network", "peach-stats", - "percent-encoding 2.1.0", - "regex", "rocket", "rocket_dyn_templates", "serde 1.0.133", "serde_json", - "snafu 0.6.10", + "temporary", "tera", "xdg", ] @@ -2740,6 +3060,19 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "log 0.4.14", + "wepoll-ffi", + "winapi 0.3.9", +] + [[package]] name = "polyval" version = "0.4.5" @@ -2764,7 +3097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f77e66f6d6d898cbbd4a09c48fd3507cfc210b7c83055de02a38b5f7a1e6d216" dependencies = [ "libc", - "time 0.1.44", + "time 0.2.27", ] [[package]] @@ -3072,6 +3405,12 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "random" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d13a3485349981c90c79112a11222c3e6e75de1d52b87a7525b3bf5361420f" + [[package]] name = "rdrand" version = "0.4.0" @@ -3176,7 +3515,7 @@ version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" dependencies = [ - "async-stream", + "async-stream 0.3.2", "async-trait", "atomic", "atty", @@ -3184,7 +3523,7 @@ dependencies = [ "bytes 1.1.0", "either", "figment", - "futures 0.3.19", + "futures 0.3.21", "indexmap", "log 0.4.14", "memchr", @@ -3492,13 +3831,24 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha3" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", "keccak", ] @@ -3511,6 +3861,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -3883,6 +4243,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "temporary" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd7ed303e9bfb202712600e621300c9dfacfdc4be55735e61bd2f26932892d2" +dependencies = [ + "random", +] + [[package]] name = "tera" version = "1.15.0" @@ -3923,6 +4292,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2 1.0.36", + "quote 1.0.14", + "syn 1.0.85", +] + [[package]] name = "thread_local" version = "1.1.3" @@ -4547,10 +4936,14 @@ dependencies = [ ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "value-bag" +version = "1.0.0-alpha.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check 0.9.4", +] [[package]] name = "vec_map" @@ -4576,6 +4969,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -4633,9 +5032,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -4643,9 +5042,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -4657,10 +5056,22 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" +name = "wasm-bindgen-futures" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote 1.0.14", "wasm-bindgen-macro-support", @@ -4668,9 +5079,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2 1.0.36", "quote 1.0.14", @@ -4681,9 +5092,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] [[package]] name = "winapi" @@ -4771,7 +5201,7 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a23fe958c70412687039c86f578938b4a0bb50ec788e96bce4d6ab00ddd5803" dependencies = [ - "dirs", + "dirs 3.0.2", ] [[package]] diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index ddd4b0d..7ba1a43 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -35,7 +35,11 @@ travis-ci = { repository = "peachcloud/peach-web", branch = "master" } maintenance = { status = "actively-developed" } [dependencies] +base64 = "0.13.0" +dirs = "4.0.0" env_logger = "0.8" +#golgi = "0.1.0" +golgi = { path = "../../../playground/rust/golgi" } lazy_static = "1.4.0" log = "0.4" nest = "1.0.0" @@ -45,6 +49,7 @@ peach-stats = { path = "../peach-stats", features = ["serde_support"] } rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +temporary = "0.6.4" tera = { version = "1.12.1", features = ["builtins"] } xdg = "2.2.0" diff --git a/peach-web/src/context/mod.rs b/peach-web/src/context/mod.rs index d2d350c..019dd88 100644 --- a/peach-web/src/context/mod.rs +++ b/peach-web/src/context/mod.rs @@ -1,2 +1,3 @@ pub mod dns; pub mod network; +pub mod scuttlebutt; diff --git a/peach-web/src/context/scuttlebutt.rs b/peach-web/src/context/scuttlebutt.rs new file mode 100644 index 0000000..9d319a6 --- /dev/null +++ b/peach-web/src/context/scuttlebutt.rs @@ -0,0 +1,588 @@ +use std::collections::HashMap; + +use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; +use peach_lib::sbot::{SbotConfig, SbotStatus}; +use rocket::{futures::TryStreamExt, serde::Serialize}; + +use crate::{error::PeachWebError, utils}; + +// HELPER FUNCTIONS + +pub async fn init_sbot_with_config( + sbot_config: &Option, +) -> Result { + // initialise sbot connection with ip:port and shscap from config file + let sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None).await? + } + None => Sbot::init(None, None).await?, + }; + + Ok(sbot_client) +} + +// CONTEXT STRUCTS AND BUILDERS + +#[derive(Debug, Serialize)] +pub struct StatusContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // latest sequence number for the local log + pub latest_seq: Option, +} + +impl StatusContext { + pub fn default() -> Self { + StatusContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Scuttlebutt Status".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + latest_seq: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // retrieve the local id + let id = sbot_client.whoami().await?; + + let history_stream = sbot_client.create_history_stream(id).await?; + let mut msgs: Vec = history_stream.try_collect().await?; + + // reverse the list of messages so we can easily reference the latest one + msgs.reverse(); + + // assign the sequence number of the latest msg + context.latest_seq = Some(msgs[0].sequence); + + context.sbot_config = sbot_config; + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, status data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +// peers who are blocked by the local account +#[derive(Debug, Serialize)] +pub struct BlocksContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl BlocksContext { + pub fn default() -> Self { + BlocksContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Blocks".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let blocks = sbot_client.get_blocks().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !blocks.is_empty() { + for peer in blocks.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), key.to_string()); + // we do not even attempt to find the blob for a blocked peer, + // since it may be vulgar to cause distress to the local peer. + info.insert("blob_exists".to_string(), "false".to_string()); + // push profile info to peer_list vec + peer_info.push(info) + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +// peers who are followed by the local account +#[derive(Debug, Serialize)] +pub struct FollowsContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl FollowsContext { + pub fn default() -> Self { + FollowsContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Follows".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), key.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match utils::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + // push profile info to peer_list vec + peer_info.push(info) + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +// peers who follow and are followed by the local account (friends) +#[derive(Debug, Serialize)] +pub struct FriendsContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl FriendsContext { + pub fn default() -> Self { + FriendsContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Friends".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let peer_id = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&peer_id).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), peer_id.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match utils::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + + // check if the peer follows us (making us friends) + let follow_query = RelationshipQuery { + source: peer_id.to_string(), + dest: local_id.clone(), + }; + + // query follow state + match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => { + // only push profile info to peer_list vec if they follow us + peer_info.push(info) + } + _ => (), + }; + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +#[derive(Debug, Serialize)] +pub struct ProfileContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // is this the local profile or the profile of a peer? + pub is_local_profile: bool, + // an ssb_id which may or may not be the local public key + pub id: Option, + pub name: Option, + pub description: Option, + pub image: Option, + // the path to the blob defined in the `image` field (aka the profile picture) + pub blob_path: Option, + // whether or not the blob exists in the blobstore (ie. is saved on disk) + pub blob_exists: bool, + // relationship state (if the profile being viewed is not for the local public key) + pub following: Option, + pub blocking: Option, +} + +impl ProfileContext { + pub fn default() -> Self { + ProfileContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Profile".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + is_local_profile: true, + id: None, + name: None, + description: None, + image: None, + blob_path: None, + blob_exists: false, + following: None, + blocking: None, + } + } + + pub async fn build(ssb_id: Option) -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + + // if an ssb_id has been provided to the context builder, we assume that + // the profile info being retrieved is for a peer (ie. not for our local + // profile) + let id = if ssb_id.is_some() { + // we are not dealing with the local profile + context.is_local_profile = false; + + // we're safe to unwrap here because we know it's `Some(id)` + let peer_id = ssb_id.unwrap(); + + // determine relationship between peer and local id + let follow_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query follow state + context.following = match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => Some(true), + Ok(following) if following == "false" => Some(false), + _ => None, + }; + + // TODO: i don't like that we have to instantiate the same query object + // twice. see if we can streamline this in golgi + let block_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query block state + context.blocking = match sbot_client.friends_is_blocking(block_query).await { + Ok(blocking) if blocking == "true" => Some(true), + Ok(blocking) if blocking == "false" => Some(false), + _ => None, + }; + + peer_id + } else { + // if an ssb_id has not been provided, retrieve the local id using whoami + context.is_local_profile = true; + + local_id + }; + + // TODO: add relationship state context if not local profile + // ie. lookup is_following and is_blocking, set context accordingly + + // retrieve the profile info for the given id + let info = sbot_client.get_profile_info(&id).await?; + // set each context field accordingly + for (key, val) in info { + match key.as_str() { + "name" => context.name = Some(val), + "description" => context.description = Some(val), + "image" => context.image = Some(val), + _ => (), + } + } + + // assign the ssb public key to the context + // (could be for the local profile or a peer) + context.id = Some(id); + + // determine the path to the blob defined by the value of `context.image` + if let Some(ref blob_id) = context.image { + context.blob_path = match blobs::get_blob_path(&blob_id) { + Ok(path) => { + // if we get the path, check if the blob is in the blobstore. + // this allows us to default to a placeholder image in the template + if let Ok(exists) = utils::blob_is_stored_locally(&path).await { + context.blob_exists = exists + }; + + Some(path) + } + Err(_) => None, + } + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +#[derive(Debug, Serialize)] +pub struct PrivateContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // local peer id (whoami) + pub id: Option, + // id of the peer being messaged + pub recipient_id: Option, +} + +impl PrivateContext { + pub fn default() -> Self { + PrivateContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Private Messages".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + id: None, + recipient_id: None, + } + } + + pub async fn build(recipient_id: Option) -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + context.recipient_id = recipient_id; + + let local_id = sbot_client.whoami().await?; + context.id = Some(local_id); + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, private messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} diff --git a/peach-web/src/error.rs b/peach-web/src/error.rs index 6288b6b..0d5453b 100644 --- a/peach-web/src/error.rs +++ b/peach-web/src/error.rs @@ -1,5 +1,8 @@ //! Custom error type representing all possible error variants for peach-web. +use std::io::Error as IoError; + +use golgi::GolgiError; use peach_lib::error::PeachError; use peach_lib::{serde_json, serde_yaml}; use serde_json::error::Error as JsonError; @@ -8,19 +11,27 @@ use serde_yaml::Error as YamlError; /// Custom error type encapsulating all possible errors for the web application. #[derive(Debug)] pub enum PeachWebError { - Json(JsonError), - Yaml(YamlError), FailedToRegisterDynDomain(String), + Golgi(GolgiError), + HomeDir, + Io(IoError), + Json(JsonError), + OsString, PeachLib { source: PeachError, msg: String }, + Yaml(YamlError), } impl std::error::Error for PeachWebError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match *self { - PeachWebError::Json(ref source) => Some(source), - PeachWebError::Yaml(ref source) => Some(source), PeachWebError::FailedToRegisterDynDomain(_) => None, + PeachWebError::Golgi(ref source) => Some(source), + PeachWebError::HomeDir => None, + PeachWebError::Io(ref source) => Some(source), + PeachWebError::Json(ref source) => Some(source), + PeachWebError::OsString => None, PeachWebError::PeachLib { ref source, .. } => Some(source), + PeachWebError::Yaml(ref source) => Some(source), } } } @@ -28,28 +39,44 @@ impl std::error::Error for PeachWebError { impl std::fmt::Display for PeachWebError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { - PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), - PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source), PeachWebError::FailedToRegisterDynDomain(ref msg) => { write!(f, "DYN DNS error: {}", msg) } + PeachWebError::Golgi(ref source) => write!(f, "Golgi error: {}", source), + PeachWebError::HomeDir => write!( + f, + "Filesystem error: failed to determine home directory path" + ), + PeachWebError::Io(ref source) => write!(f, "IO error: {}", source), + PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), + PeachWebError::OsString => write!( + f, + "Filesystem error: failed to convert OsString to String for go-ssb directory path" + ), PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source), + PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source), } } } +impl From for PeachWebError { + fn from(err: GolgiError) -> PeachWebError { + PeachWebError::Golgi(err) + } +} + +impl From for PeachWebError { + fn from(err: IoError) -> PeachWebError { + PeachWebError::Io(err) + } +} + impl From for PeachWebError { fn from(err: JsonError) -> PeachWebError { PeachWebError::Json(err) } } -impl From for PeachWebError { - fn from(err: YamlError) -> PeachWebError { - PeachWebError::Yaml(err) - } -} - impl From for PeachWebError { fn from(err: PeachError) -> PeachWebError { PeachWebError::PeachLib { @@ -58,3 +85,9 @@ impl From for PeachWebError { } } } + +impl From for PeachWebError { + fn from(err: YamlError) -> PeachWebError { + PeachWebError::Yaml(err) + } +} diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 14a0a03..1e7f1b4 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -22,8 +22,6 @@ //! `Serialize`. The fields of the context object are available in the context //! of the template to be rendered. -#![feature(proc_macro_hygiene, decl_macro)] - mod context; pub mod error; mod router; diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index be05163..ee43d47 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -1,13 +1,16 @@ use rocket::{catchers, fs::FileServer, routes, Build, Rocket}; use rocket_dyn_templates::Template; -use crate::routes::{ - authentication::*, - catchers::*, - index::*, - scuttlebutt::*, - settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*, theme::*}, - status::{device::*, network::*, scuttlebutt::*}, +use crate::{ + routes::{ + authentication::*, + catchers::*, + index::*, + scuttlebutt::*, + settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*, theme::*}, + status::{device::*, network::*, scuttlebutt::*}, + }, + utils, }; /// Create a Rocket instance and mount PeachPub routes, fileserver and @@ -15,6 +18,10 @@ use crate::routes::{ /// settings and status routes related to networking and the device (memory, /// hard disk, CPU etc.). pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { + // set the `.ssb-go` path in order to mount the blob fileserver + let ssb_path = utils::get_go_ssb_path().expect("define ssb-go dir path"); + let blobstore = format!("{}/blobs/sha256", ssb_path); + rocket .mount( "/", @@ -62,12 +69,29 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { .mount( "/scuttlebutt", routes![ - peers, friends, follows, followers, blocks, profile, private, follow, unfollow, - block, publish, + invites, + create_invite, + peers, + search, + search_post, + friends, + follows, + blocks, + profile, + update_profile, + update_profile_post, + private, + private_post, + follow, + unfollow, + block, + unblock, + publish, ], ) .mount("/status", routes![scuttlebutt_status]) .mount("/", FileServer::from("static")) + .mount("/blob", FileServer::from(blobstore).rank(-1)) .register("/", catchers![not_found, internal_error, forbidden]) .attach(Template::fairing()) } diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 0d884a7..01aa108 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -1,109 +1,353 @@ //! Routes for Scuttlebutt related functionality. +use log::debug; +use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::{ form::{Form, FromForm}, + fs::TempFile, get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::{Deserialize, Serialize}, uri, }; -use rocket_dyn_templates::Template; +use rocket_dyn_templates::{tera::Context, Template}; -use crate::routes::authentication::Authenticated; -use crate::utils; +use crate::{ + context::{ + scuttlebutt, + scuttlebutt::{ + BlocksContext, FollowsContext, FriendsContext, PrivateContext, ProfileContext, + }, + }, + routes::authentication::Authenticated, + utils, +}; + +// HELPER FUNCTIONS + +/// Check to see if the go-sbot.service process is currently active. +/// Return an error in the form of a `String` if the process +/// check command fails. Otherwise, return the state of the process. +fn is_sbot_active() -> Result { + // retrieve go-sbot systemd process status + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => return Err(format!("Failed to read sbot status: {}", e)), + }; + + if sbot_status.state == Some("active".to_string()) { + Ok(true) + } else { + Ok(false) + } +} + +/// Ensure that the given public key is a valid ed25519 key. +fn validate_public_key(public_key: &str, redirect_url: String) -> Result<(), Flash> { + // ensure the id starts with the correct sigil link + if !public_key.starts_with('@') { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: expected '@' sigil as first character", + )); + } + + // find the dot index denoting the start of the algorithm definition tag + let dot_index = match public_key.rfind('.') { + Some(index) => index, + None => { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: no dot index was found", + )) + } + }; + + // check hashing algorithm (must end with ".ed25519") + if !&public_key.ends_with(".ed25519") { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: hashing algorithm must be ed25519", + )); + } + + // obtain the base64 portion (substring) of the public key + let base64_str = &public_key[1..dot_index]; + + // length of a base64 encoded ed25519 public key + if base64_str.len() != 44 { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: base64 data length is incorrect", + )); + } + + Ok(()) +} + +// HELPERS AND ROUTES FOR INVITES + +#[get("/invites")] +pub fn invites(flash: Option, _auth: Authenticated) -> Template { + // retrieve current ui theme + let theme = utils::get_theme(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/scuttlebutt/peers".to_string())); + context.insert("title", &Some("Invites".to_string())); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + match flash.kind() { + // we've been passed a freshly-generated invite code (redirect from post) + "code" => { + context.insert("invite_code", &Some(flash.message().to_string())); + context.insert("flash_name", &Some("success".to_string())); + context.insert("flash_msg", &Some("Generated invite code".to_string())); + } + _ => { + // add flash message contents to the context object + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + } + } + }; + + Template::render("scuttlebutt/invites", &context.into_json()) +} + +#[derive(Debug, FromForm)] +pub struct Invite { + pub uses: u16, +} + +#[post("/invites", data = "")] +pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash { + let uses = invite.uses; + + let url = uri!("/scuttlebutt", invites); + + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Generating Scuttlebutt invite code"); + match sbot_client.invite_create(uses).await { + // construct a custom flash msg to pass along the invite code + Ok(code) => Flash::new(Redirect::to(url), "code", code), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to create invite code: {}", e), + ), + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ) + } + // failed to retrieve go-sbot systemd process status + Err(e) => return Flash::error(Redirect::to(url), e) + } +} // HELPERS AND ROUTES FOR /private -#[derive(Debug, Serialize)] -pub struct PrivateContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} +/// A private message composition and publication page. +#[get("/private?")] +pub async fn private( + mut public_key: Option, + flash: Option>, + _auth: Authenticated, +) -> Template { + if let Some(ref key) = public_key { + // `url_decode` replaces '+' with ' ', so we need to revert that + public_key = Some(key.replace(' ', "+")); + } -impl PrivateContext { - pub fn build() -> PrivateContext { - PrivateContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, + // build the private context object + let context = PrivateContext::build(public_key).await; + + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/private", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = PrivateContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/private", &context) } } } -/// A private message composition and publication page. -#[get("/private")] -pub fn private(flash: Option, _auth: Authenticated) -> Template { +#[derive(Debug, FromForm)] +pub struct Private { + pub id: String, + pub text: String, + pub recipient: String, +} + +/// Publish a private message. +#[post("/private", data = "")] +pub async fn private_post(private: Form, _auth: Authenticated) -> Flash { + let url = uri!("/scuttlebutt", private(None::)); + + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let id = &private.id; + let text = &private.text; + let recipient = &private.recipient; + // now we need to add the local id to the recipients vector, + // otherwise the local id will not be able to read the message. + let recipients = vec![id.to_string(), recipient.to_string()]; + + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Publishing a new Scuttlebutt private message"); + match sbot_client + .publish_private(text.to_string(), recipients) + .await + { + Ok(_) => { + Flash::success(Redirect::to(url), format!("Published private message")) + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to publish private message: {}", e), + ), + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new private message cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + // failed to retrieve go-sbot systemd process status + Err(e) => return Flash::error(Redirect::to(url), e), + } +} + +// HELPERS AND ROUTES FOR /search + +/// Search for a peer. +#[get("/search")] +pub fn search(flash: Option, _auth: Authenticated) -> Template { // retrieve current ui theme let theme = utils::get_theme(); - let mut context = PrivateContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Private Messages".to_string()); - context.theme = Some(theme); + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read().ok(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("sbot_status", &sbot_status); + context.insert("title", &Some("Search")); + context.insert("back", &Some("/scuttlebutt/peers")); // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("scuttlebutt/messages", &context) + Template::render("scuttlebutt/search", &context.into_json()) +} + +#[derive(Debug, FromForm)] +pub struct Peer { + // public key + pub public_key: String, +} + +/// Accept the peer search form and redirect to the profile for that peer. +#[post("/search", data = "")] +pub async fn search_post(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; + + let search_url = "/scuttlebutt/search".to_string(); + + // validate the key before redirecting to profile url + if let Err(flash) = validate_public_key(&public_key, search_url) { + // redirect with error message + return flash; + } + + // key has not been validated and we can redirect to the profile page + let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); + + Flash::new( + Redirect::to(profile_url), + // this flash msg will not be displayed in the receiving template + "ignore", + "Public key validated for profile lookup", + ) } // HELPERS AND ROUTES FOR /peers -#[derive(Debug, Serialize)] -pub struct PeerContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} - -impl PeerContext { - pub fn build() -> PeerContext { - PeerContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, - } - } -} - -/// A peer menu which allows navigating to lists of friends, follows, followers and blocks. +/// A peer menu which allows navigating to lists of friends, follows, followers +/// and blocks, as well as accessing the invite creation form. #[get("/peers")] pub fn peers(flash: Option, _auth: Authenticated) -> Template { // retrieve current ui theme let theme = utils::get_theme(); - let mut context = PeerContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Scuttlebutt Peers".to_string()); - context.theme = Some(theme); + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("title", &Some("Scuttlebutt Peers")); + context.insert("back", &Some("/")); // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("scuttlebutt/peers", &context) + Template::render("scuttlebutt/peers", &context.into_json()) } // HELPERS AND ROUTES FOR /post/publish -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct Post { pub text: String, } @@ -113,41 +357,85 @@ pub struct Post { /// message describing the outcome of the action (may be successful or /// unsuccessful). #[post("/publish", data = "")] -pub fn publish(post: Form, _auth: Authenticated) -> Flash { +pub async fn publish(post: Form, _auth: Authenticated) -> Flash { let post_text = &post.text; - // perform the sbotcli publish action using post_text - // if successful, redirect to home profile page and flash "success" - // if error, redirect to home profile page and flash "error" - // redirect to the profile template without public key ("home" / local profile) - let pub_key: std::option::Option<&str> = None; - let profile_url = uri!(profile(pub_key)); + let url = uri!("/scuttlebutt", profile(None::)); - // consider adding the message reference to the flash message (or render it in the template for - // `profile` - Flash::success(Redirect::to(profile_url), "Published public post") + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Publishing new Scuttlebutt public post"); + match sbot_client.publish_post(post_text).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Published post")), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to publish post: {}", e), + ), + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), + } } // HELPERS AND ROUTES FOR /follow -#[derive(Debug, Deserialize, FromForm)] -pub struct PublicKey { - pub key: String, -} - /// Follow a Scuttlebutt profile specified by the given public key. /// Redirects to the appropriate profile page with a flash message describing /// the outcome of the action (may be successful or unsuccessful). -#[post("/follow", data = "")] -pub fn follow(pub_key: Form, _auth: Authenticated) -> Flash { - let public_key = &pub_key.key; - // perform the sbotcli follow action using &pub_key.0 - // if successful, redirect to profile page with provided public key and flash "success" - // if error, redirect to profile page with provided public key and flash "error" - // redirect to the profile template with provided public key - let profile_url = uri!(profile(Some(public_key))); - let success_msg = format!("Followed {}", public_key); +#[post("/follow", data = "")] +pub async fn follow(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; + let url = uri!("/scuttlebutt", profile(Some(public_key))); - Flash::success(Redirect::to(profile_url), success_msg) + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Following Scuttlebutt peer"); + match sbot_client.follow(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Followed peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to follow peer: {}", e)) + } + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), + } } // HELPERS AND ROUTES FOR /unfollow @@ -155,17 +443,44 @@ pub fn follow(pub_key: Form, _auth: Authenticated) -> Flash /// Unfollow a Scuttlebutt profile specified by the given public key. /// Redirects to the appropriate profile page with a flash message describing /// the outcome of the action (may be successful or unsuccessful). -#[post("/unfollow", data = "")] -pub fn unfollow(pub_key: Form, _auth: Authenticated) -> Flash { - let public_key = &pub_key.key; - // perform the sbotcli unfollow action using &pub_key.0 - // if successful, redirect to profile page with provided public key and flash "success" - // if error, redirect to profile page with provided public key and flash "error" - // redirect to the profile template with provided public key - let profile_url = uri!(profile(Some(public_key))); - let success_msg = format!("Unfollowed {}", public_key); +#[post("/unfollow", data = "")] +pub async fn unfollow(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; - Flash::success(Redirect::to(profile_url), success_msg) + let url = uri!("/scuttlebutt", profile(Some(public_key))); + + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Unfollowing Scuttlebutt peer"); + match sbot_client.unfollow(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Unfollowed peer")), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to unfollow peer: {}", e), + ), + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), + } } // HELPERS AND ROUTES FOR /block @@ -173,243 +488,392 @@ pub fn unfollow(pub_key: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash { - let public_key = &pub_key.key; - // perform the sbotcli block action using &pub_key.0 - // if successful, redirect to profile page with provided public key and flash "success" - // if error, redirect to profile page with provided public key and flash "error" - // redirect to the profile template with provided public key - let profile_url = uri!(profile(Some(public_key))); - let success_msg = format!("Blocked {}", public_key); +#[post("/block", data = "")] +pub async fn block(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; + let url = uri!("/scuttlebutt", profile(Some(public_key))); - Flash::success(Redirect::to(profile_url), success_msg) + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Blocking Scuttlebutt peer"); + match sbot_client.block(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Blocked peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to block peer: {}", e)) + } + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), + } } -// HELPERS AND ROUTES FOR /profile +// HELPERS AND ROUTES FOR /unblock -#[derive(Debug, Serialize)] -pub struct ProfileContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, +/// Unblock a Scuttlebutt profile specified by the given public key. +/// Redirects to the appropriate profile page with a flash message describing +/// the outcome of the action (may be successful or unsuccessful). +#[post("/unblock", data = "")] +pub async fn unblock(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; + let url = uri!("/scuttlebutt", profile(Some(public_key))); + + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Unblocking Scuttlebutt peer"); + match sbot_client.unblock(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Unblocked peer")), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to unblock peer: {}", e), + ), + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), + } } -impl ProfileContext { - pub fn build() -> ProfileContext { - ProfileContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, +// ROUTES FOR /profile + +/// A Scuttlebutt profile, specified by a public key. It may be our own profile +/// or the profile of a peer. If the public key query parameter is not provided, +/// the local profile is displayed (ie. the profile of the public key associated +/// with the local PeachCloud device). +#[get("/profile?")] +pub async fn profile( + mut public_key: Option, + flash: Option>, + _auth: Authenticated, +) -> Template { + if let Some(ref key) = public_key { + // `url_decode` replaces '+' with ' ', so we need to revert that + public_key = Some(key.replace(' ', "+")); + } + + // build the profile context object + let context = ProfileContext::build(public_key).await; + + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/profile", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = ProfileContext::default(); + + // flash name and msg will be `Some` if the sbot is inactive (in + // that case, they are set by the context builder). + // otherwise, we need to assign the name and returned error msg + // to the flash. + if context.flash_name.is_none() || context.flash_msg.is_none() { + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + } + + Template::render("scuttlebutt/profile", &context) } } } -/// A Scuttlebutt profile, specified by a public key. It may be our own profile or the profile of a peer. If not public key query parameter is provided, the local profile is displayed (ie. the profile of the public key associated with the local PeachCloud device). -#[get("/profile?")] -pub fn profile( - pub_key: Option<&str>, - flash: Option, +// HELPERS AND ROUTES FOR /profile/update + +/// Serve a form for the purpose of updating the name, description and picture +/// for the local Scuttlebutt profile. +#[get("/profile/update")] +pub async fn update_profile(flash: Option>, _auth: Authenticated) -> Template { + // build the profile context object + let context = ProfileContext::build(None).await; + + match context { + // we were able to build the context without errors + Ok(mut context) => { + context.back = Some("/scuttlebutt/profile".to_string()); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/update_profile", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = ProfileContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/update_profile", &context) + } + } +} + +#[derive(Debug, FromForm)] +pub struct Profile<'f> { + pub id: String, + pub current_name: String, + pub current_description: String, + pub new_name: String, + pub new_description: String, + pub image: TempFile<'f>, +} + +/// Update the name, description and picture for the local Scuttlebutt profile. +/// Redirects to profile page of the PeachCloud local identity with a flash +/// message describing the outcome of the action (may be successful or +/// unsuccessful). +#[post("/profile/update", data = "")] +pub async fn update_profile_post( + mut profile: Form>, _auth: Authenticated, -) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +) -> Flash { + let url = uri!("/scuttlebutt", update_profile); - let mut context = ProfileContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Profile".to_string()); - context.theme = Some(theme); + // we only want to try and interact with the sbot if it's active + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + // track whether the name, description or image have been updated + let mut name_updated: bool = false; + let mut description_updated: bool = false; + let image_updated: bool; - Template::render("scuttlebutt/profile", &context) + // only update the name if it has changed + if profile.new_name != profile.current_name { + debug!("Publishing new Scuttlebutt profile name"); + let publish_name_res = sbot_client.publish_name(&profile.new_name).await; + if publish_name_res.is_err() { + return Flash::error( + Redirect::to(url), + format!("Failed to update name: {}", publish_name_res.unwrap_err()), + ); + } else { + name_updated = true + } + } + + // only update the description if it has changed + if profile.new_description != profile.current_description { + debug!("Publishing new Scuttlebutt profile description"); + let publish_description_res = sbot_client + .publish_description(&profile.new_description) + .await; + + if publish_description_res.is_err() { + return Flash::error( + Redirect::to(url), + format!( + "Failed to update description: {}", + publish_description_res.unwrap_err() + ), + ); + } else { + description_updated = true + } + } + + // only update the image if a file was uploaded + if profile.image.name().is_some() { + match utils::write_blob_to_store(&mut profile.image).await { + Ok(blob_id) => { + // if the file was successfully added to the blobstore, + // publish an about image message with the blob id + let publish_image_res = sbot_client.publish_image(&blob_id).await; + + if publish_image_res.is_err() { + return Flash::error( + Redirect::to(url), + format!( + "Failed to update image: {}", + publish_image_res.unwrap_err() + ), + ); + } else { + image_updated = true + } + } + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to add image to blobstore: {}", e), + ) + } + } + } else { + image_updated = false + } + + if name_updated || description_updated || image_updated { + return Flash::success(Redirect::to(url), "Profile updated"); + } else { + // no updates were made but no errors were encountered either + return Flash::success(Redirect::to(url), "Profile info unchanged"); + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, profile data cannot be updated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), + } } // HELPERS AND ROUTES FOR /friends -#[derive(Debug, Serialize)] -pub struct FriendsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} +/// A list of friends (mutual follows), with each list item displaying the +/// name, image and public key of the peer. +#[get("/friends")] +pub async fn friends(flash: Option>, _auth: Authenticated) -> Template { + // build the friends context object + let context = FriendsContext::build().await; -impl FriendsContext { - pub fn build() -> FriendsContext { - FriendsContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/peers_list", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = FriendsContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/peers_list", &context) } } } -/// A list of friends (mutual follows), with each list item displaying the name, image and public -/// key of the peer. -#[get("/friends")] -pub fn friends(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = FriendsContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Friends".to_string()); - context.theme = Some(theme); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) -} - // HELPERS AND ROUTES FOR /follows -#[derive(Debug, Serialize)] -pub struct FollowsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} - -impl FollowsContext { - pub fn build() -> FollowsContext { - FollowsContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, - } - } -} - /// A list of follows (peers we follow who do not follow us), with each list item displaying the name, image and public /// key of the peer. #[get("/follows")] -pub fn follows(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +pub async fn follows(flash: Option>, _auth: Authenticated) -> Template { + // build the follows context object + let context = FollowsContext::build().await; - let mut context = FollowsContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Follows".to_string()); - context.theme = Some(theme); + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; + Template::render("scuttlebutt/peers_list", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = FollowsContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); - Template::render("scuttlebutt/peers_list", &context) -} - -// HELPERS AND ROUTES FOR /followers - -#[derive(Debug, Serialize)] -pub struct FollowersContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} - -impl FollowersContext { - pub fn build() -> FollowersContext { - FollowersContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, + Template::render("scuttlebutt/peers_list", &context) } } } -/// A list of followers (peers who follow us but who we do not follow), with each list item displaying the name, image and public -/// key of the peer. -#[get("/followers")] -pub fn followers(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = FollowersContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Followers".to_string()); - context.theme = Some(theme); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) -} - // HELPERS AND ROUTES FOR /blocks -#[derive(Debug, Serialize)] -pub struct BlocksContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} +/// A list of blocks (peers we've blocked previously), with each list item +/// displaying the name, image and public key of the peer. +#[get("/blocks")] +pub async fn blocks(flash: Option>, _auth: Authenticated) -> Template { + // build the blocks context object + let context = BlocksContext::build().await; -impl BlocksContext { - pub fn build() -> BlocksContext { - BlocksContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/peers_list", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = BlocksContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/peers_list", &context) } } } - -/// A list of blocks (peers we've blocked previously), with each list item displaying the name, image and public -/// key of the peer. -#[get("/blocks")] -pub fn blocks(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = BlocksContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Blocks".to_string()); - context.theme = Some(theme); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) -} diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 99b97b9..34aa61d 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -1,40 +1,37 @@ -use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::{get, State}; -use rocket_dyn_templates::{tera::Context, Template}; +use rocket_dyn_templates::Template; use crate::routes::authentication::Authenticated; -use crate::utils; -use crate::RocketConfig; +use crate::{context::scuttlebutt::StatusContext, RocketConfig}; // HELPERS AND ROUTES FOR /status/scuttlebutt #[get("/scuttlebutt")] -pub fn scuttlebutt_status(_auth: Authenticated, config: &State) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +pub async fn scuttlebutt_status(_auth: Authenticated, config: &State) -> Template { + let context = StatusContext::build().await; - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read().ok(); - - // retrieve go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("sbot_status", &sbot_status); - context.insert("sbot_config", &sbot_config); - context.insert("flash_name", &None::<()>); - context.insert("flash_msg", &None::<()>); - context.insert("title", &Some("Scuttlebutt Status")); - - // define back arrow url based on mode - if config.standalone_mode { + let back = if config.standalone_mode { // return to home page - context.insert("back", &Some("/")); + Some("/".to_string()) } else { // return to status menu - context.insert("back", &Some("/status")); - } + Some("/status".to_string()) + }; - Template::render("status/scuttlebutt", &context.into_json()) + match context { + Ok(mut context) => { + // define back arrow url based on mode + context.back = back; + + Template::render("status/scuttlebutt", &context) + } + Err(_) => { + let mut context = StatusContext::default(); + + // define back arrow url based on mode + context.back = back; + + Template::render("status/scuttlebutt", &context) + } + } } diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs index fee26fa..5b85099 100644 --- a/peach-web/src/utils.rs +++ b/peach-web/src/utils.rs @@ -1,11 +1,88 @@ pub mod monitor; -use log::info; -use rocket::response::{Redirect, Responder}; -use rocket::serde::Serialize; -use rocket_dyn_templates::Template; +use std::io::prelude::*; +use std::{fs, fs::File, path::Path}; -use crate::THEME; +use dirs; +use golgi::blobs; +use log::info; +use peach_lib::sbot::SbotConfig; +use rocket::{ + fs::TempFile, + response::{Redirect, Responder}, + serde::Serialize, +}; +use rocket_dyn_templates::Template; +use temporary::Directory; + +use crate::{error::PeachWebError, THEME}; + +// FILEPATH FUNCTIONS + +// return the path of the ssb-go directory +pub fn get_go_ssb_path() -> Result { + let go_ssb_path = match SbotConfig::read() { + Ok(conf) => conf.repo, + // return the default path if unable to read `config.toml` + Err(_) => { + // determine the home directory + let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + // add the go-ssb subdirectory + home_path.push(".ssb-go"); + // convert the PathBuf to a String + home_path + .into_os_string() + .into_string() + .map_err(|_| PeachWebError::OsString)? + } + }; + + Ok(go_ssb_path) +} + +// check whether a blob is in the blobstore +pub async fn blob_is_stored_locally(blob_path: &str) -> Result { + let go_ssb_path = get_go_ssb_path()?; + let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); + let blob_exists_locally = Path::new(&complete_path).exists(); + + Ok(blob_exists_locally) +} + +// take the path to a file, add it to the blobstore and return the blob id +pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { + // create temporary directory and path + let temp_dir = Directory::new("blob")?; + // we performed a `file.name().is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = file.name().expect("retrieving filename from uploaded file"); + let temp_path = temp_dir.join(filename); + + // write file to temporary path + file.persist_to(&temp_path).await?; + + // open the file and read it into a buffer + let mut file = File::open(&temp_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + // hash the bytes representing the file + let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + + // define the blobstore path and blob filename + let (blob_dir, blob_filename) = hex_hash.split_at(2); + let go_ssb_path = get_go_ssb_path()?; + let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + + // create the blobstore sub-directory + fs::create_dir_all(&blobstore_sub_dir)?; + + // copy the file to the blobstore + let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); + fs::copy(temp_path, blob_path)?; + + Ok(blob_id) +} // THEME FUNCTIONS diff --git a/peach-web/static/css/peachcloud.css b/peach-web/static/css/peachcloud.css index 250e1be..676d43a 100644 --- a/peach-web/static/css/peachcloud.css +++ b/peach-web/static/css/peachcloud.css @@ -277,6 +277,18 @@ body { padding-bottom: 1rem; } +.capsule-profile { + margin-left: 1rem; + margin-right: 1rem; +} + +@media only screen and (min-width: 600px) { + .capsule-profile { + margin-left: 0; + margin-right: 0; + } +} + @media only screen and (min-width: 600px) { .capsule-container { margin-left: 0; @@ -728,6 +740,7 @@ form { height: 7rem; overflow: auto; resize: vertical; + width: 100%; } .alert-input { @@ -753,7 +766,7 @@ form { font-family: var(--sans-serif); font-size: var(--font-size-7); display: block; - /* margin-bottom: 2px; */ + margin-bottom: 2px; } .label-medium { diff --git a/peach-web/static/icons/pencil.svg b/peach-web/static/icons/pencil.svg old mode 100755 new mode 100644 diff --git a/peach-web/static/icons/user.svg b/peach-web/static/icons/user.svg old mode 100755 new mode 100644 diff --git a/peach-web/templates/scuttlebutt/invites.html.tera b/peach-web/templates/scuttlebutt/invites.html.tera new file mode 100644 index 0000000..3a0e960 --- /dev/null +++ b/peach-web/templates/scuttlebutt/invites.html.tera @@ -0,0 +1,20 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+
+ + + {% if invite_code %} +

{{ invite_code }}

+ {% endif %} +
+ + + Cancel +
+ + {% include "snippets/flash_message" %} +
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/messages.html.tera b/peach-web/templates/scuttlebutt/messages.html.tera deleted file mode 100644 index d3da0af..0000000 --- a/peach-web/templates/scuttlebutt/messages.html.tera +++ /dev/null @@ -1,10 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera index b78906f..23d667f 100644 --- a/peach-web/templates/scuttlebutt/peers.html.tera +++ b/peach-web/templates/scuttlebutt/peers.html.tera @@ -5,10 +5,11 @@ diff --git a/peach-web/templates/scuttlebutt/peers_list.html.tera b/peach-web/templates/scuttlebutt/peers_list.html.tera index 2b61d78..c2dcfb4 100644 --- a/peach-web/templates/scuttlebutt/peers_list.html.tera +++ b/peach-web/templates/scuttlebutt/peers_list.html.tera @@ -1,16 +1,24 @@ {%- extends "nav" -%} {%- block card %}
+ {%- if peers %} + {%- else %} +

No follows found

+ {%- endif %}
{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/private.html.tera b/peach-web/templates/scuttlebutt/private.html.tera new file mode 100644 index 0000000..70177b9 --- /dev/null +++ b/peach-web/templates/scuttlebutt/private.html.tera @@ -0,0 +1,23 @@ +{%- extends "nav" -%} +{%- block card %} + +
+ {# only render the private message elements if the sbot is active #} + {%- if sbot_status and sbot_status.state == "active" %} +
+
+ + +
+ + + + + + +
+ {%- endif %} + + {% include "snippets/flash_message" %} +
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index 74b1c31..3d0b4b3 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -2,31 +2,71 @@ {%- block card %}
+ {# only render the profile info elements if the sbot is active #} + {%- if sbot_status and sbot_status.state == "active" %} -
+
+ {% if is_local_profile %} - Profile picture + + Edit + + {% endif %} - Profile picture + {# only try to render profile pic if we have the blob #} + {%- if blob_path and blob_exists == true %} + Profile picture + {% else %} + {# render a placeholder profile picture (icon) #} + Profile picture + {% endif %} -

{ name }

- -

{ description }

+

{{ name }}

+ +

{{ description }}

+ {% if is_local_profile %} -
+ - +
+ {% else %}
- Follow - Block - Private Message + {% if following == false %} +
+ + +
+ {% elif following == true %} +
+ + +
+ {% else %} +

Unable to determine follow state

+ {% endif %} + {% if blocking == false %} +
+ + +
+ {% elif blocking == true %} +
+ + +
+ {% else %} +

Unable to determine block state

+ {% endif %} + Send Private Message
+ {%- endif %} + {%- endif %} {% include "snippets/flash_message" %}
diff --git a/peach-web/templates/scuttlebutt/search.html.tera b/peach-web/templates/scuttlebutt/search.html.tera new file mode 100644 index 0000000..d5e508d --- /dev/null +++ b/peach-web/templates/scuttlebutt/search.html.tera @@ -0,0 +1,16 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+
+ + +
+ + +
+ + {% include "snippets/flash_message" %} +
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/update_profile.html.tera b/peach-web/templates/scuttlebutt/update_profile.html.tera new file mode 100644 index 0000000..00c5199 --- /dev/null +++ b/peach-web/templates/scuttlebutt/update_profile.html.tera @@ -0,0 +1,27 @@ +{%- extends "nav" -%} +{%- block card %} + {# ASSIGN VARIABLES #} + {# ---------------- #} + +
+
+
+ + + + + + +
+ + + +
+ + Cancel +
+
+ + {% include "snippets/flash_message" %} +
+{%- endblock card -%} diff --git a/peach-web/templates/snippets/flash_message.html.tera b/peach-web/templates/snippets/flash_message.html.tera index 0b7fc3b..ca56bfd 100644 --- a/peach-web/templates/snippets/flash_message.html.tera +++ b/peach-web/templates/snippets/flash_message.html.tera @@ -5,6 +5,9 @@ {%- elif flash_msg and flash_name == "info" %}
{{ flash_msg }}.
+{%- elif flash_msg and flash_name == "warning" %} + +
{{ flash_msg }}.
{%- elif flash_msg and flash_name == "error" %}
{{ flash_msg }}.
diff --git a/peach-web/templates/status/scuttlebutt.html.tera b/peach-web/templates/status/scuttlebutt.html.tera index 32d04bc..998efad 100644 --- a/peach-web/templates/status/scuttlebutt.html.tera +++ b/peach-web/templates/status/scuttlebutt.html.tera @@ -3,12 +3,12 @@ {# ASSIGN VARIABLES #} {# ---------------- #} {%- if sbot_status.memory -%} - {% set mem = sbot_status.memory / 1024 / 1024 | round -%} + {% set mem = sbot_status.memory / 1024 / 1024 | round | int -%} {%- else -%} {% set mem = "X" -%} {%- endif -%} {%- if sbot_status.blobstore -%} - {% set blobs = sbot_status.blobstore / 1024 / 1024 | round -%} + {% set blobs = sbot_status.blobstore / 1024 / 1024 | round | int -%} {%- else -%} {% set blobs = "X" -%} {%- endif -%} @@ -52,33 +52,13 @@

-
-
- - -
-
- - -
-
- - -
-
- - +
+
+ +
-