From 2d6db6d0592868ca6d104d3b482176a4e5798648 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Wed, 1 Sep 2021 19:50:35 -0400 Subject: [PATCH 01/21] Update dependencies --- Cargo.lock | 277 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 6 +- 2 files changed, 140 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7878924..fbdbc5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" +checksum = "5cb8958da437716f3f31b0e76f8daf36554128517d7df37ceba7df00f09622ee" dependencies = [ "actix-codec", "actix-connect", @@ -118,7 +118,7 @@ dependencies = [ "log", "mime", "percent-encoding", - "pin-project 1.0.7", + "pin-project 1.0.8", "rand", "regex", "serde", @@ -304,7 +304,7 @@ dependencies = [ "fxhash", "log", "mime", - "pin-project 1.0.7", + "pin-project 1.0.8", "regex", "serde", "serde_json", @@ -336,7 +336,7 @@ dependencies = [ "actix-service", "actix-web", "futures", - "pin-project 1.0.7", + "pin-project 1.0.8", "prometheus", ] @@ -380,9 +380,9 @@ checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] name = "async-trait" -version = "0.1.50" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" +checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" dependencies = [ "proc-macro2", "quote", @@ -461,9 +461,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" @@ -516,15 +516,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" [[package]] name = "bytemuck" -version = "1.7.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966d2ab714d0f785dbac0a0396251a35280aeb42413281617d0209ab4898435" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" [[package]] name = "byteorder" @@ -540,9 +540,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "bytestring" @@ -550,14 +550,14 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" dependencies = [ - "bytes 1.0.1", + "bytes 1.1.0", ] [[package]] name = "cc" -version = "1.0.68" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cfg-if" @@ -630,9 +630,9 @@ checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" [[package]] name = "cpufeatures" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -713,9 +713,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array", "subtle", @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "diesel" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bba51ca66f57261fd17cadf8b73e4775cc307d0521d855de3f5de91a8f074e0e" +checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" dependencies = [ "byteorder", "diesel_derives", @@ -829,9 +829,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ "cfg-if 1.0.0", "libc", @@ -841,9 +841,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -904,9 +904,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" dependencies = [ "futures-channel", "futures-core", @@ -919,9 +919,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" dependencies = [ "futures-core", "futures-sink", @@ -929,15 +929,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" [[package]] name = "futures-executor" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" dependencies = [ "futures-core", "futures-task", @@ -946,15 +946,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" [[package]] name = "futures-macro" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" dependencies = [ "autocfg", "proc-macro-hack", @@ -965,21 +965,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" [[package]] name = "futures-task" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" [[package]] name = "futures-util" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" dependencies = [ "autocfg", "futures-channel", @@ -1104,20 +1104,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" dependencies = [ - "bytes 1.0.1", + "bytes 1.1.0", "fnv", "itoa", ] [[package]] name = "httparse" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" [[package]] name = "humantime" @@ -1216,9 +1216,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" dependencies = [ "cfg-if 1.0.0", ] @@ -1246,9 +1246,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "jpeg-decoder" @@ -1261,9 +1261,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.51" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] @@ -1312,9 +1312,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.98" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "libsqlite3-sys" @@ -1334,9 +1334,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] @@ -1367,9 +1367,9 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "maybe-uninit" @@ -1379,9 +1379,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memoffset" @@ -1596,9 +1596,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -1607,9 +1607,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", "instant", @@ -1621,18 +1621,18 @@ dependencies = [ [[package]] name = "path-absolutize" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0feea2f0a6009a0fefe6ee3aae1871e4a57d7f2aae4681ac4a34a201071c9b9" +checksum = "b288298a7a3a7b42539e3181ba590d32f2d91237b0691ed5f103875c754b3bf5" dependencies = [ "path-dedot", ] [[package]] name = "path-dedot" -version = "3.0.12" +version = "3.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a14ca47b49e6abd75cf68db85e1e161d9f2b675716894a18af0e9add0266b26" +checksum = "4bfa72956f6be8524f7f7e2b07972dda393cb0008a6df4451f658b7e1bd1af80" dependencies = [ "once_cell", ] @@ -1674,11 +1674,11 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" +checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" dependencies = [ - "pin-project-internal 1.0.7", + "pin-project-internal 1.0.8", ] [[package]] @@ -1694,9 +1694,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" +checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" dependencies = [ "proc-macro2", "quote", @@ -1723,9 +1723,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" [[package]] name = "png" @@ -1759,9 +1759,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ "unicode-xid", ] @@ -1783,9 +1783,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.24.1" +version = "2.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db50e77ae196458ccd3dc58a31ea1a90b0698ab1b7928d89f644c25d72070267" +checksum = "23129d50f2c9355ced935fce8a08bd706ee2e7ce2b3b33bf61dace0e379ac63a" [[package]] name = "quick-error" @@ -1795,9 +1795,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -1870,9 +1870,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] @@ -1993,18 +1993,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.126" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2", "quote", @@ -2013,9 +2013,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", @@ -2036,9 +2036,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer", "cfg-if 1.0.0", @@ -2055,9 +2055,9 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" [[package]] name = "sha2" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer", "cfg-if 1.0.0", @@ -2088,15 +2088,15 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" +checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "socket2" @@ -2175,15 +2175,15 @@ checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", @@ -2201,18 +2201,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -2278,9 +2278,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" dependencies = [ "tinyvec_macros", ] @@ -2329,9 +2329,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.26" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", "log", @@ -2341,9 +2341,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" dependencies = [ "lazy_static", ] @@ -2354,7 +2354,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.7", + "pin-project 1.0.8", "tracing", ] @@ -2409,9 +2409,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "ucd-trie" @@ -2436,12 +2436,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-normalization" @@ -2494,9 +2491,9 @@ dependencies = [ [[package]] name = "v_escape_derive" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668" +checksum = "f29769400af8b264944b851c961a4a6930e76604f59b1fcd51246bab6a296c8c" dependencies = [ "nom", "proc-macro2", @@ -2557,9 +2554,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.74" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2567,9 +2564,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.74" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", @@ -2582,9 +2579,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.74" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2592,9 +2589,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.74" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ "proc-macro2", "quote", @@ -2605,15 +2602,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.74" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "web-sys" -version = "0.3.51" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 7ff382e..9d9a997 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3.5" jsonwebtoken = "7.2.0" serde = "1" serde_json = "1" -diesel = { version = "1.4.5", features = ["sqlite"] } +diesel = { version = "1.4.8", features = ["sqlite"] } hmac = "0.11" sha2 = "0.9" chrono = "0.4" @@ -27,9 +27,9 @@ dotenv = "0.15" bcrypt = "0.9" image = { version = "0.23", default-features = false, features = ["jpeg", "png", "jpeg_rayon"] } walkdir = "2" -rayon = "1.3" +rayon = "1.5" notify = "4.0" -path-absolutize = "3.0.6" +path-absolutize = "3.0" log="0.4" env_logger="0.8" actix-web-prom = "0.5.1" From 8939ffbaf5262eeb2767a769216da6c228c20d12 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Fri, 3 Sep 2021 19:34:38 -0400 Subject: [PATCH 02/21] Create Tag tables and Add Tag endpoint --- .../2021-09-02-000740_create_tags/down.sql | 3 + .../2021-09-02-000740_create_tags/up.sql | 11 +++ src/data/mod.rs | 6 ++ src/database/mod.rs | 4 +- src/database/models.rs | 28 ++++++- src/database/schema.rs | 24 +++++- src/main.rs | 84 ++++++++++++++++++- 7 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 migrations/2021-09-02-000740_create_tags/down.sql create mode 100644 migrations/2021-09-02-000740_create_tags/up.sql diff --git a/migrations/2021-09-02-000740_create_tags/down.sql b/migrations/2021-09-02-000740_create_tags/down.sql new file mode 100644 index 0000000..e0745fc --- /dev/null +++ b/migrations/2021-09-02-000740_create_tags/down.sql @@ -0,0 +1,3 @@ +DROP TABLE tags; +DROP TABLE tagged_photo; + diff --git a/migrations/2021-09-02-000740_create_tags/up.sql b/migrations/2021-09-02-000740_create_tags/up.sql new file mode 100644 index 0000000..4cbb6ef --- /dev/null +++ b/migrations/2021-09-02-000740_create_tags/up.sql @@ -0,0 +1,11 @@ +CREATE TABLE tags ( + id INTEGER PRIMARY KEY NOT NULL, + name TEXT NOT NULL +); + +CREATE TABLE tagged_photo ( + id INTEGER PRIMARY KEY NOT NULL, + photo_name TEXT NOT NULL, + tag_id INTEGER NOT NULL, + CONSTRAINT tagid FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE ON UPDATE CASCADE +); diff --git a/src/data/mod.rs b/src/data/mod.rs index 1dabfa8..c5bfcf1 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -133,6 +133,12 @@ impl From for MetadataResponse { } } +#[derive(Debug, Deserialize)] +pub struct AddTagRequest { + pub file_name: String, + pub tag_name: String, +} + #[cfg(test)] mod tests { use super::Claims; diff --git a/src/database/mod.rs b/src/database/mod.rs index aac67ce..328e310 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -9,7 +9,7 @@ use std::{ use crate::database::models::{Favorite, InsertFavorite, InsertUser, User}; pub mod models; -mod schema; +pub mod schema; pub trait UserDao { fn create_user(&self, user: &str, password: &str) -> Option; @@ -81,7 +81,7 @@ impl UserDao for SqliteUserDao { } } -fn connect() -> SqliteConnection { +pub fn connect() -> SqliteConnection { let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set"); SqliteConnection::establish(&db_url).expect("Error connecting to DB") } diff --git a/src/database/models.rs b/src/database/models.rs index dca23ba..fecd900 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -1,4 +1,4 @@ -use crate::database::schema::{favorites, users}; +use crate::database::schema::{favorites, tagged_photo, tags, users}; use serde::Serialize; #[derive(Insertable)] @@ -29,3 +29,29 @@ pub struct Favorite { pub userid: i32, pub path: String, } + +#[derive(Serialize, Queryable, Clone, Debug)] +pub struct Tag { + pub id: i32, + pub name: String, +} + +#[derive(Insertable, Clone, Debug)] +#[table_name = "tags"] +pub struct InsertTag { + pub name: String, +} + +#[derive(Insertable, Clone, Debug)] +#[table_name = "tagged_photo"] +pub struct InsertTaggedPhoto { + pub tag_id: i32, + pub photo_name: String, +} + +#[derive(Queryable, Clone, Debug)] +pub struct TaggedPhoto { + pub id: i32, + pub photo_name: String, + pub tag_id: i32, +} diff --git a/src/database/schema.rs b/src/database/schema.rs index e254eee..3bb2549 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -6,6 +6,21 @@ table! { } } +table! { + tagged_photo (id) { + id -> Integer, + photo_name -> Text, + tag_id -> Integer, + } +} + +table! { + tags (id) { + id -> Integer, + name -> Text, + } +} + table! { users (id) { id -> Integer, @@ -14,4 +29,11 @@ table! { } } -allow_tables_to_appear_in_same_query!(favorites, users,); +joinable!(tagged_photo -> tags (tag_id)); + +allow_tables_to_appear_in_same_query!( + favorites, + tagged_photo, + tags, + users, +); diff --git a/src/main.rs b/src/main.rs index 545e0b5..6dcf71b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ use actix_web::{ web::{self, BufMut, BytesMut, HttpRequest, HttpResponse}, App, HttpServer, Responder, }; +use diesel::prelude::*; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use rayon::prelude::*; @@ -34,11 +35,12 @@ use crate::auth::login; use crate::data::*; use crate::database::*; use crate::files::{is_image_or_video, is_valid_path}; +use crate::models::{InsertTag, InsertTaggedPhoto, Tag, TaggedPhoto}; use crate::video::*; mod auth; mod data; -mod database; +pub mod database; mod files; mod video; @@ -292,6 +294,85 @@ async fn delete_favorite( } } +#[post("image/tags")] +async fn add_tag(_: Claims, body: web::Json) -> impl Responder { + let tag = body.tag_name.clone(); + + use database::schema::tags; + + let connection = &connect(); + let tag_id = tags::table + .filter(tags::name.eq(&tag)) + .get_result::(connection) + .optional() + .map_or(-1, |t| { + if let Some(t) = t { + t.id + } else { + match diesel::insert_into(tags::table) + .values(InsertTag { name: tag.clone() }) + .execute(connection) + .and_then(|_| { + no_arg_sql_function!( + last_insert_rowid, + diesel::sql_types::Integer, + "Represents the SQL last_insert_row() function" + ); + diesel::select(last_insert_rowid).get_results::(connection) + }) { + Err(e) => { + error!("Error inserting tag: '{}'. {:?}", tag, e); + -1 + } + Ok(id) => { + debug!("Inserted tag: '{}' with id: {:?}", tag, id); + *id.first().expect("We should have just inserted the row") + } + } + } + }); + + if tag_id == -1 { + HttpResponse::InternalServerError() + } else { + use database::schema::tagged_photo; + + let file_name = body.file_name.clone(); + + match tagged_photo::table + .filter(tagged_photo::photo_name.eq(&file_name)) + .filter(tagged_photo::tag_id.eq(tag_id)) + .get_result::(connection) + .optional() + { + Ok(Some(_)) => HttpResponse::NoContent(), + Ok(None) => diesel::insert_into(tagged_photo::table) + .values(InsertTaggedPhoto { + tag_id, + photo_name: file_name.clone(), + }) + .execute(connection) + .map(|_| { + debug!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); + + HttpResponse::Created() + }) + .unwrap_or_else(|e| { + error!( + "Error inserting tagged photo: '{}' -> '{}'. {:?}", + tag_id, body.file_name, e + ); + + HttpResponse::InternalServerError() + }), + Err(e) => { + error!("Error querying tagged photo: {:?}", e); + HttpResponse::InternalServerError() + } + } + } +} + fn create_thumbnails() { let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined"); let thumbnail_directory: &Path = Path::new(thumbs); @@ -474,6 +555,7 @@ fn main() -> std::io::Result<()> { .service(put_add_favorite) .service(delete_favorite) .service(get_file_metadata) + .service(add_tag) .app_data(app_data.clone()) .data::>(Box::new(user_dao)) .data::>(Box::new(favorites_dao)) From 9d925be84de6a84ad7c6798a288772c90ebc49b9 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sun, 5 Sep 2021 22:15:53 -0400 Subject: [PATCH 03/21] Improve add tag endpoint and add get tag endpoint Flattened out the add tag logic to make it more functional. --- src/main.rs | 102 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6dcf71b..92ce082 100644 --- a/src/main.rs +++ b/src/main.rs @@ -301,13 +301,13 @@ async fn add_tag(_: Claims, body: web::Json) -> impl Responder { use database::schema::tags; let connection = &connect(); - let tag_id = tags::table + match tags::table .filter(tags::name.eq(&tag)) .get_result::(connection) .optional() - .map_or(-1, |t| { + .and_then(|t| { if let Some(t) = t { - t.id + Ok(t.id) } else { match diesel::insert_into(tags::table) .values(InsertTag { name: tag.clone() }) @@ -318,57 +318,80 @@ async fn add_tag(_: Claims, body: web::Json) -> impl Responder { diesel::sql_types::Integer, "Represents the SQL last_insert_row() function" ); - diesel::select(last_insert_rowid).get_results::(connection) + diesel::select(last_insert_rowid).get_result::(connection) }) { Err(e) => { error!("Error inserting tag: '{}'. {:?}", tag, e); - -1 + Err(e) } Ok(id) => { debug!("Inserted tag: '{}' with id: {:?}", tag, id); - *id.first().expect("We should have just inserted the row") + Ok(id) } } } - }); + }) + .map(|tag_id| { + use database::schema::tagged_photo; - if tag_id == -1 { - HttpResponse::InternalServerError() - } else { - use database::schema::tagged_photo; + let file_name = body.file_name.clone(); - let file_name = body.file_name.clone(); + match tagged_photo::table + .filter(tagged_photo::photo_name.eq(&file_name)) + .filter(tagged_photo::tag_id.eq(tag_id)) + .get_result::(connection) + .optional() + { + Ok(Some(_)) => HttpResponse::NoContent(), + Ok(None) => diesel::insert_into(tagged_photo::table) + .values(InsertTaggedPhoto { + tag_id, + photo_name: file_name.clone(), + }) + .execute(connection) + .map(|_| { + debug!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); - match tagged_photo::table - .filter(tagged_photo::photo_name.eq(&file_name)) - .filter(tagged_photo::tag_id.eq(tag_id)) - .get_result::(connection) - .optional() - { - Ok(Some(_)) => HttpResponse::NoContent(), - Ok(None) => diesel::insert_into(tagged_photo::table) - .values(InsertTaggedPhoto { - tag_id, - photo_name: file_name.clone(), - }) - .execute(connection) - .map(|_| { - debug!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); - - HttpResponse::Created() - }) - .unwrap_or_else(|e| { - error!( - "Error inserting tagged photo: '{}' -> '{}'. {:?}", - tag_id, body.file_name, e - ); + HttpResponse::Created() + }) + .unwrap_or_else(|e| { + error!( + "Error inserting tagged photo: '{}' -> '{}'. {:?}", + tag_id, body.file_name, e + ); + HttpResponse::InternalServerError() + }), + Err(e) => { + error!("Error querying tagged photo: {:?}", e); HttpResponse::InternalServerError() - }), - Err(e) => { - error!("Error querying tagged photo: {:?}", e); - HttpResponse::InternalServerError() + } } + }) { + Ok(resp) => resp, + Err(e) => { + error!("{:?}", e); + HttpResponse::InternalServerError() + } + } +} + +#[get("image/tags")] +async fn get_tags(_: Claims, request: web::Query) -> impl Responder { + use schema::tagged_photo; + use schema::tags; + + match tags::table + .left_join(tagged_photo::table) + .filter(tagged_photo::photo_name.eq(&request.path)) + .select((tags::id, tags::name)) + .get_results::(&connect()) + { + Ok(tags) => HttpResponse::Ok().json(tags), + Err(e) => { + error!("Error getting tags for image: '{}'. {:?}", request.path, e); + + HttpResponse::InternalServerError().finish() } } } @@ -556,6 +579,7 @@ fn main() -> std::io::Result<()> { .service(delete_favorite) .service(get_file_metadata) .service(add_tag) + .service(get_tags) .app_data(app_data.clone()) .data::>(Box::new(user_dao)) .data::>(Box::new(favorites_dao)) From 2e5ac8861c087efaa80b3f5f3650b9ff04974874 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Mon, 11 Oct 2021 21:32:17 -0400 Subject: [PATCH 04/21] Add created timestamps for tags --- migrations/2021-09-02-000740_create_tags/up.sql | 4 +++- src/database/models.rs | 4 ++++ src/database/schema.rs | 2 ++ src/main.rs | 9 +++++++-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/migrations/2021-09-02-000740_create_tags/up.sql b/migrations/2021-09-02-000740_create_tags/up.sql index 4cbb6ef..9938d55 100644 --- a/migrations/2021-09-02-000740_create_tags/up.sql +++ b/migrations/2021-09-02-000740_create_tags/up.sql @@ -1,11 +1,13 @@ CREATE TABLE tags ( id INTEGER PRIMARY KEY NOT NULL, - name TEXT NOT NULL + name TEXT NOT NULL, + created_time BIGINT NOT NULL ); CREATE TABLE tagged_photo ( id INTEGER PRIMARY KEY NOT NULL, photo_name TEXT NOT NULL, tag_id INTEGER NOT NULL, + created_time BIGINT NOT NULL, CONSTRAINT tagid FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE ON UPDATE CASCADE ); diff --git a/src/database/models.rs b/src/database/models.rs index fecd900..13ccc4d 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -34,12 +34,14 @@ pub struct Favorite { pub struct Tag { pub id: i32, pub name: String, + pub created_time: i64, } #[derive(Insertable, Clone, Debug)] #[table_name = "tags"] pub struct InsertTag { pub name: String, + pub created_time: i64, } #[derive(Insertable, Clone, Debug)] @@ -47,6 +49,7 @@ pub struct InsertTag { pub struct InsertTaggedPhoto { pub tag_id: i32, pub photo_name: String, + pub created_time: i64, } #[derive(Queryable, Clone, Debug)] @@ -54,4 +57,5 @@ pub struct TaggedPhoto { pub id: i32, pub photo_name: String, pub tag_id: i32, + pub created_time: i64, } diff --git a/src/database/schema.rs b/src/database/schema.rs index 3bb2549..340c604 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -11,6 +11,7 @@ table! { id -> Integer, photo_name -> Text, tag_id -> Integer, + created_time -> BigInt, } } @@ -18,6 +19,7 @@ table! { tags (id) { id -> Integer, name -> Text, + created_time -> BigInt, } } diff --git a/src/main.rs b/src/main.rs index 92ce082..2a3ce64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate diesel; extern crate rayon; use actix_web_prom::PrometheusMetrics; +use chrono::Utc; use futures::stream::StreamExt; use lazy_static::lazy_static; use prometheus::{self, IntGauge}; @@ -310,7 +311,10 @@ async fn add_tag(_: Claims, body: web::Json) -> impl Responder { Ok(t.id) } else { match diesel::insert_into(tags::table) - .values(InsertTag { name: tag.clone() }) + .values(InsertTag { + name: tag.clone(), + created_time: Utc::now().timestamp(), + }) .execute(connection) .and_then(|_| { no_arg_sql_function!( @@ -347,6 +351,7 @@ async fn add_tag(_: Claims, body: web::Json) -> impl Responder { .values(InsertTaggedPhoto { tag_id, photo_name: file_name.clone(), + created_time: Utc::now().timestamp(), }) .execute(connection) .map(|_| { @@ -384,7 +389,7 @@ async fn get_tags(_: Claims, request: web::Query) -> impl Resp match tags::table .left_join(tagged_photo::table) .filter(tagged_photo::photo_name.eq(&request.path)) - .select((tags::id, tags::name)) + .select((tags::id, tags::name, tags::created_time)) .get_results::(&connect()) { Ok(tags) => HttpResponse::Ok().json(tags), From 14ab02a1ec9e78ba7027d26f40de917fac9071a7 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Mon, 11 Oct 2021 21:32:44 -0400 Subject: [PATCH 05/21] Elevate insertion logs to info and fix error logs --- src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2a3ce64..1be144d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -255,11 +255,11 @@ async fn put_add_favorite( HttpResponse::Ok() } Err(e) => { - info!("{:?} {}. for user: {}", e, body.path, user_id); + error!("{:?} {}. for user: {}", e, body.path, user_id); HttpResponse::BadRequest() } Ok(_) => { - debug!("Adding favorite \"{}\" for userid: {}", body.path, user_id); + info!("Adding favorite \"{}\" for userid: {}", body.path, user_id); HttpResponse::Created() } } @@ -284,7 +284,7 @@ async fn delete_favorite( .await .unwrap(); - debug!( + info!( "Removing favorite \"{}\" for userid: {}", body.path, user_id ); @@ -329,7 +329,7 @@ async fn add_tag(_: Claims, body: web::Json) -> impl Responder { Err(e) } Ok(id) => { - debug!("Inserted tag: '{}' with id: {:?}", tag, id); + info!("Inserted tag: '{}' with id: {:?}", tag, id); Ok(id) } } @@ -355,7 +355,7 @@ async fn add_tag(_: Claims, body: web::Json) -> impl Responder { }) .execute(connection) .map(|_| { - debug!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); + info!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); HttpResponse::Created() }) From 9cd19d03eb4a3244af6436bc69b1ba8d6e44da9f Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Tue, 30 Nov 2021 20:07:00 -0500 Subject: [PATCH 06/21] Create Delete Tag endpoint --- src/main.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main.rs b/src/main.rs index 1be144d..6c62264 100644 --- a/src/main.rs +++ b/src/main.rs @@ -401,6 +401,39 @@ async fn get_tags(_: Claims, request: web::Query) -> impl Resp } } +#[delete("image/tags")] +async fn remove_tagged_photo(_: Claims, request: web::Json) -> impl Responder { + use schema::tags; + match tags::table + .filter(tags::name.eq(&request.tag_name)) + .get_result::(&connect()) + .optional() + .and_then(|tag| { + if let Some(tag) = tag { + use schema::tagged_photo; + diesel::delete( + tagged_photo::table + .filter(tagged_photo::tag_id.eq(tag.id)) + .filter(tagged_photo::photo_name.eq(&request.file_name)), + ) + .execute(&connect()) + .map(|_| HttpResponse::Ok()) + } else { + info!("No tag found with name '{}'", &request.tag_name); + Ok(HttpResponse::NotFound()) + } + }) { + Ok(status) => status, + Err(err) => { + error!( + "Error removing tag '{}' from file: {}. {:?}", + &request.tag_name, &request.file_name, err + ); + HttpResponse::InternalServerError() + } + } +} + fn create_thumbnails() { let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined"); let thumbnail_directory: &Path = Path::new(thumbs); @@ -585,6 +618,7 @@ fn main() -> std::io::Result<()> { .service(get_file_metadata) .service(add_tag) .service(get_tags) + .service(remove_tagged_photo) .app_data(app_data.clone()) .data::>(Box::new(user_dao)) .data::>(Box::new(favorites_dao)) From 40c79d13db64e1fdf6c611d429fc3a96ab75348a Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Thu, 17 Mar 2022 22:07:33 -0400 Subject: [PATCH 07/21] Move tags to their own module --- src/database/models.rs | 32 +------- src/main.rs | 146 +-------------------------------- src/tags.rs | 181 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 174 deletions(-) create mode 100644 src/tags.rs diff --git a/src/database/models.rs b/src/database/models.rs index 13ccc4d..dca23ba 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -1,4 +1,4 @@ -use crate::database::schema::{favorites, tagged_photo, tags, users}; +use crate::database::schema::{favorites, users}; use serde::Serialize; #[derive(Insertable)] @@ -29,33 +29,3 @@ pub struct Favorite { pub userid: i32, pub path: String, } - -#[derive(Serialize, Queryable, Clone, Debug)] -pub struct Tag { - pub id: i32, - pub name: String, - pub created_time: i64, -} - -#[derive(Insertable, Clone, Debug)] -#[table_name = "tags"] -pub struct InsertTag { - pub name: String, - pub created_time: i64, -} - -#[derive(Insertable, Clone, Debug)] -#[table_name = "tagged_photo"] -pub struct InsertTaggedPhoto { - pub tag_id: i32, - pub photo_name: String, - pub created_time: i64, -} - -#[derive(Queryable, Clone, Debug)] -pub struct TaggedPhoto { - pub id: i32, - pub photo_name: String, - pub tag_id: i32, - pub created_time: i64, -} diff --git a/src/main.rs b/src/main.rs index 1bade46..b26d7ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ extern crate rayon; use actix_web::web::Data; use actix_web_prom::PrometheusMetricsBuilder; -use chrono::Utc; use futures::stream::StreamExt; use lazy_static::lazy_static; use prometheus::{self, IntGauge}; @@ -24,7 +23,6 @@ use actix_web::{ web::{self, BufMut, BytesMut}, App, HttpRequest, HttpResponse, HttpServer, Responder, }; -use diesel::prelude::*; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use rayon::prelude::*; @@ -34,15 +32,16 @@ use crate::auth::login; use crate::data::*; use crate::database::*; use crate::files::{is_image_or_video, is_valid_full_path}; -use crate::models::{InsertTag, InsertTaggedPhoto, Tag, TaggedPhoto}; use crate::state::AppState; +use crate::tags::*; use crate::video::*; mod auth; mod data; -pub mod database; +mod database; mod files; mod state; +mod tags; mod video; #[cfg(test)] @@ -320,145 +319,6 @@ async fn delete_favorite( } } -#[post("image/tags")] -async fn add_tag(_: Claims, body: web::Json) -> impl Responder { - let tag = body.tag_name.clone(); - - use database::schema::tags; - - let connection = &connect(); - match tags::table - .filter(tags::name.eq(&tag)) - .get_result::(connection) - .optional() - .and_then(|t| { - if let Some(t) = t { - Ok(t.id) - } else { - match diesel::insert_into(tags::table) - .values(InsertTag { - name: tag.clone(), - created_time: Utc::now().timestamp(), - }) - .execute(connection) - .and_then(|_| { - no_arg_sql_function!( - last_insert_rowid, - diesel::sql_types::Integer, - "Represents the SQL last_insert_row() function" - ); - diesel::select(last_insert_rowid).get_result::(connection) - }) { - Err(e) => { - error!("Error inserting tag: '{}'. {:?}", tag, e); - Err(e) - } - Ok(id) => { - info!("Inserted tag: '{}' with id: {:?}", tag, id); - Ok(id) - } - } - } - }) - .map(|tag_id| { - use database::schema::tagged_photo; - - let file_name = body.file_name.clone(); - - match tagged_photo::table - .filter(tagged_photo::photo_name.eq(&file_name)) - .filter(tagged_photo::tag_id.eq(tag_id)) - .get_result::(connection) - .optional() - { - Ok(Some(_)) => HttpResponse::NoContent(), - Ok(None) => diesel::insert_into(tagged_photo::table) - .values(InsertTaggedPhoto { - tag_id, - photo_name: file_name.clone(), - created_time: Utc::now().timestamp(), - }) - .execute(connection) - .map(|_| { - info!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); - - HttpResponse::Created() - }) - .unwrap_or_else(|e| { - error!( - "Error inserting tagged photo: '{}' -> '{}'. {:?}", - tag_id, body.file_name, e - ); - - HttpResponse::InternalServerError() - }), - Err(e) => { - error!("Error querying tagged photo: {:?}", e); - HttpResponse::InternalServerError() - } - } - }) { - Ok(resp) => resp, - Err(e) => { - error!("{:?}", e); - HttpResponse::InternalServerError() - } - } -} - -#[get("image/tags")] -async fn get_tags(_: Claims, request: web::Query) -> impl Responder { - use schema::tagged_photo; - use schema::tags; - - match tags::table - .left_join(tagged_photo::table) - .filter(tagged_photo::photo_name.eq(&request.path)) - .select((tags::id, tags::name, tags::created_time)) - .get_results::(&connect()) - { - Ok(tags) => HttpResponse::Ok().json(tags), - Err(e) => { - error!("Error getting tags for image: '{}'. {:?}", request.path, e); - - HttpResponse::InternalServerError().finish() - } - } -} - -#[delete("image/tags")] -async fn remove_tagged_photo(_: Claims, request: web::Json) -> impl Responder { - use schema::tags; - match tags::table - .filter(tags::name.eq(&request.tag_name)) - .get_result::(&connect()) - .optional() - .and_then(|tag| { - if let Some(tag) = tag { - use schema::tagged_photo; - diesel::delete( - tagged_photo::table - .filter(tagged_photo::tag_id.eq(tag.id)) - .filter(tagged_photo::photo_name.eq(&request.file_name)), - ) - .execute(&connect()) - .map(|_| HttpResponse::Ok()) - } else { - info!("No tag found with name '{}'", &request.tag_name); - Ok(HttpResponse::NotFound()) - } - }) { - Ok(status) => status, - Err(err) => { - error!( - "Error removing tag '{}' from file: {}. {:?}", - &request.tag_name, &request.file_name, err - ); - HttpResponse::InternalServerError() - } - } -} - fn create_thumbnails() { let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined"); let thumbnail_directory: &Path = Path::new(thumbs); diff --git a/src/tags.rs b/src/tags.rs new file mode 100644 index 0000000..bbd1fe1 --- /dev/null +++ b/src/tags.rs @@ -0,0 +1,181 @@ +use crate::{ + connect, + data::AddTagRequest, + database, + database::schema::{tagged_photo, tags}, + schema, Claims, ThumbnailRequest, +}; +use actix_web::{delete, get, post, web, HttpResponse, Responder}; +use chrono::Utc; +use diesel::prelude::*; +use log::{error, info}; +use serde::Serialize; + +#[post("image/tags")] +pub async fn add_tag(_: Claims, body: web::Json) -> impl Responder { + let tag = body.tag_name.clone(); + + use database::schema::tags; + + let connection = &connect(); + match tags::table + .filter(tags::name.eq(&tag)) + .get_result::(connection) + .optional() + .and_then(|t| { + if let Some(t) = t { + Ok(t.id) + } else { + match diesel::insert_into(tags::table) + .values(InsertTag { + name: tag.clone(), + created_time: Utc::now().timestamp(), + }) + .execute(connection) + .and_then(|_| { + no_arg_sql_function!( + last_insert_rowid, + diesel::sql_types::Integer, + "Represents the SQL last_insert_row() function" + ); + diesel::select(last_insert_rowid).get_result::(connection) + }) { + Err(e) => { + error!("Error inserting tag: '{}'. {:?}", tag, e); + Err(e) + } + Ok(id) => { + info!("Inserted tag: '{}' with id: {:?}", tag, id); + Ok(id) + } + } + } + }) + .map(|tag_id| { + use database::schema::tagged_photo; + + let file_name = body.file_name.clone(); + + match tagged_photo::table + .filter(tagged_photo::photo_name.eq(&file_name)) + .filter(tagged_photo::tag_id.eq(tag_id)) + .get_result::(connection) + .optional() + { + Ok(Some(_)) => HttpResponse::NoContent(), + Ok(None) => diesel::insert_into(tagged_photo::table) + .values(InsertTaggedPhoto { + tag_id, + photo_name: file_name.clone(), + created_time: Utc::now().timestamp(), + }) + .execute(connection) + .map(|_| { + info!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); + + HttpResponse::Created() + }) + .unwrap_or_else(|e| { + error!( + "Error inserting tagged photo: '{}' -> '{}'. {:?}", + tag_id, body.file_name, e + ); + + HttpResponse::InternalServerError() + }), + Err(e) => { + error!("Error querying tagged photo: {:?}", e); + HttpResponse::InternalServerError() + } + } + }) { + Ok(resp) => resp, + Err(e) => { + error!("{:?}", e); + HttpResponse::InternalServerError() + } + } +} + +#[get("image/tags")] +pub async fn get_tags(_: Claims, request: web::Query) -> impl Responder { + use schema::tagged_photo; + use schema::tags; + + match tags::table + .left_join(tagged_photo::table) + .filter(tagged_photo::photo_name.eq(&request.path)) + .select((tags::id, tags::name, tags::created_time)) + .get_results::(&connect()) + { + Ok(tags) => HttpResponse::Ok().json(tags), + Err(e) => { + error!("Error getting tags for image: '{}'. {:?}", request.path, e); + + HttpResponse::InternalServerError().finish() + } + } +} + +#[delete("image/tags")] +pub async fn remove_tagged_photo(_: Claims, request: web::Json) -> impl Responder { + use schema::tags; + match tags::table + .filter(tags::name.eq(&request.tag_name)) + .get_result::(&connect()) + .optional() + .and_then(|tag| { + if let Some(tag) = tag { + use schema::tagged_photo; + diesel::delete( + tagged_photo::table + .filter(tagged_photo::tag_id.eq(tag.id)) + .filter(tagged_photo::photo_name.eq(&request.file_name)), + ) + .execute(&connect()) + .map(|_| HttpResponse::Ok()) + } else { + info!("No tag found with name '{}'", &request.tag_name); + Ok(HttpResponse::NotFound()) + } + }) { + Ok(status) => status, + Err(err) => { + error!( + "Error removing tag '{}' from file: {}. {:?}", + &request.tag_name, &request.file_name, err + ); + HttpResponse::InternalServerError() + } + } +} + +#[derive(Serialize, Queryable, Clone, Debug)] +pub struct Tag { + pub id: i32, + pub name: String, + pub created_time: i64, +} + +#[derive(Insertable, Clone, Debug)] +#[table_name = "tags"] +pub struct InsertTag { + pub name: String, + pub created_time: i64, +} + +#[derive(Insertable, Clone, Debug)] +#[table_name = "tagged_photo"] +pub struct InsertTaggedPhoto { + pub tag_id: i32, + pub photo_name: String, + pub created_time: i64, +} + +#[derive(Queryable, Clone, Debug)] +pub struct TaggedPhoto { + pub id: i32, + pub photo_name: String, + pub tag_id: i32, + pub created_time: i64, +} From 68bfcbf85fe01703f5426db4737afc0f47d1e827 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sat, 18 Mar 2023 14:43:41 -0400 Subject: [PATCH 08/21] Update and Migrate Diesel to 2.0 Almost have tag support working, still figuring out how to get photo tags. --- Cargo.lock | 991 ++++++++++++++++++++++++++------------------ Cargo.toml | 3 +- src/auth.rs | 12 +- src/data/mod.rs | 17 +- src/database/mod.rs | 45 +- src/error.rs | 14 + src/main.rs | 83 ++-- src/tags.rs | 430 +++++++++++++------ 8 files changed, 1009 insertions(+), 586 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index d3cb830..b408aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.6.10", ] [[package]] @@ -40,14 +40,14 @@ dependencies = [ "memchr", "pin-project-lite", "tokio", - "tokio-util 0.7.0", + "tokio-util 0.7.7", ] [[package]] name = "actix-files" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81bde9a79336aa51ebed236e91fc1a0528ff67cfdf4f68ca4c61ede9fd26fb5" +checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689" dependencies = [ "actix-http", "actix-service", @@ -68,16 +68,16 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.0.4" +version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", - "ahash", - "base64 0.13.0", + "ahash 0.8.3", + "base64 0.21.0", "bitflags", "brotli", "bytes", @@ -93,13 +93,15 @@ dependencies = [ "itoa", "language-tags", "local-channel", - "log", "mime", "percent-encoding", "pin-project-lite", "rand", - "sha-1", + "sha1", "smallvec", + "tokio", + "tokio-util 0.7.7", + "tracing", "zstd", ] @@ -110,7 +112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -133,23 +135,22 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" dependencies = [ "bytestring", - "firestorm", "http", - "log", "regex", "serde", + "tracing", ] [[package]] name = "actix-rt" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" dependencies = [ "actix-macros", "futures-core", @@ -158,16 +159,16 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" +checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" dependencies = [ "actix-rt", "actix-service", "actix-utils", "futures-core", "futures-util", - "mio 0.8.2", + "mio 0.8.6", "num_cpus", "socket2", "tokio", @@ -187,9 +188,9 @@ dependencies = [ [[package]] name = "actix-utils" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" dependencies = [ "local-waker", "pin-project-lite", @@ -197,9 +198,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.0.1" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e5ebffd51d50df56a3ae0de0e59487340ca456f05dd0b90c0a7a6dd6a74d31" +checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" dependencies = [ "actix-codec", "actix-http", @@ -210,7 +211,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash", + "ahash 0.7.6", "bytes", "bytestring", "cfg-if 1.0.0", @@ -219,6 +220,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", + "http", "itoa", "language-tags", "log", @@ -231,20 +233,20 @@ dependencies = [ "serde_urlencoded", "smallvec", "socket2", - "time 0.3.7", + "time 0.3.20", "url", ] [[package]] name = "actix-web-codegen" -version = "4.0.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -267,7 +269,7 @@ checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -294,34 +296,55 @@ dependencies = [ ] [[package]] -name = "aho-corasick" -version = "0.7.18" +name = "ahash" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] -name = "anyhow" -version = "1.0.56" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" [[package]] name = "askama_escape" @@ -335,7 +358,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -354,9 +377,15 @@ checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "bcrypt" @@ -364,7 +393,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4d0faafe9e089674fc3efdb311ff5253d445c79d85d1d28bd3ace76d45e7164" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "blowfish", "getrandom", ] @@ -386,9 +415,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -406,9 +435,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.3.3" +version = "3.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -417,9 +446,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.2" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -427,15 +456,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "bytemuck" -version = "1.8.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" @@ -445,24 +474,24 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "bytestring" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" dependencies = [ "bytes", ] [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -481,14 +510,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", - "time 0.1.43", + "time 0.1.45", + "wasm-bindgen", "winapi 0.3.9", ] @@ -501,6 +532,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -515,20 +556,26 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.7", + "time 0.3.20", "version_check", ] [[package]] -name = "cpufeatures" -version = "0.2.1" +name = "core-foundation-sys" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -544,9 +591,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbfe11fe19ff083c48923cf179540e8cd0535903dc35e178a1fdeeb59aef51f" +checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -554,9 +601,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -565,33 +612,31 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if 1.0.0", - "lazy_static", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -607,6 +652,50 @@ dependencies = [ "subtle", ] +[[package]] +name = "cxx" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.0", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.0", +] + [[package]] name = "deflate" version = "0.8.6" @@ -627,29 +716,40 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] name = "diesel" -version = "1.4.8" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" +checksum = "4391a22b19c916e50bec4d6140f29bdda3e3bb187223fe6e3ea0b6e4d1021c04" dependencies = [ - "byteorder", "diesel_derives", "libsqlite3-sys", ] [[package]] name = "diesel_derives" -version = "1.4.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" +checksum = "0ad74fdcf086be3d4fdd142f67937678fe60ed431c3b2f08599e7687269410c4" dependencies = [ + "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "diesel_migrations" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ae22beef5e9d6fab9225ddb073c1c6c1a7a6ded5019d5da11d1e5c5adc34e2" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", ] [[package]] @@ -663,11 +763,11 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.4", "crypto-common", ] @@ -679,15 +779,15 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "either" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encoding_rs" -version = "0.8.30" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if 1.0.0", ] @@ -707,32 +807,24 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.15" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", - "winapi 0.3.9", + "windows-sys", ] -[[package]] -name = "firestorm" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d6188b8804df28032815ea256b6955c9625c24da7525f387a7af02fbb8f01" - [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ - "cfg-if 1.0.0", "crc32fast", - "libc", - "miniz_oxide 0.4.4", + "miniz_oxide 0.6.2", ] [[package]] @@ -743,11 +835,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -788,9 +879,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" dependencies = [ "futures-channel", "futures-core", @@ -803,9 +894,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" dependencies = [ "futures-core", "futures-sink", @@ -813,15 +904,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" dependencies = [ "futures-core", "futures-task", @@ -830,38 +921,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ "futures-channel", "futures-core", @@ -877,9 +968,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -887,20 +978,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "h2" -version = "0.3.12" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", @@ -911,15 +1002,15 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.7", "tracing", ] [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" @@ -930,6 +1021,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hmac" version = "0.11.0" @@ -942,9 +1042,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -959,9 +1059,9 @@ checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" [[package]] name = "httparse" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -976,12 +1076,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] -name = "idna" -version = "0.2.3" +name = "iana-time-zone" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi 0.3.9", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -1016,6 +1139,7 @@ dependencies = [ "bcrypt", "chrono", "diesel", + "diesel_migrations", "dotenv", "env_logger", "futures", @@ -1036,9 +1160,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -1084,15 +1208,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] @@ -1108,9 +1232,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -1159,25 +1283,34 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.120" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libsqlite3-sys" -version = "0.22.2" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" dependencies = [ "pkg-config", "vcpkg", ] [[package]] -name = "local-channel" -version = "0.1.2" +name = "link-cplusplus" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6246c68cf195087205a0512559c97e15eaf95198bf0e206d662092cdcb03fe9f" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" dependencies = [ "futures-core", "futures-sink", @@ -1187,49 +1320,65 @@ dependencies = [ [[package]] name = "local-waker" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "902eb695eb0591864543cbfbf6d742510642a605a61fc5e97fe6ceb5a30ac4fb" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] +[[package]] +name = "migrations_internals" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c493c09323068c01e54c685f7da41a9ccf9219735c3766fbfd6099806ea08fbc" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "migrations_macros" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8ff27a350511de30cdabb77147501c36ef02e0451d957abea2f30caffb2b58" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + [[package]] name = "mime" version = "0.3.16" @@ -1257,12 +1406,11 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", - "autocfg", ] [[package]] @@ -1278,7 +1426,7 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", @@ -1286,16 +1434,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "miow 0.3.7", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -1322,20 +1468,11 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "net2" -version = "0.2.37" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1360,15 +1497,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "num-bigint" version = "0.2.6" @@ -1382,9 +1510,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1392,9 +1520,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", @@ -1414,37 +1542,28 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.10.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -1460,24 +1579,24 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.7", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", @@ -1489,9 +1608,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if 1.0.0", "libc", @@ -1502,24 +1621,24 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "path-absolutize" -version = "3.0.11" +version = "3.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b288298a7a3a7b42539e3181ba590d32f2d91237b0691ed5f103875c754b3bf5" +checksum = "0f1d4993b16f7325d90c18c3c6a3327db7808752db8d208cea0acee0abd52c52" dependencies = [ "path-dedot", ] [[package]] name = "path-dedot" -version = "3.0.14" +version = "3.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfa72956f6be8524f7f7e2b07972dda393cb0008a6df4451f658b7e1bd1af80" +checksum = "9a81540d94551664b72b72829b12bd167c73c9d25fbac0e04fafa8023f7e4901" dependencies = [ "once_cell", ] @@ -1530,22 +1649,22 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", "once_cell", "regex", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1555,9 +1674,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "png" @@ -1573,45 +1692,69 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "prometheus" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ "cfg-if 1.0.0", "fnv", "lazy_static", "memchr", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "protobuf", "thiserror", ] [[package]] name = "protobuf" -version = "2.27.1" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quote" -version = "1.0.16" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1639,52 +1782,49 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rayon" -version = "1.5.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.5.5" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1693,9 +1833,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "ring" @@ -1723,9 +1863,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" @@ -1743,36 +1883,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "semver" -version = "1.0.6" +name = "scratch" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "707de5fcf5df2b5788fca98dd7eab490bc2fd9b7ef1404defc462833b83f25ca" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "78997f4555c22a7971214540c4a661291970619afd56de19f77e0de86296e1e5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.0", ] [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", @@ -1792,14 +1938,14 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -1817,9 +1963,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -1837,21 +1983,24 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi 0.3.9", @@ -1871,110 +2020,131 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.89" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff13bb1732bccfe3b246f3fdb09edfd51c01d6f5299b7ccd9457c2e4e37774" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.0", ] [[package]] name = "time" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] [[package]] name = "time" -version = "0.3.7" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa", - "libc", - "num_threads", + "serde", + "time-core", "time-macros", ] [[package]] -name = "time-macros" -version = "0.2.3" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.17.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ + "autocfg", "bytes", "libc", "memchr", - "mio 0.8.2", - "once_cell", - "parking_lot 0.12.0", + "mio 0.8.6", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", - "winapi 0.3.9", + "windows-sys", ] [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes", "futures-core", @@ -1986,23 +2156,32 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.0" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", ] [[package]] name = "tracing" -version = "0.1.32" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", @@ -2012,11 +2191,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.23" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -2031,9 +2210,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unchecked-index" @@ -2052,24 +2231,30 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "7d502c968c6a838ead8e69b2ee18ec708802f99db92a0d156705ec9ef801993b" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "unicode-width" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "untrusted" @@ -2079,13 +2264,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -2103,20 +2287,19 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi 0.3.9", "winapi-util", ] [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasi" @@ -2126,9 +2309,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2136,24 +2319,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2161,28 +2344,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -2233,46 +2416,69 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] -name = "windows_aarch64_msvc" -version = "0.32.0" +name = "windows_aarch64_gnullvm" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "ws2_32-sys" @@ -2286,18 +2492,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.10.0+zstd.1.5.2" +version = "0.12.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.4+zstd.1.5.2" +version = "6.0.4+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee" +checksum = "7afb4b54b8910cf5447638cb54bf4e8a65cbedd783af98b98c62ffe91f185543" dependencies = [ "libc", "zstd-sys", @@ -2305,10 +2511,11 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +version = "2.0.7+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" dependencies = [ "cc", "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 8aafd6d..e4d3fd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ futures = "0.3.5" jsonwebtoken = "7.2.0" serde = "1" serde_json = "1" -diesel = { version = "1.4.8", features = ["sqlite"] } +diesel = { version = "2.0.2", features = ["sqlite"] } +diesel_migrations = "2.0.0" hmac = "0.11" sha2 = "0.9" chrono = "0.4" diff --git a/src/auth.rs b/src/auth.rs index f938ffb..c4cccef 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,3 +1,4 @@ +use std::sync::Mutex; use actix_web::Responder; use actix_web::{ web::{self, Json}, @@ -15,12 +16,13 @@ use crate::{ #[allow(dead_code)] async fn register( user: Json, - user_dao: web::Data, + user_dao: web::Data>, ) -> impl Responder { if !user.username.is_empty() && user.password.len() > 5 && user.password == user.confirmation { - if user_dao.user_exists(&user.username) { + let mut dao = user_dao.lock().expect("Unable to get UserDao"); + if dao.user_exists(&user.username) { HttpResponse::BadRequest() - } else if let Some(_user) = user_dao.create_user(&user.username, &user.password) { + } else if let Some(_user) = dao.create_user(&user.username, &user.password) { HttpResponse::Ok() } else { HttpResponse::InternalServerError() @@ -30,9 +32,11 @@ async fn register( } } -pub async fn login(creds: Json, user_dao: web::Data) -> HttpResponse { +pub async fn login(creds: Json, user_dao: web::Data>) -> HttpResponse { debug!("Logging in: {}", creds.username); + let mut user_dao = user_dao.lock().expect("Unable to get UserDao"); + if let Some(user) = user_dao.get_user(&creds.username, &creds.password) { let claims = Claims { sub: user.id.to_string(), diff --git a/src/data/mod.rs b/src/data/mod.rs index 228380b..235e96e 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -16,12 +16,27 @@ pub struct Token<'a> { pub token: &'a str, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct Claims { pub sub: String, pub exp: i64, } +#[cfg(test)] +pub mod helper { + use super::Claims; + use chrono::{Duration, Utc}; + + impl Claims { + pub fn valid_user(user_id: String) -> Self { + Claims { + sub: user_id, + exp: (Utc::now() + Duration::minutes(1)).timestamp(), + } + } + } +} + pub fn secret_key() -> String { if cfg!(test) { String::from("test_key") diff --git a/src/database/mod.rs b/src/database/mod.rs index 328e310..073bbf7 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -2,9 +2,9 @@ use bcrypt::{hash, verify, DEFAULT_COST}; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; use std::{ - ops::Deref, sync::{Arc, Mutex}, }; +use std::ops::DerefMut; use crate::database::models::{Favorite, InsertFavorite, InsertUser, User}; @@ -12,9 +12,9 @@ pub mod models; pub mod schema; pub trait UserDao { - fn create_user(&self, user: &str, password: &str) -> Option; - fn get_user(&self, user: &str, password: &str) -> Option; - fn user_exists(&self, user: &str) -> bool; + fn create_user(&mut self, user: &str, password: &str) -> Option; + fn get_user(&mut self, user: &str, password: &str) -> Option; + fn user_exists(&mut self, user: &str) -> bool; } pub struct SqliteUserDao { @@ -31,7 +31,7 @@ impl SqliteUserDao { impl UserDao for SqliteUserDao { // TODO: Should probably use Result here - fn create_user(&self, user: &str, pass: &str) -> std::option::Option { + fn create_user(&mut self, user: &str, pass: &str) -> Option { use schema::users::dsl::*; let hashed = hash(pass, DEFAULT_COST); @@ -41,12 +41,12 @@ impl UserDao for SqliteUserDao { username: user, password: &hash, }) - .execute(&self.connection) + .execute(&mut self.connection) .unwrap(); users .filter(username.eq(username)) - .load::(&self.connection) + .load::(&mut self.connection) .unwrap() .first() .cloned() @@ -55,12 +55,12 @@ impl UserDao for SqliteUserDao { } } - fn get_user(&self, user: &str, pass: &str) -> Option { + fn get_user(&mut self, user: &str, pass: &str) -> Option { use schema::users::dsl::*; match users .filter(username.eq(user)) - .load::(&self.connection) + .load::(&mut self.connection) .unwrap_or_default() .first() { @@ -69,12 +69,12 @@ impl UserDao for SqliteUserDao { } } - fn user_exists(&self, user: &str) -> bool { + fn user_exists(&mut self, user: &str) -> bool { use schema::users::dsl::*; users .filter(username.eq(user)) - .load::(&self.connection) + .load::(&mut self.connection) .unwrap_or_default() .first() .is_some() @@ -109,9 +109,9 @@ pub enum DbErrorKind { } pub trait FavoriteDao: Sync + Send { - fn add_favorite(&self, user_id: i32, favorite_path: &str) -> Result; - fn remove_favorite(&self, user_id: i32, favorite_path: String); - fn get_favorites(&self, user_id: i32) -> Result, DbError>; + fn add_favorite(&mut self, user_id: i32, favorite_path: &str) -> Result; + fn remove_favorite(&mut self, user_id: i32, favorite_path: String); + fn get_favorites(&mut self, user_id: i32) -> Result, DbError>; } pub struct SqliteFavoriteDao { @@ -127,15 +127,14 @@ impl SqliteFavoriteDao { } impl FavoriteDao for SqliteFavoriteDao { - fn add_favorite(&self, user_id: i32, favorite_path: &str) -> Result { + fn add_favorite(&mut self, user_id: i32, favorite_path: &str) -> Result { use schema::favorites::dsl::*; - let connection = self.connection.lock().unwrap(); - let connection = connection.deref(); + let mut connection = self.connection.lock().expect("Unable to get FavoriteDao"); if favorites .filter(userid.eq(user_id).and(path.eq(&favorite_path))) - .first::(connection) + .first::(connection.deref_mut()) .is_err() { diesel::insert_into(favorites) @@ -143,28 +142,28 @@ impl FavoriteDao for SqliteFavoriteDao { userid: &user_id, path: favorite_path, }) - .execute(connection) + .execute(connection.deref_mut()) .map_err(|_| DbError::new(DbErrorKind::InsertError)) } else { Err(DbError::exists()) } } - fn remove_favorite(&self, user_id: i32, favorite_path: String) { + fn remove_favorite(&mut self, user_id: i32, favorite_path: String) { use schema::favorites::dsl::*; diesel::delete(favorites) .filter(userid.eq(user_id).and(path.eq(favorite_path))) - .execute(self.connection.lock().unwrap().deref()) + .execute(self.connection.lock().unwrap().deref_mut()) .unwrap(); } - fn get_favorites(&self, user_id: i32) -> Result, DbError> { + fn get_favorites(&mut self, user_id: i32) -> Result, DbError> { use schema::favorites::dsl::*; favorites .filter(userid.eq(user_id)) - .load::(self.connection.lock().unwrap().deref()) + .load::(self.connection.lock().unwrap().deref_mut()) .map_err(|_| DbError::new(DbErrorKind::QueryError)) } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..01b6bae --- /dev/null +++ b/src/error.rs @@ -0,0 +1,14 @@ +use actix_web::{error::InternalError, http::StatusCode}; + +pub trait IntoHttpError { + fn into_http_internal_err(self) -> Result; +} + +impl IntoHttpError for Result { + fn into_http_internal_err(self) -> Result { + self.map_err(|e| { + log::error!("Map to err: {:?}", e); + InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR).into() + }) + } +} diff --git a/src/main.rs b/src/main.rs index b26d7ae..4f589f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate rayon; use actix_web::web::Data; use actix_web_prom::PrometheusMetricsBuilder; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use futures::stream::StreamExt; use lazy_static::lazy_static; use prometheus::{self, IntGauge}; @@ -14,6 +15,8 @@ use std::{ io::ErrorKind, path::{Path, PathBuf}, }; +use std::error::Error; +use std::sync::Mutex; use walkdir::{DirEntry, WalkDir}; use actix_files::NamedFile; @@ -23,6 +26,7 @@ use actix_web::{ web::{self, BufMut, BytesMut}, App, HttpRequest, HttpResponse, HttpServer, Responder, }; +use diesel::sqlite::Sqlite; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use rayon::prelude::*; @@ -39,6 +43,7 @@ use crate::video::*; mod auth; mod data; mod database; +mod error; mod files; mod state; mod tags; @@ -60,12 +65,14 @@ lazy_static! { .unwrap(); } +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + #[get("/image")] async fn get_image( _claims: Claims, request: HttpRequest, req: web::Query, - app_state: web::Data, + app_state: Data, ) -> impl Responder { if let Some(path) = is_valid_full_path(&app_state.base_path, &req.path) { if req.size.is_some() { @@ -97,7 +104,7 @@ async fn get_image( async fn get_file_metadata( _: Claims, path: web::Query, - app_state: web::Data, + app_state: Data, ) -> impl Responder { match is_valid_full_path(&app_state.base_path, &path.path) .ok_or_else(|| ErrorKind::InvalidData.into()) @@ -119,7 +126,7 @@ async fn get_file_metadata( async fn upload_image( _: Claims, mut payload: mp::Multipart, - app_state: web::Data, + app_state: Data, ) -> impl Responder { let mut file_content: BytesMut = BytesMut::new(); let mut file_name: Option = None; @@ -148,7 +155,7 @@ async fn upload_image( if !file_content.is_empty() { let full_path = PathBuf::from(&path).join(file_name.unwrap()); if let Some(full_path) = - is_valid_full_path(&app_state.base_path, full_path.to_str().unwrap_or("")) + is_valid_full_path(&app_state.base_path, full_path.to_str().unwrap_or("")) { if !full_path.is_file() && is_image_or_video(&full_path) { let mut file = File::create(full_path).unwrap(); @@ -170,7 +177,7 @@ async fn upload_image( #[post("/video/generate")] async fn generate_video( _claims: Claims, - app_state: web::Data, + app_state: Data, body: web::Json, ) -> impl Responder { let filename = PathBuf::from(&body.path); @@ -200,7 +207,7 @@ async fn stream_video( request: HttpRequest, _: Claims, path: web::Query, - app_state: web::Data, + app_state: Data, ) -> impl Responder { let playlist = &path.path; debug!("Playlist: {}", playlist); @@ -235,9 +242,10 @@ async fn get_video_part( #[get("image/favorites")] async fn favorites( claims: Claims, - favorites_dao: web::Data>, + favorites_dao: Data>>, ) -> impl Responder { - match web::block(move || favorites_dao.get_favorites(claims.sub.parse::().unwrap())).await + match web::block(move || favorites_dao.lock() + .expect("Unable to get FavoritesDao").get_favorites(claims.sub.parse::().unwrap())).await { Ok(Ok(favorites)) => { let favorites = favorites @@ -262,14 +270,14 @@ async fn favorites( async fn put_add_favorite( claims: Claims, body: web::Json, - favorites_dao: web::Data>, + favorites_dao: Data>>, ) -> impl Responder { if let Ok(user_id) = claims.sub.parse::() { let path = body.path.clone(); match web::block::<_, Result>(move || { - favorites_dao.add_favorite(user_id, &path) + favorites_dao.lock().expect("Unable to get FavoritesDao").add_favorite(user_id, &path) }) - .await + .await { Ok(Err(e)) if e.kind == DbErrorKind::AlreadyExists => { debug!("Favorite: {} exists for user: {}", &body.path, user_id); @@ -298,15 +306,15 @@ async fn put_add_favorite( async fn delete_favorite( claims: Claims, body: web::Query, - favorites_dao: web::Data>, + favorites_dao: Data>>, ) -> impl Responder { if let Ok(user_id) = claims.sub.parse::() { let path = body.path.clone(); web::block(move || { - favorites_dao.remove_favorite(user_id, path); + favorites_dao.lock().expect("Unable to get favorites dao").remove_favorite(user_id, path); }) - .await - .unwrap(); + .await + .unwrap(); info!( "Removing favorite \"{}\" for userid: {}", @@ -325,7 +333,7 @@ fn create_thumbnails() { let images = PathBuf::from(dotenv::var("BASE_PATH").unwrap()); - walkdir::WalkDir::new(&images) + WalkDir::new(&images) .into_iter() .collect::>>() .into_par_iter() @@ -422,12 +430,15 @@ fn main() -> std::io::Result<()> { dotenv::dotenv().ok(); env_logger::init(); + run_migrations(&mut connect()) + .expect("Failed to run migrations"); + create_thumbnails(); watch_files(); let system = actix::System::new(); system.block_on(async { - let app_data = web::Data::new(AppState::default()); + let app_data = Data::new(AppState::default()); let labels = HashMap::new(); let prometheus = PrometheusMetricsBuilder::new("api") @@ -447,6 +458,7 @@ fn main() -> std::io::Result<()> { HttpServer::new(move || { let user_dao = SqliteUserDao::new(); let favorites_dao = SqliteFavoriteDao::new(); + let tag_dao = SqliteTagDao::default(); App::new() .wrap(middleware::Logger::default()) .service(web::resource("/login").route(web::post().to(login::))) @@ -460,21 +472,32 @@ fn main() -> std::io::Result<()> { .service(put_add_favorite) .service(delete_favorite) .service(get_file_metadata) - .service(add_tag) - .service(get_tags) - .service(remove_tagged_photo) + .service( + web::resource("image/tags") + .route(web::post().to(add_tag::)) + .route(web::get().to(get_all_tags::)) + .route(web::get().to(get_tags::)) + .route(web::delete().to(remove_tagged_photo::)), + ) .app_data(app_data.clone()) - .app_data::>(Data::new(user_dao)) - .app_data::>>(Data::new(Box::new(favorites_dao))) + .app_data::>>(Data::new(Mutex::new(user_dao))) + .app_data::>>>(Data::new(Mutex::new(Box::new(favorites_dao)))) + .app_data::>>(Data::new(Mutex::new(tag_dao))) .wrap(prometheus.clone()) }) - .bind(dotenv::var("BIND_URL").unwrap())? - .bind("localhost:8088")? - .run() - .await + .bind(dotenv::var("BIND_URL").unwrap())? + .bind("localhost:8088")? + .run() + .await }) } +fn run_migrations(connection: &mut impl MigrationHarness) -> Result<(), Box> { + connection.run_pending_migrations(MIGRATIONS)?; + + Ok(()) +} + fn watch_files() { std::thread::spawn(|| { let (wtx, wrx) = channel(); @@ -492,10 +515,10 @@ fn watch_files() { let image_base_path = PathBuf::from(env::var("BASE_PATH").unwrap()); let image_relative = orig.strip_prefix(&image_base_path).unwrap(); if let Ok(old_thumbnail) = - env::var("THUMBNAILS").map(PathBuf::from).map(|mut base| { - base.push(image_relative); - base - }) + env::var("THUMBNAILS").map(PathBuf::from).map(|mut base| { + base.push(image_relative); + base + }) { if let Err(e) = std::fs::remove_file(&old_thumbnail) { error!( diff --git a/src/tags.rs b/src/tags.rs index bbd1fe1..4d263d0 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,153 +1,73 @@ -use crate::{ - connect, - data::AddTagRequest, - database, - database::schema::{tagged_photo, tags}, - schema, Claims, ThumbnailRequest, -}; -use actix_web::{delete, get, post, web, HttpResponse, Responder}; +use crate::{connect, data::AddTagRequest, error::IntoHttpError, schema, Claims, ThumbnailRequest}; +use actix_web::{web, HttpResponse, Responder}; +use anyhow::Context; use chrono::Utc; use diesel::prelude::*; -use log::{error, info}; +use log::info; +use schema::{tagged_photo, tags}; use serde::Serialize; +use std::borrow::BorrowMut; +use std::sync::Mutex; -#[post("image/tags")] -pub async fn add_tag(_: Claims, body: web::Json) -> impl Responder { - let tag = body.tag_name.clone(); +pub async fn add_tag( + _: Claims, + body: web::Json, + tag_dao: web::Data>, +) -> impl Responder { + let tag_name = body.tag_name.clone(); - use database::schema::tags; + let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao"); - let connection = &connect(); - match tags::table - .filter(tags::name.eq(&tag)) - .get_result::(connection) - .optional() - .and_then(|t| { - if let Some(t) = t { - Ok(t.id) + tag_dao + .get_all_tags() + .and_then(|tags| { + if let Some(tag) = tags.iter().find(|t| t.name == tag_name) { + Ok(tag.clone()) } else { - match diesel::insert_into(tags::table) - .values(InsertTag { - name: tag.clone(), - created_time: Utc::now().timestamp(), - }) - .execute(connection) - .and_then(|_| { - no_arg_sql_function!( - last_insert_rowid, - diesel::sql_types::Integer, - "Represents the SQL last_insert_row() function" - ); - diesel::select(last_insert_rowid).get_result::(connection) - }) { - Err(e) => { - error!("Error inserting tag: '{}'. {:?}", tag, e); - Err(e) - } - Ok(id) => { - info!("Inserted tag: '{}' with id: {:?}", tag, id); - Ok(id) - } - } + tag_dao.create_tag(&tag_name) } }) - .map(|tag_id| { - use database::schema::tagged_photo; - - let file_name = body.file_name.clone(); - - match tagged_photo::table - .filter(tagged_photo::photo_name.eq(&file_name)) - .filter(tagged_photo::tag_id.eq(tag_id)) - .get_result::(connection) - .optional() - { - Ok(Some(_)) => HttpResponse::NoContent(), - Ok(None) => diesel::insert_into(tagged_photo::table) - .values(InsertTaggedPhoto { - tag_id, - photo_name: file_name.clone(), - created_time: Utc::now().timestamp(), - }) - .execute(connection) - .map(|_| { - info!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); - - HttpResponse::Created() - }) - .unwrap_or_else(|e| { - error!( - "Error inserting tagged photo: '{}' -> '{}'. {:?}", - tag_id, body.file_name, e - ); - - HttpResponse::InternalServerError() - }), - Err(e) => { - error!("Error querying tagged photo: {:?}", e); - HttpResponse::InternalServerError() - } - } - }) { - Ok(resp) => resp, - Err(e) => { - error!("{:?}", e); - HttpResponse::InternalServerError() - } - } + .and_then(|tag| tag_dao.tag_file(&body.file_name, tag.id)) + .map(|_| HttpResponse::Ok()) + .into_http_internal_err() } -#[get("image/tags")] -pub async fn get_tags(_: Claims, request: web::Query) -> impl Responder { - use schema::tagged_photo; - use schema::tags; - - match tags::table - .left_join(tagged_photo::table) - .filter(tagged_photo::photo_name.eq(&request.path)) - .select((tags::id, tags::name, tags::created_time)) - .get_results::(&connect()) - { - Ok(tags) => HttpResponse::Ok().json(tags), - Err(e) => { - error!("Error getting tags for image: '{}'. {:?}", request.path, e); - - HttpResponse::InternalServerError().finish() - } - } +pub async fn get_tags( + _: Claims, + request: web::Query, + tag_dao: web::Data>, +) -> impl Responder { + let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao"); + tag_dao + .get_tags_for_path(&request.path) + .map(|tags| HttpResponse::Ok().json(tags)) + .into_http_internal_err() } -#[delete("image/tags")] -pub async fn remove_tagged_photo(_: Claims, request: web::Json) -> impl Responder { - use schema::tags; - match tags::table - .filter(tags::name.eq(&request.tag_name)) - .get_result::(&connect()) - .optional() - .and_then(|tag| { - if let Some(tag) = tag { - use schema::tagged_photo; - diesel::delete( - tagged_photo::table - .filter(tagged_photo::tag_id.eq(tag.id)) - .filter(tagged_photo::photo_name.eq(&request.file_name)), - ) - .execute(&connect()) - .map(|_| HttpResponse::Ok()) +pub async fn get_all_tags(_: Claims, tag_dao: web::Data>) -> impl Responder { + let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao"); + tag_dao + .get_all_tags() + .map(|tags| HttpResponse::Ok().json(tags)) + .into_http_internal_err() +} + +pub async fn remove_tagged_photo( + _: Claims, + request: web::Json, + tag_dao: web::Data>, +) -> impl Responder { + let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao"); + tag_dao + .remove_tag(&request.tag_name, &request.file_name) + .map(|result| { + if result.is_some() { + HttpResponse::Ok() } else { - info!("No tag found with name '{}'", &request.tag_name); - Ok(HttpResponse::NotFound()) + HttpResponse::NotFound() } - }) { - Ok(status) => status, - Err(err) => { - error!( - "Error removing tag '{}' from file: {}. {:?}", - &request.tag_name, &request.file_name, err - ); - HttpResponse::InternalServerError() - } - } + }) + .into_http_internal_err() } #[derive(Serialize, Queryable, Clone, Debug)] @@ -179,3 +99,243 @@ pub struct TaggedPhoto { pub tag_id: i32, pub created_time: i64, } + +pub trait TagDao { + fn get_all_tags(&mut self) -> anyhow::Result>; + fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result>; + fn create_tag(&mut self, name: &str) -> anyhow::Result; + fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result>; + fn tag_file(&mut self, path: &str, tag_id: i32) -> anyhow::Result; +} + +pub struct SqliteTagDao { + connection: SqliteConnection, +} + +impl SqliteTagDao { + fn new(connection: SqliteConnection) -> Self { + SqliteTagDao { connection } + } +} + +impl Default for SqliteTagDao { + fn default() -> Self { + SqliteTagDao::new(connect()) + } +} + +impl TagDao for SqliteTagDao { + fn get_all_tags(&mut self) -> anyhow::Result> { + tags::table + .get_results(&mut self.connection) + .with_context(|| "Unable to get all tags") + } + + fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result> { + tags::table + .left_join(tagged_photo::table) + .filter(tagged_photo::photo_name.eq(&path)) + .select((tags::id, tags::name, tags::created_time)) + .get_results::(self.connection.borrow_mut()) + .with_context(|| "Unable to get tags from Sqlite") + } + + fn create_tag(&mut self, name: &str) -> anyhow::Result { + diesel::insert_into(tags::table) + .values(InsertTag { + name: name.to_string(), + created_time: Utc::now().timestamp(), + }) + .execute(&mut self.connection) + .with_context(|| "Unable to insert tag in Sqlite") + .and_then(|_| { + no_arg_sql_function!( + last_insert_rowid, + diesel::sql_types::Integer, + "Represents the SQL last_insert_row() function" + ); + diesel::select(last_insert_rowid) + .get_result::(&mut self.connection) + .with_context(|| "Unable to get last inserted tag from Sqlite") + }) + .and_then(|id| { + tags::table + .left_join(tagged_photo::table) + .filter(tagged_photo::id.eq(id)) + .select((tags::id, tags::name, tags::created_time)) + .get_result::(self.connection.borrow_mut()) + .with_context(|| "Unable to get tagged photo from Sqlite") + }) + } + + fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result> { + tags::table + .filter(tags::name.eq(tag_name)) + .get_result::(self.connection.borrow_mut()) + .optional() + .with_context(|| format!("Unable to get tag '{}'", tag_name)) + .and_then(|tag| { + if let Some(tag) = tag { + diesel::delete( + tagged_photo::table + .filter(tagged_photo::tag_id.eq(tag.id)) + .filter(tagged_photo::photo_name.eq(path)), + ) + .execute(&mut self.connection) + .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) + .map(|_| Some(())) + } else { + info!("No tag found with name '{}'", tag_name); + Ok(None) + } + }) + } + + fn tag_file(&mut self, path: &str, tag_id: i32) -> anyhow::Result { + diesel::insert_into(tagged_photo::table) + .values(InsertTaggedPhoto { + tag_id, + photo_name: path.to_string(), + created_time: Utc::now().timestamp(), + }) + .execute(self.connection.borrow_mut()) + .with_context(|| "Unable to insert tag into sqlite") + .and_then(|tagged_id| { + tagged_photo::table + .find(tagged_id as i32) + .first(self.connection.borrow_mut()) + .with_context(|| "Error getting inserted tagged photo") + }) + } +} + +#[cfg(test)] +mod tests { + use actix_web::web::Data; + use std::{borrow::Borrow, cell::RefCell, collections::HashMap}; + + use diesel::result::Error::NotFound; + + use super::*; + + struct TestTagDao { + tags: RefCell>, + tagged_photos: RefCell>>, + } + + impl TestTagDao { + fn new() -> Self { + Self { + tags: RefCell::new(vec![]), + tagged_photos: RefCell::new(HashMap::new()), + } + } + } + + impl TagDao for TestTagDao { + fn get_all_tags(&self) -> anyhow::Result> { + Ok(self.tags.borrow().clone()) + } + + fn get_tags_for_path(&self, path: &str) -> anyhow::Result> { + Ok(self + .tagged_photos + .borrow() + .get(path) + .unwrap_or(&vec![]) + .clone()) + } + + fn create_tag(&self, name: &str) -> anyhow::Result { + let tag = Tag { + id: 0, + name: name.to_string(), + created_time: Utc::now().timestamp(), + }; + self.tags.borrow_mut().push(tag.clone()); + + Ok(tag) + } + + fn remove_tag(&self, tag_name: &str, path: &str) -> anyhow::Result> { + let mut clone = { + let photo_tags = &self.tagged_photos.borrow()[path]; + photo_tags.clone() + }; + + clone.retain(|t| t.name != tag_name); + self.tagged_photos + .borrow_mut() + .insert(path.to_string(), clone); + + let index = self.tags.borrow().iter().position(|t| t.name == tag_name); + if let Some(index) = index { + self.tags.borrow_mut().remove(index); + Ok(Some(())) + } else { + Ok(None) + } + } + + fn tag_file(&self, path: &str, tag_id: i32) -> anyhow::Result { + if let Some(tag) = self.tags.borrow().iter().find(|t| t.id == tag_id) { + let tagged_photo = TaggedPhoto { + id: self.tagged_photos.borrow().len() as i32, + tag_id: tag.id, + created_time: Utc::now().timestamp(), + photo_name: path.to_string(), + }; + + //TODO: Add to existing tags (? huh) + self.tagged_photos + .borrow_mut() + .insert(path.to_string(), vec![tag.clone()]); + + Ok(tagged_photo) + } else { + Err(NotFound.into()) + } + } + } + + #[actix_rt::test] + async fn add_new_tag_test() { + let tag_dao = Data::new(TestTagDao::new()); + let claims = Claims::valid_user(String::from("1")); + let body = AddTagRequest { + file_name: String::from("test.png"), + tag_name: String::from("test-tag"), + }; + + add_tag(claims, web::Json(body), tag_dao.clone()).await; + + let tags = tag_dao.get_all_tags().unwrap(); + assert!(tags.len() == 1); + assert!(tags.first().unwrap().name == "test-tag"); + assert!(tag_dao.tagged_photos.borrow()["test.png"].len() == 1) + } + + #[actix_rt::test] + async fn remove_tag_test() { + let tag_dao = Data::new(TestTagDao::new()); + let claims = Claims::valid_user(String::from("1")); + let add_request = AddTagRequest { + file_name: String::from("test.png"), + tag_name: String::from("test-tag"), + }; + + let remove_request = AddTagRequest { + file_name: String::from("test.png"), + tag_name: String::from("test-tag"), + }; + + add_tag(claims.clone(), web::Json(add_request), tag_dao.clone()).await; + remove_tagged_photo(claims, web::Json(remove_request), tag_dao.clone()).await; + + let tags = tag_dao.get_all_tags().unwrap(); + assert!(tags.is_empty()); + let tagged_photos = tag_dao.tagged_photos.borrow(); + let previously_added_tagged_photo = tagged_photos.get("test.png").unwrap(); + assert!(previously_added_tagged_photo.len() == 0) + } +} From f98d3261a7039bc4f57f69b65e613c356716fada Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sat, 18 Mar 2023 21:00:26 -0400 Subject: [PATCH 09/21] Fix tags all and tags for photo endpoints --- src/main.rs | 60 +++++++++++++++++++++++++++++++++-------------------- src/tags.rs | 8 +++++-- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4f589f9..81f9f7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,15 +8,15 @@ use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use futures::stream::StreamExt; use lazy_static::lazy_static; use prometheus::{self, IntGauge}; +use std::error::Error; use std::sync::mpsc::channel; +use std::sync::Mutex; use std::{collections::HashMap, io::prelude::*}; use std::{env, fs::File}; use std::{ io::ErrorKind, path::{Path, PathBuf}, }; -use std::error::Error; -use std::sync::Mutex; use walkdir::{DirEntry, WalkDir}; use actix_files::NamedFile; @@ -155,7 +155,7 @@ async fn upload_image( if !file_content.is_empty() { let full_path = PathBuf::from(&path).join(file_name.unwrap()); if let Some(full_path) = - is_valid_full_path(&app_state.base_path, full_path.to_str().unwrap_or("")) + is_valid_full_path(&app_state.base_path, full_path.to_str().unwrap_or("")) { if !full_path.is_file() && is_image_or_video(&full_path) { let mut file = File::create(full_path).unwrap(); @@ -244,8 +244,13 @@ async fn favorites( claims: Claims, favorites_dao: Data>>, ) -> impl Responder { - match web::block(move || favorites_dao.lock() - .expect("Unable to get FavoritesDao").get_favorites(claims.sub.parse::().unwrap())).await + match web::block(move || { + favorites_dao + .lock() + .expect("Unable to get FavoritesDao") + .get_favorites(claims.sub.parse::().unwrap()) + }) + .await { Ok(Ok(favorites)) => { let favorites = favorites @@ -275,9 +280,12 @@ async fn put_add_favorite( if let Ok(user_id) = claims.sub.parse::() { let path = body.path.clone(); match web::block::<_, Result>(move || { - favorites_dao.lock().expect("Unable to get FavoritesDao").add_favorite(user_id, &path) + favorites_dao + .lock() + .expect("Unable to get FavoritesDao") + .add_favorite(user_id, &path) }) - .await + .await { Ok(Err(e)) if e.kind == DbErrorKind::AlreadyExists => { debug!("Favorite: {} exists for user: {}", &body.path, user_id); @@ -311,10 +319,13 @@ async fn delete_favorite( if let Ok(user_id) = claims.sub.parse::() { let path = body.path.clone(); web::block(move || { - favorites_dao.lock().expect("Unable to get favorites dao").remove_favorite(user_id, path); + favorites_dao + .lock() + .expect("Unable to get favorites dao") + .remove_favorite(user_id, path); }) - .await - .unwrap(); + .await + .unwrap(); info!( "Removing favorite \"{}\" for userid: {}", @@ -430,8 +441,7 @@ fn main() -> std::io::Result<()> { dotenv::dotenv().ok(); env_logger::init(); - run_migrations(&mut connect()) - .expect("Failed to run migrations"); + run_migrations(&mut connect()).expect("Failed to run migrations"); create_thumbnails(); watch_files(); @@ -475,24 +485,28 @@ fn main() -> std::io::Result<()> { .service( web::resource("image/tags") .route(web::post().to(add_tag::)) - .route(web::get().to(get_all_tags::)) .route(web::get().to(get_tags::)) .route(web::delete().to(remove_tagged_photo::)), ) + .service(web::resource("image/tags/all").route(web::get().to(get_all_tags::))) .app_data(app_data.clone()) .app_data::>>(Data::new(Mutex::new(user_dao))) - .app_data::>>>(Data::new(Mutex::new(Box::new(favorites_dao)))) + .app_data::>>>(Data::new(Mutex::new(Box::new( + favorites_dao, + )))) .app_data::>>(Data::new(Mutex::new(tag_dao))) .wrap(prometheus.clone()) }) - .bind(dotenv::var("BIND_URL").unwrap())? - .bind("localhost:8088")? - .run() - .await + .bind(dotenv::var("BIND_URL").unwrap())? + .bind("localhost:8088")? + .run() + .await }) } -fn run_migrations(connection: &mut impl MigrationHarness) -> Result<(), Box> { +fn run_migrations( + connection: &mut impl MigrationHarness, +) -> Result<(), Box> { connection.run_pending_migrations(MIGRATIONS)?; Ok(()) @@ -515,10 +529,10 @@ fn watch_files() { let image_base_path = PathBuf::from(env::var("BASE_PATH").unwrap()); let image_relative = orig.strip_prefix(&image_base_path).unwrap(); if let Ok(old_thumbnail) = - env::var("THUMBNAILS").map(PathBuf::from).map(|mut base| { - base.push(image_relative); - base - }) + env::var("THUMBNAILS").map(PathBuf::from).map(|mut base| { + base.push(image_relative); + base + }) { if let Err(e) = std::fs::remove_file(&old_thumbnail) { error!( diff --git a/src/tags.rs b/src/tags.rs index 4d263d0..b69ee67 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -3,7 +3,7 @@ use actix_web::{web, HttpResponse, Responder}; use anyhow::Context; use chrono::Utc; use diesel::prelude::*; -use log::info; +use log::{debug, info}; use schema::{tagged_photo, tags}; use serde::Serialize; use std::borrow::BorrowMut; @@ -132,6 +132,7 @@ impl TagDao for SqliteTagDao { } fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result> { + debug!("Getting Tags for path: {:?}", path); tags::table .left_join(tagged_photo::table) .filter(tagged_photo::photo_name.eq(&path)) @@ -149,6 +150,7 @@ impl TagDao for SqliteTagDao { .execute(&mut self.connection) .with_context(|| "Unable to insert tag in Sqlite") .and_then(|_| { + debug!("Inserted tag: {:?}", name); no_arg_sql_function!( last_insert_rowid, diesel::sql_types::Integer, @@ -159,6 +161,7 @@ impl TagDao for SqliteTagDao { .with_context(|| "Unable to get last inserted tag from Sqlite") }) .and_then(|id| { + debug!("Got id: {:?} for inserted tag: {:?}", id, name); tags::table .left_join(tagged_photo::table) .filter(tagged_photo::id.eq(id)) @@ -199,8 +202,9 @@ impl TagDao for SqliteTagDao { created_time: Utc::now().timestamp(), }) .execute(self.connection.borrow_mut()) - .with_context(|| "Unable to insert tag into sqlite") + .with_context(|| "Unable to tag file in sqlite") .and_then(|tagged_id| { + debug!("Inserted tagged photo: {:?}", tagged_id); tagged_photo::table .find(tagged_id as i32) .first(self.connection.borrow_mut()) From 8bcd9440bf50d8740719f4fd68136ab721711559 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sun, 19 Mar 2023 12:13:04 -0400 Subject: [PATCH 10/21] Add Endpoint for Batch add/removal of tags on a file --- src/main.rs | 1 + src/tags.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 81f9f7d..f340a01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -489,6 +489,7 @@ fn main() -> std::io::Result<()> { .route(web::delete().to(remove_tagged_photo::)), ) .service(web::resource("image/tags/all").route(web::get().to(get_all_tags::))) + .service(web::resource("image/tags/batch").route(web::post().to(update_tags::))) .app_data(app_data.clone()) .app_data::>>(Data::new(Mutex::new(user_dao))) .app_data::>>>(Data::new(Mutex::new(Box::new( diff --git a/src/tags.rs b/src/tags.rs index b69ee67..44df9b4 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -5,7 +5,7 @@ use chrono::Utc; use diesel::prelude::*; use log::{debug, info}; use schema::{tagged_photo, tags}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::borrow::BorrowMut; use std::sync::Mutex; @@ -70,7 +70,42 @@ pub async fn remove_tagged_photo( .into_http_internal_err() } -#[derive(Serialize, Queryable, Clone, Debug)] +pub async fn update_tags(_: Claims, tag_dao: web::Data>, request: web::Json) -> impl Responder { + let mut dao = tag_dao.lock() + .expect("Unable to get TagDao"); + + return dao.get_tags_for_path(&request.file_name) + .and_then(|existing_tags| { + dao.get_all_tags().map(|all| (existing_tags, all)) + }) + .and_then(|(existing_tags, all_tags)| { + let tags_to_remove = existing_tags.iter() + .filter(|&t| !request.tag_ids.contains(&t.id)) + .collect::>(); + + for tag in tags_to_remove { + info!("Removing tag {:?} from file: {:?}", tag.name, request.file_name); + dao.remove_tag(&tag.name, &request.file_name) + .expect(&format!("Unable to remove tag {:?}", &tag.name)); + } + + let new_tags = all_tags.iter() + .filter(|&t| !existing_tags.contains(t) && request.tag_ids.contains(&t.id)) + .collect::>(); + + for new_tag in new_tags { + info!("Adding tag {:?} to file: {:?}", new_tag.name, request.file_name); + + dao.tag_file(&request.file_name, new_tag.id) + .with_context(|| format!("Unable to tag file {:?} with tag: {:?}", request.file_name, new_tag.name)) + .unwrap(); + } + + Ok(HttpResponse::Ok()) + }).into_http_internal_err() +} + +#[derive(Serialize, Queryable, Clone, Debug, PartialEq)] pub struct Tag { pub id: i32, pub name: String, @@ -100,6 +135,12 @@ pub struct TaggedPhoto { pub created_time: i64, } +#[derive(Debug, Deserialize)] +pub struct AddTagsRequest { + pub file_name: String, + pub tag_ids: Vec, +} + pub trait TagDao { fn get_all_tags(&mut self) -> anyhow::Result>; fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result>; @@ -184,9 +225,9 @@ impl TagDao for SqliteTagDao { .filter(tagged_photo::tag_id.eq(tag.id)) .filter(tagged_photo::photo_name.eq(path)), ) - .execute(&mut self.connection) - .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) - .map(|_| Some(())) + .execute(&mut self.connection) + .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) + .map(|_| Some(())) } else { info!("No tag found with name '{}'", tag_name); Ok(None) From fbcfc68e01baac769169c6cda1cac7c0d2e6fd52 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sun, 19 Mar 2023 12:29:33 -0400 Subject: [PATCH 11/21] Fix tests --- src/auth.rs | 12 ++++++------ src/tags.rs | 35 ++++++++++++++++++++--------------- src/testhelpers.rs | 6 +++--- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index c4cccef..b49dcce 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -67,7 +67,7 @@ mod tests { #[actix_rt::test] async fn test_login_reports_200_when_user_exists() { - let dao = TestUserDao::new(); + let mut dao = TestUserDao::new(); dao.create_user("user", "pass"); let j = Json(LoginRequest { @@ -75,14 +75,14 @@ mod tests { password: "pass".to_string(), }); - let response = login::(j, web::Data::new(dao)).await; + let response = login::(j, web::Data::new(Mutex::new(dao))).await; assert_eq!(response.status(), 200); } #[actix_rt::test] async fn test_login_returns_token_on_success() { - let dao = TestUserDao::new(); + let mut dao = TestUserDao::new(); dao.create_user("user", "password"); let j = Json(LoginRequest { @@ -90,7 +90,7 @@ mod tests { password: "password".to_string(), }); - let response = login::(j, web::Data::new(dao)).await; + let response = login::(j, web::Data::new(Mutex::new(dao))).await; assert_eq!(response.status(), 200); let response_text: String = response.read_to_str(); @@ -100,7 +100,7 @@ mod tests { #[actix_rt::test] async fn test_login_reports_404_when_user_does_not_exist() { - let dao = TestUserDao::new(); + let mut dao = TestUserDao::new(); dao.create_user("user", "password"); let j = Json(LoginRequest { @@ -108,7 +108,7 @@ mod tests { password: "password".to_string(), }); - let response = login::(j, web::Data::new(dao)).await; + let response = login::(j, web::Data::new(Mutex::new(dao))).await; assert_eq!(response.status(), 404); } diff --git a/src/tags.rs b/src/tags.rs index 44df9b4..916336f 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -102,7 +102,7 @@ pub async fn update_tags(_: Claims, tag_dao: web::Data>, req } Ok(HttpResponse::Ok()) - }).into_http_internal_err() + }).into_http_internal_err(); } #[derive(Serialize, Queryable, Clone, Debug, PartialEq)] @@ -278,11 +278,11 @@ mod tests { } impl TagDao for TestTagDao { - fn get_all_tags(&self) -> anyhow::Result> { + fn get_all_tags(&mut self) -> anyhow::Result> { Ok(self.tags.borrow().clone()) } - fn get_tags_for_path(&self, path: &str) -> anyhow::Result> { + fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result> { Ok(self .tagged_photos .borrow() @@ -291,7 +291,7 @@ mod tests { .clone()) } - fn create_tag(&self, name: &str) -> anyhow::Result { + fn create_tag(&mut self, name: &str) -> anyhow::Result { let tag = Tag { id: 0, name: name.to_string(), @@ -302,7 +302,7 @@ mod tests { Ok(tag) } - fn remove_tag(&self, tag_name: &str, path: &str) -> anyhow::Result> { + fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result> { let mut clone = { let photo_tags = &self.tagged_photos.borrow()[path]; photo_tags.clone() @@ -322,7 +322,7 @@ mod tests { } } - fn tag_file(&self, path: &str, tag_id: i32) -> anyhow::Result { + fn tag_file(&mut self, path: &str, tag_id: i32) -> anyhow::Result { if let Some(tag) = self.tags.borrow().iter().find(|t| t.id == tag_id) { let tagged_photo = TaggedPhoto { id: self.tagged_photos.borrow().len() as i32, @@ -345,24 +345,27 @@ mod tests { #[actix_rt::test] async fn add_new_tag_test() { - let tag_dao = Data::new(TestTagDao::new()); + let mut tag_dao = TestTagDao::new(); let claims = Claims::valid_user(String::from("1")); let body = AddTagRequest { file_name: String::from("test.png"), tag_name: String::from("test-tag"), }; - add_tag(claims, web::Json(body), tag_dao.clone()).await; + let mut tag_data = Data::new(Mutex::new(tag_dao)); + add_tag(claims, web::Json(body), tag_data.clone()).await; + let mut tag_dao = tag_data.lock().unwrap(); let tags = tag_dao.get_all_tags().unwrap(); - assert!(tags.len() == 1); - assert!(tags.first().unwrap().name == "test-tag"); - assert!(tag_dao.tagged_photos.borrow()["test.png"].len() == 1) + assert_eq!(tags.len(), 1); + assert_eq!(tags.first().unwrap().name, "test-tag"); + let tagged_photos = tag_dao.tagged_photos.borrow(); + assert_eq!(tagged_photos["test.png"].len(), 1) } #[actix_rt::test] async fn remove_tag_test() { - let tag_dao = Data::new(TestTagDao::new()); + let mut tag_dao = TestTagDao::new(); let claims = Claims::valid_user(String::from("1")); let add_request = AddTagRequest { file_name: String::from("test.png"), @@ -374,13 +377,15 @@ mod tests { tag_name: String::from("test-tag"), }; - add_tag(claims.clone(), web::Json(add_request), tag_dao.clone()).await; - remove_tagged_photo(claims, web::Json(remove_request), tag_dao.clone()).await; + let tag_data = Data::new(Mutex::new(tag_dao)); + add_tag(claims.clone(), web::Json(add_request), tag_data.clone()).await; + remove_tagged_photo(claims, web::Json(remove_request), tag_data.clone()).await; + let mut tag_dao = tag_data.lock().unwrap(); let tags = tag_dao.get_all_tags().unwrap(); assert!(tags.is_empty()); let tagged_photos = tag_dao.tagged_photos.borrow(); let previously_added_tagged_photo = tagged_photos.get("test.png").unwrap(); - assert!(previously_added_tagged_photo.len() == 0) + assert_eq!(previously_added_tagged_photo.len(), 0) } } diff --git a/src/testhelpers.rs b/src/testhelpers.rs index f51e066..e6097db 100644 --- a/src/testhelpers.rs +++ b/src/testhelpers.rs @@ -20,7 +20,7 @@ impl TestUserDao { } impl UserDao for TestUserDao { - fn create_user(&self, username: &str, password: &str) -> Option { + fn create_user(&mut self, username: &str, password: &str) -> Option { let u = User { id: (self.user_map.borrow().len() + 1) as i32, username: username.to_string(), @@ -32,7 +32,7 @@ impl UserDao for TestUserDao { Some(u) } - fn get_user(&self, user: &str, pass: &str) -> Option { + fn get_user(&mut self, user: &str, pass: &str) -> Option { match self .user_map .borrow() @@ -47,7 +47,7 @@ impl UserDao for TestUserDao { } } - fn user_exists(&self, user: &str) -> bool { + fn user_exists(&mut self, user: &str) -> bool { self.user_map .borrow() .iter() From de4041bd17bfed7f0da4e78b6969dfacc0440c1f Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sun, 19 Mar 2023 15:05:20 -0400 Subject: [PATCH 12/21] Refactor tags services and added tests Implemented some functionality which will allow the service configuration of the app to be split among the features for readability and testability. --- src/auth.rs | 7 +- src/database/mod.rs | 4 +- src/database/schema.rs | 7 +- src/main.rs | 11 +-- src/service.rs | 16 +++++ src/tags.rs | 148 ++++++++++++++++++++++++++++++++--------- 6 files changed, 143 insertions(+), 50 deletions(-) create mode 100644 src/service.rs diff --git a/src/auth.rs b/src/auth.rs index b49dcce..5c64312 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,4 +1,3 @@ -use std::sync::Mutex; use actix_web::Responder; use actix_web::{ web::{self, Json}, @@ -7,6 +6,7 @@ use actix_web::{ use chrono::{Duration, Utc}; use jsonwebtoken::{encode, EncodingKey, Header}; use log::{debug, error}; +use std::sync::Mutex; use crate::{ data::{secret_key, Claims, CreateAccountRequest, LoginRequest, Token}, @@ -32,7 +32,10 @@ async fn register( } } -pub async fn login(creds: Json, user_dao: web::Data>) -> HttpResponse { +pub async fn login( + creds: Json, + user_dao: web::Data>, +) -> HttpResponse { debug!("Logging in: {}", creds.username); let mut user_dao = user_dao.lock().expect("Unable to get UserDao"); diff --git a/src/database/mod.rs b/src/database/mod.rs index 073bbf7..df97315 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,10 +1,8 @@ use bcrypt::{hash, verify, DEFAULT_COST}; use diesel::prelude::*; use diesel::sqlite::SqliteConnection; -use std::{ - sync::{Arc, Mutex}, -}; use std::ops::DerefMut; +use std::sync::{Arc, Mutex}; use crate::database::models::{Favorite, InsertFavorite, InsertUser, User}; diff --git a/src/database/schema.rs b/src/database/schema.rs index 340c604..25a217e 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -33,9 +33,4 @@ table! { joinable!(tagged_photo -> tags (tag_id)); -allow_tables_to_appear_in_same_query!( - favorites, - tagged_photo, - tags, - users, -); +allow_tables_to_appear_in_same_query!(favorites, tagged_photo, tags, users,); diff --git a/src/main.rs b/src/main.rs index f340a01..d2ed44f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ use crate::auth::login; use crate::data::*; use crate::database::*; use crate::files::{is_image_or_video, is_valid_full_path}; +use crate::service::ServiceBuilder; use crate::state::AppState; use crate::tags::*; use crate::video::*; @@ -49,6 +50,7 @@ mod state; mod tags; mod video; +mod service; #[cfg(test)] mod testhelpers; @@ -482,14 +484,7 @@ fn main() -> std::io::Result<()> { .service(put_add_favorite) .service(delete_favorite) .service(get_file_metadata) - .service( - web::resource("image/tags") - .route(web::post().to(add_tag::)) - .route(web::get().to(get_tags::)) - .route(web::delete().to(remove_tagged_photo::)), - ) - .service(web::resource("image/tags/all").route(web::get().to(get_all_tags::))) - .service(web::resource("image/tags/batch").route(web::post().to(update_tags::))) + .add_feature(add_tag_services::<_, SqliteTagDao>) .app_data(app_data.clone()) .app_data::>>(Data::new(Mutex::new(user_dao))) .app_data::>>>(Data::new(Mutex::new(Box::new( diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 0000000..d31b51e --- /dev/null +++ b/src/service.rs @@ -0,0 +1,16 @@ +use actix_web::App; + +pub trait ServiceBuilder { + fn add_feature(self, f: F) -> App + where + F: Fn(App) -> App; +} + +impl ServiceBuilder for App { + fn add_feature(self, create_feature: F) -> App + where + F: Fn(App) -> App, + { + create_feature(self) + } +} diff --git a/src/tags.rs b/src/tags.rs index 916336f..a55fbe5 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,5 +1,6 @@ use crate::{connect, data::AddTagRequest, error::IntoHttpError, schema, Claims, ThumbnailRequest}; -use actix_web::{web, HttpResponse, Responder}; +use actix_web::dev::{ServiceFactory, ServiceRequest}; +use actix_web::{web, App, HttpResponse, Responder}; use anyhow::Context; use chrono::Utc; use diesel::prelude::*; @@ -9,7 +10,21 @@ use serde::{Deserialize, Serialize}; use std::borrow::BorrowMut; use std::sync::Mutex; -pub async fn add_tag( +pub fn add_tag_services(app: App) -> App +where + T: ServiceFactory, +{ + app.service( + web::resource("image/tags") + .route(web::post().to(add_tag::)) + .route(web::get().to(get_tags::)) + .route(web::delete().to(remove_tagged_photo::)), + ) + .service(web::resource("image/tags/all").route(web::get().to(get_all_tags::))) + .service(web::resource("image/tags/batch").route(web::post().to(update_tags::))) +} + +async fn add_tag( _: Claims, body: web::Json, tag_dao: web::Data>, @@ -32,7 +47,7 @@ pub async fn add_tag( .into_http_internal_err() } -pub async fn get_tags( +async fn get_tags( _: Claims, request: web::Query, tag_dao: web::Data>, @@ -44,7 +59,7 @@ pub async fn get_tags( .into_http_internal_err() } -pub async fn get_all_tags(_: Claims, tag_dao: web::Data>) -> impl Responder { +async fn get_all_tags(_: Claims, tag_dao: web::Data>) -> impl Responder { let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao"); tag_dao .get_all_tags() @@ -52,7 +67,7 @@ pub async fn get_all_tags(_: Claims, tag_dao: web::Data>) -> .into_http_internal_err() } -pub async fn remove_tagged_photo( +async fn remove_tagged_photo( _: Claims, request: web::Json, tag_dao: web::Data>, @@ -70,39 +85,55 @@ pub async fn remove_tagged_photo( .into_http_internal_err() } -pub async fn update_tags(_: Claims, tag_dao: web::Data>, request: web::Json) -> impl Responder { - let mut dao = tag_dao.lock() - .expect("Unable to get TagDao"); +async fn update_tags( + _: Claims, + tag_dao: web::Data>, + request: web::Json, +) -> impl Responder { + let mut dao = tag_dao.lock().expect("Unable to get TagDao"); - return dao.get_tags_for_path(&request.file_name) - .and_then(|existing_tags| { - dao.get_all_tags().map(|all| (existing_tags, all)) - }) + return dao + .get_tags_for_path(&request.file_name) + .and_then(|existing_tags| dao.get_all_tags().map(|all| (existing_tags, all))) .and_then(|(existing_tags, all_tags)| { - let tags_to_remove = existing_tags.iter() + let tags_to_remove = existing_tags + .iter() .filter(|&t| !request.tag_ids.contains(&t.id)) .collect::>(); for tag in tags_to_remove { - info!("Removing tag {:?} from file: {:?}", tag.name, request.file_name); + info!( + "Removing tag {:?} from file: {:?}", + tag.name, request.file_name + ); dao.remove_tag(&tag.name, &request.file_name) .expect(&format!("Unable to remove tag {:?}", &tag.name)); } - let new_tags = all_tags.iter() + let new_tags = all_tags + .iter() .filter(|&t| !existing_tags.contains(t) && request.tag_ids.contains(&t.id)) .collect::>(); for new_tag in new_tags { - info!("Adding tag {:?} to file: {:?}", new_tag.name, request.file_name); + info!( + "Adding tag {:?} to file: {:?}", + new_tag.name, request.file_name + ); dao.tag_file(&request.file_name, new_tag.id) - .with_context(|| format!("Unable to tag file {:?} with tag: {:?}", request.file_name, new_tag.name)) + .with_context(|| { + format!( + "Unable to tag file {:?} with tag: {:?}", + request.file_name, new_tag.name + ) + }) .unwrap(); } Ok(HttpResponse::Ok()) - }).into_http_internal_err(); + }) + .into_http_internal_err(); } #[derive(Serialize, Queryable, Clone, Debug, PartialEq)] @@ -225,9 +256,9 @@ impl TagDao for SqliteTagDao { .filter(tagged_photo::tag_id.eq(tag.id)) .filter(tagged_photo::photo_name.eq(path)), ) - .execute(&mut self.connection) - .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) - .map(|_| Some(())) + .execute(&mut self.connection) + .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) + .map(|_| Some(())) } else { info!("No tag found with name '{}'", tag_name); Ok(None) @@ -256,16 +287,18 @@ impl TagDao for SqliteTagDao { #[cfg(test)] mod tests { - use actix_web::web::Data; - use std::{borrow::Borrow, cell::RefCell, collections::HashMap}; + use actix_web::web::{Data, Json}; + use std::{cell::RefCell, collections::HashMap}; use diesel::result::Error::NotFound; + use log::warn; use super::*; struct TestTagDao { tags: RefCell>, tagged_photos: RefCell>>, + tag_count: i32, } impl TestTagDao { @@ -273,6 +306,7 @@ mod tests { Self { tags: RefCell::new(vec![]), tagged_photos: RefCell::new(HashMap::new()), + tag_count: 0, } } } @@ -283,6 +317,9 @@ mod tests { } fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result> { + info!("Getting test tags for: {:?}", path); + warn!("Tags for path: {:?}", self.tagged_photos); + Ok(self .tagged_photos .borrow() @@ -292,13 +329,18 @@ mod tests { } fn create_tag(&mut self, name: &str) -> anyhow::Result { + self.tag_count += 1; + let tag_id = self.tag_count; + let tag = Tag { - id: 0, + id: tag_id as i32, name: name.to_string(), created_time: Utc::now().timestamp(), }; self.tags.borrow_mut().push(tag.clone()); + debug!("Created tag: {:?}", tag); + Ok(tag) } @@ -323,7 +365,11 @@ mod tests { } fn tag_file(&mut self, path: &str, tag_id: i32) -> anyhow::Result { + debug!("Tagging file: {:?} with tag_id: {:?}", path, tag_id); + if let Some(tag) = self.tags.borrow().iter().find(|t| t.id == tag_id) { + debug!("Found tag: {:?}", tag); + let tagged_photo = TaggedPhoto { id: self.tagged_photos.borrow().len() as i32, tag_id: tag.id, @@ -331,10 +377,19 @@ mod tests { photo_name: path.to_string(), }; - //TODO: Add to existing tags (? huh) - self.tagged_photos - .borrow_mut() - .insert(path.to_string(), vec![tag.clone()]); + if self.tagged_photos.borrow().contains_key(path) { + let mut photo_tags = self.tagged_photos.borrow()[path].clone(); + photo_tags.push(tag.clone()); + + self.tagged_photos + .borrow_mut() + .insert(path.to_string(), photo_tags); + } else { + //TODO: Add to existing tags (? huh) + self.tagged_photos + .borrow_mut() + .insert(path.to_string(), vec![tag.clone()]); + } Ok(tagged_photo) } else { @@ -345,14 +400,14 @@ mod tests { #[actix_rt::test] async fn add_new_tag_test() { - let mut tag_dao = TestTagDao::new(); + let tag_dao = TestTagDao::new(); let claims = Claims::valid_user(String::from("1")); let body = AddTagRequest { file_name: String::from("test.png"), tag_name: String::from("test-tag"), }; - let mut tag_data = Data::new(Mutex::new(tag_dao)); + let tag_data = Data::new(Mutex::new(tag_dao)); add_tag(claims, web::Json(body), tag_data.clone()).await; let mut tag_dao = tag_data.lock().unwrap(); @@ -365,7 +420,7 @@ mod tests { #[actix_rt::test] async fn remove_tag_test() { - let mut tag_dao = TestTagDao::new(); + let tag_dao = TestTagDao::new(); let claims = Claims::valid_user(String::from("1")); let add_request = AddTagRequest { file_name: String::from("test.png"), @@ -388,4 +443,35 @@ mod tests { let previously_added_tagged_photo = tagged_photos.get("test.png").unwrap(); assert_eq!(previously_added_tagged_photo.len(), 0) } + + #[actix_rt::test] + async fn replace_tags_keeps_existing_tags_removes_extras_adds_missing_test() { + let mut tag_dao = TestTagDao::new(); + let new_tag = tag_dao.create_tag("Test").unwrap(); + let new_tag2 = tag_dao.create_tag("Test2").unwrap(); + let _ = tag_dao.create_tag("Test3").unwrap(); + + tag_dao.tag_file("test.jpg", new_tag.id).unwrap(); + tag_dao.tag_file("test.jpg", new_tag2.id).unwrap(); + + let claims = Claims::valid_user(String::from("1")); + let tag_data = Data::new(Mutex::new(tag_dao)); + + let add_tags_request = AddTagsRequest { + tag_ids: vec![1, 3], + file_name: String::from("test.jpg"), + }; + + update_tags(claims, tag_data.clone(), Json(add_tags_request)).await; + + let tag_dao = tag_data.lock().unwrap(); + let tags_for_test_photo = &tag_dao.tagged_photos.borrow()["test.jpg"]; + + assert_eq!(tags_for_test_photo.len(), 2); + // ID of 2 was removed and 3 was added + assert_eq!( + tags_for_test_photo.iter().find(|&t| t.name == "Test2"), + None + ); + } } From 7d05c9b8bf9ba2b089802acbe59f3ae868184387 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Mon, 20 Mar 2023 08:14:12 -0400 Subject: [PATCH 13/21] Add filtering by Tag to /images endpoint --- src/data/mod.rs | 13 +++++++++++++ src/files.rs | 35 +++++++++++++++++++++++++++-------- src/main.rs | 2 +- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/data/mod.rs b/src/data/mod.rs index 235e96e..d520f7a 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -100,6 +100,19 @@ pub struct PhotosResponse { pub dirs: Vec, } +#[derive(Deserialize)] +pub struct FilesRequest { + pub path: String, + pub tag_ids: Option>, + pub tag_filter_mode: Option, +} + +#[derive(Copy, Clone, Deserialize)] +pub enum FilterMode { + Any, + All, +} + #[derive(Deserialize)] pub struct ThumbnailRequest { pub path: String, diff --git a/src/files.rs b/src/files.rs index 114bf8f..c18c608 100644 --- a/src/files.rs +++ b/src/files.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use std::fs::read_dir; use std::io; use std::path::{Path, PathBuf}; +use std::sync::Mutex; use ::anyhow; use anyhow::{anyhow, Context}; @@ -10,18 +11,19 @@ use actix_web::{ web::{self, Query}, HttpResponse, }; - use log::{debug, error}; -use crate::data::{Claims, PhotosResponse, ThumbnailRequest}; +use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse, ThumbnailRequest}; use crate::AppState; +use crate::tags::TagDao; use path_absolutize::*; -pub async fn list_photos( +pub async fn list_photos( _: Claims, - req: Query, + req: Query, app_state: web::Data, + tag_dao: web::Data>, ) -> HttpResponse { let path = &req.path; if let Some(path) = is_valid_full_path(&PathBuf::from(&app_state.base_path), path) { @@ -44,6 +46,19 @@ pub async fn list_photos( relative.to_path_buf() }) .map(|f| f.to_str().unwrap().to_string()) + .filter(|path| { + if let (Some(tag_ids), Ok(mut tag_dao)) = (&req.tag_ids, tag_dao.lock()) { + let filter_mode = &req.tag_filter_mode.unwrap_or(FilterMode::Any); + + let file_tags = tag_dao.get_tags_for_path(path).unwrap_or_default(); + return match filter_mode { + FilterMode::Any => file_tags.iter().any(|t| tag_ids.contains(&t.id)), + FilterMode::All => file_tags.iter().all(|t| tag_ids.contains(&t.id)), + }; + } + + true + }) .collect::>(); let dirs = files @@ -153,6 +168,8 @@ mod tests { }; use std::{fs, sync::Arc}; + use actix_web::web::Data; + use crate::tags::SqliteTagDao; fn setup() { let _ = env_logger::builder().is_test(true).try_init(); @@ -167,7 +184,7 @@ mod tests { exp: 12345, }; - let request: Query = Query::from_query("path=").unwrap(); + let request: Query = Query::from_query("path=").unwrap(); let mut temp_photo = std::env::temp_dir(); let mut tmp = temp_photo.clone(); @@ -187,8 +204,9 @@ mod tests { String::from("/tmp"), String::from("/tmp/thumbs"), )), + Data::new(Mutex::new(SqliteTagDao::default())), ) - .await; + .await; let status = response.status(); let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap(); @@ -215,7 +233,7 @@ mod tests { exp: 12345, }; - let request: Query = Query::from_query("path=..").unwrap(); + let request: Query = Query::from_query("path=..").unwrap(); let response = list_photos( claims, @@ -225,8 +243,9 @@ mod tests { String::from("/tmp"), String::from("/tmp/thumbs"), )), + Data::new(Mutex::new(SqliteTagDao::default())), ) - .await; + .await; assert_eq!(response.status(), 400); } diff --git a/src/main.rs b/src/main.rs index d2ed44f..5b02c45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -474,7 +474,7 @@ fn main() -> std::io::Result<()> { App::new() .wrap(middleware::Logger::default()) .service(web::resource("/login").route(web::post().to(login::))) - .service(web::resource("/photos").route(web::get().to(files::list_photos))) + .service(web::resource("/photos").route(web::get().to(files::list_photos::))) .service(get_image) .service(upload_image) .service(generate_video) From 02444de7fa43c8a8a50b28719da3a7c5b097809e Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Wed, 22 Mar 2023 13:51:32 -0400 Subject: [PATCH 14/21] Fix tagging file with new tag When tagging a file with a brand new tag, we were using the number of rows affected as the ID instead of doing the query for the ID of the row we just inserted, this should fix when we tag a photo with a new tag. --- src/tags.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/tags.rs b/src/tags.rs index a55fbe5..a481b8f 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -185,7 +185,7 @@ pub struct SqliteTagDao { } impl SqliteTagDao { - fn new(connection: SqliteConnection) -> Self { + pub(crate) fn new(connection: SqliteConnection) -> Self { SqliteTagDao { connection } } } @@ -235,8 +235,7 @@ impl TagDao for SqliteTagDao { .and_then(|id| { debug!("Got id: {:?} for inserted tag: {:?}", id, name); tags::table - .left_join(tagged_photo::table) - .filter(tagged_photo::id.eq(id)) + .filter(tags::id.eq(id)) .select((tags::id, tags::name, tags::created_time)) .get_result::(self.connection.borrow_mut()) .with_context(|| "Unable to get tagged photo from Sqlite") @@ -274,13 +273,29 @@ impl TagDao for SqliteTagDao { created_time: Utc::now().timestamp(), }) .execute(self.connection.borrow_mut()) - .with_context(|| "Unable to tag file in sqlite") + .with_context(|| format!("Unable to tag file {:?} in sqlite", path)) + .and_then(|_| { + debug!("Inserted tagged photo: {:#} -> {:?}", tag_id, path); + no_arg_sql_function!( + last_insert_rowid, + diesel::sql_types::Integer, + "Represents the SQL last_insert_row() function" + ); + diesel::select(last_insert_rowid) + .get_result::(&mut self.connection) + .with_context(|| "Unable to get last inserted tag from Sqlite") + }) .and_then(|tagged_id| { debug!("Inserted tagged photo: {:?}", tagged_id); tagged_photo::table .find(tagged_id as i32) .first(self.connection.borrow_mut()) - .with_context(|| "Error getting inserted tagged photo") + .with_context(|| { + format!( + "Error getting inserted tagged photo with id: {:?}", + tagged_id + ) + }) }) } } From 5a2dce85e8951140e6a0f837e4f406c50a03a421 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Wed, 22 Mar 2023 13:55:10 -0400 Subject: [PATCH 15/21] Fix searching by tags and fixed All FilterMode Manually parsing the tag_ids for the file filtering isn't amazing, but this works in a more friendly format. Also the All filter mode was set up in the wrong direction instead of checking that the file had ALL the tag ids provided, it checked that all the tag-ids were on a file, which is too restrictive and wouldn't show many files. Perhaps an ONLY option could exist for being even more specific. --- src/data/mod.rs | 2 +- src/files.rs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/data/mod.rs b/src/data/mod.rs index d520f7a..bc93e7e 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -103,7 +103,7 @@ pub struct PhotosResponse { #[derive(Deserialize)] pub struct FilesRequest { pub path: String, - pub tag_ids: Option>, + pub tag_ids: Option, // comma separated numbers pub tag_filter_mode: Option, } diff --git a/src/files.rs b/src/files.rs index c18c608..caa6ed3 100644 --- a/src/files.rs +++ b/src/files.rs @@ -13,7 +13,7 @@ use actix_web::{ }; use log::{debug, error}; -use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse, ThumbnailRequest}; +use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse}; use crate::AppState; use crate::tags::TagDao; @@ -46,14 +46,22 @@ pub async fn list_photos( relative.to_path_buf() }) .map(|f| f.to_str().unwrap().to_string()) - .filter(|path| { + .filter(|file_path| { if let (Some(tag_ids), Ok(mut tag_dao)) = (&req.tag_ids, tag_dao.lock()) { + let tag_ids = tag_ids + .split(',') + .filter_map(|t| t.parse().ok()) + .collect::>(); + let filter_mode = &req.tag_filter_mode.unwrap_or(FilterMode::Any); - let file_tags = tag_dao.get_tags_for_path(path).unwrap_or_default(); + let file_tags = tag_dao.get_tags_for_path(file_path).unwrap_or_default(); + return match filter_mode { FilterMode::Any => file_tags.iter().any(|t| tag_ids.contains(&t.id)), - FilterMode::All => file_tags.iter().all(|t| tag_ids.contains(&t.id)), + FilterMode::All => tag_ids + .iter() + .all(|id| file_tags.iter().any(|tag| &tag.id == id)), }; } From 4ded7089111ab294929a2f4e4abc20d16f783bd2 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Wed, 22 Mar 2023 13:58:48 -0400 Subject: [PATCH 16/21] Extract FileSystem to a trait for better testability Added some tests around filtering and searching by Tags. Added the ability to use an in-memory Sqlite DB for more integration tests. --- src/database/mod.rs | 18 ++++ src/files.rs | 248 ++++++++++++++++++++++++++++++++++++++------ src/main.rs | 15 ++- 3 files changed, 245 insertions(+), 36 deletions(-) diff --git a/src/database/mod.rs b/src/database/mod.rs index df97315..77ac42e 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -27,6 +27,24 @@ impl SqliteUserDao { } } +#[cfg(test)] +pub mod test { + use diesel::{Connection, SqliteConnection}; + use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; + + const DB_MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + + pub fn in_memory_db_connection() -> SqliteConnection { + let mut connection = SqliteConnection::establish(":memory:") + .expect("Unable to create in-memory db connection"); + connection + .run_pending_migrations(DB_MIGRATIONS) + .expect("Failure running DB migrations"); + + return connection; + } +} + impl UserDao for SqliteUserDao { // TODO: Should probably use Result here fn create_user(&mut self, user: &str, pass: &str) -> Option { diff --git a/src/files.rs b/src/files.rs index caa6ed3..92ff3e9 100644 --- a/src/files.rs +++ b/src/files.rs @@ -19,16 +19,17 @@ use crate::AppState; use crate::tags::TagDao; use path_absolutize::*; -pub async fn list_photos( +pub async fn list_photos( _: Claims, req: Query, app_state: web::Data, + file_system: web::Data, tag_dao: web::Data>, ) -> HttpResponse { let path = &req.path; - if let Some(path) = is_valid_full_path(&PathBuf::from(&app_state.base_path), path) { + + if let Ok(files) = file_system.get_files_for_path(path) { debug!("Valid path: {:?}", path); - let files = list_files(&path).unwrap_or_default(); let photos = files .iter() @@ -36,7 +37,7 @@ pub async fn list_photos( f.metadata().map_or_else( |e| { error!("Failed getting file metadata: {:?}", e); - false + f.extension().is_some() }, |md| md.is_file(), ) @@ -110,10 +111,13 @@ pub fn is_image_or_video(path: &Path) -> bool { || extension == "nef" } -pub fn is_valid_full_path + Debug>(base: &P, path: &str) -> Option { - debug!("Base: {:?}. Path: {}", base, path); +pub fn is_valid_full_path + Debug + AsRef>( + base: &P, + path: &P, +) -> Option { + debug!("Base: {:?}. Path: {:?}", base, path); - let path = PathBuf::from(path); + let path = PathBuf::from(&path); let mut path = if path.is_relative() { let mut full_path = PathBuf::new(); full_path.push(base); @@ -153,31 +157,95 @@ fn is_path_above_base_dir + Debug>( ) } +pub trait FileSystemAccess { + fn get_files_for_path(&self, path: &str) -> anyhow::Result>; +} + +pub struct RealFileSystem { + base_path: String, +} + +impl RealFileSystem { + pub(crate) fn new(base_path: String) -> RealFileSystem { + RealFileSystem { base_path } + } +} + +impl FileSystemAccess for RealFileSystem { + fn get_files_for_path(&self, path: &str) -> anyhow::Result> { + is_valid_full_path(&PathBuf::from(&self.base_path), &PathBuf::from(path)) + .map(|path| { + debug!("Valid path: {:?}", path); + list_files(&path).unwrap_or_default() + }) + .context("Invalid path") + } +} + #[cfg(test)] mod tests { + use crate::database::connect; + use crate::tags::SqliteTagDao; + use actix_web::web::Data; + use diesel::{Connection, SqliteConnection}; + use std::collections::HashMap; use std::env; + use std::ffi::OsStr; use std::fs::File; use super::*; + struct FakeFileSystem { + files: HashMap>, + err: bool, + } + + impl FakeFileSystem { + fn with_error() -> FakeFileSystem { + FakeFileSystem { + files: HashMap::new(), + err: true, + } + } + + fn new(files: HashMap>) -> FakeFileSystem { + FakeFileSystem { files, err: false } + } + } + + impl FileSystemAccess for FakeFileSystem { + fn get_files_for_path(&self, path: &str) -> anyhow::Result> { + if self.err { + Err(anyhow!("Error for test")) + } else { + if let Some(files) = self.files.get(path) { + Ok(files + .iter() + .map(|p| PathBuf::from(p)) + .collect::>()) + } else { + Ok(Vec::new()) + } + } + } + } + mod api { use super::*; use actix::Actor; - use actix_web::{ - web::{self, Query}, - HttpResponse, - }; + use actix_web::{web::Query, HttpResponse}; use crate::{ - data::{Claims, PhotosResponse, ThumbnailRequest}, + data::{Claims, PhotosResponse}, testhelpers::BodyReader, video::StreamActor, AppState, }; - use std::{fs, sync::Arc}; - use actix_web::web::Data; + use crate::database::test::in_memory_db_connection; use crate::tags::SqliteTagDao; + use actix_web::web::Data; + use std::{fs, sync::Arc}; fn setup() { let _ = env_logger::builder().is_test(true).try_init(); @@ -194,7 +262,7 @@ mod tests { let request: Query = Query::from_query("path=").unwrap(); - let mut temp_photo = std::env::temp_dir(); + let mut temp_photo = env::temp_dir(); let mut tmp = temp_photo.clone(); tmp.push("test-dir"); @@ -202,24 +270,25 @@ mod tests { temp_photo.push("photo.jpg"); - fs::File::create(temp_photo.clone()).unwrap(); + File::create(temp_photo.clone()).unwrap(); let response: HttpResponse = list_photos( claims, request, - web::Data::new(AppState::new( + Data::new(AppState::new( Arc::new(StreamActor {}.start()), String::from("/tmp"), String::from("/tmp/thumbs"), )), + Data::new(RealFileSystem::new(String::from("/tmp"))), Data::new(Mutex::new(SqliteTagDao::default())), ) - .await; + .await; let status = response.status(); + assert_eq!(status, 200); let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap(); - assert_eq!(status, 200); assert!(body.photos.contains(&String::from("photo.jpg"))); assert!(body.dirs.contains(&String::from("test-dir"))); assert!(body @@ -246,28 +315,145 @@ mod tests { let response = list_photos( claims, request, - web::Data::new(AppState::new( + Data::new(AppState::new( Arc::new(StreamActor {}.start()), String::from("/tmp"), String::from("/tmp/thumbs"), )), + Data::new(RealFileSystem::new(String::from("./"))), Data::new(Mutex::new(SqliteTagDao::default())), ) - .await; + .await; assert_eq!(response.status(), 400); } + + #[actix_rt::test] + async fn get_files_with_tag_any_filter() { + setup(); + + let claims = Claims { + sub: String::from("1"), + exp: 12345, + }; + + let request: Query = Query::from_query("path=&tag_ids=1,3").unwrap(); + + let mut tag_dao = SqliteTagDao::new(in_memory_db_connection()); + + let tag1 = tag_dao.create_tag("tag1").unwrap(); + let tag2 = tag_dao.create_tag("tag2").unwrap(); + let tag3 = tag_dao.create_tag("tag3").unwrap(); + + &tag_dao.tag_file("test.jpg", tag1.id).unwrap(); + &tag_dao.tag_file("test.jpg", tag3.id).unwrap(); + + let mut files = HashMap::new(); + files.insert( + String::from(""), + vec![ + String::from("file1.txt"), + String::from("test.jpg"), + String::from("some-other.jpg"), + ], + ); + + let response: HttpResponse = list_photos( + claims, + request, + Data::new(AppState::new( + Arc::new(StreamActor {}.start()), + String::from(""), + String::from("/tmp/thumbs"), + )), + Data::new(FakeFileSystem::new(files)), + Data::new(Mutex::new(tag_dao)), + ) + .await; + + assert_eq!(200, response.status()); + + let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap(); + assert_eq!(1, body.photos.len()); + assert!(body.photos.contains(&String::from("test.jpg"))); + } + + #[actix_rt::test] + async fn get_files_with_tag_all_filter() { + setup(); + + let claims = Claims { + sub: String::from("1"), + exp: 12345, + }; + + let mut tag_dao = SqliteTagDao::new(in_memory_db_connection()); + + let tag1 = tag_dao.create_tag("tag1").unwrap(); + let tag2 = tag_dao.create_tag("tag2").unwrap(); + let tag3 = tag_dao.create_tag("tag3").unwrap(); + + &tag_dao.tag_file("test.jpg", tag1.id).unwrap(); + &tag_dao.tag_file("test.jpg", tag3.id).unwrap(); + + // Should get filtered since it doesn't have tag3 + tag_dao.tag_file("some-other.jpg", tag1.id).unwrap(); + + let mut files = HashMap::new(); + files.insert( + String::from(""), + vec![ + String::from("file1.txt"), + String::from("test.jpg"), + String::from("some-other.jpg"), + ], + ); + + let request: Query = + Query::from_query("path=&tag_ids=1,3&tag_filter_mode=All").unwrap(); + + let response: HttpResponse = list_photos( + claims, + request, + Data::new(AppState::new( + Arc::new(StreamActor {}.start()), + String::from(""), + String::from("/tmp/thumbs"), + )), + Data::new(FakeFileSystem::new(files)), + Data::new(Mutex::new(tag_dao)), + ) + .await; + + assert_eq!(200, response.status()); + + let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap(); + assert_eq!(1, body.photos.len()); + assert!(body.photos.contains(&String::from("test.jpg"))); + } } #[test] fn directory_traversal_test() { let base = env::temp_dir(); - assert_eq!(None, is_valid_full_path(&base, "../")); - assert_eq!(None, is_valid_full_path(&base, "..")); - assert_eq!(None, is_valid_full_path(&base, "fake/../../../")); - assert_eq!(None, is_valid_full_path(&base, "../../../etc/passwd")); - assert_eq!(None, is_valid_full_path(&base, "..//etc/passwd")); - assert_eq!(None, is_valid_full_path(&base, "../../etc/passwd")); + assert_eq!(None, is_valid_full_path(&base, &PathBuf::from("../"))); + assert_eq!(None, is_valid_full_path(&base, &PathBuf::from(".."))); + assert_eq!( + None, + is_valid_full_path(&base, &PathBuf::from("fake/../../../")) + ); + assert_eq!( + None, + is_valid_full_path(&base, &PathBuf::from("../../../etc/passwd")) + ); + assert_eq!( + None, + is_valid_full_path(&base, &PathBuf::from("..//etc/passwd")) + ); + assert_eq!( + None, + is_valid_full_path(&base, &PathBuf::from("../../etc/passwd")) + ); } #[test] @@ -277,7 +463,7 @@ mod tests { test_file.push("test.png"); File::create(test_file).unwrap(); - assert!(is_valid_full_path(&base, "test.png").is_some()); + assert!(is_valid_full_path(&base, &PathBuf::from("test.png")).is_some()); } #[test] @@ -288,7 +474,7 @@ mod tests { let mut test_file = PathBuf::from(&base); test_file.push(path); - assert_eq!(None, is_valid_full_path(&base, path)); + assert_eq!(None, is_valid_full_path(&base, &test_file)); } #[test] @@ -298,11 +484,11 @@ mod tests { test_file.push("test.png"); File::create(&test_file).unwrap(); - assert!(is_valid_full_path(&base, test_file.to_str().unwrap()).is_some()); + assert!(is_valid_full_path(&base, &test_file).is_some()); assert_eq!( Some(PathBuf::from("/tmp/test.png")), - is_valid_full_path(&base, "/tmp/test.png") + is_valid_full_path(&base, &PathBuf::from("/tmp/test.png")) ); } diff --git a/src/main.rs b/src/main.rs index 5b02c45..ad09092 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ use log::{debug, error, info}; use crate::auth::login; use crate::data::*; use crate::database::*; -use crate::files::{is_image_or_video, is_valid_full_path}; +use crate::files::{is_image_or_video, is_valid_full_path, RealFileSystem}; use crate::service::ServiceBuilder; use crate::state::AppState; use crate::tags::*; @@ -156,9 +156,10 @@ async fn upload_image( let path = file_path.unwrap_or_else(|| app_state.base_path.clone()); if !file_content.is_empty() { let full_path = PathBuf::from(&path).join(file_name.unwrap()); - if let Some(full_path) = - is_valid_full_path(&app_state.base_path, full_path.to_str().unwrap_or("")) - { + if let Some(full_path) = is_valid_full_path( + &app_state.base_path, + &full_path.to_str().unwrap().to_string(), + ) { if !full_path.is_file() && is_image_or_video(&full_path) { let mut file = File::create(full_path).unwrap(); file.write_all(&file_content).unwrap(); @@ -474,7 +475,10 @@ fn main() -> std::io::Result<()> { App::new() .wrap(middleware::Logger::default()) .service(web::resource("/login").route(web::post().to(login::))) - .service(web::resource("/photos").route(web::get().to(files::list_photos::))) + .service( + web::resource("/photos") + .route(web::get().to(files::list_photos::)), + ) .service(get_image) .service(upload_image) .service(generate_video) @@ -486,6 +490,7 @@ fn main() -> std::io::Result<()> { .service(get_file_metadata) .add_feature(add_tag_services::<_, SqliteTagDao>) .app_data(app_data.clone()) + .app_data::>(Data::new(RealFileSystem::new(app_data.base_path.clone()))) .app_data::>>(Data::new(Mutex::new(user_dao))) .app_data::>>>(Data::new(Mutex::new(Box::new( favorites_dao, From ae8665f632a2118bcb26de0ce8fa1379d8ddce97 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Wed, 22 Mar 2023 15:17:48 -0400 Subject: [PATCH 17/21] Fix clippy warnings --- src/files.rs | 10 +++++----- src/main.rs | 6 +++--- src/tags.rs | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/files.rs b/src/files.rs index 92ff3e9..dcd8cb5 100644 --- a/src/files.rs +++ b/src/files.rs @@ -345,8 +345,8 @@ mod tests { let tag2 = tag_dao.create_tag("tag2").unwrap(); let tag3 = tag_dao.create_tag("tag3").unwrap(); - &tag_dao.tag_file("test.jpg", tag1.id).unwrap(); - &tag_dao.tag_file("test.jpg", tag3.id).unwrap(); + let _ = &tag_dao.tag_file("test.jpg", tag1.id).unwrap(); + let _ = &tag_dao.tag_file("test.jpg", tag3.id).unwrap(); let mut files = HashMap::new(); files.insert( @@ -390,11 +390,11 @@ mod tests { let mut tag_dao = SqliteTagDao::new(in_memory_db_connection()); let tag1 = tag_dao.create_tag("tag1").unwrap(); - let tag2 = tag_dao.create_tag("tag2").unwrap(); + let _tag2 = tag_dao.create_tag("tag2").unwrap(); let tag3 = tag_dao.create_tag("tag3").unwrap(); - &tag_dao.tag_file("test.jpg", tag1.id).unwrap(); - &tag_dao.tag_file("test.jpg", tag3.id).unwrap(); + let _ = &tag_dao.tag_file("test.jpg", tag1.id).unwrap(); + let _ = &tag_dao.tag_file("test.jpg", tag3.id).unwrap(); // Should get filtered since it doesn't have tag3 tag_dao.tag_file("some-other.jpg", tag1.id).unwrap(); diff --git a/src/main.rs b/src/main.rs index ad09092..7e1e1c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -216,7 +216,7 @@ async fn stream_video( debug!("Playlist: {}", playlist); // Extract video playlist dir to dotenv - if !playlist.starts_with("tmp") && is_valid_full_path(&app_state.base_path, playlist) != None { + if !playlist.starts_with("tmp") && is_valid_full_path(&app_state.base_path, playlist).is_some() { HttpResponse::BadRequest().finish() } else if let Ok(file) = NamedFile::open(playlist) { file.into_response(&request) @@ -363,7 +363,7 @@ fn create_thumbnails() { if ext == "mp4" || ext == "mov" { let relative_path = &entry.path().strip_prefix(&images).unwrap(); let thumb_path = Path::new(thumbnail_directory).join(relative_path); - std::fs::create_dir_all(&thumb_path.parent().unwrap()) + std::fs::create_dir_all(thumb_path.parent().unwrap_or_else(|| panic!("Thumbnail {:?} has no parent?", thumb_path))) .expect("Error creating directory"); debug!("Generating video thumbnail: {:?}", thumb_path); @@ -395,7 +395,7 @@ fn create_thumbnails() { .map(|(image, path)| { let relative_path = &path.strip_prefix(&images).unwrap(); let thumb_path = Path::new(thumbnail_directory).join(relative_path); - std::fs::create_dir_all(&thumb_path.parent().unwrap()) + std::fs::create_dir_all(thumb_path.parent().unwrap()) .expect("There was an issue creating directory"); debug!("Saving thumbnail: {:?}", thumb_path); image.save(thumb_path).expect("Failure saving thumbnail"); diff --git a/src/tags.rs b/src/tags.rs index a481b8f..164eace 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -92,10 +92,10 @@ async fn update_tags( ) -> impl Responder { let mut dao = tag_dao.lock().expect("Unable to get TagDao"); - return dao + dao .get_tags_for_path(&request.file_name) .and_then(|existing_tags| dao.get_all_tags().map(|all| (existing_tags, all))) - .and_then(|(existing_tags, all_tags)| { + .map(|(existing_tags, all_tags)| { let tags_to_remove = existing_tags .iter() .filter(|&t| !request.tag_ids.contains(&t.id)) @@ -107,7 +107,7 @@ async fn update_tags( tag.name, request.file_name ); dao.remove_tag(&tag.name, &request.file_name) - .expect(&format!("Unable to remove tag {:?}", &tag.name)); + .unwrap_or_else(|err| panic!("{:?} Unable to remove tag {:?}", err, &tag.name)); } let new_tags = all_tags @@ -131,9 +131,9 @@ async fn update_tags( .unwrap(); } - Ok(HttpResponse::Ok()) + HttpResponse::Ok() }) - .into_http_internal_err(); + .into_http_internal_err() } #[derive(Serialize, Queryable, Clone, Debug, PartialEq)] @@ -288,7 +288,7 @@ impl TagDao for SqliteTagDao { .and_then(|tagged_id| { debug!("Inserted tagged photo: {:?}", tagged_id); tagged_photo::table - .find(tagged_id as i32) + .find(tagged_id) .first(self.connection.borrow_mut()) .with_context(|| { format!( From ae2642a54490ef077177b32e56f4770b995774f6 Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sat, 25 Mar 2023 20:52:20 -0400 Subject: [PATCH 18/21] Fix warnings, and general code cleanup --- src/database/models.rs | 4 ++-- src/files.rs | 14 ++++++-------- src/main.rs | 39 ++++++++++++++++++--------------------- src/tags.rs | 38 +++++++++++++++++--------------------- 4 files changed, 43 insertions(+), 52 deletions(-) diff --git a/src/database/models.rs b/src/database/models.rs index dca23ba..d4f3dcb 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -2,7 +2,7 @@ use crate::database::schema::{favorites, users}; use serde::Serialize; #[derive(Insertable)] -#[table_name = "users"] +#[diesel(table_name = users)] pub struct InsertUser<'a> { pub username: &'a str, pub password: &'a str, @@ -17,7 +17,7 @@ pub struct User { } #[derive(Insertable)] -#[table_name = "favorites"] +#[diesel(table_name = favorites)] pub struct InsertFavorite<'a> { pub userid: &'a i32, pub path: &'a str, diff --git a/src/files.rs b/src/files.rs index dcd8cb5..a6ac3d9 100644 --- a/src/files.rs +++ b/src/files.rs @@ -184,13 +184,8 @@ impl FileSystemAccess for RealFileSystem { #[cfg(test)] mod tests { - use crate::database::connect; - use crate::tags::SqliteTagDao; - use actix_web::web::Data; - use diesel::{Connection, SqliteConnection}; use std::collections::HashMap; use std::env; - use std::ffi::OsStr; use std::fs::File; use super::*; @@ -342,7 +337,7 @@ mod tests { let mut tag_dao = SqliteTagDao::new(in_memory_db_connection()); let tag1 = tag_dao.create_tag("tag1").unwrap(); - let tag2 = tag_dao.create_tag("tag2").unwrap(); + let _tag2 = tag_dao.create_tag("tag2").unwrap(); let tag3 = tag_dao.create_tag("tag3").unwrap(); let _ = &tag_dao.tag_file("test.jpg", tag1.id).unwrap(); @@ -409,8 +404,11 @@ mod tests { ], ); - let request: Query = - Query::from_query("path=&tag_ids=1,3&tag_filter_mode=All").unwrap(); + let request: Query = Query::from_query(&*format!( + "path=&tag_ids={},{}&tag_filter_mode=All", + tag1.id, tag3.id + )) + .unwrap(); let response: HttpResponse = list_photos( claims, diff --git a/src/main.rs b/src/main.rs index 7e1e1c4..877f9dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -216,7 +216,8 @@ async fn stream_video( debug!("Playlist: {}", playlist); // Extract video playlist dir to dotenv - if !playlist.starts_with("tmp") && is_valid_full_path(&app_state.base_path, playlist).is_some() { + if !playlist.starts_with("tmp") && is_valid_full_path(&app_state.base_path, playlist).is_some() + { HttpResponse::BadRequest().finish() } else if let Ok(file) = NamedFile::open(playlist) { file.into_response(&request) @@ -354,27 +355,21 @@ fn create_thumbnails() { .filter_map(|entry| entry.ok()) .filter(|entry| entry.file_type().is_file()) .filter(|entry| { - debug!("{:?}", entry.path()); - if let Some(ext) = entry - .path() - .extension() - .and_then(|ext| ext.to_str().map(|ext| ext.to_lowercase())) - { - if ext == "mp4" || ext == "mov" { - let relative_path = &entry.path().strip_prefix(&images).unwrap(); - let thumb_path = Path::new(thumbnail_directory).join(relative_path); - std::fs::create_dir_all(thumb_path.parent().unwrap_or_else(|| panic!("Thumbnail {:?} has no parent?", thumb_path))) - .expect("Error creating directory"); + if is_video(entry) { + let relative_path = &entry.path().strip_prefix(&images).unwrap(); + let thumb_path = Path::new(thumbnail_directory).join(relative_path); + std::fs::create_dir_all( + thumb_path + .parent() + .unwrap_or_else(|| panic!("Thumbnail {:?} has no parent?", thumb_path)), + ) + .expect("Error creating directory"); - debug!("Generating video thumbnail: {:?}", thumb_path); - generate_video_thumbnail(entry.path(), &thumb_path); - false - } else { - is_image(entry) - } - } else { - error!("Unable to get extension for file: {:?}", entry.path()); + debug!("Generating video thumbnail: {:?}", thumb_path); + generate_video_thumbnail(entry.path(), &thumb_path); false + } else { + is_image(entry) } }) .filter(|entry| { @@ -490,7 +485,9 @@ fn main() -> std::io::Result<()> { .service(get_file_metadata) .add_feature(add_tag_services::<_, SqliteTagDao>) .app_data(app_data.clone()) - .app_data::>(Data::new(RealFileSystem::new(app_data.base_path.clone()))) + .app_data::>(Data::new(RealFileSystem::new( + app_data.base_path.clone(), + ))) .app_data::>>(Data::new(Mutex::new(user_dao))) .app_data::>>>(Data::new(Mutex::new(Box::new( favorites_dao, diff --git a/src/tags.rs b/src/tags.rs index 164eace..40bfb1e 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -92,8 +92,7 @@ async fn update_tags( ) -> impl Responder { let mut dao = tag_dao.lock().expect("Unable to get TagDao"); - dao - .get_tags_for_path(&request.file_name) + dao.get_tags_for_path(&request.file_name) .and_then(|existing_tags| dao.get_all_tags().map(|all| (existing_tags, all))) .map(|(existing_tags, all_tags)| { let tags_to_remove = existing_tags @@ -144,14 +143,14 @@ pub struct Tag { } #[derive(Insertable, Clone, Debug)] -#[table_name = "tags"] +#[diesel(table_name = tags)] pub struct InsertTag { pub name: String, pub created_time: i64, } #[derive(Insertable, Clone, Debug)] -#[table_name = "tagged_photo"] +#[diesel(table_name = tagged_photo)] pub struct InsertTaggedPhoto { pub tag_id: i32, pub photo_name: String, @@ -220,15 +219,13 @@ impl TagDao for SqliteTagDao { created_time: Utc::now().timestamp(), }) .execute(&mut self.connection) - .with_context(|| "Unable to insert tag in Sqlite") + .with_context(|| format!("Unable to insert tag {:?} in Sqlite", name)) .and_then(|_| { - debug!("Inserted tag: {:?}", name); - no_arg_sql_function!( - last_insert_rowid, - diesel::sql_types::Integer, - "Represents the SQL last_insert_row() function" - ); - diesel::select(last_insert_rowid) + info!("Inserted tag: {:?}", name); + sql_function! { + fn last_insert_rowid() -> diesel::sql_types::Integer; + } + diesel::select(last_insert_rowid()) .get_result::(&mut self.connection) .with_context(|| "Unable to get last inserted tag from Sqlite") }) @@ -238,7 +235,9 @@ impl TagDao for SqliteTagDao { .filter(tags::id.eq(id)) .select((tags::id, tags::name, tags::created_time)) .get_result::(self.connection.borrow_mut()) - .with_context(|| "Unable to get tagged photo from Sqlite") + .with_context(|| { + format!("Unable to get tagged photo with id: {:?} from Sqlite", id) + }) }) } @@ -275,18 +274,15 @@ impl TagDao for SqliteTagDao { .execute(self.connection.borrow_mut()) .with_context(|| format!("Unable to tag file {:?} in sqlite", path)) .and_then(|_| { - debug!("Inserted tagged photo: {:#} -> {:?}", tag_id, path); - no_arg_sql_function!( - last_insert_rowid, - diesel::sql_types::Integer, - "Represents the SQL last_insert_row() function" - ); - diesel::select(last_insert_rowid) + info!("Inserted tagged photo: {:#} -> {:?}", tag_id, path); + sql_function! { + fn last_insert_rowid() -> diesel::sql_types::Integer; + } + diesel::select(last_insert_rowid()) .get_result::(&mut self.connection) .with_context(|| "Unable to get last inserted tag from Sqlite") }) .and_then(|tagged_id| { - debug!("Inserted tagged photo: {:?}", tagged_id); tagged_photo::table .find(tagged_id) .first(self.connection.borrow_mut()) From a9109fc52d9d52bb89563715d27cf652cfe5c4ee Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sat, 25 Mar 2023 20:52:51 -0400 Subject: [PATCH 19/21] Add the ability to query files by tags independent of file path --- src/data/mod.rs | 2 +- src/files.rs | 29 +++++++++++++++++++++++++++++ src/tags.rs | 18 ++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/data/mod.rs b/src/data/mod.rs index bc93e7e..063e8bf 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -107,7 +107,7 @@ pub struct FilesRequest { pub tag_filter_mode: Option, } -#[derive(Copy, Clone, Deserialize)] +#[derive(Copy, Clone, Deserialize, PartialEq)] pub enum FilterMode { Any, All, diff --git a/src/files.rs b/src/files.rs index a6ac3d9..8f64730 100644 --- a/src/files.rs +++ b/src/files.rs @@ -16,6 +16,7 @@ use log::{debug, error}; use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse}; use crate::AppState; +use crate::error::IntoHttpError; use crate::tags::TagDao; use path_absolutize::*; @@ -28,6 +29,34 @@ pub async fn list_photos( ) -> HttpResponse { let path = &req.path; + if let (Some(tag_ids), Some(filter_mode)) = (&req.tag_ids, &req.tag_filter_mode) { + if *filter_mode == FilterMode::All { + let mut dao = tag_dao.lock().expect("Unable to get TagDao"); + let tag_ids = tag_ids + .split(',') + .filter_map(|t| t.parse().ok()) + .collect::>(); + + return dao + .get_files_with_tag_ids(tag_ids.clone()) + .map(|tagged_file| { + tagged_file + .iter() + .map(|photo_name| photo_name.clone()) + .collect() + }) + .context(format!("Failed to files with tag_ids: {:?}", tag_ids)) + .map(|tagged_files| { + HttpResponse::Ok().json(PhotosResponse { + photos: tagged_files, + dirs: vec![], + }) + }) + .into_http_internal_err() + .unwrap_or_else(|e| e.error_response()); + } + } + if let Ok(files) = file_system.get_files_for_path(path) { debug!("Valid path: {:?}", path); diff --git a/src/tags.rs b/src/tags.rs index 40bfb1e..da1c268 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -177,6 +177,7 @@ pub trait TagDao { fn create_tag(&mut self, name: &str) -> anyhow::Result; fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result>; fn tag_file(&mut self, path: &str, tag_id: i32) -> anyhow::Result; + fn get_files_with_tag_ids(&mut self, tag_ids: Vec) -> anyhow::Result>; } pub struct SqliteTagDao { @@ -294,6 +295,19 @@ impl TagDao for SqliteTagDao { }) }) } + + fn get_files_with_tag_ids(&mut self, tag_ids: Vec) -> anyhow::Result> { + use diesel::dsl::*; + + tagged_photo::table + .filter(tagged_photo::tag_id.eq_any(tag_ids.clone())) + .group_by(tagged_photo::photo_name) + .select((tagged_photo::photo_name, count(tagged_photo::tag_id))) + .having(count(tagged_photo::tag_id).eq(tag_ids.len() as i64)) + .select(tagged_photo::photo_name) + .get_results::(&mut self.connection) + .with_context(|| format!("Unable to get Tagged photos with ids: {:?}", tag_ids)) + } } #[cfg(test)] @@ -407,6 +421,10 @@ mod tests { Err(NotFound.into()) } } + + fn get_files_with_tag_ids(&mut self, _tag_ids: Vec) -> anyhow::Result> { + todo!() + } } #[actix_rt::test] From 7863990c689f4788366bd9361c549534eb52678c Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Sat, 25 Mar 2023 20:59:32 -0400 Subject: [PATCH 20/21] Run clippy --- src/auth.rs | 4 ++-- src/files.rs | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 5c64312..1e5afc5 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -5,7 +5,7 @@ use actix_web::{ }; use chrono::{Duration, Utc}; use jsonwebtoken::{encode, EncodingKey, Header}; -use log::{debug, error}; +use log::{error, info}; use std::sync::Mutex; use crate::{ @@ -36,7 +36,7 @@ pub async fn login( creds: Json, user_dao: web::Data>, ) -> HttpResponse { - debug!("Logging in: {}", creds.username); + info!("Logging in: {}", creds.username); let mut user_dao = user_dao.lock().expect("Unable to get UserDao"); diff --git a/src/files.rs b/src/files.rs index 8f64730..977d45f 100644 --- a/src/files.rs +++ b/src/files.rs @@ -39,12 +39,6 @@ pub async fn list_photos( return dao .get_files_with_tag_ids(tag_ids.clone()) - .map(|tagged_file| { - tagged_file - .iter() - .map(|photo_name| photo_name.clone()) - .collect() - }) .context(format!("Failed to files with tag_ids: {:?}", tag_ids)) .map(|tagged_files| { HttpResponse::Ok().json(PhotosResponse { From 8bcd837038566276be16e08bb7c7d6f47aa75e9f Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Mon, 3 Apr 2023 08:41:03 -0400 Subject: [PATCH 21/21] Fix file upload Add a flag for new files so we can skip the exists check when seeing if the new file is within the base directory. --- src/files.rs | 28 +++++++++++++++------------- src/main.rs | 9 +++++---- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/files.rs b/src/files.rs index 977d45f..4c6b76c 100644 --- a/src/files.rs +++ b/src/files.rs @@ -137,6 +137,7 @@ pub fn is_image_or_video(path: &Path) -> bool { pub fn is_valid_full_path + Debug + AsRef>( base: &P, path: &P, + new_file: bool, ) -> Option { debug!("Base: {:?}. Path: {:?}", base, path); @@ -150,7 +151,7 @@ pub fn is_valid_full_path + Debug + AsRef>( path }; - match is_path_above_base_dir(base, &mut path) { + match is_path_above_base_dir(base, &mut path, new_file) { Ok(path) => Some(path), Err(e) => { error!("{}", e); @@ -162,6 +163,7 @@ pub fn is_valid_full_path + Debug + AsRef>( fn is_path_above_base_dir + Debug>( base: P, full_path: &mut PathBuf, + new_file: bool, ) -> anyhow::Result { full_path .absolutize() @@ -169,7 +171,7 @@ fn is_path_above_base_dir + Debug>( .map_or_else( |e| Err(anyhow!(e)), |p| { - if p.starts_with(base) && p.exists() { + if p.starts_with(base) && (new_file || p.exists()) { Ok(p.into_owned()) } else if !p.exists() { Err(anyhow!("Path does not exist: {:?}", p)) @@ -196,7 +198,7 @@ impl RealFileSystem { impl FileSystemAccess for RealFileSystem { fn get_files_for_path(&self, path: &str) -> anyhow::Result> { - is_valid_full_path(&PathBuf::from(&self.base_path), &PathBuf::from(path)) + is_valid_full_path(&PathBuf::from(&self.base_path), &PathBuf::from(path), false) .map(|path| { debug!("Valid path: {:?}", path); list_files(&path).unwrap_or_default() @@ -457,23 +459,23 @@ mod tests { #[test] fn directory_traversal_test() { let base = env::temp_dir(); - assert_eq!(None, is_valid_full_path(&base, &PathBuf::from("../"))); - assert_eq!(None, is_valid_full_path(&base, &PathBuf::from(".."))); + assert_eq!(None, is_valid_full_path(&base, &PathBuf::from("../"), false)); + assert_eq!(None, is_valid_full_path(&base, &PathBuf::from(".."), false)); assert_eq!( None, - is_valid_full_path(&base, &PathBuf::from("fake/../../../")) + is_valid_full_path(&base, &PathBuf::from("fake/../../../"), false) ); assert_eq!( None, - is_valid_full_path(&base, &PathBuf::from("../../../etc/passwd")) + is_valid_full_path(&base, &PathBuf::from("../../../etc/passwd"), false) ); assert_eq!( None, - is_valid_full_path(&base, &PathBuf::from("..//etc/passwd")) + is_valid_full_path(&base, &PathBuf::from("..//etc/passwd"), false) ); assert_eq!( None, - is_valid_full_path(&base, &PathBuf::from("../../etc/passwd")) + is_valid_full_path(&base, &PathBuf::from("../../etc/passwd"), false) ); } @@ -484,7 +486,7 @@ mod tests { test_file.push("test.png"); File::create(test_file).unwrap(); - assert!(is_valid_full_path(&base, &PathBuf::from("test.png")).is_some()); + assert!(is_valid_full_path(&base, &PathBuf::from("test.png"), false).is_some()); } #[test] @@ -495,7 +497,7 @@ mod tests { let mut test_file = PathBuf::from(&base); test_file.push(path); - assert_eq!(None, is_valid_full_path(&base, &test_file)); + assert_eq!(None, is_valid_full_path(&base, &test_file, false)); } #[test] @@ -505,11 +507,11 @@ mod tests { test_file.push("test.png"); File::create(&test_file).unwrap(); - assert!(is_valid_full_path(&base, &test_file).is_some()); + assert!(is_valid_full_path(&base, &test_file, false).is_some()); assert_eq!( Some(PathBuf::from("/tmp/test.png")), - is_valid_full_path(&base, &PathBuf::from("/tmp/test.png")) + is_valid_full_path(&base, &PathBuf::from("/tmp/test.png"), false) ); } diff --git a/src/main.rs b/src/main.rs index 877f9dc..95aa587 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ async fn get_image( req: web::Query, app_state: Data, ) -> impl Responder { - if let Some(path) = is_valid_full_path(&app_state.base_path, &req.path) { + if let Some(path) = is_valid_full_path(&app_state.base_path, &req.path, false) { if req.size.is_some() { let relative_path = path .strip_prefix(&app_state.base_path) @@ -108,7 +108,7 @@ async fn get_file_metadata( path: web::Query, app_state: Data, ) -> impl Responder { - match is_valid_full_path(&app_state.base_path, &path.path) + match is_valid_full_path(&app_state.base_path, &path.path, false) .ok_or_else(|| ErrorKind::InvalidData.into()) .and_then(File::open) .and_then(|file| file.metadata()) @@ -159,6 +159,7 @@ async fn upload_image( if let Some(full_path) = is_valid_full_path( &app_state.base_path, &full_path.to_str().unwrap().to_string(), + true, ) { if !full_path.is_file() && is_image_or_video(&full_path) { let mut file = File::create(full_path).unwrap(); @@ -188,7 +189,7 @@ async fn generate_video( if let Some(name) = filename.file_stem() { let filename = name.to_str().expect("Filename should convert to string"); let playlist = format!("tmp/{}.m3u8", filename); - if let Some(path) = is_valid_full_path(&app_state.base_path, &body.path) { + if let Some(path) = is_valid_full_path(&app_state.base_path, &body.path, false) { if let Ok(child) = create_playlist(path.to_str().unwrap(), &playlist).await { app_state .stream_manager @@ -216,7 +217,7 @@ async fn stream_video( debug!("Playlist: {}", playlist); // Extract video playlist dir to dotenv - if !playlist.starts_with("tmp") && is_valid_full_path(&app_state.base_path, playlist).is_some() + if !playlist.starts_with("tmp") && is_valid_full_path(&app_state.base_path, playlist, false).is_some() { HttpResponse::BadRequest().finish() } else if let Ok(file) = NamedFile::open(playlist) {