commit 57771fbf0dc001d7ce8063f15755bf48aeb6cbb7 Author: asonix Date: Tue Dec 8 15:59:55 2020 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6f75b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/sled diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..89683be --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2384 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "actix-codec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project 0.4.27", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-connect" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "derive_more", + "either", + "futures-util", + "http", + "log", + "rustls", + "tokio-rustls", + "trust-dns-proto", + "trust-dns-resolver", + "webpki", +] + +[[package]] +name = "actix-http" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" +dependencies = [ + "actix-codec", + "actix-connect", + "actix-rt", + "actix-service", + "actix-threadpool", + "actix-tls", + "actix-utils", + "base64 0.13.0", + "bitflags", + "brotli2", + "bytes", + "cookie", + "copyless", + "derive_more", + "either", + "encoding_rs", + "flate2", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "h2", + "http", + "httparse", + "indexmap", + "itoa", + "language-tags", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project 1.0.2", + "rand", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "slab", + "time", +] + +[[package]] +name = "actix-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd1f7dbda1645bf7da33554db60891755f6c01c1b2169e2f4c492098d30c235" +dependencies = [ + "bytestring", + "http", + "log", + "regex", + "serde", +] + +[[package]] +name = "actix-rt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" +dependencies = [ + "actix-macros", + "actix-threadpool", + "copyless", + "futures-channel", + "futures-util", + "smallvec", + "tokio", +] + +[[package]] +name = "actix-server" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-channel", + "futures-util", + "log", + "mio", + "mio-uds", + "num_cpus", + "slab", + "socket2", +] + +[[package]] +name = "actix-service" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" +dependencies = [ + "futures-util", + "pin-project 0.4.27", +] + +[[package]] +name = "actix-testing" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" +dependencies = [ + "actix-macros", + "actix-rt", + "actix-server", + "actix-service", + "log", + "socket2", +] + +[[package]] +name = "actix-threadpool" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30" +dependencies = [ + "derive_more", + "futures-channel", + "lazy_static", + "log", + "num_cpus", + "parking_lot", + "threadpool", +] + +[[package]] +name = "actix-tls" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" +dependencies = [ + "actix-codec", + "actix-service", + "actix-utils", + "futures-util", + "rustls", + "tokio-rustls", + "webpki", + "webpki-roots", +] + +[[package]] +name = "actix-utils" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "bitflags", + "bytes", + "either", + "futures-channel", + "futures-sink", + "futures-util", + "log", + "pin-project 0.4.27", + "slab", +] + +[[package]] +name = "actix-web" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-testing", + "actix-threadpool", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "awc", + "bytes", + "derive_more", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "log", + "mime", + "pin-project 1.0.2", + "regex", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", + "socket2", + "time", + "tinyvec", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "addr2line" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "anyhow" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "awc" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "base64 0.13.0", + "bytes", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "log", + "mime", + "percent-encoding", + "rand", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", +] + +[[package]] +name = "backtrace" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bcrypt" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d0faafe9e089674fc3efdb311ff5253d445c79d85d1d28bd3ace76d45e7164" +dependencies = [ + "base64 0.13.0", + "blowfish", + "getrandom 0.2.0", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b" +dependencies = [ + "byteorder", + "cipher", + "opaque-debug", +] + +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "bytecount" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytestring" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "const_fn" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + +[[package]] +name = "cookie" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" +dependencies = [ + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "derive_more" +version = "0.99.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encoding_rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-as-inner" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "flate2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" + +[[package]] +name = "futures-executor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" + +[[package]] +name = "futures-macro" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" + +[[package]] +name = "futures-task" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project 1.0.2", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "humantime" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi 0.3.9", + "winreg", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 0.1.10", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" + +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + +[[package]] +name = "nom_locate" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67484adf5711f94f2f28b653bf231bff8e438be33bf5b0f35935a0db4f618a2" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + +[[package]] +name = "num-bigint" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" + +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c6d9b8427445284a09c55be860a15855ab580a417ccad9da88f5a06787ced0" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pict-rs-aggregator" +version = "0.1.0" +dependencies = [ + "actix-web", + "anyhow", + "bcrypt", + "dotenv", + "env_logger", + "futures", + "mime", + "ructe", + "serde", + "serde_json", + "sled", + "structopt", + "thiserror", + "url", + "uuid", +] + +[[package]] +name = "pin-project" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +dependencies = [ + "pin-project-internal 0.4.27", +] + +[[package]] +name = "pin-project" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +dependencies = [ + "pin-project-internal 1.0.2", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + +[[package]] +name = "pin-project-lite" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.15", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "regex" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "ring" +version = "0.16.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rsass" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1144f0d9fd5df1f8fbac387e91b73a2d315e013ee6beb48353a71d8ec2c5ab" +dependencies = [ + "lazy_static", + "nom", + "nom_locate", + "num-bigint", + "num-integer", + "num-rational", + "num-traits", + "rand", +] + +[[package]] +name = "ructe" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3507c22423c8be907293f0aa684b08ac62efb20e0768639fdfbce833481fd30" +dependencies = [ + "base64 0.12.3", + "bytecount", + "itertools", + "md5", + "mime", + "nom", + "rsass", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" +dependencies = [ + "base64 0.12.3", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "signal-hook-registry" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "sled" +version = "0.34.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", + "zstd", +] + +[[package]] +name = "smallvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" + +[[package]] +name = "socket2" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "standback" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" +dependencies = [ + "version_check", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d7ad61edd59bfcc7e80dababf0f4aed2e6d5e0ba1659356ae889752dfc12ff" +dependencies = [ + "bytes", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-uds", + "pin-project-lite 0.1.11", + "signal-hook-registry", + "slab", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-rustls" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" +dependencies = [ + "futures-core", + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.1.11", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.0", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +dependencies = [ + "pin-project 0.4.27", + "tracing", +] + +[[package]] +name = "trust-dns-proto" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53861fcb288a166aae4c508ae558ed18b53838db728d4d310aad08270a7d4c2b" +dependencies = [ + "async-trait", + "backtrace", + "enum-as-inner", + "futures", + "idna", + "lazy_static", + "log", + "rand", + "smallvec", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6759e8efc40465547b0dfce9500d733c65f969a4cbbfbe3ccf68daaa46ef179e" +dependencies = [ + "backtrace", + "cfg-if 0.1.10", + "futures", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", + "serde", +] + +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand", + "serde", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" + +[[package]] +name = "web-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" +dependencies = [ + "webpki", +] + +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "zstd" +version = "0.5.3+zstd.1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "2.0.5+zstd.1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.4.17+zstd.1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b" +dependencies = [ + "cc", + "glob", + "itertools", + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2b8c75f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pict-rs-aggregator" +version = "0.1.0" +authors = ["asonix "] +edition = "2018" +build = "src/build.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = { version = "3.3.2", features = ["rustls"] } +anyhow = "1.0" +bcrypt = "0.9" +env_logger = "0.8.2" +futures = "0.3" +mime = "0.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +sled = { version = "0.34.6", features = ["zstd"] } +structopt = "0.3.21" +thiserror = "1.0" +url = { version = "2.2", features = ["serde"] } +uuid = { version = "0.8.1", features = ["serde", "v4"] } + +[build-dependencies] +anyhow = "1.0" +dotenv = "0.15.0" +ructe = { version = "0.13.0", features = ["sass", "mime03"] } diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile new file mode 100644 index 0000000..36594f7 --- /dev/null +++ b/docker/dev/Dockerfile @@ -0,0 +1,42 @@ +# Build +FROM ekidd/rust-musl-builder:1.48.0 as rust + +ARG OUT_DIR=/tmp + +# Cache deps +WORKDIR /app +RUN sudo chown -R rust:rust . +RUN USER=root cargo new server +WORKDIR /app/server + +COPY Cargo.toml Cargo.lock ./ + +RUN sudo chown -R rust:rust . +RUN mkdir -p ./src/bin ./scss ./templates ./static \ + && touch ./scss/layout.scss \ + && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs + +COPY src/build.rs ./src/build.rs + +RUN cargo build --release +RUN rm -rf ./src ./scss ./templates + +COPY src ./src/ +COPY scss ./scss/ +COPY templates ./templates/ +COPY static ./static/ + +# Build for release +RUN cargo build --release --frozen + +FROM alpine:3.12 + +# Copy resources +COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/release/picture-aggregator /app/picture-aggregator + +RUN addgroup -g 1000 pictrs +RUN adduser -D -s /bin/sh -u 1000 -G pictrs pictrs +RUN chown pictrs:pictrs /app/picture-aggregator +USER pictrs +EXPOSE 8080 +CMD ["/app/picture-aggregator"] diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml new file mode 100644 index 0000000..4330617 --- /dev/null +++ b/docker/dev/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.3' + +services: + pictrs: + image: asonix/pictrs:v0.2.6-r0 + user: root + restart: always + volumes: + - ./volumes/pictrs:/mnt + + picture-aggregator: + build: + context: ../../ + dockerfile: docker/dev/Dockerfile + user: root + environment: + - PICTRS_AGGREGATOR_UPSTREAM=http://pictrs:8080 + ports: + - "127.0.0.1:8082:8082" + restart: always diff --git a/docker/dev/volumes/pictrs/sled/db-0.34/conf b/docker/dev/volumes/pictrs/sled/db-0.34/conf new file mode 100644 index 0000000..4154d7c --- /dev/null +++ b/docker/dev/volumes/pictrs/sled/db-0.34/conf @@ -0,0 +1,4 @@ +segment_size: 524288 +use_compression: false +version: 0.34 +vQÁ \ No newline at end of file diff --git a/docker/dev/volumes/pictrs/sled/db-0.34/db b/docker/dev/volumes/pictrs/sled/db-0.34/db new file mode 100644 index 0000000..caced43 Binary files /dev/null and b/docker/dev/volumes/pictrs/sled/db-0.34/db differ diff --git a/docker/prod/Dockerfile.amd64 b/docker/prod/Dockerfile.amd64 new file mode 100644 index 0000000..676a996 --- /dev/null +++ b/docker/prod/Dockerfile.amd64 @@ -0,0 +1,74 @@ +FROM rustembedded/cross:x86_64-unknown-linux-musl AS x86_64-builder + +ARG UID=991 +ARG GID=991 + +ENV TOOLCHAIN=stable +ENV TARGET=x86_64-unknown-linux-musl +ENV TOOL=x86_64-linux-musl + +RUN \ + apt-get update && \ + apt-get upgrade -y + +RUN \ + addgroup --gid "${GID}" build && \ + adduser \ + --disabled-password \ + --gecos "" \ + --ingroup build \ + --uid "${UID}" \ + --home /opt/build \ + build + +ADD https://sh.rustup.rs /opt/build/rustup.sh + +RUN \ + chown -R build:build /opt/build + +USER build +WORKDIR /opt/build + +ENV PATH=/opt/build/.cargo/bin:/usr/local/musl/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +RUN \ + chmod +x rustup.sh && \ + ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ + rustup target add $TARGET + +FROM x86_64-builder as builder + +ARG TAG=main +ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs-aggregator +ARG BINARY=pict-rs-aggregator + +RUN \ + git clone -b $TAG $REPOSITORY repo + +WORKDIR /opt/build/repo + +RUN \ + cargo build --release --target $TARGET && \ + $TOOL-strip target/$TARGET/release/$BINARY + +FROM amd64/alpine:3.12 + +ARG UID=991 +ARG GID=991 +ARG BINARY=pict-rs-aggregator + +COPY --from=builder /opt/build/repo/target/x86_64-unknown-linux-musl/release/$BINARY /usr/bin/$BINARY + +RUN \ + apk add tini && \ + addgroup -g $GID pictrs && \ + adduser -D -G pictrs -u $UID -g "" -h /opt/pictrs pictrs + +RUN \ + chown -R pictrs:pictrs /mnt + +VOLUME /mnt +WORKDIR /opt/pictrs +USER pictrs +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/usr/bin/pict-rs-aggregator"] diff --git a/docker/prod/Dockerfile.arm32v7 b/docker/prod/Dockerfile.arm32v7 new file mode 100644 index 0000000..b7de29c --- /dev/null +++ b/docker/prod/Dockerfile.arm32v7 @@ -0,0 +1,74 @@ +FROM rustembedded/cross:arm-unknown-linux-musleabihf AS arm32v7-builder + +ARG UID=991 +ARG GID=991 + +ENV TOOLCHAIN=stable +ENV TARGET=arm-unknown-linux-musleabihf +ENV TOOL=arm-linux-musleabihf + +RUN \ + apt-get update && \ + apt-get upgrade -y + +RUN \ + addgroup --gid "${GID}" build && \ + adduser \ + --disabled-password \ + --gecos "" \ + --ingroup build \ + --uid "${UID}" \ + --home /opt/build \ + build + +ADD https://sh.rustup.rs /opt/build/rustup.sh + +RUN \ + chown -R build:build /opt/build + +USER build +WORKDIR /opt/build + +ENV PATH=/opt/build/.cargo/bin:/usr/local/musl/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +RUN \ + chmod +x rustup.sh && \ + ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ + rustup target add $TARGET + +FROM arm32v7-builder as builder + +ARG TAG=main +ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs-aggregator +ARG BINARY=pict-rs-aggregator + +RUN \ + git clone -b $TAG $REPOSITORY repo + +WORKDIR /opt/build/repo + +RUN \ + cargo build --release --target $TARGET && \ + $TOOL-strip target/$TARGET/release/$BINARY + +FROM arm32v7/alpine:3.12 + +ARG UID=991 +ARG GID=991 +ARG BINARY=pict-rs-aggregator + +COPY --from=builder /opt/build/repo/target/arm-unknown-linux-musleabihf/release/$BINARY /usr/bin/$BINARY + +RUN \ + apk add tini && \ + addgroup -g $GID pictrs && \ + adduser -D -G pictrs -u $UID -g "" -h /opt/pictrs pictrs + +RUN \ + chown -R pictrs:pictrs /mnt + +VOLUME /mnt +WORKDIR /opt/pictrs +USER pictrs +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/usr/bin/pict-rs-aggregator"] diff --git a/docker/prod/Dockerfile.arm64v8 b/docker/prod/Dockerfile.arm64v8 new file mode 100644 index 0000000..ea0408d --- /dev/null +++ b/docker/prod/Dockerfile.arm64v8 @@ -0,0 +1,74 @@ +FROM rustembedded/cross:aarch64-unknown-linux-musl AS aarch64-builder + +ARG UID=991 +ARG GID=991 + +ENV TOOLCHAIN=stable +ENV TARGET=aarch64-unknown-linux-musl +ENV TOOL=aarch64-linux-musl + +RUN \ + apt-get update && \ + apt-get upgrade -y + +RUN \ + addgroup --gid "${GID}" build && \ + adduser \ + --disabled-password \ + --gecos "" \ + --ingroup build \ + --uid "${UID}" \ + --home /opt/build \ + build + +ADD https://sh.rustup.rs /opt/build/rustup.sh + +RUN \ + chown -R build:build /opt/build + +USER build +WORKDIR /opt/build + +ENV PATH=/opt/build/.cargo/bin:/usr/local/musl/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +RUN \ + chmod +x rustup.sh && \ + ./rustup.sh --default-toolchain $TOOLCHAIN --profile minimal -y && \ + rustup target add $TARGET + +FROM aarch64-builder as builder + +ARG TAG=main +ARG REPOSITORY=https://git.asonix.dog/asonix/pict-rs-aggregator +ARG BINARY=pict-rs-aggregator + +RUN \ + git clone -b $TAG $REPOSITORY repo + +WORKDIR /opt/build/repo + +RUN \ + cargo build --release --target $TARGET && \ + $TOOL-strip target/$TARGET/release/$BINARY + +FROM arm64v8/alpine:3.12 + +ARG UID=991 +ARG GID=991 +ARG BINARY=pict-rs-aggregator + +COPY --from=builder /opt/build/repo/target/aarch64-unknown-linux-musl/release/$BINARY /usr/bin/$BINARY + +RUN \ + apk add tini && \ + addgroup -g $GID pictrs && \ + adduser -D -G pictrs -u $UID -g "" -h /opt/pictrs pictrs + +RUN \ + chown -R pictrs:pictrs /mnt + +VOLUME /mnt +WORKDIR /opt/pictrs +USER pictrs +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/usr/bin/pict-rs-aggregator"] diff --git a/docker/prod/deploy.sh b/docker/prod/deploy.sh new file mode 100755 index 0000000..be5b621 --- /dev/null +++ b/docker/prod/deploy.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +function require() { + if [ "$1" = "" ]; then + echo "input '$2' required" + print_help + exit 1 + fi +} + +function print_help() { + echo "deploy.sh" + echo "" + echo "Usage:" + echo " deploy.sh [tag]" + echo "" + echo "Args:" + echo " tag: The git tag to be applied to the repository and docker build" + echo " branch: The git branch to use for tagging and publishing" +} + +function build_image() { + tag=$1 + arch=$2 + + docker build \ + --pull \ + --build-arg TAG=$tag \ + -t asonix/pictrs-aggregator:$arch-$tag \ + -t asonix/pictrs-aggregator:$arch-latest \ + -f Dockerfile.$arch \ + . + + docker push asonix/pictrs-aggregator:$arch-$tag + docker push asonix/pictrs-aggregator:$arch-latest +} + +# Creating the new tag +new_tag="$1" +branch="$2" + +require "$new_tag" "tag" +require "$branch" "branch" + +if ! docker run --rm -it arm64v8/alpine:3.11 /bin/sh -c 'echo "docker is configured correctly"' +then + echo "docker is not configured to run on qemu-emulated architectures, fixing will require sudo" + sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes +fi + +set -xe + +git checkout $branch + +# Changing the docker-compose prod +sed -i "s/asonix\/pictrs-aggregator:.*/asonix\/pictrs-aggregator:$new_tag/" docker-compose.yml +git add ../prod/docker-compose.yml + +# The commit +git commit -m"Version $new_tag" +git tag $new_tag + +# Push +git push origin $new_tag +git push + +# Build for arm64v8, arm32v7, and amd64 +build_image $new_tag arm64v8 +build_image $new_tag arm32v7 +build_image $new_tag amd64 + +# Build for other archs +# TODO + +./manifest.sh asonix/pictrs-aggregator $new_tag +./manifest.sh asonix/pictrs-aggregator latest diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml new file mode 100644 index 0000000..323de66 --- /dev/null +++ b/docker/prod/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.3' + +services: + pictrs: + image: asonix/pictrs:latest + restart: always + volumes: + - ./volumes/pictrs:/mnt + + pictrs-aggregator: + image: asonix/pictrs-aggregator:v0.2.3 + ports: + - "8082:8082" + restart: always + environment: + - PICTRS_AGGREGATOR_UPSTREAM=http://pictrs:8080 + volumes: + - ./volumes/aggregator:/opt/pictrs diff --git a/docker/prod/manifest.sh b/docker/prod/manifest.sh new file mode 100755 index 0000000..a249ef0 --- /dev/null +++ b/docker/prod/manifest.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +function require() { + if [ "$1" = "" ]; then + echo "input '$2' required" + print_help + exit 1 + fi +} +function print_help() { + echo "deploy.sh" + echo "" + echo "Usage:" + echo " manifest.sh [repository] [tag]" + echo "" + echo "Args:" + echo " repository: The docker repository hosting the images" + echo " tag: The git tag to be applied to the image manifest" +} + +repo=$1 +new_tag=$2 + +require "$repo" "repository" +require "$new_tag" "tag" + +set -xe + +docker manifest create $repo:$new_tag \ + -a $repo:arm64v8-$new_tag \ + -a $repo:arm32v7-$new_tag \ + -a $repo:amd64-$new_tag + +docker manifest annotate $repo:$new_tag \ + $repo:arm64v8-$new_tag --os linux --arch arm64 --variant v8 + +docker manifest annotate $repo:$new_tag \ + $repo:arm32v7-$new_tag --os linux --arch arm --variant v7 + +docker manifest annotate $repo:$new_tag \ + $repo:amd64-$new_tag --os linux --arch amd64 + +docker manifest push $repo:$new_tag --purge diff --git a/scss/layout.scss b/scss/layout.scss new file mode 100644 index 0000000..d191829 --- /dev/null +++ b/scss/layout.scss @@ -0,0 +1,264 @@ +* { + box-sizing: border-box; +} + +body { + color: #fff; + background-color: #333; + font-family: sans-serif; + margin: 0; +} + +section { + max-width: 700px; + margin: auto; + margin-bottom: 32px; + background-color: #fff; + color: #333; + border: 1px solid #e5e5e5; + border-radius: 3px; +} + +.content-group { + padding: 16px 32px; + border-bottom: 1px solid #e5e5e5; + + &.even { + padding: 32px; + } + + &:last-child { + border-bottom: none; + } +} + +article .content-group { + &:last-child { + border-bottom: 1px solid #e5e5e5; + } +} + +.title { + max-width: 700px; + margin: auto; + + h1 { + font-weight: 500; + } +} + +.subtitle { + margin: 0; +} + +h3 { + margin: 0; +} + +a { + &, + &:focus, + &:active { + color: #c92a60; + } + + &:hover { + color: #9d2a60; + } +} + +.button { + position: relative; + overflow: hidden; + + padding: 8px 16px; + margin: 0 8px; + display: inline-block; + cursor: pointer; + border-radius: 3px; + + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + + &, & * { + user-select: none; + } + + &.outline { + border: 1px solid #c92a60; + background-color: #fff; + color: #c92a60; + + &:hover { + border-color: #9d2a60; + background-color: #fff; + color: #9d2a60; + } + } + + &.plain { + border: 1px solid #e5e5e5; + background-color: #f5f5f5; + color: #222; + + &:hover { + border-color: #e0e0e0; + background-color: #f0f0f0; + } + } + + &.submit { + border: 1px solid #9d2a60; + background-color: #c92a60; + color: #fff; + + &:hover { + border-color: #4a1a31; + background-color: #aa2452; + } + } + + .action { + display: block; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + margin: 0; + padding: 0; + font-size: 20px; + cursor: pointer; + opacity: 0; + filter: alpha(opacity=0); + width: 100%; + border: 1px solid; + } +} + +.button-space { + margin: 0 -8px; + padding-top: 16px; +} + +ul { + margin: 0; + padding: 0; + list-style: none; +} + +.input-wrapper { + width: 300px; + padding: 8px 0; + + label { + display: block; + } + + .input-title { + padding: 4px 0; + font-weight: 500; + } + textarea.input { + resize: vertical; + } + .input { + width: 100%; + padding: 8px 12px; + border: 1px solid #e5e5e5; + background-color: #fff; + box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1) inset; + border-radius: 3px; + } + .input:hover, + .input:focus { + box-shadow: 0px 0px 5px #c92a603b inset; + } + .input:focus { + border: 1px solid #c92a60; + } +} + +.edit-row { + display: flex; + flex-direction: row-reverse; + + .edit-item { + width: 100%; + flex: 1; + } + + .edit-item + .edit-item { + padding-right: 32px; + } +} + +.image-box { + max-width: 100%; + background-color: #f5f5f5; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + border-radius: 3px; + + img { + display: block; + width: 100%; + border-radius: 3px; + } +} + +.image-meta { + padding: 8px 0 0; + + .image-title { + font-weight: 500; + font-size: 18px; + padding: 8px 0; + } + + .image-description { + background-color: #f5f5f5; + border: 1px solid #e5e5e5; + border-radius: 3px; + padding: 8px; + } +} + +@media (max-width: 700px) { + section { + border-radius: 0; + } + + .content-group { + &, &.even { + padding: 16px; + } + } + + .input-wrapper { + width: 100%; + } + + .button-space { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + } + + .button { + margin: 4px 8px; + flex: 1; + min-width: 150px; + text-align: center; + } + + .title { + padding: 0 16px; + } + + .edit-row { + flex-direction: column; + + .edit-item + .edit-item { + padding-right: 0; + } + } +} diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..3d45a5b --- /dev/null +++ b/src/build.rs @@ -0,0 +1,12 @@ +use ructe::Ructe; + +fn main() -> Result<(), anyhow::Error> { + dotenv::dotenv().ok(); + let mut ructe = Ructe::from_env()?; + let mut statics = ructe.statics()?; + statics.add_sass_file("scss/layout.scss")?; + statics.add_files("static")?; + ructe.compile_templates("templates")?; + + Ok(()) +} diff --git a/src/connection.rs b/src/connection.rs new file mode 100644 index 0000000..4269072 --- /dev/null +++ b/src/connection.rs @@ -0,0 +1,172 @@ +use crate::pict::{Extension, Images}; +use actix_web::{ + body::BodyStream, client::Client, http::StatusCode, web, HttpRequest, HttpResponse, + ResponseError, +}; +use url::Url; + +pub(crate) static VALID_SIZES: &[u16] = &[80, 160, 320, 640, 1080, 2160]; + +pub(crate) struct Connection { + upstream: Url, + client: Client, +} + +impl Connection { + pub(crate) fn new(upstream: Url, client: Client) -> Self { + Connection { upstream, client } + } + + pub(crate) async fn thumbnail( + &self, + size: u16, + file: &str, + extension: Extension, + req: &HttpRequest, + ) -> Result { + if !VALID_SIZES.contains(&size) { + return Err(SizeError(size).into()); + } + + self.proxy(self.thumbnail_url(size, file, extension), req) + .await + } + + pub(crate) async fn image( + &self, + file: &str, + req: &HttpRequest, + ) -> Result { + self.proxy(self.image_url(file), req).await + } + + pub(crate) async fn upload( + &self, + req: &HttpRequest, + body: web::Payload, + ) -> Result { + let client_request = self.client.request_from(self.upload_url(), req.head()); + + let client_request = if let Some(addr) = req.head().peer_addr { + client_request.header("X-Forwareded-For", addr.to_string()) + } else { + client_request + }; + + let mut res = client_request + .send_stream(body) + .await + .map_err(|_| UploadError::Request)?; + + let images = res.json::().await.map_err(|_| UploadError::Json)?; + + Ok(images) + } + + pub(crate) async fn delete(&self, file: &str, token: &str) -> Result<(), UploadError> { + let res = self + .client + .delete(self.delete_url(file, token)) + .send() + .await + .map_err(|_| UploadError::Request)?; + + if !res.status().is_success() { + return Err(UploadError::Status); + } + + Ok(()) + } + + fn upload_url(&self) -> String { + let mut url = self.upstream.clone(); + url.set_path("/image"); + + url.to_string() + } + + fn thumbnail_url(&self, size: u16, file: &str, extension: Extension) -> String { + let mut url = self.upstream.clone(); + url.set_path(&format!("/image/process.{}", extension)); + url.set_query(Some(&format!("src={}&thumbnail={}", file, size))); + + url.to_string() + } + + fn image_url(&self, file: &str) -> String { + let mut url = self.upstream.clone(); + url.set_path(&format!("/image/original/{}", file,)); + + url.to_string() + } + + fn delete_url(&self, file: &str, token: &str) -> String { + let mut url = self.upstream.clone(); + url.set_path(&format!("/image/delete/{}/{}", token, file)); + + url.to_string() + } + + async fn proxy( + &self, + url: String, + req: &HttpRequest, + ) -> Result { + let client_request = self.client.request_from(url, req.head()); + let client_request = if let Some(addr) = req.head().peer_addr { + client_request.header("X-Forwarded-For", addr.to_string()) + } else { + client_request + }; + + let res = client_request.no_decompress().send().await?; + + let mut client_res = HttpResponse::build(res.status()); + + for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") { + client_res.header(name.clone(), value.clone()); + } + + Ok(client_res.body(BodyStream::new(res))) + } +} + +#[derive(Clone, Debug, thiserror::Error)] +pub(crate) enum UploadError { + #[error("There was an error uploading the image")] + Request, + + #[error("There was an error parsing the image response")] + Json, + + #[error("Request returned bad HTTP status")] + Status, +} + +impl ResponseError for UploadError { + fn status_code(&self) -> StatusCode { + StatusCode::INTERNAL_SERVER_ERROR + } + + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .content_type(mime::TEXT_PLAIN.essence_str()) + .body(self.to_string()) + } +} + +#[derive(Clone, Debug, thiserror::Error)] +#[error("The requested size is invalid, {0}")] +struct SizeError(u16); + +impl ResponseError for SizeError { + fn status_code(&self) -> StatusCode { + StatusCode::BAD_REQUEST + } + + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .content_type(mime::TEXT_PLAIN.essence_str()) + .body(self.to_string()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6c43e72 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,582 @@ +use actix_web::{ + client::Client, + http::{ + header::{CacheControl, CacheDirective, ContentType, LastModified, LOCATION}, + StatusCode, + }, + web, HttpRequest, HttpResponse, ResponseError, Scope, +}; +use sled::Db; +use std::{io::Cursor, net::SocketAddr, time::SystemTime}; +use structopt::StructOpt; +use url::Url; +use uuid::Uuid; + +include!(concat!(env!("OUT_DIR"), "/templates.rs")); + +const HOURS: u32 = 60 * 60; +const DAYS: u32 = 24 * HOURS; + +mod connection; +mod middleware; +mod pict; +mod store; +mod ui; + +use self::{connection::Connection, middleware::ValidToken, store::Store}; + +#[derive(Clone, Debug, StructOpt)] +pub struct Config { + #[structopt( + short, + long, + env = "PICTRS_AGGREGATOR_ADDR", + default_value = "0.0.0.0:8082", + help = "The address and port the server binds to" + )] + addr: SocketAddr, + + #[structopt( + short, + long, + env = "PICTRS_AGGREGATOR_UPSTREAM", + default_value = "http://localhost:8080", + help = "The url of the upstream pict-rs server" + )] + upstream: Url, +} + +pub fn accept() -> &'static str { + "image/png,image/jpeg,image/webp,.jpg,.jpeg,.png,.webp" +} + +impl Config { + pub fn bind_address(&self) -> SocketAddr { + self.addr + } +} + +#[derive(Clone)] +pub struct State { + upstream: Url, + scope: String, + store: Store, + db: Db, +} + +impl State { + fn scoped(&self, s: &str) -> String { + if self.scope == "" && s == "" { + "/".to_string() + } else if s == "" { + self.scope.clone() + } else if self.scope == "" { + format!("/{}", s) + } else { + format!("{}/{}", self.scope, s) + } + } + + fn create_aggregation_path(&self) -> String { + self.scoped("") + } + + fn edit_aggregation_path(&self, id: Uuid, token: &ValidToken) -> String { + self.scoped(&format!("{}?token={}", id, token.token)) + } + + fn update_aggregation_path(&self, id: Uuid, token: &ValidToken) -> String { + self.scoped(&format!("{}?token={}", id, token.token)) + } + + fn delete_aggregation_path(&self, id: Uuid, token: &ValidToken) -> String { + self.scoped(&format!("{}/delete?token={}", id, token.token)) + } + + fn public_aggregation_path(&self, id: Uuid) -> String { + self.scoped(&format!("{}", id)) + } + + fn create_entry_path(&self, aggregation_id: Uuid, token: &ValidToken) -> String { + self.scoped(&format!("{}/entry?token={}", aggregation_id, token.token)) + } + + fn update_entry_path(&self, aggregation_id: Uuid, id: Uuid, token: &ValidToken) -> String { + self.scoped(&format!( + "{}/entry/{}?token={}", + aggregation_id, id, token.token + )) + } + + fn delete_entry_path(&self, aggregation_id: Uuid, id: Uuid, token: &ValidToken) -> String { + self.scoped(&format!( + "{}/entry/{}/delete?token={}", + aggregation_id, id, token.token + )) + } + + fn statics_path(&self, file: &str) -> String { + self.scoped(&format!("static/{}", file)) + } + + fn thumbnail_path(&self, entry: &Entry, size: u16, extension: pict::Extension) -> String { + self.scoped(&format!( + "image/thumbnail.{}?src={}&size={}", + extension, entry.filename, size + )) + } + + fn srcset(&self, entry: &Entry, extension: pict::Extension) -> String { + connection::VALID_SIZES + .iter() + .map(|size| format!("{} {}w", self.thumbnail_path(entry, *size, extension), size,)) + .collect::>() + .join(", ") + } + + fn image_path(&self, entry: &Entry) -> String { + self.scoped(&format!("image/full/{}", entry.filename)) + } +} + +pub fn state(config: Config, scope: &str, db: Db) -> Result { + Ok(State { + upstream: config.upstream, + scope: scope.to_string(), + store: Store::new(&db)?, + db, + }) +} + +pub fn service(client: Client, state: State) -> Scope { + web::scope(&state.scoped("")) + .data(Connection::new(state.upstream.clone(), client)) + .data(state) + .service(web::resource("/static/{filename}").route(web::get().to(static_files))) + .service(web::resource("404").route(web::get().to(not_found))) + .service( + web::scope("image") + .service(web::resource("/thumbnail.{extension}").route(web::get().to(thumbnail))) + .service(web::resource("/full/{filename}").route(web::get().to(image))), + ) + .service( + web::resource("") + .route(web::get().to(index)) + .route(web::post().to(create_aggregation)), + ) + .service( + web::scope("/{aggregation}") + .wrap(middleware::Verify) + .service( + web::resource("") + .route(web::get().to(aggregation)) + .route(web::post().to(update_aggregation)), + ) + .service(web::resource("/delete").route(web::get().to(delete_aggregation))) + .service( + web::scope("/entry") + .service(web::resource("").route(web::post().to(upload))) + .service( + web::scope("/{entry}") + .service(web::resource("").route(web::post().to(update_entry))) + .service( + web::resource("/delete").route(web::get().to(delete_entry)), + ), + ), + ), + ) +} + +fn to_edit_page(id: Uuid, token: &ValidToken, state: &State) -> HttpResponse { + HttpResponse::SeeOther() + .header(LOCATION, state.edit_aggregation_path(id, token)) + .finish() +} + +fn to_404(state: &State) -> HttpResponse { + HttpResponse::MovedPermanently() + .header(LOCATION, state.create_aggregation_path()) + .finish() +} + +fn to_home(state: &State) -> HttpResponse { + HttpResponse::SeeOther() + .header(LOCATION, state.create_aggregation_path()) + .finish() +} + +async fn static_files(filename: web::Path, state: web::Data) -> HttpResponse { + let filename = filename.into_inner(); + + if let Some(data) = self::templates::statics::StaticFile::get(&filename) { + return HttpResponse::Ok() + .set(LastModified(SystemTime::now().into())) + .set(CacheControl(vec![ + CacheDirective::Public, + CacheDirective::MaxAge(365 * DAYS), + CacheDirective::Extension("immutable".to_owned(), None), + ])) + .set(ContentType(data.mime.clone())) + .body(data.content); + } + + to_404(&state) +} + +async fn not_found(state: web::Data) -> Result { + let mut cursor = Cursor::new(vec![]); + + self::templates::not_found(&mut cursor, &state)?; + + Ok(HttpResponse::NotFound() + .content_type(mime::TEXT_HTML.essence_str()) + .body(cursor.into_inner())) +} + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("{0}")] + Render(#[from] std::io::Error), + + #[error("{0}")] + Store(#[from] self::store::Error), + + #[error("{0}")] + Upload(#[from] self::connection::UploadError), + + #[error("{0}")] + UploadString(String), +} + +impl ResponseError for Error { + fn status_code(&self) -> StatusCode { + StatusCode::INTERNAL_SERVER_ERROR + } + + fn error_response(&self) -> HttpResponse { + match self { + Self::Store(self::store::Error::NotFound) => HttpResponse::MovedPermanently() + .header(LOCATION, "/404") + .finish(), + _ => HttpResponse::build(self.status_code()) + .content_type(mime::TEXT_PLAIN.essence_str()) + .body(format!("{}", self)), + } + } +} + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct Aggregation { + title: String, + description: String, +} + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct Entry { + title: String, + description: String, + filename: String, + delete_token: String, +} + +#[derive(Clone, serde::Deserialize)] +pub struct Token { + token: Uuid, +} + +impl Token { + fn hash(&self) -> Result { + use bcrypt::{hash, DEFAULT_COST}; + + Ok(TokenStorage { + token: hash(self.token.as_bytes(), DEFAULT_COST)?, + }) + } +} + +#[derive(serde::Deserialize, serde::Serialize)] +struct TokenStorage { + token: String, +} + +impl TokenStorage { + fn verify(&self, token: &Token) -> Result { + bcrypt::verify(&token.token.as_bytes(), &self.token) + } +} + +#[derive(serde::Deserialize)] +struct AggregationPath { + aggregation: Uuid, +} + +impl AggregationPath { + fn key(&self) -> String { + format!("{}", self.aggregation) + } + + fn entry_range(&self) -> std::ops::Range> { + let base = format!("{}/entry/", self.aggregation).as_bytes().to_vec(); + let mut start = base.clone(); + let mut end = base; + + start.push(0x0); + end.push(0xff); + + start..end + } + + fn token_key(&self) -> String { + format!("{}/token", self.aggregation) + } +} + +#[derive(serde::Deserialize)] +struct EntryPath { + aggregation: Uuid, + entry: Uuid, +} + +impl EntryPath { + fn key(&self) -> String { + format!("{}/entry/{}", self.aggregation, self.entry) + } +} + +async fn upload( + req: HttpRequest, + pl: web::Payload, + path: web::Path, + token: ValidToken, + conn: web::Data, + state: web::Data, +) -> Result { + let images = conn.upload(&req, pl).await?; + + if images.is_err() { + return Err(Error::UploadString(images.message().to_owned())); + } + + let image = images + .files() + .next() + .ok_or(Error::UploadString("Missing file".to_owned()))?; + + let entry = Entry { + title: String::new(), + description: String::new(), + filename: image.file().to_owned(), + delete_token: image.delete_token().to_owned(), + }; + + let entry_path = EntryPath { + aggregation: path.aggregation, + entry: Uuid::new_v4(), + }; + + store::CreateEntry { + entry_path: &entry_path, + entry: &entry, + } + .exec(&state.store) + .await?; + + Ok(to_edit_page(path.aggregation, &token, &state)) +} + +#[derive(serde::Deserialize)] +struct ImagePath { + filename: String, +} + +async fn image( + req: HttpRequest, + path: web::Path, + conn: web::Data, +) -> Result { + conn.image(&path.filename, &req).await +} + +#[derive(serde::Deserialize)] +struct ThumbnailPath { + extension: pict::Extension, +} + +#[derive(serde::Deserialize)] +struct ThumbnailQuery { + src: String, + size: u16, +} + +async fn thumbnail( + req: HttpRequest, + path: web::Path, + query: web::Query, + conn: web::Data, +) -> Result { + conn.thumbnail(query.size, &query.src, path.extension, &req) + .await +} + +async fn index(state: web::Data) -> Result { + let mut cursor = Cursor::new(vec![]); + + self::templates::index(&mut cursor, &state)?; + + Ok(HttpResponse::Ok() + .content_type(mime::TEXT_HTML.essence_str()) + .body(cursor.into_inner())) +} + +async fn aggregation( + path: web::Path, + token: Option, + state: web::Data, +) -> Result { + match token { + Some(token) => edit_aggregation(path, token, state).await, + None => view_aggregation(path, state).await, + } +} + +async fn view_aggregation( + path: web::Path, + state: web::Data, +) -> Result { + let aggregation = state.store.aggregation(&path).await?; + let entries = state.store.entries(path.entry_range()).await?; + + let mut cursor = Cursor::new(vec![]); + + self::templates::view_aggregation(&mut cursor, &aggregation, &entries, &state)?; + + Ok(HttpResponse::Ok() + .content_type(mime::TEXT_HTML.essence_str()) + .body(cursor.into_inner())) +} + +async fn edit_aggregation( + path: web::Path, + token: ValidToken, + state: web::Data, +) -> Result { + let aggregation = state.store.aggregation(&path).await?; + let entries = state.store.entries(path.entry_range()).await?; + + let mut cursor = Cursor::new(vec![]); + + self::templates::edit_aggregation( + &mut cursor, + &aggregation, + path.aggregation, + &entries, + &token, + &state, + )?; + + Ok(HttpResponse::Ok() + .content_type(mime::TEXT_HTML.essence_str()) + .body(cursor.into_inner())) +} + +async fn create_aggregation( + aggregation: web::Form, + state: web::Data, +) -> Result { + let aggregation_id = Uuid::new_v4(); + let aggregation_path = AggregationPath { + aggregation: aggregation_id, + }; + let token = Token { + token: Uuid::new_v4(), + }; + + store::CreateAggregation { + aggregation_path: &aggregation_path, + aggregation: &aggregation, + token: &token, + } + .exec(&state.store) + .await?; + + Ok(to_edit_page( + aggregation_path.aggregation, + &ValidToken { token: token.token }, + &state, + )) +} + +async fn update_aggregation( + path: web::Path, + form: web::Form, + token: ValidToken, + state: web::Data, +) -> Result { + store::UpdateAggregation { + aggregation_path: &path, + aggregation: &form, + } + .exec(&state.store) + .await?; + + Ok(to_edit_page(path.aggregation, &token, &state)) +} + +async fn update_entry( + entry_path: web::Path, + entry: web::Form, + token: ValidToken, + state: web::Data, +) -> Result { + store::UpdateEntry { + entry_path: &entry_path, + entry: &entry, + } + .exec(&state.store) + .await?; + + Ok(to_edit_page(entry_path.aggregation, &token, &state)) +} + +async fn delete_entry( + entry_path: web::Path, + token: ValidToken, + conn: web::Data, + state: web::Data, +) -> Result { + let entry = state.store.entry(&entry_path).await?; + + conn.delete(&entry.filename, &entry.delete_token).await?; + + store::DeleteEntry { + entry_path: &entry_path, + } + .exec(&state.store) + .await?; + + Ok(to_edit_page(entry_path.aggregation, &token, &state)) +} + +async fn delete_aggregation( + path: web::Path, + _token: ValidToken, + conn: web::Data, + state: web::Data, +) -> Result { + let entries = state.store.entries(path.entry_range()).await?; + + let future_vec = entries + .iter() + .map(|(_, entry)| conn.delete(&entry.filename, &entry.delete_token)) + .collect::>(); + + futures::future::try_join_all(future_vec).await?; + + store::DeleteAggregation { + aggregation_path: &path, + } + .exec(&state.store) + .await?; + + Ok(to_home(&state)) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..505df96 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,36 @@ +use actix_web::{ + client::Client, + middleware::{Compress, Logger}, + App, HttpServer, +}; +use structopt::StructOpt; + +#[actix_web::main] +async fn main() -> Result<(), anyhow::Error> { + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "info,pict_rs_aggregator=info,actix_web=info"); + } + + env_logger::init(); + + let db = sled::open("sled/db-0-34")?; + let config = pict_rs_aggregator::Config::from_args(); + let bind_address = config.bind_address(); + let state = pict_rs_aggregator::state(config, "", db)?; + + HttpServer::new(move || { + let client = Client::builder() + .header("User-Agent", "pict_rs_aggregator-v0.1.0") + .finish(); + + App::new() + .wrap(Logger::default()) + .wrap(Compress::default()) + .service(pict_rs_aggregator::service(client, state.clone())) + }) + .bind(bind_address)? + .run() + .await?; + + Ok(()) +} diff --git a/src/middleware.rs b/src/middleware.rs new file mode 100644 index 0000000..d8b6c46 --- /dev/null +++ b/src/middleware.rs @@ -0,0 +1,154 @@ +use actix_web::{ + dev::{Payload, Service, ServiceRequest, Transform}, + http::StatusCode, + web, FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError, +}; +use futures::{ + channel::oneshot, + future::{ok, LocalBoxFuture, Ready}, +}; +use std::task::{Context, Poll}; +use uuid::Uuid; + +pub(crate) struct Verify; +pub(crate) struct VerifyMiddleware(S); + +pub struct ValidToken { + pub(crate) token: Uuid, +} + +impl FromRequest for ValidToken { + type Error = TokenError; + type Future = LocalBoxFuture<'static, Result>; + type Config = (); + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let res = req + .extensions_mut() + .remove::>() + .ok_or(TokenError); + + Box::pin(async move { res?.await.map_err(|_| TokenError) }) + } +} + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Invalid token")] +pub struct TokenError; + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Invalid token")] +struct VerifyError; + +impl ResponseError for TokenError { + fn status_code(&self) -> StatusCode { + StatusCode::UNAUTHORIZED + } + + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .content_type(mime::TEXT_PLAIN.essence_str()) + .body(self.to_string()) + } +} + +impl ResponseError for VerifyError { + fn status_code(&self) -> StatusCode { + StatusCode::UNAUTHORIZED + } + + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()) + .content_type(mime::TEXT_PLAIN.essence_str()) + .body(self.to_string()) + } +} + +impl Transform for Verify +where + S: Service, + S::Future: 'static, +{ + type Request = S::Request; + type Response = S::Response; + type Error = S::Error; + type InitError = (); + type Transform = VerifyMiddleware; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ok(VerifyMiddleware(service)) + } +} + +impl Service for VerifyMiddleware +where + S: Service, + S::Future: 'static, +{ + type Request = S::Request; + type Response = S::Response; + type Error = S::Error; + type Future = LocalBoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx) + } + + fn call(&mut self, req: S::Request) -> Self::Future { + let (req, pl) = req.into_parts(); + + let state_fut = web::Data::::extract(&req); + let token_fut = Option::>::extract(&req); + let path_fut = web::Path::::extract(&req); + + let req = ServiceRequest::from_parts(req, pl) + .map_err(|_| VerifyError) + .unwrap(); + + let (tx, rx) = oneshot::channel(); + + req.extensions_mut().insert(rx); + + let service_fut = self.0.call(req); + + Box::pin(async move { + let state = state_fut.await?; + let path = path_fut.await?; + let token = token_fut.await?; + + if let Some(token) = token { + let token = token.into_inner(); + + if verify(&path, token.clone(), &state).await.is_ok() { + tx.send(ValidToken { token: token.token }) + .map_err(|_| VerifyError)?; + } else { + drop(tx); + } + } else { + drop(tx); + } + + service_fut.await + }) + } +} + +async fn verify( + path: &crate::AggregationPath, + token: crate::Token, + state: &crate::State, +) -> Result<(), VerifyError> { + let token_storage = state.store.token(path).await.map_err(|_| VerifyError)?; + + let verified = web::block(move || token_storage.verify(&token)) + .await + .map_err(|_| VerifyError)?; + + if !verified { + return Err(VerifyError); + } + + Ok(()) +} diff --git a/src/pict.rs b/src/pict.rs new file mode 100644 index 0000000..b93c404 --- /dev/null +++ b/src/pict.rs @@ -0,0 +1,53 @@ +#[derive(Clone, Copy, serde::Deserialize)] +pub(crate) enum Extension { + #[serde(rename = "jpg")] + Jpg, + + #[serde(rename = "webp")] + Webp, +} + +impl std::fmt::Display for Extension { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Jpg => write!(f, "jpg"), + Self::Webp => write!(f, "webp"), + } + } +} + +#[derive(serde::Deserialize)] +pub(crate) struct Image { + file: String, + delete_token: String, +} + +impl Image { + pub(crate) fn file(&self) -> &str { + &self.file + } + + pub(crate) fn delete_token(&self) -> &str { + &self.delete_token + } +} + +#[derive(serde::Deserialize)] +pub(crate) struct Images { + msg: String, + files: Option>, +} + +impl Images { + pub(crate) fn is_err(&self) -> bool { + self.files.is_none() + } + + pub(crate) fn message(&self) -> &str { + &self.msg + } + + pub(crate) fn files(&self) -> impl Iterator { + self.files.iter().flat_map(|v| v.iter()) + } +} diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 0000000..36ac6ef --- /dev/null +++ b/src/store.rs @@ -0,0 +1,285 @@ +use crate::{Aggregation, AggregationPath, Entry, EntryPath, Token}; +use actix_web::web; +use sled::{Db, Tree}; +use std::ops::Range; +use uuid::Uuid; + +#[derive(Clone)] +pub(crate) struct Store { + tree: Tree, +} + +pub(crate) struct CreateAggregation<'a> { + pub(crate) aggregation_path: &'a AggregationPath, + pub(crate) aggregation: &'a Aggregation, + pub(crate) token: &'a Token, +} + +pub(crate) struct UpdateAggregation<'a> { + pub(crate) aggregation_path: &'a AggregationPath, + pub(crate) aggregation: &'a Aggregation, +} + +pub(crate) struct DeleteAggregation<'a> { + pub(crate) aggregation_path: &'a AggregationPath, +} + +pub(crate) struct CreateEntry<'a> { + pub(crate) entry_path: &'a EntryPath, + pub(crate) entry: &'a Entry, +} + +pub(crate) struct UpdateEntry<'a> { + pub(crate) entry_path: &'a EntryPath, + pub(crate) entry: &'a Entry, +} + +pub(crate) struct DeleteEntry<'a> { + pub(crate) entry_path: &'a EntryPath, +} + +impl<'a> CreateAggregation<'a> { + pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> { + store.create_aggregation(self).await + } +} + +impl<'a> UpdateAggregation<'a> { + pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> { + store.update_aggregation(self).await + } +} + +impl<'a> DeleteAggregation<'a> { + pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> { + store.delete_aggregation(self).await + } +} + +impl<'a> CreateEntry<'a> { + pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> { + store.create_entry(self).await + } +} + +impl<'a> UpdateEntry<'a> { + pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> { + store.update_entry(self).await + } +} + +impl<'a> DeleteEntry<'a> { + pub(crate) async fn exec(self, store: &Store) -> Result<(), Error> { + store.delete_entry(self).await + } +} + +impl Store { + pub(crate) fn new(db: &Db) -> Result { + Ok(Store { + tree: db.open_tree("aggregations")?, + }) + } + + async fn create_aggregation(&self, config: CreateAggregation<'_>) -> Result<(), Error> { + let aggregation_key = config.aggregation_path.key(); + let aggregation_value = serde_json::to_string(&config.aggregation)?; + + let token_key = config.aggregation_path.token_key(); + let token2 = config.token.clone(); + let token_value = serde_json::to_string(&web::block(move || token2.hash()).await?)?; + + let tree = self.tree.clone(); + + web::block(move || { + tree.transaction(move |tree| { + tree.insert(aggregation_key.as_bytes(), aggregation_value.as_bytes())?; + tree.insert(token_key.as_bytes(), token_value.as_bytes())?; + Ok(()) + }) + }) + .await?; + + Ok(()) + } + + async fn update_aggregation(&self, config: UpdateAggregation<'_>) -> Result<(), Error> { + let aggregation_key = config.aggregation_path.key(); + let aggregation_value = serde_json::to_string(&config.aggregation)?; + + let tree = self.tree.clone(); + + web::block(move || tree.insert(aggregation_key.as_bytes(), aggregation_value.as_bytes())) + .await?; + + Ok(()) + } + + async fn delete_aggregation(&self, config: DeleteAggregation<'_>) -> Result<(), Error> { + let entry_range = config.aggregation_path.entry_range(); + let token_key = config.aggregation_path.token_key(); + let aggregation_key = config.aggregation_path.key(); + + let tree = self.tree.clone(); + + web::block(move || { + let entries = tree + .range(entry_range) + .keys() + .collect::, _>>()?; + + tree.transaction(move |tree| { + for key in &entries { + tree.remove(key)?; + } + tree.remove(token_key.as_bytes())?; + tree.remove(aggregation_key.as_bytes())?; + Ok(()) + })?; + + Ok(()) as Result<(), Error> + }) + .await?; + + Ok(()) + } + + async fn create_entry(&self, config: CreateEntry<'_>) -> Result<(), Error> { + let entry_key = config.entry_path.key(); + let entry_value = serde_json::to_string(&config.entry)?; + + let tree = self.tree.clone(); + + web::block(move || tree.insert(entry_key.as_bytes(), entry_value.as_bytes())).await?; + + Ok(()) + } + + async fn update_entry(&self, config: UpdateEntry<'_>) -> Result<(), Error> { + let entry_key = config.entry_path.key(); + let entry_value = serde_json::to_string(&config.entry)?; + + let tree = self.tree.clone(); + + web::block(move || tree.insert(entry_key.as_bytes(), entry_value.as_bytes())).await?; + + Ok(()) + } + + async fn delete_entry(&self, config: DeleteEntry<'_>) -> Result<(), Error> { + let entry_key = config.entry_path.key(); + let tree = self.tree.clone(); + + web::block(move || tree.remove(entry_key)).await?; + + Ok(()) + } + + pub(crate) async fn aggregation( + &self, + path: &AggregationPath, + ) -> Result { + let aggregation_key = path.key(); + let tree = self.tree.clone(); + + let opt = web::block(move || tree.get(aggregation_key.as_bytes())).await?; + + match opt { + Some(a) => { + let aggregation = serde_json::from_slice(&a)?; + Ok(aggregation) + } + None => Err(Error::NotFound), + } + } + + pub(crate) async fn entry(&self, path: &EntryPath) -> Result { + let entry_key = path.key(); + let tree = self.tree.clone(); + + let opt = web::block(move || tree.get(entry_key.as_bytes())).await?; + + match opt { + Some(e) => { + let entry = serde_json::from_slice(&e)?; + Ok(entry) + } + None => Err(Error::NotFound), + } + } + + pub(crate) async fn entries(&self, range: Range>) -> Result, Error> { + let tree = self.tree.clone(); + + let v = web::block(move || { + tree.range(range) + .map(|res| match res { + Err(e) => Err(e.into()), + Ok((k, v)) => { + let string_key = String::from_utf8_lossy(&k); + let entry_uuid = string_key.split(|b| b == '/').rev().next().unwrap(); + let uuid: Uuid = entry_uuid.parse()?; + + let string_value = String::from_utf8_lossy(&v); + let entry: Entry = serde_json::from_str(&string_value)?; + Ok((uuid, entry)) + } + }) + .collect::, Error>>() + }) + .await?; + + Ok(v) + } + + pub(crate) async fn token(&self, path: &AggregationPath) -> Result { + let token_key = path.token_key(); + let tree = self.tree.clone(); + + let token_opt = web::block(move || tree.get(token_key.as_bytes())).await?; + + let token = match token_opt { + Some(token_ivec) => serde_json::from_slice(&token_ivec)?, + None => return Err(Error::NotFound), + }; + + Ok(token) + } +} + +#[derive(Debug, thiserror::Error)] +pub(crate) enum Error { + #[error("{0}")] + Json(#[from] serde_json::Error), + + #[error("{0}")] + Uuid(#[from] uuid::Error), + + #[error("{0}")] + Sled(#[from] sled::Error), + + #[error("{0}")] + Transaction(#[from] sled::transaction::TransactionError), + + #[error("{0}")] + Bcrypt(#[from] bcrypt::BcryptError), + + #[error("Panic in blocking operation")] + Blocking, + + #[error("Item is not found")] + NotFound, +} + +impl From> for Error +where + E: std::fmt::Debug, + Error: From, +{ + fn from(err: actix_web::error::BlockingError) -> Self { + match err { + actix_web::error::BlockingError::Error(e) => e.into(), + _ => Error::Blocking, + } + } +} diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..ab125c5 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,5 @@ +pub enum ButtonKind { + Submit, + Outline, + Plain, +} diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..5b1375a Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/file_upload.js b/static/file_upload.js new file mode 100644 index 0000000..890d1d8 --- /dev/null +++ b/static/file_upload.js @@ -0,0 +1,22 @@ +(function(fn) { + if (document.readyState === "complete" || document.readyState === "interactive") { + setTimeout(fn, 1); + } else { + document.addEventListener("DOMContentLoaded", fn); + } +})(function() { + var container = document.getElementById("file-input-container"); + var text = container.getElementsByTagName("span")[0]; + var input = container.getElementsByTagName("input")[0]; + if (!text || !input) { + console.err("Error fetching file upload"); + return; + } + + input.addEventListener("change", function(event) { + if (event.target.files && event.target.files.length === 1) { + console.log(event.target.files[0]); + text.textContent = "Selected: " + event.target.files[0].name; + } + }); +}); diff --git a/templates/button.rs.html b/templates/button.rs.html new file mode 100644 index 0000000..1998cff --- /dev/null +++ b/templates/button.rs.html @@ -0,0 +1,24 @@ +@use crate::ui::ButtonKind; + +@(text: &str, kind: ButtonKind) + +@match kind { + ButtonKind::Submit => { +
+ @text + +
+ } + ButtonKind::Plain => { +
+ @text + +
+ } + ButtonKind::Outline => { +
+ @text + +
+ } +} diff --git a/templates/button_link.rs.html b/templates/button_link.rs.html new file mode 100644 index 0000000..61e7cdd --- /dev/null +++ b/templates/button_link.rs.html @@ -0,0 +1,25 @@ +@use crate::ui::ButtonKind; + +@(text: &str, href: &str, kind: ButtonKind) + +@match kind { + ButtonKind::Submit => { +
+ @text + @text +
+ } + ButtonKind::Plain => { +
+ @text + @text +
+ } + ButtonKind::Outline => { +
+ @text + @text +
+ } +} + diff --git a/templates/edit_aggregation.rs.html b/templates/edit_aggregation.rs.html new file mode 100644 index 0000000..76df146 --- /dev/null +++ b/templates/edit_aggregation.rs.html @@ -0,0 +1,96 @@ +@use crate::{ui::ButtonKind, Aggregation, Entry, State, ValidToken}; +@use super::{button, button_link, image_preview, file_input, layout, text_area, text_input, statics::file_upload_js}; +@use uuid::Uuid; + +@(aggregation: &Aggregation, aggregation_id: Uuid, entries: &[(Uuid, Entry)], token: &ValidToken, state: &State) + +@:layout(state, "Edit Aggregation", None, { + +}, { +
+
+

Share Aggregation

+
+ +
+
+
+

Edit Aggregation

+
+ +
+
+ @:text_input("title", Some("Title"), Some(&aggregation.title)) + @:text_area("description", Some("Description"), Some(&aggregation.description)) +
+ @:button("Update Aggregation", ButtonKind::Submit) + @:button_link("Delete Aggregation", &state.delete_aggregation_path(aggregation_id, token), ButtonKind::Outline) +
+
+
+
    + @for (id, entry) in entries { +
  • +
    +
    +
    + @:image_preview(entry, state) +
    +
    @entry.title
    +
    @entry.description
    +
    +
    +
    +
    + @:text_input("title", Some("Title"), Some(&entry.title)) + @:text_area("description", Some("Description"), Some(&entry.description)) + + +
    + @:button("Update Image", ButtonKind::Submit) + @:button_link("Delete Image", &state.delete_entry_path(aggregation_id, *id, token), ButtonKind::Outline) +
    +
    +
    +
    +
    +
  • + } +
+
+
+
+
+
+

Add Image

+
+
+
+ @:file_input("images[]", Some("Select Image"), Some(crate::accept()), false) +
+
+ @:button("Upload", ButtonKind::Submit) +
+
+
+
+
+}) diff --git a/templates/file_input.rs.html b/templates/file_input.rs.html new file mode 100644 index 0000000..355573b --- /dev/null +++ b/templates/file_input.rs.html @@ -0,0 +1,34 @@ +@(name: &str, title: Option<&str>, accept: Option<&str>, multiple: bool) + +
+ @match (title, accept, multiple) { + (Some(title), Some(accept), true) => { + @title + + } + (Some(title), Some(accept), false) => { + @title + + } + (Some(title), None, true) => { + @title + + } + (Some(title), None, false) => { + @title + + } + (None, Some(accept), true) => { + + } + (None, Some(accept), false) => { + + } + (None, None, true) => { + + } + (None, None, false) => { + + } + } +
diff --git a/templates/image_preview.rs.html b/templates/image_preview.rs.html new file mode 100644 index 0000000..a39fae8 --- /dev/null +++ b/templates/image_preview.rs.html @@ -0,0 +1,21 @@ +@use crate::{pict::Extension, Entry, State}; + +@(entry: &Entry, state: &State) + +
+ + + + @entry.description + +
diff --git a/templates/index.rs.html b/templates/index.rs.html new file mode 100644 index 0000000..d5a4b40 --- /dev/null +++ b/templates/index.rs.html @@ -0,0 +1,23 @@ +@use crate::{ui::ButtonKind, State}; +@use super::{layout, text_area, text_input, button}; + +@(state: &State) + +@:layout(state, "Aggregation", None, {}, { +
+
+
+
+

Create Aggregation

+
+
+ @:text_input("title", Some("Title"), None) + @:text_area("description", Some("Description"), None) +
+
+ @:button("Create Aggregation", ButtonKind::Submit) +
+
+
+
+}) diff --git a/templates/layout.rs.html b/templates/layout.rs.html new file mode 100644 index 0000000..f10bd93 --- /dev/null +++ b/templates/layout.rs.html @@ -0,0 +1,28 @@ +@use crate::State; +@use super::statics::{layout_css, favicon_ico}; + +@(state: &State, title: &str, description: Option<&str>, head: Content, body: Content) + + + + + + + + @title + + + @if let Some(description) = description { + + } else { + + } + + + @:head() + + +

pict-rs aggregator

+ @:body() + + diff --git a/templates/not_found.rs.html b/templates/not_found.rs.html new file mode 100644 index 0000000..5221a6e --- /dev/null +++ b/templates/not_found.rs.html @@ -0,0 +1,15 @@ +@use crate::State; +@use super::layout; + +@(state: &State) + +@:layout(state, "Not Found", None, {}, { +
+
+

Not Found

+
+ +
+}) diff --git a/templates/text_area.rs.html b/templates/text_area.rs.html new file mode 100644 index 0000000..5bcca72 --- /dev/null +++ b/templates/text_area.rs.html @@ -0,0 +1,21 @@ +@(name: &str, title: Option<&str>, value: Option<&str>) + +
+ @if let Some(title) = title { + + } else { + @if let Some(value) = value { + + } else { + + } + } +
+ diff --git a/templates/text_input.rs.html b/templates/text_input.rs.html new file mode 100644 index 0000000..525dff5 --- /dev/null +++ b/templates/text_input.rs.html @@ -0,0 +1,20 @@ +@(name: &str, title: Option<&str>, value: Option<&str>) + +
+ @if let Some(title) = title { + + } else { + @if let Some(value) = value { + + } else { + + } + } +
diff --git a/templates/view_aggregation.rs.html b/templates/view_aggregation.rs.html new file mode 100644 index 0000000..636ada2 --- /dev/null +++ b/templates/view_aggregation.rs.html @@ -0,0 +1,32 @@ +@use crate::{Aggregation, Entry, State}; +@use super::{layout, image_preview}; +@use uuid::Uuid; + +@(aggregation: &Aggregation, entries: &[(Uuid, Entry)], state: &State) + +@:layout(state, "Aggregation", None, {}, { +
+
+
+

@aggregation.title

+
+
+

@aggregation.description

+
+
+
    + @for (_, entry) in entries { +
  • +
    + @:image_preview(entry, state) +
    +
    @entry.title
    +
    @entry.description
    +
    +
    +
  • + } +
+
+}) +