From 32a0ecc1e64676e110858bd62b60fa0b526666d5 Mon Sep 17 00:00:00 2001 From: Ben Sherriff Date: Fri, 12 Jul 2024 12:19:08 -0400 Subject: [PATCH] Updated versions --- .env.TEMPLATE => .env | 0 .gitignore | 2 - Makefile | 2 + service/{.env.TEMPLATE => .env} | 4 +- service/Cargo.lock | 1030 +++++++++++------ service/Cargo.toml | 47 +- service/Makefile | 59 +- service/docker-compose.yml | 20 +- service/rust-toolchain.toml | 3 + service/rustfmt.toml | 3 + service/src/airports/mod.rs | 2 +- service/src/airports/model.rs | 121 +- service/src/airports/routes.rs | 160 ++- service/src/auth/mod.rs | 55 +- service/src/auth/model.rs | 63 +- service/src/auth/routes.rs | 294 +++-- service/src/db/mod.rs | 60 +- service/src/error_handler.rs | 55 +- service/src/main.rs | 9 +- service/src/metars/mod.rs | 2 +- service/src/metars/model.rs | 292 +++-- service/src/metars/routes.rs | 33 +- service/src/scheduler.rs | 39 +- service/src/users/mod.rs | 2 +- service/src/users/routes.rs | 107 +- ui/{.env.TEMPLATE => .env} | 2 +- ui/Makefile | 2 + ui/src/api/airport.ts | 3 + ui/src/components/Header/UserMenu.tsx | 114 ++ ui/src/components/Header/index.tsx | 150 +-- .../Header/{header.css => styles.css} | 14 +- ui/src/components/Metars/MapTiles.tsx | 9 +- 32 files changed, 1763 insertions(+), 995 deletions(-) rename .env.TEMPLATE => .env (100%) rename service/{.env.TEMPLATE => .env} (89%) create mode 100644 service/rust-toolchain.toml create mode 100644 service/rustfmt.toml rename ui/{.env.TEMPLATE => .env} (71%) create mode 100644 ui/src/components/Header/UserMenu.tsx rename ui/src/components/Header/{header.css => styles.css} (63%) diff --git a/.env.TEMPLATE b/.env similarity index 100% rename from .env.TEMPLATE rename to .env diff --git a/.gitignore b/.gitignore index df08952..fba87f6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,3 @@ yarn-error.log* target/ dist/ -*.env -.env.local diff --git a/Makefile b/Makefile index 5b5b419..30bd92e 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ SHELL := /bin/bash GIT_HASH ?= $(shell git log --format="%h" -n 1) include .env +-include .env.local +export .PHONY: help build start stop lint diff --git a/service/.env.TEMPLATE b/service/.env similarity index 89% rename from service/.env.TEMPLATE rename to service/.env index b282fef..31007e4 100644 --- a/service/.env.TEMPLATE +++ b/service/.env @@ -11,7 +11,7 @@ DATABASE_PORT=5432 REDIS_HOST=localhost REDIS_PORT=6379 -MINIO_ROOT_USER=weather +MINIO_ROOT_USER=aviation MINIO_ROOT_PASSWORD= MINIO_HOST=localhost MINIO_PORT=9000 @@ -22,6 +22,6 @@ SERVICE_PORT=5000 KEYS_DIR_PATH= ACCESS_TOKEN_MAXAGE=5 -REFRESH_TOKEN_MAXAGE=30 +REFRESH_TOKEN_MAXAGE=1440 GOV_API_URL=https://aviationweather.gov/cgi-bin/data diff --git a/service/Cargo.lock b/service/Cargo.lock index 67d4453..5e21747 100644 --- a/service/Cargo.lock +++ b/service/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-cors" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e" +checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" dependencies = [ "actix-utils", "actix-web", @@ -36,16 +36,16 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.4.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" +checksum = "3ae682f693a9cd7b058f2b0b5d9a6d7728a8555779bedbbc35dd88528611d020" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", - "ahash 0.8.3", - "base64 0.21.4", + "ahash", + "base64 0.22.1", "bitflags 2.4.0", "brotli", "bytes", @@ -54,8 +54,8 @@ dependencies = [ "encoding_rs", "flate2", "futures-core", - "h2", - "http", + "h2 0.3.26", + "http 0.2.9", "httparse", "httpdate", "itoa", @@ -80,19 +80,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] name = "actix-multipart" -version = "0.6.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b960e2aea75f49c8f069108063d12a48d329fc8b60b786dfc7552a9d5918d2d" +checksum = "d5118a26dee7e34e894f7e85aa0ee5080ae4c18bf03c0e30d49a80e418f00a53" dependencies = [ "actix-multipart-derive", "actix-utils", "actix-web", - "bytes", "derive_more", "futures-core", "futures-util", @@ -101,6 +100,7 @@ dependencies = [ "log", "memchr", "mime", + "rand", "serde", "serde_json", "serde_plain", @@ -110,26 +110,28 @@ dependencies = [ [[package]] name = "actix-multipart-derive" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0a77f836d869f700e5b47ac7c3c8b9c8bc82e4aec861954c6198abee3ebd4d" +checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b" dependencies = [ "darling", "parse-size", "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] name = "actix-router" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", - "http", + "cfg-if", + "http 0.2.9", "regex", + "regex-lite", "serde", "tracing", ] @@ -156,7 +158,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2 0.5.4", + "socket2 0.5.7", "tokio", "tracing", ] @@ -184,9 +186,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.4.0" +version = "4.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" +checksum = "1988c02af8d2b718c05bc4aeb6a66395b7cdf32858c2c71131e5637a8c05a9ff" dependencies = [ "actix-codec", "actix-http", @@ -197,7 +199,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash 0.8.3", + "ahash", "bytes", "bytestring", "cfg-if", @@ -213,36 +215,37 @@ dependencies = [ "once_cell", "pin-project-lite", "regex", + "regex-lite", "serde", "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.4", + "socket2 0.5.7", "time", "url", ] [[package]] name = "actix-web-codegen" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] name = "actix-web-httpauth" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775" +checksum = "456348ed9dcd72a13a1f4a660449fafdecee9ac8205552e286809eb5b0b29bd3" dependencies = [ "actix-utils", "actix-web", - "base64 0.21.4", + "base64 0.22.1", "futures-core", "futures-util", "log", @@ -266,25 +269,15 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -326,6 +319,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "arc-swap" version = "1.6.0" @@ -334,9 +376,9 @@ checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "argon2" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", @@ -352,16 +394,22 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] -name = "attohttpc" -version = "0.22.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f77d243921b0979fbbd728dd2d5162e68ac8252976797c24eb5b3a6af9090dc" dependencies = [ - "http", + "http 0.2.9", "log", "native-tls", "serde", @@ -377,12 +425,12 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "aws-creds" -version = "0.34.1" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3776743bb68d4ad02ba30ba8f64373f1be4e082fe47651767171ce75bb2f6cf5" +checksum = "390ad3b77f3e21e01a4a0355865853b681daf1988510b0b15e31c0c4ae7eb0f6" dependencies = [ "attohttpc", - "dirs", + "home", "log", "quick-xml", "rust-ini", @@ -416,18 +464,18 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -466,9 +514,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.3.4" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -477,9 +525,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -530,9 +578,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -540,9 +588,15 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.6", ] +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "combine" version = "4.6.6" @@ -557,6 +611,26 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -592,9 +666,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -608,6 +682,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -639,7 +719,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] @@ -650,7 +730,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] @@ -677,9 +757,9 @@ dependencies = [ [[package]] name = "diesel" -version = "2.1.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c8a2cb22327206568569e5a45bb5a2c946455efdd76e24d15b7e82171af95e" +checksum = "62d6dcd069e7b5fe49a302411f759d4cf1cf2c27fe798ef46fb8baefc053dd2b" dependencies = [ "bitflags 2.4.0", "byteorder", @@ -694,21 +774,22 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.1.1" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e054665eaf6d97d1e7125512bb2d35d07c73ac86cc6920174cb42d1ab697a554" +checksum = "59de76a222c2b8059f789cbe07afbfd8deb8c31dd0bc2a21f85e256c1def8259" dependencies = [ "diesel_table_macro_syntax", + "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] name = "diesel_migrations" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac" +checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" dependencies = [ "diesel", "migrations_internals", @@ -717,11 +798,11 @@ dependencies = [ [[package]] name = "diesel_table_macro_syntax" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn 2.0.32", + "syn 2.0.71", ] [[package]] @@ -735,31 +816,14 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dlv-list" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] [[package]] name = "dotenv" @@ -767,6 +831,26 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dsl_auto_type" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0892a17df262a24294c382f0d5997571006e7a4348b4327557c4ff1cd4a8bccc" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -777,16 +861,26 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.10.0" +name = "env_filter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ - "humantime", - "is-terminal", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", ] [[package]] @@ -803,7 +897,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -879,9 +973,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -889,9 +983,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" @@ -906,38 +1000,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -968,8 +1062,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -980,17 +1076,36 @@ checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 1.9.3", + "http 0.2.9", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -999,12 +1114,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.7", -] +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" @@ -1013,10 +1125,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] -name = "hermit-abi" -version = "0.3.2" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -1033,6 +1145,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.9" @@ -1044,6 +1165,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -1051,7 +1183,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1083,9 +1238,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -1097,6 +1251,43 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1104,12 +1295,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.27", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1149,16 +1376,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.0.0" @@ -1176,15 +1393,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] -name = "is-terminal" -version = "0.4.9" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itoa" @@ -1212,11 +1424,12 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.1.0" +version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155c4d7e39ad04c172c5e3a99c434ea3b4a7ba7960b38ecd562b270b097cce09" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ "base64 0.21.4", + "js-sys", "pem", "ring", "serde", @@ -1232,9 +1445,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -1242,17 +1455,6 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" -[[package]] -name = "libredox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.0", - "libc", - "redox_syscall 0.4.1", -] - [[package]] name = "linux-raw-sys" version = "0.4.11" @@ -1289,9 +1491,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "maybe-async" @@ -1318,9 +1520,9 @@ checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "migrations_internals" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" +checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" dependencies = [ "serde", "toml", @@ -1328,9 +1530,9 @@ dependencies = [ [[package]] name = "migrations_macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" +checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" dependencies = [ "migrations_internals", "proc-macro2", @@ -1363,14 +1565,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1459,7 +1661,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] @@ -1482,12 +1684,12 @@ dependencies = [ [[package]] name = "ordered-multimap" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" dependencies = [ "dlv-list", - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -1508,9 +1710,9 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -1569,7 +1771,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] @@ -1592,9 +1794,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "postgis_diesel" -version = "2.2.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8253e9ecb290114a2cd4b7412ac03ebf92814daa89a96390b19f2933288d52d" +checksum = "f400a855195746f648f50fad9cb9f1246144809ef769850f6b5122fcd4cf48a6" dependencies = [ "byteorder", "diesel", @@ -1618,18 +1820,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.26.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", "serde", @@ -1637,9 +1839,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1687,9 +1889,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.23.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec" dependencies = [ "arc-swap", "async-trait", @@ -1703,7 +1905,7 @@ dependencies = [ "r2d2", "ryu", "sha1_smol", - "socket2 0.4.9", + "socket2 0.5.7", "tokio", "tokio-retry", "tokio-util", @@ -1719,31 +1921,11 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" -version = "1.10.2" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1753,15 +1935,21 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -1770,20 +1958,23 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.21" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78fdbab6a7e1d7b13cc8ff10197f47986b41c639300cc3c8158cac7847c9bbef" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.21.4", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls", + "hyper-tls 0.6.0", + "hyper-util", "ipnet", "js-sys", "log", @@ -1792,18 +1983,18 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", - "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", "winreg", ] @@ -1819,14 +2010,14 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "rust-ini" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" dependencies = [ "cfg-if", "ordered-multimap", @@ -1834,33 +2025,37 @@ dependencies = [ [[package]] name = "rust-s3" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2ac5ff6acfbe74226fa701b5ef793aaa054055c13ebb7060ad36942956e027" +checksum = "c6679da8efaf4c6f0c161de0961dfe95fb6e9049c398d6fbdada2639f053aedb" dependencies = [ "async-trait", "aws-creds", "aws-region", - "base64 0.13.1", + "base64 0.21.4", "bytes", "cfg-if", "futures", "hex", "hmac", - "http", + "http 0.2.9", + "hyper 0.14.27", + "hyper-tls 0.5.0", "log", "maybe-async", "md5", "minidom", + "native-tls", "percent-encoding", "quick-xml", - "reqwest", "serde", "serde_derive", + "serde_json", "sha2", "thiserror", "time", "tokio", + "tokio-native-tls", "tokio-stream", "url", ] @@ -1890,7 +2085,47 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustls" +version = "0.23.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1922,7 +2157,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1971,29 +2206,29 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -2011,9 +2246,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -2038,6 +2273,7 @@ dependencies = [ "actix-multipart", "actix-web", "actix-web-httpauth", + "ahash", "argon2", "chrono", "diesel", @@ -2054,7 +2290,6 @@ dependencies = [ "regex", "reqwest", "rust-s3", - "rustix", "serde", "serde_json", "tokio", @@ -2121,9 +2356,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smartstring" @@ -2148,12 +2383,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2193,15 +2428,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.32" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2231,18 +2472,9 @@ checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall", "rustix", - "windows-sys", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", + "windows-sys 0.48.0", ] [[package]] @@ -2262,7 +2494,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] @@ -2293,6 +2525,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2310,9 +2551,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -2321,20 +2562,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.7", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", ] [[package]] @@ -2358,6 +2599,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -2385,9 +2637,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", @@ -2397,26 +2649,47 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.22.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" dependencies = [ - "indexmap 2.0.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -2495,10 +2768,16 @@ dependencies = [ ] [[package]] -name = "uuid" -version = "1.4.1" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "serde", @@ -2552,7 +2831,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", "wasm-bindgen-shared", ] @@ -2586,7 +2865,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2597,19 +2876,6 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" -[[package]] -name = "wasm-streams" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.64" @@ -2636,15 +2902,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2657,7 +2914,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2666,7 +2923,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2675,13 +2941,29 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 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", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2690,36 +2972,78 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2727,50 +3051,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "winnow" -version = "0.5.15" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] -name = "zstd" -version = "0.12.4" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "6.0.6" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" dependencies = [ - "libc", "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/service/Cargo.toml b/service/Cargo.toml index 7cb8451..240f22a 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -10,28 +10,29 @@ license = "GPL-3.0-or-later" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -actix-web = "4.4.0" -actix-cors = "0.6.4" -actix-web-httpauth = "0.8.1" -actix-multipart = "0.6.1" -chrono = { version = "0.4.31", features = ["serde"] } +actix-web = "4.8.0" +actix-cors = "0.7.0" +actix-web-httpauth = "0.8.2" +actix-multipart = "0.7.2" +chrono = { version = "0.4.38", features = ["serde"] } dotenv = "0.15.0" -diesel = { version = "2.1.2", features = ["postgres", "r2d2", "uuid", "chrono", "serde_json"] } -postgis_diesel = { version = "2.2.1", features = ["serde"] } -diesel_migrations = { version = "2.1.0", features = ["postgres"] } -env_logger = "0.10.0" -lazy_static = "1.4.0" +diesel = { version = "2.2.1", features = ["postgres", "r2d2", "uuid", "chrono", "serde_json"] } +postgis_diesel = { version = "2.4.0", features = ["serde"] } +diesel_migrations = { version = "2.2.0", features = ["postgres"] } +env_logger = "0.11.3" +lazy_static = "1.5.0" r2d2 = "0.8.10" -reqwest = "0.11.21" -serde = {version = "1.0.188", features = ["derive"]} -serde_json = "1.0.107" -tokio = { version = "1.32.0", features = ["macros", "rt", "time"] } -uuid = { version = "1.4.1", features = ["serde", "v4"] } -log = "0.4.20" -argon2 = "0.5.2" -jsonwebtoken = "9.0.0" -redis = { version = "0.23.3", features = ["tokio-comp", "connection-manager", "r2d2"] } -rustix = "0.38.19" # https://github.com/imsnif/bandwhich/issues/284 -regex = "1.10.2" -futures-util = "0.3.29" -rust-s3 = "0.33.0" +reqwest = "0.12.5" +serde = {version = "1.0.204", features = ["derive"]} +serde_json = "1.0.120" +tokio = { version = "1.38.0", features = ["macros", "rt", "time"] } +uuid = { version = "1.10.0", features = ["serde", "v4"] } +log = "0.4.22" +argon2 = "0.5.3" +jsonwebtoken = "9.3.0" +redis = { version = "0.25.4", features = ["tokio-comp", "connection-manager", "r2d2"] } +#rustix = "0.38.19" # https://github.com/imsnif/bandwhich/issues/284 +ahash = "0.8.11" # https://github.com/tkaitchuck/aHash/issues/200 +regex = "1.10.5" +futures-util = "0.3.30" +rust-s3 = "0.34.0" diff --git a/service/Makefile b/service/Makefile index 6ef868c..37dc699 100644 --- a/service/Makefile +++ b/service/Makefile @@ -4,6 +4,8 @@ SHELL := /bin/bash GIT_HASH ?= $(shell git log --format="%h" -n 1) include .env +-include .env.local +export .PHONY: help build start stop lint @@ -12,40 +14,49 @@ help: ## This info @cat Makefile | grep -E '^[a-zA-Z\/_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @echo -build: ## Build the Docker image - docker compose build +format: ## Format code + @echo "Formatting code..." + @cargo fmt + @echo "Format complete" -tag: ## Tag the Docker image - docker tag aviation-service:latest aviation-service:${GIT_HASH} +run: ## Run the service + @cargo run -utils: ## Start the utils - docker compose up -d db - docker compose up -d redis - docker compose up -d minio +clean: ## Cleanup + @echo "Cleaning up..." + @cargo clean + @rm -rf ../keys + @echo "Cleanup complete" up: ## Start the Docker containers - docker compose up -d + @docker compose --profile backend up -d down: ## Stop the Docker containers - docker compose down + @docker compose --profile backend down connect: ## Connect to the PSQL DB - docker exec -it ${DATABASE_CONTAINER} psql -U postgres + @docker exec -it ${DATABASE_CONTAINER} psql -U postgres -clean: ## Cleanup Docker containers - docker compose down && \ - docker image rm aviation-service || \ - docker network rm aviation-frontend || \ - docker network rm aviation-backend +docker-build: ## Build the Docker image + @docker compose build + +docker-tag: ## Tag the Docker image + @docker tag aviation-service:latest aviation-service:${GIT_HASH} + +docker-run: ## Start the service + @docker compose --profile service up -d + +docker-clean: ## Cleanup Docker containers + @docker compose --profile backend --profile service down -v clean-db: ## Remove database - docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "DROP DATABASE IF EXISTS \"${DATABASE_NAME}\";"' - docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "CREATE DATABASE \"${DATABASE_NAME}\";"' || true + @docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "DROP DATABASE IF EXISTS \"${DATABASE_NAME}\";"' + @docker exec -i ${DATABASE_CONTAINER} sh -c 'PGPASSWORD=${DATABASE_PASSWORD} psql -U ${DATABASE_USER} -d postgres -c "CREATE DATABASE \"${DATABASE_NAME}\";"' || true -generate: ## Generate RSA keys - mkdir ../keys/ - openssl genrsa -out ../keys/access_private_key.pem 4096 - openssl rsa -in ../keys/access_private_key.pem -pubout -outform PEM -out ../keys/access_public_key.pem - openssl genrsa -out ../keys/refresh_private_key.pem 4096 - openssl rsa -in ../keys/refresh_private_key.pem -pubout -outform PEM -out ../keys/refresh_public_key.pem +generate-keys: ## Generate RSA keys + @mkdir ../keys/ + @openssl genrsa -out ../keys/access_private_key.pem 4096 + @openssl rsa -in ../keys/access_private_key.pem -pubout -outform PEM -out ../keys/access_public_key.pem + @openssl genrsa -out ../keys/refresh_private_key.pem 4096 + @openssl rsa -in ../keys/refresh_private_key.pem -pubout -outform PEM -out ../keys/refresh_public_key.pem \ No newline at end of file diff --git a/service/docker-compose.yml b/service/docker-compose.yml index 9e5856d..a0d4c6f 100644 --- a/service/docker-compose.yml +++ b/service/docker-compose.yml @@ -1,12 +1,15 @@ -version: '3' +x-env_file: &env + - path: .env + required: true + - path: .env.local + required: false name: aviation services: db: image: postgis/postgis:latest container_name: aviation-db - env_file: - - .env + env_file: *env environment: POSTGRES_USER: ${DATABASE_USER} POSTGRES_PASSWORD: ${DATABASE_PASSWORD} @@ -18,6 +21,8 @@ services: - "${DATABASE_PORT:-5432}:5432" networks: - backend + profiles: + - backend restart: unless-stopped redis: image: redis:latest @@ -28,6 +33,8 @@ services: - ${REDIS_PORT:-6379}:6379 networks: - backend + profiles: + - backend restart: unless-stopped minio: image: minio/minio @@ -42,13 +49,14 @@ services: - ${MINIO_PORT_INTERNAL:-9001}:9001 networks: - backend + profiles: + - backend command: server --console-address ":9001" /data restart: unless-stopped service: container_name: aviation-service - env_file: - - .env + env_file: *env environment: DATABASE_HOST: db DATABASE_PORT: 5432 @@ -70,6 +78,8 @@ services: networks: - frontend - backend + profiles: + - service restart: unless-stopped volumes: diff --git a/service/rust-toolchain.toml b/service/rust-toolchain.toml new file mode 100644 index 0000000..02d090f --- /dev/null +++ b/service/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "clippy"] \ No newline at end of file diff --git a/service/rustfmt.toml b/service/rustfmt.toml new file mode 100644 index 0000000..76f62b4 --- /dev/null +++ b/service/rustfmt.toml @@ -0,0 +1,3 @@ +indent_style = "Block" +reorder_imports = false +tab_spaces = 2 \ No newline at end of file diff --git a/service/src/airports/mod.rs b/service/src/airports/mod.rs index 6666fdc..6fbb137 100644 --- a/service/src/airports/mod.rs +++ b/service/src/airports/mod.rs @@ -2,4 +2,4 @@ mod model; mod routes; pub use model::*; -pub use routes::init_routes; \ No newline at end of file +pub use routes::init_routes; diff --git a/service/src/airports/model.rs b/service/src/airports/model.rs index a730998..72cad50 100644 --- a/service/src/airports/model.rs +++ b/service/src/airports/model.rs @@ -67,8 +67,8 @@ impl Into for Airport { error!("{}", err); serde_json::Value::Null } - } - } + }, + }; } } @@ -95,7 +95,7 @@ pub enum AirportCategory { #[serde(rename = "balloonport")] Balloonport, #[serde(rename = "unknown")] - Unknown + Unknown, } impl FromStr for AirportCategory { @@ -109,7 +109,7 @@ impl FromStr for AirportCategory { "closed" => Ok(AirportCategory::Closed), "seaplane_base" => Ok(AirportCategory::Seaplane), "balloonport" => Ok(AirportCategory::Balloonport), - _ => Ok(AirportCategory::Unknown) + _ => Ok(AirportCategory::Unknown), } } } @@ -124,7 +124,7 @@ impl Display for AirportCategory { AirportCategory::Closed => write!(f, "closed"), AirportCategory::Seaplane => write!(f, "seaplane_base"), AirportCategory::Balloonport => write!(f, "balloonport"), - AirportCategory::Unknown => write!(f, "unknown") + AirportCategory::Unknown => write!(f, "unknown"), } } } @@ -141,7 +141,7 @@ pub struct QueryAirport { pub municipality: String, pub has_metar: bool, pub point: Point, - pub data: serde_json::Value + pub data: serde_json::Value, } #[derive(Debug)] @@ -151,7 +151,8 @@ pub struct QueryFilters { pub bounds: Option>, pub categories: Option>, pub order_field: Option, - pub order_by: Option + pub order_by: Option, + pub has_metar: Option, } impl Default for QueryFilters { @@ -162,7 +163,8 @@ impl Default for QueryFilters { bounds: None, categories: None, order_field: None, - order_by: None + order_by: None, + has_metar: None, } } } @@ -170,7 +172,7 @@ impl Default for QueryFilters { #[derive(Debug)] pub enum QueryOrderBy { Asc, - Desc + Desc, } impl FromStr for QueryOrderBy { @@ -179,7 +181,7 @@ impl FromStr for QueryOrderBy { match s { "asc" => Ok(QueryOrderBy::Asc), "desc" => Ok(QueryOrderBy::Desc), - _ => Err(()) + _ => Err(()), } } } @@ -204,7 +206,7 @@ impl FromStr for QueryOrderField { "iso_country" => Ok(QueryOrderField::Country), "iso_region" => Ok(QueryOrderField::Region), "municipality" => Ok(QueryOrderField::Municipality), - _ => Err(()) + _ => Err(()), } } } @@ -229,7 +231,7 @@ impl QueryAirport { QueryOrderField::Municipality => format!("{}, municipality ASC", query), }; }; - }, + } QueryOrderBy::Desc => { if let Some(order_field) = &filters.order_field { query = match order_field { @@ -249,7 +251,12 @@ impl QueryAirport { let airports: Vec = match sql_query(query).load(&mut conn) { Ok(a) => a, - Err(err) => return Err(ServiceError { status: 500, message: format!("{}", err) }) + Err(err) => { + return Err(ServiceError { + status: 500, + message: format!("{}", err), + }) + } }; Ok(airports) } @@ -268,12 +275,17 @@ impl QueryAirport { #[derive(Debug, Queryable, QueryableByName)] #[diesel(table_name = airports)] struct Count { - count: i64 + count: i64, } let count: Vec = match sql_query(query).load(&mut conn) { Ok(a) => a, - Err(err) => return Err(ServiceError { status: 500, message: format!("{}", err) }) + Err(err) => { + return Err(ServiceError { + status: 500, + message: format!("{}", err), + }) + } }; return Ok(count[0].count); } @@ -286,7 +298,10 @@ impl QueryAirport { if let Some(bounds) = &filters.bounds { // convert bounds to a WKT polygon if bounds.rings.len() > 1 { - return Err(ServiceError { status: 400, message: "Only one polygon is allowed".to_string() }) + return Err(ServiceError { + status: 400, + message: "Only one polygon is allowed".to_string(), + }); } else { let mut points: Vec = vec![]; bounds.rings.iter().for_each(|ring| { @@ -295,28 +310,58 @@ impl QueryAirport { }); }); let bounds = format!("POLYGON(({}))", points.join(",")); - parts.push(format!("ST_Contains(ST_GeomFromText('{}', 4326), point)", bounds)); + parts.push(format!( + "ST_Contains(ST_GeomFromText('{}', 4326), point)", + bounds + )); } } if let Some(categories) = &filters.categories { - parts.push(format!("({})", categories.iter().map(|category| format!("category = '{}'", category.to_string())).collect::>().join(" OR "))); + parts.push(format!( + "({})", + categories + .iter() + .map(|category| format!("category = '{}'", category.to_string())) + .collect::>() + .join(" OR ") + )); } fn sanitize_icao(icao: &str) -> String { // Sanitize search to only allow [a-zA-Z0-9-\\s] - icao.chars().filter(|c| c.is_alphanumeric() || *c == '-' || *c == ' ').collect::() + icao + .chars() + .filter(|c| c.is_alphanumeric() || *c == '-' || *c == ' ') + .collect::() } if &filters.icaos.is_some() == &true && &filters.name.is_some() == &true { let icaos = filters.icaos.as_ref().unwrap(); let name = sanitize_icao(filters.name.as_ref().unwrap()); - let icao_part = format!("({})", icaos.iter().map(|icao| format!("icao ILIKE '{}'", sanitize_icao(icao))).collect::>().join(" OR ")); + let icao_part = format!( + "({})", + icaos + .iter() + .map(|icao| format!("icao ILIKE '{}'", sanitize_icao(icao))) + .collect::>() + .join(" OR ") + ); let name_part = format!("name ILIKE '%{}%'", name); parts.push(format!("({} OR {})", icao_part, name_part)); } else if let Some(icaos) = &filters.icaos { - parts.push(format!("({})", icaos.iter().map(|icao| format!("icao ILIKE '{}'", sanitize_icao(icao))).collect::>().join(" OR "))); + parts.push(format!( + "({})", + icaos + .iter() + .map(|icao| format!("icao ILIKE '{}'", sanitize_icao(icao))) + .collect::>() + .join(" OR ") + )); } else if let Some(name) = &filters.name { let search = sanitize_icao(name); parts.push(format!("name ILIKE '%{}%'", search)); } + if let Some(has_metar) = &filters.has_metar { + parts.push(format!("has_metar = {}", has_metar)); + } if parts.len() > 0 { query = format!("{} WHERE {}", query, parts.join(" AND ")); @@ -327,27 +372,33 @@ impl QueryAirport { pub fn get(icao: &str) -> Result { let mut conn = db::connection()?; - let airport = airports::table.filter(airports::icao.eq(icao)).first(&mut conn)?; + let airport = airports::table + .filter(airports::icao.eq(icao)) + .first(&mut conn)?; Ok(airport) } pub fn insert(airport: Self) -> Result { - let mut conn: r2d2::PooledConnection> = db::connection()?; + let mut conn: r2d2::PooledConnection> = + db::connection()?; let airport = Self::from(airport); let airport = diesel::insert_into(airports::table) - .values(airport) - .get_result(&mut conn)?; + .values(airport) + .on_conflict_do_nothing() + .get_result(&mut conn)?; Ok(airport) } - pub fn insert_all (airports: Vec) -> Result, ServiceError> { - let mut conn: r2d2::PooledConnection> = db::connection()?; + pub fn insert_all(airports: Vec) -> Result, ServiceError> { + let mut conn: r2d2::PooledConnection> = + db::connection()?; let mut inserted_airports: Vec = vec![]; for airport in airports { let airport = Self::from(airport); let airport = diesel::insert_into(airports::table) - .values(airport) - .get_result(&mut conn)?; + .values(airport) + .on_conflict_do_nothing() + .get_result(&mut conn)?; inserted_airports.push(airport); } Ok(inserted_airports) @@ -356,17 +407,19 @@ impl QueryAirport { pub fn update(airport: Self) -> Result { let mut conn = db::connection()?; let airport = diesel::update(airports::table) - .filter(airports::icao.eq(airport.icao.clone())) - .set(airport) - .get_result(&mut conn)?; + .filter(airports::icao.eq(airport.icao.clone())) + .set(airport) + .get_result(&mut conn)?; Ok(airport) } pub fn delete(icao: Option) -> Result { let mut conn = db::connection()?; let res = match icao { - Some(icao) => diesel::delete(airports::table.filter(airports::icao.eq(icao))).execute(&mut conn)?, - None => diesel::delete(airports::table).execute(&mut conn)? + Some(icao) => { + diesel::delete(airports::table.filter(airports::icao.eq(icao))).execute(&mut conn)? + } + None => diesel::delete(airports::table).execute(&mut conn)?, }; Ok(res) } diff --git a/service/src/airports/routes.rs b/service/src/airports/routes.rs index 98008e8..1355d61 100644 --- a/service/src/airports/routes.rs +++ b/service/src/airports/routes.rs @@ -1,7 +1,11 @@ use std::str::FromStr; use futures_util::stream::StreamExt as _; -use crate::{airports::{QueryAirport, QueryFilters, QueryOrderField, QueryOrderBy, Airport, AirportCategory}, db::{Response, Metadata}, auth::{JwtAuth, verify_role}}; +use crate::{ + airports::{QueryAirport, QueryFilters, QueryOrderField, QueryOrderBy, Airport, AirportCategory}, + db::{Response, Metadata}, + auth::{JwtAuth, verify_role}, +}; use actix_multipart::Multipart; use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest, ResponseError}; use log::{error, warn}; @@ -16,21 +20,22 @@ struct GetAllParameters { categories: Option, order_field: Option, order_by: Option, + has_metar: Option, limit: Option, - page: Option + page: Option, } #[post("/import")] async fn import(mut payload: Multipart, auth: JwtAuth) -> HttpResponse { if let Err(err) = verify_role(&auth, "admin") { - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); }; while let Some(item) = payload.next().await { let mut bytes = web::BytesMut::new(); let mut field = match item { Ok(field) => field, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; // Build bytes from chunks @@ -57,10 +62,10 @@ async fn import(mut payload: Multipart, auth: JwtAuth) -> HttpResponse { // Convert Vec to Vec and insert into database let query_airports: Vec = airports.into_iter().map(|a| a.into()).collect(); match QueryAirport::insert_all(query_airports) { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; - }; + } HttpResponse::Ok().finish() } @@ -70,83 +75,117 @@ async fn get_all(req: HttpRequest) -> HttpResponse { let mut filters = QueryFilters::default(); filters.icaos = match ¶ms.icaos { Some(i) => Some(i.split(",").map(|s| s.to_string()).collect()), - None => None + None => None, }; filters.name = params.name.clone(); filters.categories = match ¶ms.categories { - Some(c) => Some(c.split(",").map(|s| AirportCategory::from_str(s).unwrap()).collect()), - None => None + Some(c) => Some( + c.split(",") + .map(|s| AirportCategory::from_str(s).unwrap()) + .collect(), + ), + None => None, }; filters.bounds = match ¶ms.bounds { Some(b) => { let bounds: Vec<&str> = b.split(",").collect(); if bounds.len() != 4 { warn!("Expected 4 bounds, received {}: {}", bounds.len(), b); - return HttpResponse::UnprocessableEntity().body(format!("Received {}; expected NE_LAT,NE_LON,SW_LAT,SW_LON", b)) + return HttpResponse::UnprocessableEntity().body(format!( + "Received {}; expected NE_LAT,NE_LON,SW_LAT,SW_LON", + b + )); } let ne_lat = match bounds[0].parse::() { Ok(b) => b, Err(err) => { warn!("{}", err); - return HttpResponse::UnprocessableEntity().body(format!("{}", err)) + return HttpResponse::UnprocessableEntity().body(format!("{}", err)); } }; let ne_lon = match bounds[1].parse::() { Ok(b) => b, Err(err) => { warn!("{}", err); - return HttpResponse::UnprocessableEntity().body(format!("{}", err)) + return HttpResponse::UnprocessableEntity().body(format!("{}", err)); } }; let sw_lat = match bounds[2].parse::() { Ok(b) => b, Err(err) => { warn!("{}", err); - return HttpResponse::UnprocessableEntity().body(format!("{}", err)) + return HttpResponse::UnprocessableEntity().body(format!("{}", err)); } }; let sw_lon = match bounds[3].parse::() { Ok(b) => b, Err(err) => { warn!("{}", err); - return HttpResponse::UnprocessableEntity().body(format!("{}", err)) + return HttpResponse::UnprocessableEntity().body(format!("{}", err)); } }; let mut polygon: Polygon = Polygon::new(Some(4326)); - polygon.add_point(Point { x: sw_lon, y: sw_lat, srid: Some(4326) }); - polygon.add_point(Point { x: ne_lon, y: sw_lat, srid: Some(4326) }); - polygon.add_point(Point { x: ne_lon, y: ne_lat, srid: Some(4326) }); - polygon.add_point(Point { x: sw_lon, y: ne_lat, srid: Some(4326) }); - polygon.add_point(Point { x: sw_lon, y: sw_lat, srid: Some(4326) }); + polygon.add_point(Point { + x: sw_lon, + y: sw_lat, + srid: Some(4326), + }); + polygon.add_point(Point { + x: ne_lon, + y: sw_lat, + srid: Some(4326), + }); + polygon.add_point(Point { + x: ne_lon, + y: ne_lat, + srid: Some(4326), + }); + polygon.add_point(Point { + x: sw_lon, + y: ne_lat, + srid: Some(4326), + }); + polygon.add_point(Point { + x: sw_lon, + y: sw_lat, + srid: Some(4326), + }); Some(polygon) - }, - None => None + } + None => None, }; filters.order_by = match ¶ms.order_by { Some(o) => Some(QueryOrderBy::from_str(&o).unwrap()), - None => None + None => None, }; filters.order_field = match ¶ms.order_field { Some(o) => Some(QueryOrderField::from_str(&o).unwrap()), - None => None + None => None, + }; + filters.has_metar = match ¶ms.has_metar { + Some(h) => Some(h.parse::().unwrap()), + None => None, }; let limit = match params.limit { Some(l) => l, - None => 100 + None => 100, }; let page = match params.page { Some(p) => p, - None => 1 + None => 1, }; let total = match QueryAirport::get_count(&filters) { Ok(t) => t, - Err(_) => 0 + Err(_) => 0, }; - let pages = ((total as f64) / (if limit <= 0 { 1 } else { limit} as f64)).ceil() as i64; + let pages = ((total as f64) / (if limit <= 0 { 1 } else { limit } as f64)).ceil() as i64; - match web::block(move || QueryAirport::get_all(&filters, limit, page)).await.unwrap() { + match web::block(move || QueryAirport::get_all(&filters, limit, page)) + .await + .unwrap() + { Ok(a) => { // Convert Vec to Vec let mut airports: Vec = vec![]; @@ -155,9 +194,14 @@ async fn get_all(req: HttpRequest) -> HttpResponse { } HttpResponse::Ok().json(Response { data: airports, - meta: Some(Metadata { page, limit, pages, total }) + meta: Some(Metadata { + page, + limit, + pages, + total, + }), }) - }, + } Err(err) => { error!("{}", err); err.to_http_response() @@ -172,9 +216,14 @@ async fn get(icao: web::Path) -> HttpResponse { let airport: Airport = a.into(); HttpResponse::Ok().json(Response { data: airport, - meta: Some(Metadata { page: 1, limit: 1, pages: 1, total: 1 }) + meta: Some(Metadata { + page: 1, + limit: 1, + pages: 1, + total: 1, + }), }) - }, + } Err(err) => { error!("{}", err); err.to_http_response() @@ -185,15 +234,15 @@ async fn get(icao: web::Path) -> HttpResponse { #[post("")] async fn create(airport: web::Json, auth: JwtAuth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; let query_airport: QueryAirport = airport.into_inner().into(); match QueryAirport::insert(query_airport) { Ok(a) => { let airport: Airport = a.into(); HttpResponse::Ok().json(airport) - }, + } Err(err) => { error!("{}", err); err.to_http_response() @@ -202,17 +251,21 @@ async fn create(airport: web::Json, auth: JwtAuth) -> HttpResponse { } #[put("/{icao}")] -async fn update(_icao: web::Path, airport: web::Json, auth: JwtAuth) -> HttpResponse { +async fn update( + _icao: web::Path, + airport: web::Json, + auth: JwtAuth, +) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; let query_airport: QueryAirport = airport.into_inner().into(); match QueryAirport::update(query_airport) { Ok(a) => { let airport: Airport = a.into(); HttpResponse::Ok().json(airport) - }, + } Err(err) => { error!("{}", err); err.to_http_response() @@ -223,8 +276,8 @@ async fn update(_icao: web::Path, airport: web::Json, auth: Jwt #[delete("")] async fn delete_all(auth: JwtAuth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; match QueryAirport::delete(None) { Ok(_) => HttpResponse::NoContent().finish(), @@ -238,8 +291,8 @@ async fn delete_all(auth: JwtAuth) -> HttpResponse { #[delete("/{icao}")] async fn delete(icao: web::Path, auth: JwtAuth) -> HttpResponse { let _ = match verify_role(&auth, "admin") { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; match QueryAirport::delete(Some(icao.into_inner())) { Ok(_) => HttpResponse::NoContent().finish(), @@ -251,13 +304,14 @@ async fn delete(icao: web::Path, auth: JwtAuth) -> HttpResponse { } pub fn init_routes(config: &mut web::ServiceConfig) { - config.service(web::scope("airports") - .service(get_all) - .service(get) - .service(create) - .service(update) - .service(delete) - .service(delete_all) - .service(import) + config.service( + web::scope("airports") + .service(get_all) + .service(get) + .service(create) + .service(update) + .service(delete) + .service(delete_all) + .service(import), ); -} \ No newline at end of file +} diff --git a/service/src/auth/mod.rs b/service/src/auth/mod.rs index 9c56134..f87e195 100644 --- a/service/src/auth/mod.rs +++ b/service/src/auth/mod.rs @@ -1,6 +1,11 @@ use std::env; -use argon2::{password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError}, Argon2, PasswordHash}; +use argon2::{ + password_hash::{ + rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError, + }, + Argon2, PasswordHash, +}; use jsonwebtoken::{DecodingKey, EncodingKey, Header, encode, decode, Validation, Algorithm}; use serde::{Deserialize, Serialize}; @@ -13,20 +18,20 @@ use crate::error_handler::ServiceError; #[derive(Debug, Serialize, Deserialize)] struct TokenClaims { - sub: String, // Subject - token_uuid: String, // Token UUID - iss: String, // Issuer - exp: i64, // Expiration time - iat: i64, // Issued At - nbf: i64 // Not Before + sub: String, // Subject + token_uuid: String, // Token UUID + iss: String, // Issuer + exp: i64, // Expiration time + iat: i64, // Issued At + nbf: i64, // Not Before } #[derive(Debug, Serialize, Deserialize)] pub struct TokenDetails { - pub token: Option, - pub token_uuid: uuid::Uuid, - pub email: String, - pub expires_in: Option + pub token: Option, + pub token_uuid: uuid::Uuid, + pub email: String, + pub expires_in: Option, } pub fn verify_token(token: &str, public_key: &str) -> Result { @@ -35,7 +40,12 @@ pub fn verify_token(token: &str, public_key: &str) -> Result(token, &key, &validation)?; let email = decoded.claims.sub; let token_uuid = uuid::Uuid::parse_str(decoded.claims.token_uuid.as_str()).unwrap(); - Ok(TokenDetails { token: None, token_uuid, email, expires_in: None }) + Ok(TokenDetails { + token: None, + token_uuid, + email, + expires_in: None, + }) } pub fn generate_access_token(email: &str) -> Result { @@ -54,17 +64,22 @@ pub fn generate_refresh_token(email: &str) -> Result .parse::() .expect("REFRESH_TOKEN_MAXAGE must be an integer"); let keys_dir = env::var("KEYS_DIR_PATH")?; - let refresh_private_key = std::fs::read_to_string(format!("{}/refresh_private_key.pem", keys_dir))?; + let refresh_private_key = + std::fs::read_to_string(format!("{}/refresh_private_key.pem", keys_dir))?; generate_token(&email, refresh_token_max_age, &refresh_private_key) } -pub fn generate_token(email: &str, ttl: i64, private_key: &str) -> Result { +pub fn generate_token( + email: &str, + ttl: i64, + private_key: &str, +) -> Result { let now = chrono::Utc::now(); let mut token_details = TokenDetails { token: None, token_uuid: uuid::Uuid::new_v4(), email: email.to_string(), - expires_in: Some((now + chrono::Duration::minutes(ttl)).timestamp()) + expires_in: Some((now + chrono::Duration::minutes(ttl)).timestamp()), }; let claims = TokenClaims { sub: token_details.email.clone(), @@ -72,7 +87,7 @@ pub fn generate_token(email: &str, ttl: i64, private_key: &str) -> Result Result Result { let salt = SaltString::generate(&mut OsRng); - Ok(Argon2::default().hash_password(password, &salt)?.to_string()) + Ok( + Argon2::default() + .hash_password(password, &salt)? + .to_string(), + ) } pub fn verify_password(hash: &str, password: &[u8]) -> Result<(), HashError> { let parsed_hash = PasswordHash::new(hash)?; Ok(Argon2::default().verify_password(password, &parsed_hash)?) -} \ No newline at end of file +} diff --git a/service/src/auth/model.rs b/service/src/auth/model.rs index c57556b..ada83f9 100644 --- a/service/src/auth/model.rs +++ b/service/src/auth/model.rs @@ -1,4 +1,7 @@ -use std::{future::{ready, Ready}, env}; +use std::{ + future::{ready, Ready}, + env, +}; use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, http}; use diesel::prelude::*; use log::error; @@ -93,7 +96,10 @@ impl InsertUser { Ok(user) } - pub fn update_profile_picture(email: &str, profile_picture: Option<&str>) -> Result { + pub fn update_profile_picture( + email: &str, + profile_picture: Option<&str>, + ) -> Result { let mut conn = connection()?; let user = diesel::update(users::table) .filter(users::email.eq(&email)) @@ -136,7 +142,7 @@ impl From for ResponseUser { #[derive(Debug, Serialize, Deserialize)] pub struct JwtAuth { pub token: uuid::Uuid, - pub user: ResponseUser + pub user: ResponseUser, } impl FromRequest for JwtAuth { @@ -147,18 +153,23 @@ impl FromRequest for JwtAuth { .cookie("access_token") .map(|c| c.value().to_string()) .or_else(|| { - req.headers().get(http::header::AUTHORIZATION) - .map(|h| h.to_str().unwrap().split_at(7).1.to_string()) + req + .headers() + .get(http::header::AUTHORIZATION) + .map(|h| h.to_str().unwrap().split_at(7).1.to_string()) }) { - Some(token) => token, - None => return ready(Err(ActixError::from(ServiceError { + Some(token) => token, + None => { + return ready(Err(ActixError::from(ServiceError { status: 401, - message: "Unauthorized".to_string() + message: "Unauthorized".to_string(), }))) - }; + } + }; let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set"); - let public_key = std::fs::read_to_string(format!("{}/access_public_key.pem", keys_dir)).expect("Failed to read access public key"); + let public_key = std::fs::read_to_string(format!("{}/access_public_key.pem", keys_dir)) + .expect("Failed to read access public key"); let access_token_details = match verify_token(&access_token, &public_key) { Ok(token_details) => token_details, @@ -166,21 +177,22 @@ impl FromRequest for JwtAuth { error!("Failed to verify access token: {}", err); return ready(Err(ActixError::from(ServiceError { status: 401, - message: format!("Failed to verify access token: {}", err) - }))) + message: format!("Failed to verify access token: {}", err), + }))); } }; - let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap(); - + let access_token_uuid = + uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap(); + let mut conn = match crate::db::redis_connection() { Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); return ready(Err(ActixError::from(ServiceError { status: 500, - message: format!("Failed to get redis connection: {}", err) - }))) + message: format!("Failed to get redis connection: {}", err), + }))); } }; let user_email = match conn.get::<_, String>(access_token_uuid.clone().to_string()) { @@ -188,19 +200,22 @@ impl FromRequest for JwtAuth { Err(_) => { return ready(Err(ActixError::from(ServiceError { status: 401, - message: format!("Access token was not found") + message: format!("Access token was not found"), }))) } }; match QueryUser::get_by_email(&user_email) { - Ok(user) => { - ready(Ok(JwtAuth { token: access_token_uuid, user: user.into() })) + Ok(user) => ready(Ok(JwtAuth { + token: access_token_uuid, + user: user.into(), + })), + Err(_) => { + return ready(Err(ActixError::from(ServiceError { + status: 401, + message: format!("User was not found"), + }))) } - Err(_) => return ready(Err(ActixError::from(ServiceError { - status: 401, - message: format!("User was not found") - }))) } } } @@ -211,7 +226,7 @@ pub fn verify_role(auth: &JwtAuth, role: &str) -> Result<(), ServiceError> { } else { Err(ServiceError { status: 403, - message: "Forbidden".to_string() + message: "Forbidden".to_string(), }) } } diff --git a/service/src/auth/routes.rs b/service/src/auth/routes.rs index 679b203..25d22ba 100644 --- a/service/src/auth/routes.rs +++ b/service/src/auth/routes.rs @@ -1,24 +1,32 @@ use std::env; -use actix_web::{get, post, web, HttpResponse, ResponseError, cookie::{Cookie, time::Duration}, HttpRequest}; +use actix_web::{ + get, post, web, HttpResponse, ResponseError, + cookie::{Cookie, time::Duration}, + HttpRequest, +}; use log::error; use redis::AsyncCommands; use serde::{Serialize, Deserialize}; use crate::{error_handler::ServiceError, db::Response}; -use crate::{auth::{LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, JwtAuth, verify_token, generate_access_token, generate_refresh_token}, db}; +use crate::{ + auth::{ + LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, JwtAuth, verify_token, + generate_access_token, generate_refresh_token, + }, + db, +}; #[post("/register")] async fn register(user: web::Json) -> HttpResponse { let register_user = user.0; let insert_user: InsertUser = match register_user.convert_to_insert() { Ok(user) => user, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; match InsertUser::insert(insert_user) { - Ok(_) => { - HttpResponse::Created().finish() - }, + Ok(_) => HttpResponse::Created().finish(), Err(err) => { // Obfuscate the service error message to prevent leaking database details if err.status == 409 { @@ -36,7 +44,7 @@ async fn login(request: web::Json) -> HttpResponse { let query_user = match QueryUser::get_by_email(&email) { Ok(query_user) => query_user, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; let hash = &query_user.hash; let password = request.password.as_bytes(); @@ -46,15 +54,15 @@ async fn login(request: web::Json) -> HttpResponse { Ok(token_details) => token_details, Err(err) => { error!("Failed to generate access token: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; - + let refresh_token_details = match generate_refresh_token(&email) { Ok(token_details) => token_details, Err(err) => { error!("Failed to generate refresh token: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; @@ -62,7 +70,7 @@ async fn login(request: web::Json) -> HttpResponse { Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; @@ -76,54 +84,74 @@ async fn login(request: web::Json) -> HttpResponse { .parse::() .expect("REFRESH_TOKEN_MAXAGE must be an integer"); - let access_result: redis::RedisResult<()> = conn.set_ex(access_token_details.token_uuid.to_string(), &email, (access_token_max_age * 60) as usize).await; + let access_result: redis::RedisResult<()> = conn + .set_ex( + access_token_details.token_uuid.to_string(), + &email, + (access_token_max_age * 60) as u64, + ) + .await; if let Err(err) = access_result { error!("Failed to set access token in redis: {}", err); return ResponseError::error_response(&ServiceError { status: 500, - message: format!("Failed to set access token in redis: {}", err) - }) + message: format!("Failed to set access token in redis: {}", err), + }); }; - let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token_details.token_uuid.to_string(), &email, (refresh_token_max_age * 60) as usize).await; + let refresh_result: redis::RedisResult<()> = conn + .set_ex( + refresh_token_details.token_uuid.to_string(), + &email, + (refresh_token_max_age * 60) as u64, + ) + .await; if let Err(err) = refresh_result { error!("Failed to set refresh token in redis: {}", err); return ResponseError::error_response(&ServiceError { status: 500, - message: format!("Failed to set refresh token in redis: {}", err) - }) + message: format!("Failed to set refresh token in redis: {}", err), + }); }; - let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap()) - .path("/") - .max_age(Duration::new(access_token_max_age * 60, 0)) - .http_only(true) - .secure(true) - .finish(); - let refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap()) - .path("/") - .max_age(Duration::new(refresh_token_max_age * 60, 0)) - .http_only(true) - .secure(true) - .finish(); + let access_cookie = + Cookie::build("access_token", access_token_details.token.clone().unwrap()) + .path("/") + .max_age(Duration::new(access_token_max_age * 60, 0)) + .http_only(true) + .secure(true) + .finish(); + let refresh_cookie = Cookie::build( + "refresh_token", + refresh_token_details.token.clone().unwrap(), + ) + .path("/") + .max_age(Duration::new(refresh_token_max_age * 60, 0)) + .http_only(true) + .secure(true) + .finish(); let logged_in_cookie = Cookie::build("logged_in", "true") .path("/") .max_age(Duration::new(access_token_max_age * 60, 0)) .http_only(false) .finish(); - let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap(); + let access_token_uuid = + uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap(); HttpResponse::Ok() .cookie(access_cookie) .cookie(refresh_cookie) .cookie(logged_in_cookie) - .json(JwtAuth { token: access_token_uuid, user: query_user.into() }) - }, + .json(JwtAuth { + token: access_token_uuid, + user: query_user.into(), + }) + } Err(err) => ResponseError::error_response(&ServiceError { status: 401, - message: err.to_string() - }) + message: err.to_string(), + }), } } @@ -135,13 +163,13 @@ async fn session(req: HttpRequest) -> HttpResponse { Some(cookie) => { let access_token = cookie.value().to_string(); let public_key = std::fs::read_to_string(format!("{}access_public_key.pem", keys_dir)) - .expect("Unable to read refresh public key"); + .expect("Unable to read refresh public key"); match verify_token(&access_token, &public_key) { Ok(_) => true, - Err(_) => false + Err(_) => false, } - }, - None => false + } + None => false, }; if !has_session { // If there is a refresh_token cookie, check if it is valid @@ -149,40 +177,44 @@ async fn session(req: HttpRequest) -> HttpResponse { Some(cookie) => { let refresh_token = cookie.value().to_string(); let public_key = std::fs::read_to_string(format!("{}/refresh_public_key.pem", keys_dir)) - .expect("Unable to read refresh public key"); + .expect("Unable to read refresh public key"); match verify_token(&refresh_token, &public_key) { Ok(_) => return HttpResponse::Ok().json(true), - Err(_) => return HttpResponse::Ok().json(false) + Err(_) => return HttpResponse::Ok().json(false), }; - }, - None => return HttpResponse::Ok().json(false) + } + None => return HttpResponse::Ok().json(false), }; } else { - return HttpResponse::Ok().json(true) + return HttpResponse::Ok().json(true); } } #[derive(Serialize, Deserialize)] struct RefreshParams { - refresh_token_rotation: Option + refresh_token_rotation: Option, } #[get("/refresh")] async fn refresh(req: HttpRequest) -> HttpResponse { let params = match web::Query::::from_query(req.query_string()) { Ok(params) => params, - Err(err) => return ResponseError::error_response(&ServiceError { - status: 422, - message: err.to_string() - }) + Err(err) => { + return ResponseError::error_response(&ServiceError { + status: 422, + message: err.to_string(), + }) + } }; let refresh_token = match req.cookie("refresh_token") { Some(cookie) => cookie.value().to_string(), - None => return ResponseError::error_response(&ServiceError { - status: 401, - message: "Refresh token not found".to_string() - }) + None => { + return ResponseError::error_response(&ServiceError { + status: 401, + message: "Refresh token not found".to_string(), + }) + } }; let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set"); @@ -190,7 +222,7 @@ async fn refresh(req: HttpRequest) -> HttpResponse { .expect("Unable to read refresh public key"); let refresh_token_details = match verify_token(&refresh_token, &public_key) { Ok(token_details) => token_details, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; let email = refresh_token_details.email.clone(); @@ -201,7 +233,7 @@ async fn refresh(req: HttpRequest) -> HttpResponse { Ok(token_details) => token_details, Err(err) => { error!("Failed to generate access token: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; @@ -209,7 +241,7 @@ async fn refresh(req: HttpRequest) -> HttpResponse { Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; @@ -222,94 +254,117 @@ async fn refresh(req: HttpRequest) -> HttpResponse { .expect("Unable to read access public key"); match verify_token(&access_token, &public_key) { Ok(token_details) => { - let _: redis::RedisResult<()> = conn.del(token_details.token_uuid.to_string()).await; - - }, + let _: redis::RedisResult<()> = conn.del(token_details.token_uuid.to_string()).await; + } Err(_) => {} }; - }, + } None => {} }; - + let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE") .expect("ACCESS_TOKEN_MAXAGE must be set") .parse::() .expect("ACCESS_TOKEN_MAXAGE must be an integer"); - - let access_result: redis::RedisResult<()> = conn.set_ex(access_token_details.token_uuid.to_string(), &email, (access_token_max_age * 60) as usize).await; + + let access_result: redis::RedisResult<()> = conn + .set_ex( + access_token_details.token_uuid.to_string(), + &email, + (access_token_max_age * 60) as u64, + ) + .await; if let Err(err) = access_result { error!("Failed to set access token in redis: {}", err); return ResponseError::error_response(&ServiceError { status: 500, - message: format!("Failed to set access token in redis: {}", err) - }) + message: format!("Failed to set access token in redis: {}", err), + }); }; - - let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap()) - .path("/") - .max_age(Duration::new(access_token_max_age * 60, 0)) - .http_only(true) - .secure(true) - .finish(); + + let access_cookie = + Cookie::build("access_token", access_token_details.token.clone().unwrap()) + .path("/") + .max_age(Duration::new(access_token_max_age * 60, 0)) + .http_only(true) + .secure(true) + .finish(); let logged_in_cookie = Cookie::build("logged_in", "true") .path("/") .max_age(Duration::new(access_token_max_age * 60, 0)) .http_only(false) .finish(); - - let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap(); + + let access_token_uuid = + uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap(); // Refresh the refresh token if requested let refresh_token_rotation = match params.refresh_token_rotation { Some(refresh_token_rotation) => refresh_token_rotation, - None => false + None => false, }; if refresh_token_rotation { // Delete the old refresh token - let _: redis::RedisResult<()> = conn.del(refresh_token_details.token_uuid.to_string()).await; + let _: redis::RedisResult<()> = + conn.del(refresh_token_details.token_uuid.to_string()).await; let refresh_token_details = match generate_refresh_token(&refresh_token_details.email) { Ok(token_details) => token_details, Err(err) => { error!("Failed to generate refresh token: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; - + let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE") .expect("REFRESH_TOKEN_MAXAGE must be set") .parse::() .expect("REFRESH_TOKEN_MAXAGE must be an integer"); - let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token_details.token_uuid.to_string(), &refresh_token_details.email, (refresh_token_max_age * 60) as usize).await; + let refresh_result: redis::RedisResult<()> = conn + .set_ex( + refresh_token_details.token_uuid.to_string(), + &refresh_token_details.email, + (refresh_token_max_age * 60) as u64, + ) + .await; if let Err(err) = refresh_result { error!("Failed to set refresh token in redis: {}", err); return ResponseError::error_response(&ServiceError { status: 500, - message: format!("Failed to set refresh token in redis: {}", err) - }) + message: format!("Failed to set refresh token in redis: {}", err), + }); }; - - let refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap()) - .path("/") - .max_age(Duration::new(refresh_token_max_age * 60, 0)) - .http_only(true) - .secure(true) - .finish(); - + + let refresh_cookie = Cookie::build( + "refresh_token", + refresh_token_details.token.clone().unwrap(), + ) + .path("/") + .max_age(Duration::new(refresh_token_max_age * 60, 0)) + .http_only(true) + .secure(true) + .finish(); + HttpResponse::Ok() .cookie(refresh_cookie) .cookie(access_cookie) .cookie(logged_in_cookie) - .json(JwtAuth { token: access_token_uuid, user: query_user.into() }) + .json(JwtAuth { + token: access_token_uuid, + user: query_user.into(), + }) } else { HttpResponse::Ok() - .cookie(access_cookie) - .cookie(logged_in_cookie) - .json(JwtAuth { token: access_token_uuid, user: query_user.into() }) + .cookie(access_cookie) + .cookie(logged_in_cookie) + .json(JwtAuth { + token: access_token_uuid, + user: query_user.into(), + }) } - }, - Err(err) => return ResponseError::error_response(&err) + } + Err(err) => return ResponseError::error_response(&err), } } @@ -317,37 +372,41 @@ async fn refresh(req: HttpRequest) -> HttpResponse { async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse { let refresh_token = match req.cookie("refresh_token") { Some(cookie) => cookie.value().to_string(), - None => return ResponseError::error_response(&ServiceError { - status: 401, - message: "Refresh token not found".to_string() - }) + None => { + return ResponseError::error_response(&ServiceError { + status: 401, + message: "Refresh token not found".to_string(), + }) + } }; let keys_dir = env::var("KEYS_DIR_PATH").expect("KEYS_DIR_PATH must be set"); let public_key = std::fs::read_to_string(format!("{}/refresh_public_key.pem", keys_dir)) .expect("Unable to read refresh public key"); let refresh_token_details = match verify_token(&refresh_token, &public_key) { Ok(token_details) => token_details, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; let mut conn = match db::redis_async_connection().await { Ok(conn) => conn, Err(err) => { error!("Failed to get redis connection: {}", err); - return ResponseError::error_response(&err) + return ResponseError::error_response(&err); } }; - let access_result: redis::RedisResult<()> = conn.del(&[ - refresh_token_details.token_uuid.to_string(), - auth.token.to_string() - ]).await; + let access_result: redis::RedisResult<()> = conn + .del(&[ + refresh_token_details.token_uuid.to_string(), + auth.token.to_string(), + ]) + .await; if let Err(err) = access_result { error!("Failed to set access token in redis: {}", err); return ResponseError::error_response(&ServiceError { status: 500, - message: format!("Failed to set access token in redis: {}", err) - }) + message: format!("Failed to set access token in redis: {}", err), + }); }; let access_cookie = Cookie::build("access_token", "") @@ -365,7 +424,7 @@ async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse { .max_age(Duration::new(-1, 0)) .http_only(true) .finish(); - + HttpResponse::Ok() .cookie(access_cookie) .cookie(refresh_cookie) @@ -382,7 +441,7 @@ async fn me(auth: JwtAuth) -> HttpResponse { async fn roles() -> HttpResponse { HttpResponse::Ok().json(Response { data: vec!["admin", "user"], - meta: None + meta: None, }) } @@ -397,12 +456,13 @@ pub fn init_routes(config: &mut web::ServiceConfig) { u.role = "admin".to_string(); u.verified = true; let _ = InsertUser::insert(u); - config.service(web::scope("auth") - .service(register) - .service(login) - .service(refresh) - .service(logout) - .service(me) - .service(roles) - ); -} \ No newline at end of file + config.service( + web::scope("auth") + .service(register) + .service(login) + .service(refresh) + .service(logout) + .service(me) + .service(roles), + ); +} diff --git a/service/src/db/mod.rs b/service/src/db/mod.rs index 7ccd927..e59b94e 100644 --- a/service/src/db/mod.rs +++ b/service/src/db/mod.rs @@ -1,7 +1,10 @@ use crate::error_handler::ServiceError; use diesel::{r2d2::ConnectionManager, PgConnection}; -use redis::{Client as RedisClient, aio::Connection as RedisConnection}; -use s3::{Bucket, Region, creds::Credentials, BucketConfiguration, request::ResponseData, bucket_ops::CreateBucketResponse}; +use redis::{Client as RedisClient, aio::MultiplexedConnection as RedisConnection}; +use s3::{ + Bucket, Region, creds::Credentials, BucketConfiguration, request::ResponseData, + bucket_ops::CreateBucketResponse, +}; use serde::{Deserialize, Serialize}; use crate::diesel_migrations::MigrationHarness; use lazy_static::lazy_static; @@ -23,9 +26,15 @@ lazy_static! { let host = env::var("DATABASE_HOST").unwrap_or("localhost".to_string()); let name = env::var("DATABASE_NAME").expect("Database name is not set"); let port = env::var("DATABASE_PORT").unwrap_or("5432".to_string()); - let url = format!("postgres://{}:{}@{}:{}/{}", username, password, host, port, name); + let url = format!( + "postgres://{}:{}@{}:{}/{}", + username, password, host, port, name + ); let manager = ConnectionManager::::new(url); - Pool::builder().test_on_check_out(true).build(manager).expect("Failed to create db pool") + Pool::builder() + .test_on_check_out(true) + .build(manager) + .expect("Failed to create db pool") }; static ref REDIS: RedisClient = { let host = env::var("REDIS_HOST").unwrap_or("localhost".to_string()); @@ -39,21 +48,23 @@ lazy_static! { let user = env::var("MINIO_ROOT_USER").expect("MINIO_ROOT_USER is not set"); let password = env::var("MINIO_ROOT_PASSWORD").expect("MINIO_ROOT_PASSWORD is not set"); let base_url = format!("http://{}:{}", url, port); - + let region = Region::Custom { region: "".to_string(), endpoint: base_url, }; - + let credentials = Credentials { access_key: Some(user), secret_key: Some(password), security_token: None, session_token: None, - expiration: None + expiration: None, }; - - Bucket::new("aviation", region.clone(), credentials.clone()).expect("Failed to create S3 Bucket").with_path_style() + + Bucket::new("aviation", region.clone(), credentials.clone()) + .expect("Failed to create S3 Bucket") + .with_path_style() }; } @@ -63,22 +74,21 @@ pub async fn init() { lazy_static::initialize(&BUCKET); match create_bucket().await { Ok(_) => info!("Bucket initialized"), - Err(err) => { - match err.status { - 409 => warn!("Bucket already exists"), - _ => error!("Failed to initialize bucket; {}", err.message) - } - } + Err(err) => match err.status { + 409 => warn!("Bucket already exists"), + _ => error!("Failed to initialize bucket; {}", err.message), + }, }; let mut pool: DbConnection = connection().expect("Failed to get db connection"); match pool.run_pending_migrations(MIGRATIONS) { Ok(_) => info!("Database initialized"), - Err(err) => error!("Failed to initialize database; {}", err) + Err(err) => error!("Failed to initialize database; {}", err), }; } pub fn connection() -> Result { - POOL.get() + POOL + .get() .map_err(|e| ServiceError::new(500, format!("Failed getting db connection: {}", e))) } @@ -88,7 +98,7 @@ pub fn redis_connection() -> Result { } pub async fn redis_async_connection() -> Result { - let conn = REDIS.get_async_connection().await?; + let conn = REDIS.get_multiplexed_async_connection().await?; Ok(conn) } @@ -109,10 +119,16 @@ async fn create_bucket() -> Result { secret_key: Some(password), security_token: None, session_token: None, - expiration: None + expiration: None, }; let bucket_name = "aviation"; - let response = Bucket::create_with_path_style(bucket_name, region, credentials, BucketConfiguration::default()).await?; + let response = Bucket::create_with_path_style( + bucket_name, + region, + credentials, + BucketConfiguration::default(), + ) + .await?; Ok(response) } @@ -135,7 +151,7 @@ pub async fn delete_file(path: &str) -> Result { #[derive(Serialize, Deserialize)] pub struct Response { pub data: T, - pub meta: Option + pub meta: Option, } #[derive(Serialize, Deserialize)] @@ -149,5 +165,5 @@ pub struct Metadata { #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct Coordinate { pub lon: f64, - pub lat: f64 + pub lat: f64, } diff --git a/service/src/error_handler.rs b/service/src/error_handler.rs index 726fb72..3886ad7 100644 --- a/service/src/error_handler.rs +++ b/service/src/error_handler.rs @@ -14,10 +14,7 @@ pub struct ServiceError { impl ServiceError { pub fn new(status: u16, message: String) -> ServiceError { - ServiceError { - status, - message, - } + ServiceError { status, message } } pub fn to_http_response(&self) -> HttpResponse { @@ -46,27 +43,24 @@ impl From for ServiceError { impl From for ServiceError { fn from(error: std::env::VarError) -> ServiceError { - ServiceError::new(500, format!("Unknown environment variable error: {}", error)) + ServiceError::new( + 500, + format!("Unknown environment variable error: {}", error), + ) } } impl From for ServiceError { fn from(error: DieselError) -> ServiceError { match error { - DieselError::DatabaseError(kind, err) => { - match kind { - diesel::result::DatabaseErrorKind::UniqueViolation => { - ServiceError::new(409, err.message().to_string()) - }, - _ => ServiceError::new(500, err.message().to_string()) + DieselError::DatabaseError(kind, err) => match kind { + diesel::result::DatabaseErrorKind::UniqueViolation => { + ServiceError::new(409, err.message().to_string()) } + _ => ServiceError::new(500, err.message().to_string()), }, - DieselError::NotFound => { - ServiceError::new(404, "The record was not found".to_string()) - }, - DieselError::SerializationError(err) => { - ServiceError::new(422, err.to_string()) - }, + DieselError::NotFound => ServiceError::new(404, "The record was not found".to_string()), + DieselError::SerializationError(err) => ServiceError::new(422, err.to_string()), err => ServiceError::new(500, format!("Unknown Diesel error: {}", err)), } } @@ -105,13 +99,24 @@ impl From for ServiceError { impl From for ServiceError { fn from(error: s3::error::S3Error) -> ServiceError { match error { - s3::error::S3Error::Credentials(err) => ServiceError::new(500, format!("Unknown s3 credentials error: {}", err)), - s3::error::S3Error::FromUtf8(err) => ServiceError::new(500, format!("Unknown s3 from utf8 error: {}", err)), - s3::error::S3Error::FmtError(err) => ServiceError::new(500, format!("Unknown s3 fmt error: {}", err)), - s3::error::S3Error::HeaderToStr(err) => ServiceError::new(500, format!("Unknown s3 header to str error: {}", err)), - s3::error::S3Error::HmacInvalidLength(err) => ServiceError::new(500, format!("Unknown s3 hmac invalid length error: {}", err)), - s3::error::S3Error::Http(status, msg) => ServiceError::new(status, msg), - _ => ServiceError::new(500, format!("Unknown s3 error: {}", error)) + s3::error::S3Error::Credentials(err) => { + ServiceError::new(500, format!("Unknown s3 credentials error: {}", err)) + } + s3::error::S3Error::FromUtf8(err) => { + ServiceError::new(500, format!("Unknown s3 from utf8 error: {}", err)) + } + s3::error::S3Error::FmtError(err) => { + ServiceError::new(500, format!("Unknown s3 fmt error: {}", err)) + } + s3::error::S3Error::HeaderToStr(err) => { + ServiceError::new(500, format!("Unknown s3 header to str error: {}", err)) + } + s3::error::S3Error::HmacInvalidLength(err) => ServiceError::new( + 500, + format!("Unknown s3 hmac invalid length error: {}", err), + ), + s3::error::S3Error::Http(error) => ServiceError::new(error.status_code().as_u16(), error.to_string()), + _ => ServiceError::new(500, format!("Unknown s3 error: {}", error)), } } } @@ -130,4 +135,4 @@ impl ResponseError for ServiceError { HttpResponse::build(status).json(json!({ "status": status.as_u16(), "message": message })) } -} \ No newline at end of file +} diff --git a/service/src/main.rs b/service/src/main.rs index f8ba3d0..5a7b06f 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -14,15 +14,15 @@ mod auth; mod db; mod error_handler; mod metars; -mod users; mod scheduler; +mod users; #[actix_web::main] async fn main() -> std::io::Result<()> { dotenv().ok(); env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "warn,service=info")); db::init().await; - // scheduler::update_airports(); + scheduler::update_airports(); let host = env::var("SERVICE_HOST").unwrap_or("localhost".to_string()); let port = env::var("SERVICE_PORT").unwrap_or("5000".to_string()); @@ -42,11 +42,12 @@ async fn main() -> std::io::Result<()> { .configure(auth::init_routes) .configure(users::init_routes) }) - .bind(format!("{}:{}", host, port)) { + .bind(format!("{}:{}", host, port)) + { Ok(b) => { info!("Binding server to {}:{}", host, port); b - }, + } Err(err) => { error!("Could not bind server: {}", err); return Err(err); diff --git a/service/src/metars/mod.rs b/service/src/metars/mod.rs index 6666fdc..6fbb137 100644 --- a/service/src/metars/mod.rs +++ b/service/src/metars/mod.rs @@ -2,4 +2,4 @@ mod model; mod routes; pub use model::*; -pub use routes::init_routes; \ No newline at end of file +pub use routes::init_routes; diff --git a/service/src/metars/model.rs b/service/src/metars/model.rs index 5089aab..a689342 100644 --- a/service/src/metars/model.rs +++ b/service/src/metars/model.rs @@ -45,7 +45,7 @@ pub struct SkyCondition { #[serde(skip_serializing_if = "Option::is_none")] pub cloud_base_ft_agl: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub significant_convective_clouds: Option + pub significant_convective_clouds: Option, } impl Default for SkyCondition { @@ -66,7 +66,7 @@ pub struct RunwayVisualRange { #[serde(skip_serializing_if = "Option::is_none")] pub variable_visibility_high_ft: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub variable_visibility_low_ft: Option + pub variable_visibility_low_ft: Option, } impl Default for RunwayVisualRange { @@ -75,7 +75,7 @@ impl Default for RunwayVisualRange { runway: "".to_string(), visibility_ft: None, variable_visibility_high_ft: None, - variable_visibility_low_ft: None + variable_visibility_low_ft: None, } } } @@ -86,7 +86,7 @@ pub enum FlightCategory { MVFR, LIFR, IFR, - UNKN + UNKN, } #[derive(Serialize, Deserialize, Debug)] @@ -132,7 +132,11 @@ impl Default for Metar { Metar { raw_text: "".to_string(), station_id: "".to_string(), - observation_time: chrono::NaiveDateTime::parse_from_str("1970-01-01T00:00:00", "%Y-%m-%dT%H:%M:%S").unwrap(), + observation_time: chrono::NaiveDateTime::parse_from_str( + "1970-01-01T00:00:00", + "%Y-%m-%dT%H:%M:%S", + ) + .unwrap(), temp_c: None, dewpoint_c: None, wind_dir_degrees: None, @@ -164,19 +168,25 @@ impl Metar { metar.raw_text = metar_string.to_owned(); let mut metar_parts: Vec<&str> = metar_string.split_whitespace().collect(); if metar_parts.len() < 4 { - warn!("Unable to parse METAR data in an unexpected format: {}", metar_string); + warn!( + "Unable to parse METAR data in an unexpected format: {}", + metar_string + ); continue; } - + // Station Identifier metar.station_id = metar_parts[0].to_string(); metar_parts.remove(0); - + // Date/Time let observation_time = metar_parts[0]; metar_parts.remove(0); if observation_time.len() != 7 { - warn!("Unable to parse observation time in {}: {}", observation_time, metar_string); + warn!( + "Unable to parse observation time in {}: {}", + observation_time, metar_string + ); continue; } let observation_time_day = &observation_time[0..2]; @@ -184,11 +194,12 @@ impl Metar { let observation_time_minute = &observation_time[4..6]; let current_time = chrono::Utc::now().naive_utc(); // Check if the observation time is from the previous month - let observation_time_month = if current_time.day() > observation_time_day.parse::().unwrap() { - current_time.month() - 1 - } else { - current_time.month() - }; + let observation_time_month = + if current_time.day() > observation_time_day.parse::().unwrap() { + current_time.month() - 1 + } else { + current_time.month() + }; // Check if the observation time is from the previous year let observation_time_year = if current_time.month() > observation_time_month { current_time.year() - 1 @@ -196,13 +207,22 @@ impl Metar { current_time.year() }; // Handle Daylight Savings Time - let observation_time_hour = if observation_time_month == 3 && observation_time_day.parse::().unwrap() < 14 { - observation_time_hour.parse::().unwrap() - 1 - } else { - observation_time_hour.parse::().unwrap() - }; - let observation_time = format!("{}-{}-{}T{}:{}:00Z", observation_time_year, observation_time_month, observation_time_day, observation_time_hour, observation_time_minute); - metar.observation_time = chrono::NaiveDateTime::parse_from_str(&observation_time, "%Y-%m-%dT%H:%M:%SZ").unwrap(); + let observation_time_hour = + if observation_time_month == 3 && observation_time_day.parse::().unwrap() < 14 { + observation_time_hour.parse::().unwrap() - 1 + } else { + observation_time_hour.parse::().unwrap() + }; + let observation_time = format!( + "{}-{}-{}T{}:{}:00Z", + observation_time_year, + observation_time_month, + observation_time_day, + observation_time_hour, + observation_time_minute + ); + metar.observation_time = + chrono::NaiveDateTime::parse_from_str(&observation_time, "%Y-%m-%dT%H:%M:%SZ").unwrap(); loop { if metar_parts.is_empty() { @@ -224,14 +244,22 @@ impl Metar { // Wind Direction and Speed let wind_re = regex::Regex::new(r"^(?:[0-9]{3}|VRB)[0-9]{2}(?:KT|MPS)$").unwrap(); - let wind_gust_re = regex::Regex::new(r"^(?:[0-9]{3}|VRB)[0-9]{2}G[0-9]{2}(?:KT|MPS)$").unwrap(); + let wind_gust_re = + regex::Regex::new(r"^(?:[0-9]{3}|VRB)[0-9]{2}G[0-9]{2}(?:KT|MPS)$").unwrap(); // Handle input error where there is a space between the numbers and units let mut value: Option = None; - if metar_parts.len() >= 2 && metar_parts[0].len() == 5 && (metar_parts[1] == "KT" || metar_parts[1] == "MPS") { + if metar_parts.len() >= 2 + && metar_parts[0].len() == 5 + && (metar_parts[1] == "KT" || metar_parts[1] == "MPS") + { value = Some(format!("{}{}", metar_parts[0], metar_parts[1])); metar_parts.remove(0); metar_parts.remove(0); - } else if metar_parts.len() >= 2 && metar_parts[0].len() == 7 && metar_parts[0].contains("G") && (metar_parts[1] == "KT" || metar_parts[1] == "MPS") { + } else if metar_parts.len() >= 2 + && metar_parts[0].len() == 7 + && metar_parts[0].contains("G") + && (metar_parts[1] == "KT" || metar_parts[1] == "MPS") + { value = Some(format!("{}{}", metar_parts[0], metar_parts[1])); metar_parts.remove(0); metar_parts.remove(0); @@ -267,10 +295,10 @@ impl Metar { metar.wind_speed_kt = Some(wind_speed_kt.parse::().unwrap()); metar.wind_gust_kt = Some(wind_gust_kt.parse::().unwrap()); } - }, + } None => {} } - + // Variable Wind Direction let variable_wind_re = regex::Regex::new(r"^[0-9]{3}V[0-9]{3}$").unwrap(); if !metar_parts.is_empty() && variable_wind_re.is_match(metar_parts[0]) { @@ -289,29 +317,67 @@ impl Metar { let visibility_left = visibility_parts[0]; let visibility_right = visibility_parts[1].parse::().unwrap(); if visibility_left.starts_with("M") { - format!("M{}", visibility_left[1..visibility_left.len()].parse::().unwrap() / visibility_right) + format!( + "M{}", + visibility_left[1..visibility_left.len()] + .parse::() + .unwrap() + / visibility_right + ) } else if visibility_left.starts_with("P") { - format!("P{}", visibility_left[1..visibility_left.len()].parse::().unwrap() / visibility_right) + format!( + "P{}", + visibility_left[1..visibility_left.len()] + .parse::() + .unwrap() + / visibility_right + ) } else { - format!("{}", visibility_left.parse::().unwrap() / visibility_right) + format!( + "{}", + visibility_left.parse::().unwrap() / visibility_right + ) } } else { visibility_str.to_string() }; metar.visibility_statute_mi = Some(visibility); - } else if !metar_parts.is_empty() && metar_parts[0].parse::().is_ok() && metar_parts.len() > 1 && visibility_re.is_match(metar_parts[1]) { + } else if !metar_parts.is_empty() + && metar_parts[0].parse::().is_ok() + && metar_parts.len() > 1 + && visibility_re.is_match(metar_parts[1]) + { let visibility_whole = metar_parts[0].parse::().unwrap(); metar_parts.remove(0); let visibility_parts: Vec<&str> = metar_parts[0].split("/").collect(); metar_parts.remove(0); let visibility_left = visibility_parts[0]; - let visibility_right = visibility_parts[1][0..visibility_parts[1].len() - 2].parse::().unwrap(); + let visibility_right = visibility_parts[1][0..visibility_parts[1].len() - 2] + .parse::() + .unwrap(); let visibility = if visibility_left.starts_with("M") { - format!("M{}", visibility_whole + (visibility_left[1..visibility_left.len()].parse::().unwrap() / visibility_right)) + format!( + "M{}", + visibility_whole + + (visibility_left[1..visibility_left.len()] + .parse::() + .unwrap() + / visibility_right) + ) } else if visibility_left.starts_with("P") { - format!("P{}", visibility_whole + (visibility_left[1..visibility_left.len()].parse::().unwrap() / visibility_right)) + format!( + "P{}", + visibility_whole + + (visibility_left[1..visibility_left.len()] + .parse::() + .unwrap() + / visibility_right) + ) } else { - format!("{}", visibility_whole + (visibility_left.parse::().unwrap() / visibility_right)) + format!( + "{}", + visibility_whole + (visibility_left.parse::().unwrap() / visibility_right) + ) }; metar.visibility_statute_mi = Some(visibility); } else if !metar_parts.is_empty() && visibility_re_m.is_match(metar_parts[0]) { @@ -328,8 +394,11 @@ impl Metar { // Runway Visual Range let rvr_re = regex::Regex::new(r"^R[0-9]{1,3}(?:L|R|C)?/[PM]?[0-9]{4}FT$").unwrap(); - let variable_rvr_re = regex::Regex::new(r"^R[0-9]{1,3}(?:L|R|C)?/[PM]?[0-9]{4}V[PM]?[0-9]{4}FT$").unwrap(); - while !metar_parts.is_empty() && (rvr_re.is_match(metar_parts[0]) || variable_rvr_re.is_match(metar_parts[0])) { + let variable_rvr_re = + regex::Regex::new(r"^R[0-9]{1,3}(?:L|R|C)?/[PM]?[0-9]{4}V[PM]?[0-9]{4}FT$").unwrap(); + while !metar_parts.is_empty() + && (rvr_re.is_match(metar_parts[0]) || variable_rvr_re.is_match(metar_parts[0])) + { let rvr_string = metar_parts[0]; metar_parts.remove(0); let mut rvr = RunwayVisualRange::default(); @@ -340,7 +409,10 @@ impl Metar { } else { let rvr_variable_parts: Vec<&str> = rvr_parts[1].split("V").collect(); if rvr_variable_parts.len() != 2 { - warn!("Unable to parse runway visual range in {}: {}", rvr_string, metar_string); + warn!( + "Unable to parse runway visual range in {}: {}", + rvr_string, metar_string + ); } else { rvr.variable_visibility_low_ft = Some(rvr_variable_parts[0].to_string()); rvr.variable_visibility_high_ft = Some(rvr_variable_parts[1].to_string()); @@ -351,8 +423,13 @@ impl Metar { // Weather Phenomena let wx_intensity = "(?:[+-]|VC)?"; let wx_descriptor = "(?:MI|PR|BC|DR|BL|SH|TS|FZ)?"; - let wx_precipitation = "(?:DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)?"; - let wx_re = regex::Regex::new(&format!(r"^{}{}{}$", wx_intensity, wx_descriptor, wx_precipitation)).unwrap(); + let wx_precipitation = + "(?:DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)?"; + let wx_re = regex::Regex::new(&format!( + r"^{}{}{}$", + wx_intensity, wx_descriptor, wx_precipitation + )) + .unwrap(); while !metar_parts.is_empty() && wx_re.is_match(metar_parts[0]) { metar.weather_phenomena.push(metar_parts[0].to_string()); metar_parts.remove(0); @@ -363,11 +440,13 @@ impl Metar { metar.sky_condition.push(SkyCondition { sky_cover: "CLR".to_string(), cloud_base_ft_agl: None, - significant_convective_clouds: None + significant_convective_clouds: None, }); metar_parts.remove(0); } - let sky_condition_re = regex::Regex::new(r"^(?:CLR|SKC|NSC|NCD|(?:FEW|SCT|BKN|OVC|VV)([0-9/]{3})?(?:CB|TCU)?)$").unwrap(); + let sky_condition_re = + regex::Regex::new(r"^(?:CLR|SKC|NSC|NCD|(?:FEW|SCT|BKN|OVC|VV)([0-9/]{3})?(?:CB|TCU)?)$") + .unwrap(); while !metar_parts.is_empty() && sky_condition_re.is_match(metar_parts[0]) { let sky_condition_string = metar_parts[0]; metar_parts.remove(0); @@ -388,7 +467,10 @@ impl Metar { sky_condition.cloud_base_ft_agl = match cloud_base_ft_agl.parse::() { Ok(c) => Some(c * 100), Err(err) => { - warn!("Unable to parse cloud base in {}: {}", sky_condition_string, err); + warn!( + "Unable to parse cloud base in {}: {}", + sky_condition_string, err + ); None } }; @@ -478,7 +560,9 @@ impl Metar { let remark = metar_parts[0]; metar_parts.remove(0); if remark == "AO1" { - metar.quality_control_flags.auto_station_without_precipication = Some(true); + metar + .quality_control_flags + .auto_station_without_precipication = Some(true); } else if remark == "AO2" { metar.quality_control_flags.auto_station_with_precipication = Some(true); } else if remark == "$" { @@ -513,10 +597,13 @@ impl Metar { } } } - + // Skip unexpected fields if !metar_parts.is_empty() { - warn!("Skipping unexpected field: '{}' ({})", metar_parts[0], metar_string); + warn!( + "Skipping unexpected field: '{}' ({})", + metar_parts[0], metar_string + ); metar_parts.remove(0); } } @@ -533,7 +620,7 @@ impl Metar { v.parse::().unwrap() } } - None => 5.0 // Assume VFR if no visibility is present + None => 5.0, // Assume VFR if no visibility is present }; // Ceiling is the lowest cloud base that is BKN or OVC let ceiling = match metar.sky_condition.first() { @@ -543,13 +630,13 @@ impl Metar { } else if s.sky_cover == "BKN" || s.sky_cover == "OVC" { match s.cloud_base_ft_agl { Some(c) => c as f64, - None => 0.0 + None => 0.0, } } else { 3000.0 // Assume VFR if no BKN or OVC sky condition is present } - }, - None => 3000.0 // Assume VFR if no sky condition is present + } + None => 3000.0, // Assume VFR if no sky condition is present }; if visibility >= 5.0 && ceiling >= 3000.0 { metar.flight_category = FlightCategory::VFR; @@ -564,19 +651,22 @@ impl Metar { metars.push(metar); } - return Ok(metars) + return Ok(metars); } fn get_missing_metar_icaos(db_metars: &Vec, station_icaos: &Vec<&str>) -> Vec { let mut missing_metar_icaos: Vec = vec![]; - let current_time = chrono::Local::now().naive_local().timestamp(); - let db_metars_set: HashSet<&str> = db_metars.iter().map(|icao| icao.station_id.as_str()).collect(); + let current_time = chrono::Local::now().naive_local().and_utc().timestamp(); + let db_metars_set: HashSet<&str> = db_metars + .iter() + .map(|icao| icao.station_id.as_str()) + .collect(); let station_icaos_set: HashSet<&str> = station_icaos.to_owned().into_iter().collect(); for difference in db_metars_set.symmetric_difference(&station_icaos_set) { missing_metar_icaos.push(difference.to_string()); } for metar in db_metars { - if current_time > (metar.observation_time.timestamp() + 3600) { + if current_time > (metar.observation_time.and_utc().timestamp() + 3600) { trace!("{} METAR data is outdated", metar.station_id); missing_metar_icaos.push(metar.station_id.to_string()); } @@ -587,7 +677,10 @@ impl Metar { async fn get_remote_metars(icaos: Vec) -> Result, ServiceError> { let gov_api_url = std::env::var("GOV_API_URL").expect("GOV_API_URL must be set"); // Query the remote API for the missing METAR data 10 at a time - let icao_chunks = icaos.chunks(10).map(|chunk| chunk.join(",")).collect::>(); + let icao_chunks = icaos + .chunks(10) + .map(|chunk| chunk.join(",")) + .collect::>(); let mut metars: Vec = vec![]; for icao_chunk in icao_chunks { let url = format!("{}/metar.php?ids={}", gov_api_url, icao_chunk); @@ -595,20 +688,37 @@ impl Metar { Ok(r) => { // Check if the status code is 200 if r.status() != 200 { - return Err(ServiceError::new(500, format!("Unable to get METAR request: {}", r.status()))); + return Err(ServiceError::new( + 500, + format!("Unable to get METAR request: {}", r.status()), + )); } match r.text().await { Ok(r) => { - let metar_chunk = r.trim().split("\n").filter(|m| !m.trim().is_empty()).collect(); + let metar_chunk = r + .trim() + .split("\n") + .filter(|m| !m.trim().is_empty()) + .collect(); match Metar::parse(metar_chunk) { Ok(m) => m, - Err(err) => return Err(err) + Err(err) => return Err(err), } - }, - Err(err) => return Err(ServiceError::new(500, format!("Unable to parse METAR request: {}", err))) + } + Err(err) => { + return Err(ServiceError::new( + 500, + format!("Unable to parse METAR request: {}", err), + )) + } } - }, - Err(err) => return Err(ServiceError::new(500, format!("Unable to get METAR request: {}", err))) + } + Err(err) => { + return Err(ServiceError::new( + 500, + format!("Unable to get METAR request: {}", err), + )) + } }; metars.append(&mut m); } @@ -633,7 +743,7 @@ impl Metar { icao: metar.station_id.to_string(), observation_time: metar.observation_time, raw_text: metar.raw_text.to_string(), - data: serde_json::to_value(metar).unwrap() + data: serde_json::to_value(metar).unwrap(), }); } return insert_metars; @@ -648,7 +758,7 @@ impl Metar { let mut db_metars = match QueryMetar::get_all(&icaos) { Ok(m) => Self::from_query(m), - Err(err) => return Err(err) + Err(err) => return Err(err), }; let missing_icaos = Self::get_missing_metar_icaos(&db_metars, &icaos); @@ -656,14 +766,18 @@ impl Metar { return Ok(db_metars); } trace!("Retrieving missing METAR data for {:?}", missing_icaos); - let missing_icaos_string: Vec = missing_icaos.iter().map(|icao| format!("{}", icao.to_string())).collect(); + let missing_icaos_string: Vec = missing_icaos + .iter() + .map(|icao| format!("{}", icao.to_string())) + .collect(); let mut airports: Vec = vec![]; - missing_icaos_string.clone().iter().for_each(|icao| { - match QueryAirport::get(icao) { + missing_icaos_string + .clone() + .iter() + .for_each(|icao| match QueryAirport::get(icao) { Ok(a) => airports.push(a), Err(_) => {} - } - }); + }); let missing_result = Self::get_remote_metars(missing_icaos_string).await; let mut missing_metars = match missing_result { Ok(m) => m, @@ -676,11 +790,14 @@ impl Metar { let insert_metars = Self::to_insert(&missing_metars); match InsertMetar::insert(&insert_metars) { Ok(rows) => trace!("Inserted {} metar rows", rows), - Err(err) => warn!("Unable to insert metar data; {}", err) + Err(err) => warn!("Unable to insert metar data; {}", err), }; // Update airports with the appropriate has_metar flag airports.iter().for_each(|airport| { - if missing_metars.iter().any(|metar| metar.station_id == airport.icao) { + if missing_metars + .iter() + .any(|metar| metar.station_id == airport.icao) + { let updated = QueryAirport { icao: airport.icao.to_string(), category: airport.category.to_string(), @@ -691,11 +808,11 @@ impl Metar { municipality: airport.municipality.to_string(), has_metar: true, point: airport.point, - data: airport.data.to_owned() + data: airport.data.to_owned(), }; match QueryAirport::update(updated) { - Ok(_) => {}, - Err(err) => warn!("Unable to update airport with has_metar flag; {}", err) + Ok(_) => {} + Err(err) => warn!("Unable to update airport with has_metar flag; {}", err), } } }); @@ -713,15 +830,21 @@ struct InsertMetar { icao: String, observation_time: chrono::NaiveDateTime, raw_text: String, - data: serde_json::Value + data: serde_json::Value, } impl InsertMetar { fn insert(metars: &Vec) -> Result { let mut conn = db::connection()?; - match diesel::insert_into(metars::table).values(metars).execute(&mut conn) { + match diesel::insert_into(metars::table) + .values(metars) + .execute(&mut conn) + { Ok(rows) => Ok(rows), - Err(err) => Err(ServiceError { status: 500, message: format!("{}", err) }) + Err(err) => Err(ServiceError { + status: 500, + message: format!("{}", err), + }), } } } @@ -733,14 +856,25 @@ struct QueryMetar { icao: String, observation_time: chrono::NaiveDateTime, raw_text: String, - data: serde_json::Value + data: serde_json::Value, } impl QueryMetar { fn get_all(icaos: &Vec<&str>) -> Result, ServiceError> { // Sanitize search to only allow [a-zA-Z0-9] - let icaos = icaos.iter().map(|icao| icao.chars().filter(|c| c.is_alphanumeric()).collect::()).collect::>(); - let station_query: Vec = icaos.iter().map(|icao| format!("'{}'", icao.to_string())).collect(); + let icaos = icaos + .iter() + .map(|icao| { + icao + .chars() + .filter(|c| c.is_alphanumeric()) + .collect::() + }) + .collect::>(); + let station_query: Vec = icaos + .iter() + .map(|icao| format!("'{}'", icao.to_string())) + .collect(); let mut conn = db::connection()?; let db_metars: Vec = match sql_query( format!("SELECT DISTINCT ON (icao) * FROM metars WHERE icao IN ({}) ORDER BY icao, observation_time DESC", station_query.join(",")) diff --git a/service/src/metars/routes.rs b/service/src/metars/routes.rs index 2ab58d6..65a3ffb 100644 --- a/service/src/metars/routes.rs +++ b/service/src/metars/routes.rs @@ -6,13 +6,13 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct MetarsResponse { - pub data: Vec, - pub meta: Metadata + pub data: Vec, + pub meta: Metadata, } #[derive(Debug, Serialize, Deserialize)] struct GetAllParameters { - icaos: Option + icaos: Option, } #[get("metars")] @@ -21,26 +21,33 @@ async fn get_all(req: HttpRequest) -> HttpResponse { let icao_option = params.icaos.clone(); let icao_string = match icao_option { Some(i) => i, - None => return HttpResponse::UnprocessableEntity().body("Missing icaos parameter") + None => return HttpResponse::UnprocessableEntity().body("Missing icaos parameter"), }; - let airports = match web::block(|| Ok::<_, ServiceError>(async {Metar::get_all(icao_string).await})) - .await - .unwrap() - .unwrap() - .await { + let airports = + match web::block(|| Ok::<_, ServiceError>(async { Metar::get_all(icao_string).await })) + .await + .unwrap() + .unwrap() + .await + { Ok(a) => a, Err(err) => { error!("{}", err); return err.to_http_response(); } - }; + }; HttpResponse::Ok().json(MetarsResponse { data: airports, - meta: Metadata { page: 0, limit: 0, pages: 0, total: 0 } + meta: Metadata { + page: 0, + limit: 0, + pages: 0, + total: 0, + }, }) } pub fn init_routes(config: &mut web::ServiceConfig) { - config.service(get_all); -} \ No newline at end of file + config.service(get_all); +} diff --git a/service/src/scheduler.rs b/service/src/scheduler.rs index 90284c0..ded94ec 100644 --- a/service/src/scheduler.rs +++ b/service/src/scheduler.rs @@ -1,5 +1,4 @@ use tokio::time::{sleep, Duration}; -use log::{warn, debug, trace}; use crate::airports::{QueryAirport, QueryFilters}; use crate::metars::Metar; @@ -9,38 +8,36 @@ pub fn update_airports() { let mut airports: Vec = vec![]; let limit = 100; loop { - debug!("METAR update start"); + log::debug!("METAR update start"); let total = match QueryAirport::get_count(&QueryFilters::default()) { Ok(t) => t, Err(err) => { - warn!("{}", err); - break + log::warn!("{}", err); + break; } }; if total != airports.len() as i64 { - debug!("{} cached airports, expected {}", airports.len(), total); + log::debug!("{} cached airports, expected {}", airports.len(), total); airports = vec![]; - let pages = ((total as f32) / (if limit <= 0 { 1 } else { limit} as f32)).ceil() as i32; + let pages = ((total as f32) / (if limit <= 0 { 1 } else { limit } as f32)).ceil() as i32; for page in 1..(pages + 1) { match QueryAirport::get_all(&QueryFilters::default(), limit, page) { - Ok(mut a) => { - airports.append(&mut a) - }, + Ok(mut a) => airports.append(&mut a), Err(err) => { - warn!("{}", err); - break + log::warn!("{}", err); + break; } } } } - debug!("Updating {} airport METARS", airports.len()); + log::debug!("Updating {} airport METARS", airports.len()); let airport_icaos: Vec = airports.iter().map(|a| a.icao.to_string()).collect(); let mut peekable = airport_icaos.into_iter().peekable(); let mut observation_time = chrono::Utc::now().timestamp(); if peekable.peek().is_none() { - debug!("No airports to update, sleeping for 1 hour"); + log::debug!("No airports to update, sleeping for 1 hour"); sleep(Duration::from_secs(3600)).await; continue; } @@ -48,30 +45,30 @@ pub fn update_airports() { while peekable.peek().is_some() { let chunk: Vec = peekable.by_ref().take(limit as usize).collect(); let icao_string = chunk.join(","); - trace!("Updating METARS for: {}", icao_string); + log::trace!("Updating METARS for: {}", icao_string); match Metar::get_all(icao_string).await { Ok(metars) => { // Find the oldest observation time for metar in metars { - if metar.observation_time.timestamp() < observation_time { - observation_time = metar.observation_time.timestamp(); + if metar.observation_time.and_utc().timestamp() < observation_time { + observation_time = metar.observation_time.and_utc().timestamp(); } } - }, + } Err(err) => { - warn!("{}", err); + log::warn!("{}", err); } } // Sleep for 100ms between chunks to avoid rate limiting sleep(Duration::from_millis(100)).await; } - debug!("METAR update complete"); + log::debug!("METAR update complete"); // Sleep until the earliest observation time is 1 hour old // Bounded by 1 and 3600 seconds let now = chrono::Utc::now().timestamp(); let sleep_time = std::cmp::min(std::cmp::max(1, now - (observation_time + 3600)), 3600); - debug!("Next update in {} seconds", sleep_time); + log::debug!("Next update in {} seconds", sleep_time); sleep(Duration::from_secs(sleep_time as u64)).await; } }); -} \ No newline at end of file +} diff --git a/service/src/users/mod.rs b/service/src/users/mod.rs index 1688bdf..fa26829 100644 --- a/service/src/users/mod.rs +++ b/service/src/users/mod.rs @@ -1,3 +1,3 @@ mod routes; -pub use routes::init_routes; \ No newline at end of file +pub use routes::init_routes; diff --git a/service/src/users/routes.rs b/service/src/users/routes.rs index 5133761..1ca6a02 100644 --- a/service/src/users/routes.rs +++ b/service/src/users/routes.rs @@ -2,15 +2,17 @@ use actix_multipart::Multipart; use actix_web::{get, post, delete, web, HttpResponse, ResponseError}; use futures_util::StreamExt; -use crate::{auth::{JwtAuth, QueryUser, InsertUser}, error_handler::ServiceError, db::{upload_file, get_file, delete_file}}; +use crate::{ + auth::{JwtAuth, QueryUser, InsertUser}, + error_handler::ServiceError, + db::{upload_file, get_file, delete_file}, +}; #[get("/favorites")] async fn get_favorites(auth: JwtAuth) -> HttpResponse { match QueryUser::get_by_email(&auth.user.email) { - Ok(user) => { - return HttpResponse::Ok().json(user.favorites) - }, - Err(err) => return ResponseError::error_response(&err) + Ok(user) => return HttpResponse::Ok().json(user.favorites), + Err(err) => return ResponseError::error_response(&err), } } @@ -20,18 +22,18 @@ async fn add_favorite(icao: web::Path, auth: JwtAuth) -> HttpResponse { Ok(user) => { if user.favorites.contains(&icao) { // Check if the airport ICAO is already in the user's favorites - return HttpResponse::Conflict().finish() + return HttpResponse::Conflict().finish(); } else { // Add the airport ICAO to the user's favorites let mut favorites = user.favorites; favorites.push(icao.into_inner()); match InsertUser::update_favorites(&user.email, favorites) { Ok(_) => return HttpResponse::Ok().finish(), - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), } } - }, - Err(err) => return ResponseError::error_response(&err) + } + Err(err) => return ResponseError::error_response(&err), } } @@ -46,14 +48,14 @@ async fn delete_favorite(icao: web::Path, auth: JwtAuth) -> HttpResponse favorites.retain(|x| x != &icao); match InsertUser::update_favorites(&user.email, favorites) { Ok(_) => return HttpResponse::Ok().finish(), - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), } } else { // Remove the airport ICAO from the user's favorites - return HttpResponse::Conflict().finish() + return HttpResponse::Conflict().finish(); } - }, - Err(err) => return ResponseError::error_response(&err) + } + Err(err) => return ResponseError::error_response(&err), } } @@ -63,40 +65,52 @@ async fn set_picture(mut payload: Multipart, auth: JwtAuth) -> HttpResponse { let mut bytes = web::BytesMut::new(); let mut field = match item { Ok(field) => field, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; let content_type = field.content_disposition(); - let filename = match content_type.get_filename() { - Some(name) => { - match name.split(".").last() { - Some(ext) => { - match ext { - "apng" | "avif" | "gif" | "jpg" | "jpeg" | "jfif" | "pjpeg" | "pjp" | "png" | "svg" | "webp" => name, - _ => return ResponseError::error_response(&ServiceError::new(400, "File extension is not supported".to_string())) - } - }, - None => return ResponseError::error_response(&ServiceError::new(400, "Unknown file extension".to_string())) + let filename = match content_type.unwrap().get_filename() { + Some(name) => match name.split(".").last() { + Some(ext) => match ext { + "apng" | "avif" | "gif" | "jpg" | "jpeg" | "jfif" | "pjpeg" | "pjp" | "png" | "svg" + | "webp" => name, + _ => { + return ResponseError::error_response(&ServiceError::new( + 400, + "File extension is not supported".to_string(), + )) + } + }, + None => { + return ResponseError::error_response(&ServiceError::new( + 400, + "Unknown file extension".to_string(), + )) } }, - None => return ResponseError::error_response(&ServiceError::new(400, "File name is not provided".to_string())) + None => { + return ResponseError::error_response(&ServiceError::new( + 400, + "File name is not provided".to_string(), + )) + } }; let path = format!("users/{}/{}", auth.user.email, filename); while let Some(chunk) = field.next().await { let data = match chunk { Ok(data) => data, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; bytes.extend_from_slice(&data); } match upload_file(&path, &bytes).await { Ok(_) => { match InsertUser::update_profile_picture(&auth.user.email, Some(&path)) { - Ok(_) => {}, - Err(err) => return ResponseError::error_response(&err) + Ok(_) => {} + Err(err) => return ResponseError::error_response(&err), }; - }, - Err(err) => return ResponseError::error_response(&err) + } + Err(err) => return ResponseError::error_response(&err), }; } HttpResponse::Ok().finish() @@ -106,12 +120,12 @@ async fn set_picture(mut payload: Multipart, auth: JwtAuth) -> HttpResponse { async fn get_picture(auth: JwtAuth) -> HttpResponse { let user = match QueryUser::get_by_email(&auth.user.email) { Ok(user) => user, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; if let Some(path) = user.profile_picture { match get_file(&path).await { Ok(bytes) => HttpResponse::Ok().body(bytes), - Err(err) => ResponseError::error_response(&err) + Err(err) => ResponseError::error_response(&err), } } else { HttpResponse::NotFound().finish() @@ -122,17 +136,15 @@ async fn get_picture(auth: JwtAuth) -> HttpResponse { async fn delete_picture(auth: JwtAuth) -> HttpResponse { let user = match QueryUser::get_by_email(&auth.user.email) { Ok(user) => user, - Err(err) => return ResponseError::error_response(&err) + Err(err) => return ResponseError::error_response(&err), }; if let Some(path) = user.profile_picture { match delete_file(&path).await { - Ok(_) => { - match InsertUser::update_profile_picture(&auth.user.email, None) { - Ok(_) => HttpResponse::Ok().finish(), - Err(err) => ResponseError::error_response(&err) - } + Ok(_) => match InsertUser::update_profile_picture(&auth.user.email, None) { + Ok(_) => HttpResponse::Ok().finish(), + Err(err) => ResponseError::error_response(&err), }, - Err(err) => ResponseError::error_response(&err) + Err(err) => ResponseError::error_response(&err), } } else { HttpResponse::NotFound().finish() @@ -140,12 +152,13 @@ async fn delete_picture(auth: JwtAuth) -> HttpResponse { } pub fn init_routes(config: &mut web::ServiceConfig) { - config.service(web::scope("users") - .service(get_favorites) - .service(add_favorite) - .service(delete_favorite) - .service(set_picture) - .service(get_picture) - .service(delete_picture) + config.service( + web::scope("users") + .service(get_favorites) + .service(add_favorite) + .service(delete_favorite) + .service(set_picture) + .service(get_picture) + .service(delete_picture), ); -} \ No newline at end of file +} diff --git a/ui/.env.TEMPLATE b/ui/.env similarity index 71% rename from ui/.env.TEMPLATE rename to ui/.env index 5af3be1..0b60bdd 100644 --- a/ui/.env.TEMPLATE +++ b/ui/.env @@ -2,4 +2,4 @@ SERVICE_HOST=service SERVICE_PORT=5000 UI_PORT=3000 -NODE_ENV=development \ No newline at end of file +NODE_ENV=development diff --git a/ui/Makefile b/ui/Makefile index 76fd8ed..04a49d8 100644 --- a/ui/Makefile +++ b/ui/Makefile @@ -4,6 +4,8 @@ SHELL := /bin/bash GIT_HASH ?= $(shell git log --format="%h" -n 1) include .env +-include .env.local +export .PHONY: help build start stop lint diff --git a/ui/src/api/airport.ts b/ui/src/api/airport.ts index a01a927..2c50239 100644 --- a/ui/src/api/airport.ts +++ b/ui/src/api/airport.ts @@ -17,6 +17,7 @@ interface GetAirportsProps { name?: string; order_field?: AirportOrderField; order_by?: 'asc' | 'desc'; + has_metar?: boolean; page?: number; limit?: number; } @@ -28,6 +29,7 @@ export async function getAirports({ name, order_field, order_by, + has_metar, limit = 10, page = 1 }: GetAirportsProps): Promise { @@ -40,6 +42,7 @@ export async function getAirports({ name: name ?? undefined, order_field: order_field ?? undefined, order_by: order_by ?? undefined, + has_metar: has_metar ?? undefined, limit, page }); diff --git a/ui/src/components/Header/UserMenu.tsx b/ui/src/components/Header/UserMenu.tsx new file mode 100644 index 0000000..b81b1bd --- /dev/null +++ b/ui/src/components/Header/UserMenu.tsx @@ -0,0 +1,114 @@ +import { User } from "@/api/auth.types"; +import { setPicture } from "@/api/users"; +import { + Menu, + UnstyledButton, + Group, + Avatar, + Card, + FileButton, + Grid, + Button, + Text +} from "@mantine/core"; +import Link from "next/link"; +import { SetterOrUpdater } from "recoil"; +import './styles.css'; + +interface UserMenuProps { + user: User; + profilePicture: File | undefined; + setProfilePicture: SetterOrUpdater; + toggle: (type: string) => void; + logout: () => Promise; +} + +export default function UserMenu({ user, profilePicture, setProfilePicture, logout, toggle }: UserMenuProps) { + + return ( + + + + + +
+ + {user.first_name} {user.last_name} + + + {user.role} + +
+
+
+
+ + + + { + if (payload) { + setPicture(payload).then((response) => { + if (response) { + setProfilePicture(payload); + } + }); + } + }} + accept='image/png,image/jpeg,image/svg+xml,image/webp,image/gif,image/apng,image/avif' + multiple={false} + > + {(props) => ( + + )} + + + {user.first_name} {user.last_name} + + + {user.role} + + + + + + + + + + + {user.role == 'admin' && ( + + + + + + )} + + + +
+ ) +} \ No newline at end of file diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx index 1b0f907..8654ae8 100644 --- a/ui/src/components/Header/index.tsx +++ b/ui/src/components/Header/index.tsx @@ -3,10 +3,8 @@ import Link from 'next/link'; import { useState } from 'react'; import { getAirport, getAirports } from '@/api/airport'; -import { Autocomplete, Avatar, Button, Card, FileButton, Grid, Group, Menu, Text, UnstyledButton } from '@mantine/core'; -import './header.css'; +import { Autocomplete, Button, Group, UnstyledButton } from '@mantine/core'; import { SetterOrUpdater, useRecoilState } from 'recoil'; -import { setPicture } from '@/api/users'; import { useToggle } from '@mantine/hooks'; import { HeaderModal } from './HeaderModal'; import { coordinatesState } from '@/state/map'; @@ -14,6 +12,8 @@ import { User } from '@/api/auth.types'; import { usePathname, useRouter } from 'next/navigation'; import { FaMoon } from "react-icons/fa6"; import { FaSun } from "react-icons/fa6"; +import UserMenu from './UserMenu'; +import './styles.css'; interface HeaderProps { user: User | undefined; @@ -77,13 +77,28 @@ export default function Header({ user, profilePicture, setProfilePicture, login, /> - +
+ + + {/* */} + + {user ? ( + + ) : ( + + + + + )} +
); } - -interface UserSectionProps { - user: User | undefined; - profilePicture: File | undefined; - setProfilePicture: SetterOrUpdater; - toggle: (type: string) => void; - logout: () => Promise; -} - -function UserSection({ user, profilePicture, setProfilePicture, logout, toggle }: UserSectionProps) { - - return ( -
- <> - {/* */} - {/* */} - {/* */} - {/* */} - {user ? ( - - - - - -
- - {user.first_name} {user.last_name} - - - {user.role} - -
-
-
-
- - - - { - if (payload) { - setPicture(payload).then((response) => { - if (response) { - setProfilePicture(payload); - } - }); - } - }} - accept='image/png,image/jpeg,image/svg+xml,image/webp,image/gif,image/apng,image/avif' - multiple={false} - > - {(props) => ( - - )} - - - {user.first_name} {user.last_name} - - - {user.role} - - - - - - - - - - - {user.role == 'admin' && ( - - - - - - )} - - - -
- ) : ( - - - - - )} - -
- ) -} diff --git a/ui/src/components/Header/header.css b/ui/src/components/Header/styles.css similarity index 63% rename from ui/src/components/Header/header.css rename to ui/src/components/Header/styles.css index 4b2addf..4624eb1 100644 --- a/ui/src/components/Header/header.css +++ b/ui/src/components/Header/styles.css @@ -6,27 +6,21 @@ color: white; } -.navbar .left { +.left { display: flex; } -.navbar .title { +.title { padding-left: 2em; padding-right: 2em; margin: auto; } -.navbar .left .search { +.search { margin: auto; } -.navbar .avatar { - padding-right: 2em; - margin-top: auto; - margin-bottom: auto; -} - -.navbar .user-section { +.right { display: flex; align-items: center; padding-right: 2em; diff --git a/ui/src/components/Metars/MapTiles.tsx b/ui/src/components/Metars/MapTiles.tsx index 1a1087f..03b388f 100644 --- a/ui/src/components/Metars/MapTiles.tsx +++ b/ui/src/components/Metars/MapTiles.tsx @@ -3,7 +3,7 @@ import { getAirports, updateAirport } from '@/api/airport'; import { Airport, AirportOrderField } from '@/api/airport.types'; import { getMetars } from '@/api/metar'; -import { LatLngBounds, icon } from 'leaflet'; +import { LatLngBounds, PointTuple, icon } from 'leaflet'; import { useEffect, useState } from 'react'; import { Marker, TileLayer, Tooltip, useMap, useMapEvents } from 'react-leaflet'; import MetarModal from './MetarModal'; @@ -70,8 +70,10 @@ export default function MapTiles() { function metarIcon(airport: Airport) { let iconUrl = '/icons/unkn.svg'; + let iconSize: PointTuple = [20, 20]; if (!airport.has_metar && airport.latest_metar == undefined) { iconUrl = '/icons/nometar.svg'; + iconSize = [10, 10]; } else if (airport.latest_metar?.flight_category == 'VFR') { iconUrl = '/icons/vfr.svg'; } else if (airport.latest_metar?.flight_category == 'MVFR') { @@ -81,10 +83,7 @@ export default function MapTiles() { } else if (airport.latest_metar?.flight_category == 'LIFR') { iconUrl = '/icons/lifr.svg'; } - return icon({ - iconUrl: iconUrl, - iconSize: [20, 20] - }) + return icon({ iconUrl, iconSize }) } useEffect(() => {