From 792d1781c9d87e1ed44e501901b28de46ee89d57 Mon Sep 17 00:00:00 2001 From: Leirn Date: Fri, 19 Jan 2024 14:51:09 +0100 Subject: [PATCH 1/7] Refactoring to prepare for mongodb --- Cargo.lock | 781 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 5 +- config.toml | 3 +- src/app/db/mod.rs | 399 ++++++++++++++++++- src/app/db/mongodb.rs | 141 +++++-- src/app/db/sqlite.rs | 423 ++++++++++++++------- src/app/messages.rs | 7 - src/app/navdata/navaid.rs | 2 +- src/main.rs | 61 ++- 9 files changed, 1598 insertions(+), 224 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c40ffdd..fb1f513 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash", - "base64", + "base64 0.21.7", "bitflags 2.4.1", "brotli", "bytes", @@ -144,7 +144,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2", + "socket2 0.5.5", "tokio", "tracing", ] @@ -205,7 +205,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", + "socket2 0.5.5", "time", "url", ] @@ -285,6 +285,69 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fde6067df7359f2d6335ec1a50c1f8f825801687d10da0cc4c6b08e3f6afd15" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "async-trait" version = "0.1.77" @@ -317,6 +380,12 @@ 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.7" @@ -335,6 +404,18 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -365,6 +446,27 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bson" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c18b51216e1f74b9d769cead6ace2f82b965b807e3d73330aabe9faec31c84" +dependencies = [ + "ahash", + "base64 0.13.1", + "bitvec", + "hex", + "indexmap 1.9.3", + "js-sys", + "once_cell", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -402,6 +504,64 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.48.5", +] + +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "convert_case" version = "0.4.0" @@ -499,6 +659,47 @@ dependencies = [ "memchr", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "deranged" version = "0.3.11" @@ -508,6 +709,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -517,7 +729,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version", + "rustc_version 0.4.0", "syn 1.0.109", ] @@ -529,6 +741,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -540,6 +753,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -569,6 +794,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "flate2" version = "1.0.28" @@ -594,6 +825,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-channel" version = "0.3.30" @@ -609,12 +846,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -635,6 +894,7 @@ checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", + "futures-macro", "futures-task", "memchr", "pin-project-lite", @@ -681,25 +941,63 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.2.11" @@ -757,7 +1055,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -778,6 +1076,46 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -788,6 +1126,16 @@ 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.1.0" @@ -795,7 +1143,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.5", + "widestring", + "windows-sys 0.48.0", + "winreg", ] [[package]] @@ -845,12 +1205,24 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -890,6 +1262,37 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.1" @@ -923,6 +1326,53 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mongodb" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c30763a5c6c52079602be44fa360ca3bfacee55fca73f4734aecd23706a7f2" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bitflags 1.3.2", + "bson", + "chrono", + "derivative", + "derive_more", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hmac", + "lazy_static", + "md-5", + "pbkdf2", + "percent-encoding", + "rand", + "rustc_version_runtime", + "rustls", + "rustls-pemfile", + "serde", + "serde_bytes", + "serde_with", + "sha-1", + "sha2", + "socket2 0.4.10", + "stringprep", + "strsim", + "take_mut", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "trust-dns-proto", + "trust-dns-resolver", + "typed-builder", + "uuid", + "webpki-roots", +] + [[package]] name = "nav_data" version = "0.1.0" @@ -931,11 +1381,14 @@ dependencies = [ "actix-cors", "actix-rt", "actix-web", - "async-trait", + "bson", + "clap", + "clap_derive", "csv", "derive_more", "env_logger", "log", + "mongodb", "reqwest", "serde", "serde_json", @@ -944,6 +1397,15 @@ dependencies = [ "zstd", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -998,6 +1460,15 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1043,6 +1514,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.35" @@ -1052,6 +1529,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1126,7 +1609,7 @@ version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -1160,6 +1643,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + [[package]] name = "ring" version = "0.17.7" @@ -1180,13 +1673,32 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.21", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f" +dependencies = [ + "rustc_version 0.2.3", + "semver 0.9.0", ] [[package]] @@ -1220,7 +1732,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] @@ -1255,12 +1767,27 @@ dependencies = [ "untrusted", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.195" @@ -1270,6 +1797,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.195" @@ -1287,6 +1823,7 @@ version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -1304,6 +1841,39 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1315,6 +1885,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1339,6 +1920,16 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.5" @@ -1385,6 +1976,29 @@ dependencies = [ "sqlite3-src", ] +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1428,6 +2042,18 @@ dependencies = [ "libc", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "termcolor" version = "1.4.1" @@ -1437,6 +2063,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "time" version = "0.3.31" @@ -1495,10 +2141,22 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.5", + "tokio-macros", "windows-sys 0.48.0", ] +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -1517,6 +2175,7 @@ checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -1549,12 +2208,68 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trust-dns-proto" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "log", + "rand", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "parking_lot", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1595,10 +2310,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "version_check" version = "0.9.4" @@ -1702,6 +2433,12 @@ version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -1733,6 +2470,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1875,6 +2621,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml index e9e0273..65cb569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,7 @@ sqlite = "0.32.0" actix-rt = "2.9.0" tokio = "1.35.1" derive_more = "0.99.17" -async-trait = "0.1.77" +mongodb = "2.8.0" +bson = "2.8.1" +clap = { version = "4.4.18", features = ["derive"] } +clap_derive = "4.4.7" diff --git a/config.toml b/config.toml index c21bddd..0299931 100644 --- a/config.toml +++ b/config.toml @@ -4,4 +4,5 @@ PORT = "8080" RUST_BACKTRACE="0" RUST_LOG="info" HTTPS="false" -TOKEN_LIST="aaaa,bbbb,cccc" \ No newline at end of file +TOKEN_LIST="aaaa,bbbb,cccc" +MONGODB_URL="mongodb://localhost:27017" \ No newline at end of file diff --git a/src/app/db/mod.rs b/src/app/db/mod.rs index 43cccf4..381c72c 100644 --- a/src/app/db/mod.rs +++ b/src/app/db/mod.rs @@ -1,12 +1,13 @@ use actix_web::web; -use async_trait::async_trait; pub mod mongodb; pub mod sqlite; -use serde_json::Value; - -use ::sqlite::Connection; +use self::mongodb::MongoDbBackend; +use self::sqlite::SqliteBackend; +use log::error; +use serde::Serialize; use std::error::Error; -use std::sync::Mutex; +use std::fmt; +use std::str::FromStr; const BRANCH_API: &str = "https://api.github.com/repos/davidmegginson/ourairports-data/branches/main"; @@ -19,33 +20,395 @@ const AIRPORT_FREQUENCY_CSV: &str = "airport-frequencies.csv"; const AIRPORT_RUNWAY_CSV: &str = "runways.csv"; const NAVAID_CSV: &str = "navaids.csv"; +#[derive(Serialize, Default)] +pub enum AirportType { + SmallAirport, + MediumAirport, + LargeAirport, + Heliport, + SeaplaneBase, + Closed, + #[default] + Unknown, +} + +impl fmt::Display for AirportType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AirportType::SmallAirport => write!(f, "small_airport"), + AirportType::MediumAirport => write!(f, "medium_airport"), + AirportType::LargeAirport => write!(f, "large_airport"), + AirportType::Heliport => write!(f, "heliport"), + AirportType::SeaplaneBase => write!(f, "seaplane_base"), + AirportType::Closed => write!(f, "closed"), + AirportType::Unknown => write!(f, "unknown"), + } + } +} +impl FromStr for AirportType { + type Err = (); + + fn from_str(input: &str) -> Result { + match input { + "small_airport" => Ok(AirportType::SmallAirport), + "medium_airport" => Ok(AirportType::MediumAirport), + "large_airport" => Ok(AirportType::LargeAirport), + "heliport" => Ok(AirportType::Heliport), + "seaplane_base" => Ok(AirportType::SeaplaneBase), + "closed" => Ok(AirportType::Closed), + _ => { + error!("Unknow airport type {}", input); + Err(()) + } + } + } +} + +#[derive(Serialize, Default)] +pub enum FrequencyType { + Approach, + Tower, + Ground, + Atis, + Ctaf, + Arcal, + Unic, + Cntr, + #[default] + Unknown, +} + +impl fmt::Display for FrequencyType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FrequencyType::Approach => write!(f, "APP"), + FrequencyType::Tower => write!(f, "TWR"), + FrequencyType::Ground => write!(f, "GND"), + FrequencyType::Atis => write!(f, "ATIS"), + FrequencyType::Ctaf => write!(f, "CTAF"), + FrequencyType::Arcal => write!(f, "ARCAL"), + FrequencyType::Unic => write!(f, "UNIC"), + FrequencyType::Cntr => write!(f, "CNTR"), + FrequencyType::Unknown => write!(f, "unknown"), + } + } +} +impl FromStr for FrequencyType { + type Err = (); + + fn from_str(input: &str) -> Result { + match input { + "APP" => Ok(FrequencyType::Approach), + "TWR" => Ok(FrequencyType::Tower), + "GND" => Ok(FrequencyType::Ground), + "ATIS" => Ok(FrequencyType::Atis), + "CTAF" => Ok(FrequencyType::Ctaf), + "ARCAL" => Ok(FrequencyType::Arcal), + "UNIC" => Ok(FrequencyType::Unic), + "CNTR" => Ok(FrequencyType::Cntr), + _ => { + error!("Unknow frequency type {}", input); + Err(()) + } + } + } +} + +#[derive(Serialize, Default)] +pub enum NavaidType { + Vor, + VorDme, + Dme, + Adf, + VorTac, + Tacan, + Ndb, + NdbDme, + #[default] + Unknown, +} + +impl fmt::Display for NavaidType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + NavaidType::Vor => write!(f, "VOR"), + NavaidType::VorDme => write!(f, "VOR-DME"), + NavaidType::Dme => write!(f, "DME"), + NavaidType::VorTac => write!(f, "VORTAC"), + NavaidType::Tacan => write!(f, "TACAN"), + NavaidType::Adf => write!(f, "ADF"), + NavaidType::Ndb => write!(f, "NDB"), + NavaidType::NdbDme => write!(f, "NDB-DME"), + NavaidType::Unknown => write!(f, "unknown"), + } + } +} +impl FromStr for NavaidType { + type Err = (); + + fn from_str(input: &str) -> Result { + match input { + "VOR" => Ok(NavaidType::Vor), + "VOR-DME" => Ok(NavaidType::VorDme), + "DME" => Ok(NavaidType::Dme), + "VORTAC" => Ok(NavaidType::VorTac), + "ADF" => Ok(NavaidType::Adf), + "NDB" => Ok(NavaidType::Ndb), + "NDB-DME" => Ok(NavaidType::NdbDme), + "TACAN" => Ok(NavaidType::Tacan), + _ => { + error!("Unknow navaid type {}", input); + Err(()) + } + } + } +} + +#[derive(Serialize, Default)] +pub struct LocationPoint { + r#type: LocationType, + coordinates: Vec, +} + +#[derive(Serialize, Default)] +pub enum LocationType { + #[default] + Point, +} + +impl fmt::Display for LocationType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LocationType::Point => write!(f, "Point"), + } + } +} +impl FromStr for LocationType { + type Err = (); + + fn from_str(input: &str) -> Result { + match input { + "Point" => Ok(LocationType::Point), + _ => Err(()), + } + } +} + +#[derive(Serialize, Default)] +pub struct Airport { + pub id: i64, + pub icao_code: String, + pub r#type: AirportType, + pub name: String, + pub location: LocationPoint, + pub elevation_ft: i64, + pub continent: String, + pub iso_country: String, + pub iso_region: String, + pub municipality: String, + pub scheduled_service: String, + pub gps_code: String, + pub iata_code: String, + pub local_code: String, + pub home_link: String, + pub wikipedia_link: String, + pub keywords: String, + pub runways: Vec, + pub frequencies: Vec, + pub navaids: Vec, +} + +#[derive(Serialize, Default)] +pub struct Runway { + pub id: i64, + pub airport_id: i64, + pub airport_icao_code: String, + pub length_ft: i64, + pub width_ft: i64, + pub surface: i64, + pub lighted: i64, + pub closed: i64, + pub le_ident: String, + pub le_location: LocationPoint, + pub le_elevation_ft: i64, + pub le_heading_deg_t: i64, + pub le_displaced_threshold_ft: i64, + pub he_ident: String, + pub he_location: LocationPoint, + pub he_elevation_ft: i64, + pub he_heading_deg_t: i64, + pub he_displaced_threshold_ft: i64, +} +#[derive(Serialize, Default)] +pub struct Frequency { + pub id: i64, + pub airport_id: i64, + pub airport_icao_code: String, + pub r#type: FrequencyType, + pub description: String, + pub frequency_mhz: f64, +} +#[derive(Serialize, Default)] +pub struct Navaid { + pub id: i64, + pub filename: String, + pub icao_code: String, + pub name: String, + pub r#type: NavaidType, + pub frequency_khz: i64, + pub location: LocationPoint, + pub elevation_ft: i64, + pub iso_country: String, + pub dme_frequency_khz: i64, + pub dme_channel: String, + pub dme_location: LocationPoint, + pub dme_elevation_ft: i64, + pub slaved_variation_deg: i64, + pub magnetic_variation_deg: i64, + pub usage_type: String, + pub power: String, + pub associated_airport: String, +} + pub struct AppState { - pub sqlite_connection: Mutex, - pub database: Box, + pub database: DatabaseBackend, } pub async fn periodical_update(app_state: web::Data) { - app_state.clone().database.periodical_update().await + let state = app_state.clone(); + state.database.periodical_update().await +} + +#[derive(Clone, Copy)] +pub enum BackendType { + SQLITE, + MONGODB, } -#[async_trait] -pub trait DatabaseBackend { - async fn periodical_update(self: &Self) {} - async fn get_airport_by_icao_code(self: &Self, icao: String) -> Result>; - async fn get_navaid_by_icao_code(self: &Self, icao: String) -> Result>; - async fn get_navaid_by_id(self: &Self, id: i64) -> Result>; - async fn search_navaid( +pub struct DatabaseBackend { + sqlite: Option, + mongo: Option, + active_backend: BackendType, +} +impl DatabaseBackend { + pub async fn new(backend_type: BackendType, path: String) -> DatabaseBackend { + let mut backend = DatabaseBackend { + sqlite: None, + mongo: None, + active_backend: backend_type, + }; + match backend_type { + BackendType::MONGODB => { + let database = MongoDbBackend::new(path.as_str()).await; + backend.mongo = Some(database) + } + BackendType::SQLITE => { + let database = SqliteBackend::new(path); + backend.sqlite = Some(database) + } + } + backend + } + + pub async fn periodical_update(&self) { + match self.active_backend { + BackendType::MONGODB => self.mongo.as_ref().unwrap().periodical_update().await, + BackendType::SQLITE => self.sqlite.as_ref().unwrap().periodical_update().await, + } + } + pub async fn get_airport_by_icao_code(&self, icao: String) -> Result> { + match self.active_backend { + BackendType::MONGODB => { + self.mongo + .as_ref() + .unwrap() + .get_airport_by_icao_code(icao) + .await + } + BackendType::SQLITE => { + self.sqlite + .as_ref() + .unwrap() + .get_airport_by_icao_code(icao) + .await + } + } + } + + pub async fn get_navaids_by_icao_code( + self: &Self, + icao: String, + ) -> Result, Box> { + match self.active_backend { + BackendType::MONGODB => { + self.mongo + .as_ref() + .unwrap() + .get_navaids_by_icao_code(icao) + .await + } + BackendType::SQLITE => { + self.sqlite + .as_ref() + .unwrap() + .get_navaids_by_icao_code(icao) + .await + } + } + } + + pub async fn get_navaid_by_id(self: &Self, id: i64) -> Result> { + match self.active_backend { + BackendType::MONGODB => self.mongo.as_ref().unwrap().get_navaid_by_id(id).await, + BackendType::SQLITE => self.sqlite.as_ref().unwrap().get_navaid_by_id(id).await, + } + } + pub async fn search_navaid( self: &Self, search: Option, page: Option, country: Option, navaid_type: Option, - ) -> Result>; - async fn search_airport( + ) -> Result, Box> { + match self.active_backend { + BackendType::MONGODB => { + self.mongo + .as_ref() + .unwrap() + .search_navaid(search, page, country, navaid_type) + .await + } + BackendType::SQLITE => { + self.sqlite + .as_ref() + .unwrap() + .search_navaid(search, page, country, navaid_type) + .await + } + } + } + pub async fn search_airport( self: &Self, search: Option, page: Option, country: Option, airport_type: Option, - ) -> Result>; + ) -> Result, Box> { + match self.active_backend { + BackendType::MONGODB => { + self.mongo + .as_ref() + .unwrap() + .search_airport(search, page, country, airport_type) + .await + } + BackendType::SQLITE => { + self.sqlite + .as_ref() + .unwrap() + .search_airport(search, page, country, airport_type) + .await + } + } + } } diff --git a/src/app/db/mongodb.rs b/src/app/db/mongodb.rs index 241108f..8c08a50 100644 --- a/src/app/db/mongodb.rs +++ b/src/app/db/mongodb.rs @@ -1,43 +1,140 @@ -use super::DatabaseBackend; -use async_trait::async_trait; -use serde_json::{json, Value}; +use bson::doc; +use log::info; +use mongodb::{options::ClientOptions, Client, Collection}; use std::error::Error; -pub struct MongoDbBackend {} +use super::{sqlite::SqliteBackend, Airport, Navaid}; + +const APP_NAME: &str = "navdata"; +const DATABASE_NAME: &str = "navdata"; +const AIRPORTS_COLLECTION: &str = "airports"; +const NAVAIDS_COLLECTION: &str = "navaids"; + +pub struct MongoDbBackend { + client: Client, +} impl MongoDbBackend { - pub fn new() -> MongoDbBackend { - MongoDbBackend {} + pub async fn new(database_adress: &str) -> MongoDbBackend { + let mut client_options = ClientOptions::parse(database_adress).await.unwrap(); + client_options.app_name = Some(APP_NAME.to_string()); + + // Get a handle to the deployment. + let client = Client::with_options(client_options).unwrap(); + + // List the names of the databases in that deployment. + info!("Check if database {} exists", DATABASE_NAME); + let mut database_exists = false; + for db_name in client.list_database_names(None, None).await.unwrap() { + println!("{}", db_name); + if db_name == DATABASE_NAME { + database_exists = true; + break; + } + } + if !database_exists { + panic!("{} database does not exists", DATABASE_NAME); + } + info!("Found database {}. Continue", DATABASE_NAME); + + // check if collections exists, create if non + let coll_list: Vec = client + .database(DATABASE_NAME) + .list_collection_names(doc! {}) + .await + .unwrap(); + println!("{:?}", coll_list); + if !coll_list.contains(&AIRPORTS_COLLECTION.to_string()) { + info!( + "Collection {} does not exists. Creating...", + AIRPORTS_COLLECTION + ); + client + .database(DATABASE_NAME) + .create_collection(AIRPORTS_COLLECTION, None) + .await + .unwrap(); + } + if !coll_list.contains(&NAVAIDS_COLLECTION.to_string()) { + info!( + "Collection {} does not exists. Creating...", + NAVAIDS_COLLECTION + ); + client + .database(DATABASE_NAME) + .create_collection(NAVAIDS_COLLECTION, None) + .await + .unwrap(); + } + + // let my_coll: Collection = client.database("db").collection("books"); + // let doc = Book { + // _id: 8, + // title: "Atonement".to_string(), + // author: "Ian McEwan".to_string(), + // }; + // let insert_one_result = my_coll.insert_one(doc, None).await?; + // println!( + // "Inserted document with _id: {}", + // insert_one_result.inserted_id + // ); + let backend = MongoDbBackend { + client: client.clone(), + }; + backend.load_database().await; + backend } -} -#[async_trait] -impl DatabaseBackend for MongoDbBackend { - async fn periodical_update(&self) {} - async fn get_airport_by_icao_code(&self, icao: String) -> Result> { - Ok(json!({})) + + async fn load_database(&self) { + let sqlite_be = SqliteBackend::new(":memory:".to_string()); + + let airports_collection: Collection = self + .client + .database(DATABASE_NAME) + .collection(AIRPORTS_COLLECTION); + loop { + let airports = sqlite_be + .search_airport(None, None, None, None) + .await + .unwrap(); + + // airports_collection.insert_many(airports, options); + + // if airports.len() == 0 { + // break; + // } + } } - async fn get_navaid_by_icao_code(&self, icao: String) -> Result> { - Ok(json!({})) + + pub async fn periodical_update(&self) {} + pub async fn get_airport_by_icao_code(&self, icao: String) -> Result> { + Ok(Airport::default()) + } + pub async fn get_navaids_by_icao_code( + &self, + icao: String, + ) -> Result, Box> { + Ok(vec![]) } - async fn get_navaid_by_id(&self, id: i64) -> Result> { - Ok(json!({})) + pub async fn get_navaid_by_id(&self, id: i64) -> Result> { + Ok(Navaid::default()) } - async fn search_navaid( + pub async fn search_navaid( &self, search: Option, page: Option, country: Option, navaid_type: Option, - ) -> Result> { - Ok(json!({})) + ) -> Result, Box> { + Ok(vec![Navaid::default()]) } - async fn search_airport( + pub async fn search_airport( &self, search: Option, page: Option, country: Option, airport_type: Option, - ) -> Result> { - Ok(json!({})) + ) -> Result, Box> { + Ok(vec![Airport::default()]) } } diff --git a/src/app/db/sqlite.rs b/src/app/db/sqlite.rs index b587d0c..e88d030 100644 --- a/src/app/db/sqlite.rs +++ b/src/app/db/sqlite.rs @@ -1,24 +1,25 @@ -use crate::app::db::DatabaseBackend; -use crate::app::messages::*; use crate::app::messages::{CSV_FORMAT_ERROR, ERROR_SQLITE_ACCESS, HTTP_USER_AGENT}; use ::sqlite::Connection; -use async_trait::async_trait; use log::{debug, info}; -use serde_json::{json, Value}; +use serde_json::Value; use sqlite::State; -use std::env; use std::error::Error; +use std::str::FromStr; use std::sync::Mutex; use std::{collections::HashMap, sync::Arc}; use tokio::time::{sleep, Duration}; +use super::{ + Airport, AirportType, Frequency, FrequencyType, LocationPoint, LocationType, Navaid, + NavaidType, Runway, +}; + pub struct SqliteBackend { connection: Arc>, } impl SqliteBackend { - pub fn new() -> SqliteBackend { - let path = env::var(PARAM_DATABASE_PATH).unwrap_or(String::from(DEFAULT_DATABASE)); + pub fn new(path: String) -> SqliteBackend { // Init DB first let connection = sqlite::open(path.clone()).expect(ERROR_SQLITE_ACCESS); @@ -67,29 +68,29 @@ impl SqliteBackend { airport_icao_code TEXT, type TEXT, description TEXT, - frequency_mhz TEXT + frequency_mhz DECIMAL ); CREATE TABLE IF NOT EXISTS airport_runways ( id INTEGER UNIQUE PRIMARY KEY NOT NULL, airport_ref INTEGER, airport_icao_code TEXT, - length_ft INTERGER, - width_ft INTERGER, - surface INTERGER, - lighted INTERGER, - closed INTERGER, - le_ident INTERGER, + length_ft INTEGER, + width_ft INTEGER, + surface INTEGER, + lighted INTEGER, + closed INTEGER, + le_ident INTEGER, le_latitude_deg DECIMAL, le_longitude_deg DECIMAL, - le_elevation_ft INTERGER, - le_heading_degT INTERGER, - le_displaced_threshold_ft INTERGER, - he_ident INTERGER, + le_elevation_ft INTEGER, + le_heading_degT INTEGER, + le_displaced_threshold_ft INTEGER, + he_ident INTEGER, he_latitude_deg DECIMAL, he_longitude_deg DECIMAL, - he_elevation_ft INTERGER, - he_heading_degT INTERGER, - he_displaced_threshold_ft INTERGER + he_elevation_ft INTEGER, + he_heading_degT INTEGER, + he_displaced_threshold_ft INTEGER ); CREATE TABLE IF NOT EXISTS navaids ( id INTEGER UNIQUE PRIMARY KEY NOT NULL, @@ -269,7 +270,7 @@ impl SqliteBackend { let query = "SELECT count(*) as count from navaids"; con.iterate(query, |result| { for &(_, value) in result.iter() { - info!("{} airport runways loaded", value.unwrap()); + info!("{} navaids loaded", value.unwrap()); } true })?; @@ -341,11 +342,7 @@ impl SqliteBackend { Ok(false) } } -} - -#[async_trait] -impl DatabaseBackend for SqliteBackend { - async fn periodical_update(&self) { + pub async fn periodical_update(&self) { loop { info!("Awake ! reloading data"); @@ -402,143 +399,258 @@ impl DatabaseBackend for SqliteBackend { } } - async fn get_airport_by_icao_code(&self, icao: String) -> Result> { - let query = "SELECT * FROM airports WHERE icao_code=?"; - let icao = icao.to_uppercase(); - - let mut data = json!({}); + pub async fn get_runways_by_icao_code( + &self, + icao: String, + ) -> Result, Box> { + let mut runways = vec![]; { + let query = "SELECT * FROM airport_runways WHERE airport_icao_code=?"; let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); - let mut statement = con.prepare(query)?; - statement.bind((1, icao.as_str()))?; + let mut runway_statement = con.prepare(query)?; + runway_statement.bind((1, icao.as_str()))?; + + while let Ok(State::Row) = runway_statement.next() { + let runway = Runway { + id: runway_statement.read::("id")?, + airport_id: runway_statement.read::("airport_ref")?, + airport_icao_code: runway_statement.read::("airport_icao_code")?, + length_ft: runway_statement.read::("length_ft")?, + width_ft: runway_statement.read::("width_ft")?, + surface: runway_statement.read::("surface")?, + lighted: runway_statement.read::("lighted")?, + closed: runway_statement.read::("closed")?, + le_ident: runway_statement.read::("le_ident")?, + le_location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + runway_statement.read::("le_longitude_deg")?, + runway_statement.read::("le_latitude_deg")?, + ], + }, + le_elevation_ft: runway_statement.read::("le_elevation_ft")?, + le_heading_deg_t: runway_statement.read::("le_heading_degT")?, + le_displaced_threshold_ft: runway_statement + .read::("le_displaced_threshold_ft")?, + he_ident: runway_statement.read::("he_ident")?, + he_location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + runway_statement.read::("he_longitude_deg")?, + runway_statement.read::("he_latitude_deg")?, + ], + }, + he_elevation_ft: runway_statement.read::("he_elevation_ft")?, + he_heading_deg_t: runway_statement.read::("he_heading_degT")?, + he_displaced_threshold_ft: runway_statement + .read::("he_displaced_threshold_ft")?, + }; + + runways.push(runway); + } + } + Ok(runways) + } - while let Ok(State::Row) = statement.next() { - for column_name in statement.column_names() { - data.as_object_mut().unwrap().insert( - column_name.clone(), - json!(statement.read::(column_name.as_str())?), - ); - } - let mut freqs = json!([]); - { - let query = "SELECT type, description, frequency_mhz FROM airport_frequencies WHERE airport_icao_code=?"; - let mut freq_statement = con.prepare(query)?; - freq_statement.bind((1, icao.as_str()))?; - - while let Ok(State::Row) = freq_statement.next() { - let mut freq = json!({}); - for column_name in freq_statement.column_names() { - freq.as_object_mut().unwrap().insert( - column_name.clone(), - json!(freq_statement.read::(column_name.as_str())?), - ); - } - freqs.as_array_mut().unwrap().push(freq); - } - } - data.as_object_mut() - .unwrap() - .insert(String::from("frequencies"), freqs); - - let mut runways = json!([]); - { - let query = "SELECT length_ft,width_ft,surface,lighted,closed,le_ident,le_latitude_deg,le_longitude_deg,le_elevation_ft,le_heading_degT,le_displaced_threshold_ft,he_ident,he_latitude_deg,he_longitude_deg,he_elevation_ft,he_heading_degT,he_displaced_threshold_ft FROM airport_runways WHERE airport_icao_code=?"; - let mut runway_statement = con.prepare(query)?; - runway_statement.bind((1, icao.as_str()))?; - - while let Ok(State::Row) = runway_statement.next() { - let mut runway = json!({}); - - for column_name in runway_statement.column_names() { - runway.as_object_mut().unwrap().insert( - column_name.clone(), - json!(runway_statement.read::(column_name.as_str())?), - ); - } - - runways.as_array_mut().unwrap().push(runway); - } - } - data.as_object_mut() - .unwrap() - .insert(String::from("runways"), runways); + pub async fn get_frequencies_by_icao_code( + &self, + icao: String, + ) -> Result, Box> { + let mut frequencies = vec![]; + + { + let query = "SELECT * FROM airport_frequencies WHERE airport_icao_code=?"; + let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); + let mut freq_statement = con.prepare(query)?; + freq_statement.bind((1, icao.as_str()))?; + + while let Ok(State::Row) = freq_statement.next() { + let frequency = Frequency { + id: freq_statement.read::("id")?, + airport_id: freq_statement.read::("airport_ref")?, + airport_icao_code: freq_statement.read::("airport_icao_code")?, + description: freq_statement.read::("description")?, + frequency_mhz: freq_statement.read::("frequency_mhz")?, + r#type: FrequencyType::from_str( + freq_statement.read::("type")?.as_str(), + ) + .unwrap(), + }; + frequencies.push(frequency); } } - let mut navaids = json!([]); + + Ok(frequencies) + } + + pub async fn get_navaids_by_airport_icao_code( + &self, + icao: String, + ) -> Result, Box> { + let mut navaids = vec![]; + let mut ids = vec![]; { - let mut ids = vec![]; - { - let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); - let query = "SELECT id FROM navaids WHERE associated_airport=?"; - let mut navaid_statement = con.prepare(query)?; - navaid_statement.bind((1, icao.as_str()))?; + let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); + let query = "SELECT id FROM navaids WHERE associated_airport=?"; + let mut navaid_statement = con.prepare(query)?; + navaid_statement.bind((1, icao.as_str()))?; - while let Ok(State::Row) = navaid_statement.next() { - ids.push(navaid_statement.read::("id")?); - } + while let Ok(State::Row) = navaid_statement.next() { + ids.push(navaid_statement.read::("id")?); } + } - for id in ids { - let navaid = self.get_navaid_by_id(id).await?; - navaids.as_array_mut().unwrap().push(navaid); + for id in ids { + let navaid = self.get_navaid_by_id(id).await?; + navaids.push(navaid); + } + Ok(navaids) + } + + pub async fn get_airport_by_icao_code(&self, icao: String) -> Result> { + let query = "SELECT * FROM airports WHERE icao_code=?"; + let icao = icao.to_uppercase(); + + let mut airport = Airport::default(); + { + let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); + let mut statement = con.prepare(query)?; + statement.bind((1, icao.clone().as_str()))?; + + while let Ok(State::Row) = statement.next() { + airport.id = statement.read::("id")?; + airport.icao_code = statement.read::("icao_code")?; + airport.r#type = + AirportType::from_str(statement.read::("type")?.as_str()).unwrap(); + airport.name = statement.read::("name")?; + airport.location = LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + statement.read::("longitude_deg")?, + statement.read::("latitude_deg")?, + ], + }; + airport.elevation_ft = statement.read::("elevation_ft")?; + airport.continent = statement.read::("continent")?; + airport.iso_country = statement.read::("iso_country")?; + airport.iso_region = statement.read::("iso_region")?; + airport.municipality = statement.read::("municipality")?; + airport.scheduled_service = statement.read::("scheduled_service")?; + airport.gps_code = statement.read::("gps_code")?; + airport.iata_code = statement.read::("iata_code")?; + airport.local_code = statement.read::("local_code")?; + airport.home_link = statement.read::("home_link")?; + airport.wikipedia_link = statement.read::("wikipedia_link")?; + airport.keywords = statement.read::("keywords")?; } } - data.as_object_mut() - .unwrap() - .insert(String::from("navaids"), navaids); - Ok(data) + airport.runways = self.get_runways_by_icao_code(icao.clone()).await?; + airport.frequencies = self.get_frequencies_by_icao_code(icao.clone()).await?; + airport.navaids = self.get_navaids_by_airport_icao_code(icao.clone()).await?; + Ok(airport) } - async fn get_navaid_by_icao_code(&self, icao: String) -> Result> { + pub async fn get_navaids_by_icao_code( + &self, + icao: String, + ) -> Result, Box> { + let mut navaids = vec![]; let query = "SELECT * FROM navaids WHERE icao_code=?"; let icao = icao.to_uppercase(); - let mut data = json!([]); - let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); let mut statement = con.prepare(query)?; statement.bind((1, icao.as_str()))?; while let Ok(State::Row) = statement.next() { - let mut navaid_data = json!({}); - - for column_name in statement.column_names() { - navaid_data.as_object_mut().unwrap().insert( - column_name.clone(), - json!(statement.read::(column_name.as_str())?), - ); - } - data.as_array_mut().unwrap().push(navaid_data); + let navaid = Navaid { + id: statement.read::("id")?, + filename: statement.read::("filename")?, + icao_code: statement.read::("icao_code")?, + name: statement.read::("elevation_ft")?, + r#type: NavaidType::from_str(statement.read::("type")?.as_str()) + .unwrap(), + frequency_khz: statement.read::("frequency_khz")?, + location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + statement.read::("longitude_deg")?, + statement.read::("latitude_deg")?, + ], + }, + elevation_ft: statement.read::("elevation_ft")?, + iso_country: statement.read::("iso_country")?, + dme_frequency_khz: statement.read::("dme_frequency_khz")?, + dme_channel: statement.read::("dme_channel")?, + dme_location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + statement.read::("dme_longitude_deg")?, + statement.read::("dme_latitude_deg")?, + ], + }, + dme_elevation_ft: statement.read::("dme_elevation_ft")?, + slaved_variation_deg: statement.read::("slaved_variation_deg")?, + magnetic_variation_deg: statement.read::("magnetic_variation_deg")?, + usage_type: statement.read::("usageType")?, + power: statement.read::("power")?, + associated_airport: statement.read::("associated_airport")?, + }; + navaids.push(navaid); } - Ok(data) + Ok(navaids) } - async fn get_navaid_by_id(&self, id: i64) -> Result> { + pub async fn get_navaid_by_id(&self, id: i64) -> Result> { let query = "SELECT * FROM navaids WHERE id=?"; let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); let mut statement = con.prepare(query)?; statement.bind((1, id))?; statement.next()?; - let mut navaid_data = json!({}); - - for column_name in statement.column_names() { - navaid_data.as_object_mut().unwrap().insert( - column_name.clone(), - json!(statement.read::(column_name.as_str())?), - ); - } - Ok(navaid_data) + let navaid = Navaid { + id: statement.read::("id")?, + filename: statement.read::("filename")?, + icao_code: statement.read::("icao_code")?, + name: statement.read::("elevation_ft")?, + r#type: NavaidType::from_str(statement.read::("type")?.as_str()).unwrap(), + frequency_khz: statement.read::("frequency_khz")?, + location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + statement.read::("longitude_deg")?, + statement.read::("latitude_deg")?, + ], + }, + elevation_ft: statement.read::("elevation_ft")?, + iso_country: statement.read::("iso_country")?, + dme_frequency_khz: statement.read::("dme_frequency_khz")?, + dme_channel: statement.read::("dme_channel")?, + dme_location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + statement.read::("dme_longitude_deg")?, + statement.read::("dme_latitude_deg")?, + ], + }, + dme_elevation_ft: statement.read::("dme_elevation_ft")?, + slaved_variation_deg: statement.read::("slaved_variation_deg")?, + magnetic_variation_deg: statement.read::("magnetic_variation_deg")?, + usage_type: statement.read::("usageType")?, + power: statement.read::("power")?, + associated_airport: statement.read::("associated_airport")?, + }; + Ok(navaid) } - async fn search_navaid( + pub async fn search_navaid( &self, search: Option, page: Option, country: Option, navaid_type: Option, - ) -> Result> { + ) -> Result, Box> { let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); // First build the query @@ -594,28 +706,53 @@ impl DatabaseBackend for SqliteBackend { } // Execute statement and get the results - let mut data = json!([]); + let mut navaids = vec![]; while let Ok(State::Row) = statement.next() { - let mut navaid_data = json!({}); - - for column_name in statement.column_names() { - navaid_data.as_object_mut().unwrap().insert( - column_name.clone(), - json!(statement.read::(column_name.as_str())?), - ); - } - data.as_array_mut().unwrap().push(navaid_data); + let navaid = Navaid { + id: statement.read::("id")?, + filename: statement.read::("filename")?, + icao_code: statement.read::("icao_code")?, + name: statement.read::("elevation_ft")?, + r#type: NavaidType::from_str(statement.read::("type")?.as_str()) + .unwrap(), + frequency_khz: statement.read::("frequency_khz")?, + location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + statement.read::("longitude_deg")?, + statement.read::("latitude_deg")?, + ], + }, + elevation_ft: statement.read::("elevation_ft")?, + iso_country: statement.read::("iso_country")?, + dme_frequency_khz: statement.read::("dme_frequency_khz")?, + dme_channel: statement.read::("dme_channel")?, + dme_location: LocationPoint { + r#type: LocationType::Point, + coordinates: vec![ + statement.read::("dme_longitude_deg")?, + statement.read::("dme_latitude_deg")?, + ], + }, + dme_elevation_ft: statement.read::("dme_elevation_ft")?, + slaved_variation_deg: statement.read::("slaved_variation_deg")?, + magnetic_variation_deg: statement.read::("magnetic_variation_deg")?, + usage_type: statement.read::("usageType")?, + power: statement.read::("power")?, + associated_airport: statement.read::("associated_airport")?, + }; + navaids.push(navaid); } - Ok(data) + Ok(navaids) } - async fn search_airport( + pub async fn search_airport( &self, search: Option, page: Option, country: Option, airport_type: Option, - ) -> Result> { + ) -> Result, Box> { let codes = { let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); @@ -680,12 +817,12 @@ impl DatabaseBackend for SqliteBackend { codes }; - let mut data = json!([]); + let mut airports = vec![]; for code in codes { - let airport_data = self.get_airport_by_icao_code(code).await?; - data.as_array_mut().unwrap().push(airport_data); + let airport = self.get_airport_by_icao_code(code).await?; + airports.push(airport); } - Ok(data) + Ok(airports) } } diff --git a/src/app/messages.rs b/src/app/messages.rs index cfcfb6b..da63230 100644 --- a/src/app/messages.rs +++ b/src/app/messages.rs @@ -3,15 +3,8 @@ pub const ERROR_SQLITE_ACCESS: &str = "Error while accessing SQLite connection"; pub const CSV_FORMAT_ERROR: &str = "CSV file does not have the right format"; // Parameters -pub const PARAM_DATABASE_PATH: &str = "DATABASE_PATH"; -pub const DEFAULT_DATABASE: &str = ":memory:"; pub const PARAM_TOKEN_LIST: &str = "TOKEN_LIST"; pub const TOKEN_COOKIE: &str = "navaid_auth_token"; -pub const PARAM_HOST: &str = "HOST"; -pub const DEFAULT_HOST: &str = "127.0.0.1"; -pub const PARAM_PORT: &str = "PORT"; -pub const DEFAULT_PORT: &str = "8080"; -pub const PORT_ERROR: &str = "$PORT cannot be converted to uint_16"; // HTTP pub const HTTP_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; diff --git a/src/app/navdata/navaid.rs b/src/app/navdata/navaid.rs index e18e6b9..e5d706c 100644 --- a/src/app/navdata/navaid.rs +++ b/src/app/navdata/navaid.rs @@ -48,7 +48,7 @@ async fn navaid_by_icao_code( info!("Request received : /navaid/{}", icao); let data = app_state .database - .get_navaid_by_icao_code(icao.to_string()) + .get_navaids_by_icao_code(icao.to_string()) .await; match data { Ok(data) => HttpResponse::Ok().json(json!({"status": "success", "navaid" : data})), diff --git a/src/main.rs b/src/main.rs index e955e82..9f86f56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,59 @@ mod app; use actix_cors::Cors; use actix_web::{middleware::Logger, web, App, HttpServer}; -use app::db::sqlite::SqliteBackend; -use app::messages::*; +use app::db::{periodical_update, AppState, BackendType, DatabaseBackend}; use app::security::simple_token::SimpleToken; +use clap::Parser; -use app::db::{periodical_update, AppState}; -use sqlite; -use std::env; -use std::sync::Mutex; +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// HTTP Server host + #[arg(short, long, default_value = "127.0.0.1")] + address: String, + + /// HTTP Server port + #[arg(short, long, default_value_t = 8080)] + port: u16, + + /// Database backend sqlite + #[arg(short, long, default_value_t = true)] + sqlite: bool, + + /// Database backend mongodb + #[arg(short, long, default_value_t = false)] + mongodb: bool, + + /// Batabase path. Use ":memory:" for in memory database for + #[arg(short, long, default_value = ":memory:")] + db_path: String, + + /// loglevel. 0 for error, 1 for warn, 2 for info, 3 for debug + #[arg(short, long, default_value_t = 2)] + loglevel: u8, +} #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init(); - let host = env::var(PARAM_HOST).unwrap_or(String::from(DEFAULT_HOST)); + let args = Args::parse(); + + let host = args.address; + let port = args.port; - let port = env::var(PARAM_PORT) - .unwrap_or(String::from(DEFAULT_PORT)) - .parse() - .expect(PORT_ERROR); + let backend_type = { + if args.mongodb { + BackendType::MONGODB + } else { + BackendType::SQLITE + } + }; + let database_path = args.db_path; - let database_path = env::var(PARAM_DATABASE_PATH).unwrap_or(String::from(DEFAULT_DATABASE)); - let connection = sqlite::open(database_path).expect(ERROR_SQLITE_ACCESS); - let backend = SqliteBackend::new(); + let backend = DatabaseBackend::new(backend_type, database_path).await; - let app_state = web::Data::new(AppState { - sqlite_connection: Mutex::new(connection), - database: Box::new(backend), - }); + let app_state = web::Data::new(AppState { database: backend }); actix_rt::spawn(periodical_update(app_state.clone())); From 9944cdf32b82f22da6b24df98fe62b32f8bbfbdf Mon Sep 17 00:00:00 2001 From: Leirn Date: Mon, 22 Jan 2024 14:56:12 +0100 Subject: [PATCH 2/7] Partially functional mongodb implementation ?search= must be given to avoid error wildcards search are missing for mongodb Geographical search is available --- Cargo.lock | 119 ++++++------- Cargo.toml | 11 +- src/app/db/mod.rs | 192 ++++++++++++++++++--- src/app/db/mongodb.rs | 337 +++++++++++++++++++++++++++++++++---- src/app/db/sqlite.rs | 26 ++- src/app/navdata/airport.rs | 35 +++- src/app/navdata/navaid.rs | 28 ++- 7 files changed, 602 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb1f513..e1db698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,9 +302,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fde6067df7359f2d6335ec1a50c1f8f825801687d10da0cc4c6b08e3f6afd15" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -765,17 +765,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -784,16 +794,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "finl_unicode" version = "1.2.0" @@ -831,6 +831,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -838,6 +853,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -892,9 +908,11 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -1164,17 +1182,6 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "is-terminal" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.52.0", -] - [[package]] name = "itoa" version = "1.0.10" @@ -1223,12 +1230,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linux-raw-sys" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" - [[package]] name = "local-channel" version = "0.1.5" @@ -1387,6 +1388,7 @@ dependencies = [ "csv", "derive_more", "env_logger", + "futures", "log", "mongodb", "reqwest", @@ -1394,6 +1396,7 @@ dependencies = [ "serde_json", "sqlite", "tokio", + "uuid", "zstd", ] @@ -1701,19 +1704,6 @@ dependencies = [ "semver 0.9.0", ] -[[package]] -name = "rustix" -version = "0.38.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" -dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - [[package]] name = "rustls" version = "0.21.10" @@ -2054,15 +2044,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.56" @@ -2322,12 +2303,25 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" dependencies = [ "getrandom", + "rand", "serde", + "uuid-macro-internal", +] + +[[package]] +name = "uuid-macro-internal" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", ] [[package]] @@ -2455,15 +2449,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 65cb569..b11ed2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ zstd = "0.13.0" actix = "0.13.1" actix-cors = "0.7" actix-web = "4.4.1" -env_logger = "0.10.0" +env_logger = "0.11.0" log = "0.4" reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = {version = "1.0.163", features = ["derive"] } @@ -24,3 +24,12 @@ mongodb = "2.8.0" bson = "2.8.1" clap = { version = "4.4.18", features = ["derive"] } clap_derive = "4.4.7" +futures = "0.3.30" + +[dependencies.uuid] +version = "1.7.0" +features = [ + "v4", # Lets you generate random UUIDs + "fast-rng", # Use a faster (but still sufficiently random) RNG + "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs +] \ No newline at end of file diff --git a/src/app/db/mod.rs b/src/app/db/mod.rs index 381c72c..5b6dde9 100644 --- a/src/app/db/mod.rs +++ b/src/app/db/mod.rs @@ -4,7 +4,7 @@ pub mod sqlite; use self::mongodb::MongoDbBackend; use self::sqlite::SqliteBackend; use log::error; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt; use std::str::FromStr; @@ -20,13 +20,14 @@ const AIRPORT_FREQUENCY_CSV: &str = "airport-frequencies.csv"; const AIRPORT_RUNWAY_CSV: &str = "runways.csv"; const NAVAID_CSV: &str = "navaids.csv"; -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub enum AirportType { SmallAirport, MediumAirport, LargeAirport, Heliport, SeaplaneBase, + BalloonPort, Closed, #[default] Unknown, @@ -40,6 +41,7 @@ impl fmt::Display for AirportType { AirportType::LargeAirport => write!(f, "large_airport"), AirportType::Heliport => write!(f, "heliport"), AirportType::SeaplaneBase => write!(f, "seaplane_base"), + AirportType::BalloonPort => write!(f, "balloonport"), AirportType::Closed => write!(f, "closed"), AirportType::Unknown => write!(f, "unknown"), } @@ -55,6 +57,7 @@ impl FromStr for AirportType { "large_airport" => Ok(AirportType::LargeAirport), "heliport" => Ok(AirportType::Heliport), "seaplane_base" => Ok(AirportType::SeaplaneBase), + "balloonport" => Ok(AirportType::BalloonPort), "closed" => Ok(AirportType::Closed), _ => { error!("Unknow airport type {}", input); @@ -64,16 +67,53 @@ impl FromStr for AirportType { } } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub enum FrequencyType { Approach, + Departure, + Arrival, + ApproachDeparture, + ArrivalDeparture, + SafetyComm, + AutoInformation, + Clearance, Tower, Ground, + Com, Atis, + Afis, + Info, Ctaf, Arcal, + Emergency, Unic, + Unicom, Cntr, + Artc, + AD, + AG, + Mult, + Ops, + Atf, + Acc, + Misc, + Pmsv, + Traffic, + Awos, + Asos, + Fss, + Fcc, + Rdo, + Rco, + Cld, + Fia, + Ils, + Military, + Private, + Ramp, + Apron, + Radar, + NotControlled, #[default] Unknown, } @@ -82,13 +122,50 @@ impl fmt::Display for FrequencyType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { FrequencyType::Approach => write!(f, "APP"), + FrequencyType::Departure => write!(f, "DEP"), + FrequencyType::Arrival => write!(f, "ARR"), + FrequencyType::ApproachDeparture => write!(f, "APP/DEP"), + FrequencyType::ArrivalDeparture => write!(f, "ARR/DEP"), FrequencyType::Tower => write!(f, "TWR"), FrequencyType::Ground => write!(f, "GND"), + FrequencyType::Com => write!(f, "COM"), + FrequencyType::Clearance => write!(f, "CLR"), + FrequencyType::Emergency => write!(f, "EMR"), FrequencyType::Atis => write!(f, "ATIS"), + FrequencyType::Afis => write!(f, "AFIS"), + FrequencyType::AutoInformation => write!(f, "AUTO INFORMATION"), + FrequencyType::SafetyComm => write!(f, "SAFETY COMM"), + FrequencyType::Info => write!(f, "INFO"), FrequencyType::Ctaf => write!(f, "CTAF"), FrequencyType::Arcal => write!(f, "ARCAL"), FrequencyType::Unic => write!(f, "UNIC"), + FrequencyType::Unicom => write!(f, "UNICOM"), FrequencyType::Cntr => write!(f, "CNTR"), + FrequencyType::Artc => write!(f, "ARTC"), + FrequencyType::AD => write!(f, "A/D"), + FrequencyType::AG => write!(f, "A/G"), + FrequencyType::Mult => write!(f, "MULT"), + FrequencyType::Ops => write!(f, "OPS"), + FrequencyType::Atf => write!(f, "ATF"), + FrequencyType::Acc => write!(f, "ACC"), + FrequencyType::Misc => write!(f, "MISC"), + FrequencyType::Pmsv => write!(f, "PMSV"), + FrequencyType::Traffic => write!(f, "TFC"), + FrequencyType::Awos => write!(f, "AWOS"), + FrequencyType::Asos => write!(f, "ASOS"), + FrequencyType::Fss => write!(f, "FSS"), + FrequencyType::Fcc => write!(f, "FCC"), + FrequencyType::Rdo => write!(f, "RDO"), + FrequencyType::Rco => write!(f, "RCO"), + FrequencyType::Cld => write!(f, "CLD"), + FrequencyType::Fia => write!(f, "FIA"), + FrequencyType::Apron => write!(f, "APRON"), + FrequencyType::Ramp => write!(f, "RMP"), + FrequencyType::Military => write!(f, "MIL"), + FrequencyType::Ils => write!(f, "ILS"), + FrequencyType::Radar => write!(f, "RADAR"), + FrequencyType::Private => write!(f, "Private"), + FrequencyType::NotControlled => write!(f, "Non controlled"), FrequencyType::Unknown => write!(f, "unknown"), } } @@ -97,24 +174,88 @@ impl FromStr for FrequencyType { type Err = (); fn from_str(input: &str) -> Result { - match input { + let input = input.to_string().to_uppercase(); + match input.as_str() { "APP" => Ok(FrequencyType::Approach), + "APR" => Ok(FrequencyType::Approach), + "APPR" => Ok(FrequencyType::Approach), + "APROAC" => Ok(FrequencyType::Approach), + "APPROACH" => Ok(FrequencyType::Approach), + "TMA" => Ok(FrequencyType::Approach), + "APP/DEP" => Ok(FrequencyType::ApproachDeparture), + "ARR/DEP" => Ok(FrequencyType::ArrivalDeparture), + "DEP" => Ok(FrequencyType::Departure), + "DEPARTURES" => Ok(FrequencyType::Departure), + "ARR" => Ok(FrequencyType::Arrival), "TWR" => Ok(FrequencyType::Tower), + "TOWER" => Ok(FrequencyType::Tower), + "EMR" => Ok(FrequencyType::Emergency), + "EMERG" => Ok(FrequencyType::Emergency), + "EMEG" => Ok(FrequencyType::Emergency), + "EMG" => Ok(FrequencyType::Emergency), + "CLR" => Ok(FrequencyType::Clearance), + "CLEARANCE" => Ok(FrequencyType::Clearance), "GND" => Ok(FrequencyType::Ground), + "GROUND" => Ok(FrequencyType::Ground), + "GRN" => Ok(FrequencyType::Ground), + "COM" => Ok(FrequencyType::Com), "ATIS" => Ok(FrequencyType::Atis), + "AFIS" => Ok(FrequencyType::Afis), + "AUTO INFO" => Ok(FrequencyType::AutoInformation), + "AUTOINFO" => Ok(FrequencyType::AutoInformation), + "AUTO-INFO" => Ok(FrequencyType::AutoInformation), + "A/A" => Ok(FrequencyType::AutoInformation), + "AUTO INFORMATION" => Ok(FrequencyType::AutoInformation), + "AUTO INFORMACION" => Ok(FrequencyType::AutoInformation), + "SAFETY COMM" => Ok(FrequencyType::SafetyComm), + "SAFETYCOM" => Ok(FrequencyType::SafetyComm), + "SAFETY COM" => Ok(FrequencyType::SafetyComm), + "FIS" => Ok(FrequencyType::Afis), + "INFO" => Ok(FrequencyType::Info), + "SIV" => Ok(FrequencyType::Info), "CTAF" => Ok(FrequencyType::Ctaf), "ARCAL" => Ok(FrequencyType::Arcal), "UNIC" => Ok(FrequencyType::Unic), + "UNICOM" => Ok(FrequencyType::Unicom), "CNTR" => Ok(FrequencyType::Cntr), + "CENTER" => Ok(FrequencyType::Cntr), + "ARTC" => Ok(FrequencyType::Artc), + "A/D" => Ok(FrequencyType::AD), + "A/G" => Ok(FrequencyType::AG), + "MULT" => Ok(FrequencyType::Mult), + "OPS" => Ok(FrequencyType::Ops), + "ATF" => Ok(FrequencyType::Atf), + "ACC" => Ok(FrequencyType::Acc), + "MISC" => Ok(FrequencyType::Misc), + "PMSV" => Ok(FrequencyType::Pmsv), + "TFC" => Ok(FrequencyType::Traffic), + "TRFC" => Ok(FrequencyType::Traffic), + "AWOS" => Ok(FrequencyType::Awos), + "ASOS" => Ok(FrequencyType::Asos), + "FSS" => Ok(FrequencyType::Fss), + "FCC" => Ok(FrequencyType::Fcc), + "RDO" => Ok(FrequencyType::Rdo), + "RCO" => Ok(FrequencyType::Rco), + "CLD" => Ok(FrequencyType::Cld), + "FIA" => Ok(FrequencyType::Fia), + "RMP" => Ok(FrequencyType::Ramp), + "APRON" => Ok(FrequencyType::Apron), + "MIL" => Ok(FrequencyType::Military), + "ILS" => Ok(FrequencyType::Ils), + "RAD" => Ok(FrequencyType::Radar), + "RDR" => Ok(FrequencyType::Radar), + "RADAR" => Ok(FrequencyType::Radar), + "PRIVATE" => Ok(FrequencyType::Private), + "NON CONTROLLED" => Ok(FrequencyType::NotControlled), _ => { error!("Unknow frequency type {}", input); - Err(()) + Ok(FrequencyType::Unknown) } } } } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub enum NavaidType { Vor, VorDme, @@ -158,19 +299,19 @@ impl FromStr for NavaidType { "TACAN" => Ok(NavaidType::Tacan), _ => { error!("Unknow navaid type {}", input); - Err(()) + Ok(NavaidType::Unknown) } } } } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct LocationPoint { r#type: LocationType, coordinates: Vec, } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub enum LocationType { #[default] Point, @@ -194,7 +335,7 @@ impl FromStr for LocationType { } } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct Airport { pub id: i64, pub icao_code: String, @@ -218,7 +359,7 @@ pub struct Airport { pub navaids: Vec, } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct Runway { pub id: i64, pub airport_id: i64, @@ -239,16 +380,17 @@ pub struct Runway { pub he_heading_deg_t: i64, pub he_displaced_threshold_ft: i64, } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct Frequency { pub id: i64, pub airport_id: i64, pub airport_icao_code: String, pub r#type: FrequencyType, + pub raw_type: String, pub description: String, pub frequency_mhz: f64, } -#[derive(Serialize, Default)] +#[derive(Serialize, Deserialize, Default)] pub struct Navaid { pub id: i64, pub filename: String, @@ -316,7 +458,10 @@ impl DatabaseBackend { BackendType::SQLITE => self.sqlite.as_ref().unwrap().periodical_update().await, } } - pub async fn get_airport_by_icao_code(&self, icao: String) -> Result> { + pub async fn get_airport_by_icao_code( + &self, + icao: String, + ) -> Result, Box> { match self.active_backend { BackendType::MONGODB => { self.mongo @@ -356,33 +501,28 @@ impl DatabaseBackend { } } } - - pub async fn get_navaid_by_id(self: &Self, id: i64) -> Result> { - match self.active_backend { - BackendType::MONGODB => self.mongo.as_ref().unwrap().get_navaid_by_id(id).await, - BackendType::SQLITE => self.sqlite.as_ref().unwrap().get_navaid_by_id(id).await, - } - } pub async fn search_navaid( self: &Self, search: Option, page: Option, country: Option, navaid_type: Option, + latitude: Option, + longitude: Option, ) -> Result, Box> { match self.active_backend { BackendType::MONGODB => { self.mongo .as_ref() .unwrap() - .search_navaid(search, page, country, navaid_type) + .search_navaid(search, page, country, navaid_type, latitude, longitude) .await } BackendType::SQLITE => { self.sqlite .as_ref() .unwrap() - .search_navaid(search, page, country, navaid_type) + .search_navaid(search, page, country, navaid_type, latitude, longitude) .await } } @@ -393,20 +533,22 @@ impl DatabaseBackend { page: Option, country: Option, airport_type: Option, + latitude: Option, + longitude: Option, ) -> Result, Box> { match self.active_backend { BackendType::MONGODB => { self.mongo .as_ref() .unwrap() - .search_airport(search, page, country, airport_type) + .search_airport(search, page, country, airport_type, latitude, longitude) .await } BackendType::SQLITE => { self.sqlite .as_ref() .unwrap() - .search_airport(search, page, country, airport_type) + .search_airport(search, page, country, airport_type, latitude, longitude) .await } } diff --git a/src/app/db/mongodb.rs b/src/app/db/mongodb.rs index 8c08a50..834c09d 100644 --- a/src/app/db/mongodb.rs +++ b/src/app/db/mongodb.rs @@ -1,9 +1,13 @@ +use super::{sqlite::SqliteBackend, Airport, Navaid}; use bson::doc; +use futures::stream::TryStreamExt; use log::info; -use mongodb::{options::ClientOptions, Client, Collection}; +use mongodb::{ + options::{ClientOptions, FindOptions, IndexOptions, ReplaceOptions, Sphere2DIndexVersion}, + Client, Collection, IndexModel, +}; use std::error::Error; - -use super::{sqlite::SqliteBackend, Airport, Navaid}; +use tokio::time::{sleep, Duration}; const APP_NAME: &str = "navdata"; const DATABASE_NAME: &str = "navdata"; @@ -37,8 +41,18 @@ impl MongoDbBackend { } info!("Found database {}. Continue", DATABASE_NAME); + let backend = MongoDbBackend { + client: client.clone(), + }; + backend.create_collections().await; + backend.create_indexes().await; + backend + } + + async fn create_collections(&self) { // check if collections exists, create if non - let coll_list: Vec = client + let coll_list: Vec = self + .client .database(DATABASE_NAME) .list_collection_names(doc! {}) .await @@ -49,7 +63,7 @@ impl MongoDbBackend { "Collection {} does not exists. Creating...", AIRPORTS_COLLECTION ); - client + self.client .database(DATABASE_NAME) .create_collection(AIRPORTS_COLLECTION, None) .await @@ -60,64 +74,208 @@ impl MongoDbBackend { "Collection {} does not exists. Creating...", NAVAIDS_COLLECTION ); - client + self.client .database(DATABASE_NAME) .create_collection(NAVAIDS_COLLECTION, None) .await .unwrap(); } + } - // let my_coll: Collection = client.database("db").collection("books"); - // let doc = Book { - // _id: 8, - // title: "Atonement".to_string(), - // author: "Ian McEwan".to_string(), - // }; - // let insert_one_result = my_coll.insert_one(doc, None).await?; - // println!( - // "Inserted document with _id: {}", - // insert_one_result.inserted_id - // ); - let backend = MongoDbBackend { - client: client.clone(), - }; - backend.load_database().await; - backend + async fn create_indexes(&self) { + // https://www.mongodb.com/docs/drivers/rust/current/fundamentals/indexes/ + + let airports_collection: Collection = self + .client + .database(DATABASE_NAME) + .collection(AIRPORTS_COLLECTION); + let navaids_collection: Collection = self + .client + .database(DATABASE_NAME) + .collection(NAVAIDS_COLLECTION); + + // id must be unique on all collections for updates + let option = IndexOptions::builder().unique(true).build(); + let index_model = IndexModel::builder() + .keys(doc! { "id": 1 }) + .options(option) + .build(); + let index = airports_collection + .create_index(index_model.clone(), None) + .await + .unwrap(); + info!("Index {} created for airports collection", index.index_name); + let index = navaids_collection + .create_index(index_model, None) + .await + .unwrap(); + info!("Index {} created for navaid collection", index.index_name); + + // icao_code are unique for airports must be unique on all collections for updates + let option = IndexOptions::builder().unique(true).build(); + let index_model = IndexModel::builder() + .keys(doc! { "iaco_code": "text" }) + .options(option) + .build(); + let index = airports_collection + .create_index(index_model.clone(), None) + .await + .unwrap(); + info!("Index {} created for airports collection", index.index_name); + + // name, municipality, iata_code, iso_country and type are mandatory to speed_up searchs on airports + let index_model = IndexModel::builder() + .keys( + doc! { "name": 1, "municipality": 1, "iata_code": 1, "iso_country": 1, "type": 1 }, + ) + .build(); + match airports_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for airports collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } + + // name, filename, associated_airport, iso_country and type are mandatory to speed_up searchs on navaids + let index_model = IndexModel::builder() + .keys( + doc! { "name": 1,"filename": 1,"associated_airport": 1,"type": 1,"iso_country": 1 }, + ) + .build(); + match navaids_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for navaids collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } + + // locations are geo-indexed + let option = IndexOptions::builder() + .sphere_2d_index_version(Sphere2DIndexVersion::V3) + .build(); + let index_model = IndexModel::builder() + .keys(doc! { "location": "2dsphere" }) + .options(option) + .build(); + let index = airports_collection + .create_index(index_model.clone(), None) + .await + .unwrap(); + info!("Index {} created for airports collection", index.index_name); + let index = navaids_collection + .create_index(index_model, None) + .await + .unwrap(); + info!("Index {} created for navaid collection", index.index_name); } async fn load_database(&self) { + // Loading data to sqlite temporarly let sqlite_be = SqliteBackend::new(":memory:".to_string()); + sqlite_be.load_airports().await.unwrap(); + sqlite_be.load_airport_frequencies().await.unwrap(); + sqlite_be.load_airport_runways().await.unwrap(); + sqlite_be.load_navaids().await.unwrap(); + // Copying airports from sqlite to mongodb let airports_collection: Collection = self .client .database(DATABASE_NAME) .collection(AIRPORTS_COLLECTION); + info!("Start adding airports"); + let mut airport_count = 0; + let mut page = 0; loop { let airports = sqlite_be - .search_airport(None, None, None, None) + .search_airport(None, Some(page), None, None, None, None) .await .unwrap(); - // airports_collection.insert_many(airports, options); + if airports.len() == 0 { + break; + } + + // let result = airports_collection + // .insert_many(airports, None) + // .await + // .unwrap(); - // if airports.len() == 0 { - // break; - // } + for airport in airports { + if airport.icao_code.len() < 4 { + continue; + } + let option = ReplaceOptions::builder().upsert(true).build(); + airports_collection + .replace_one(doc! { "id": airport.id }, airport, Some(option)) + .await + .unwrap(); + airport_count += 1; + } + // airport_count += result.inserted_ids.len(); + page += 1 } + info!("{} airports added to MongoDB", airport_count); + + // Copying navids from sqlite to mongodb + let navaids_collection: Collection = self + .client + .database(DATABASE_NAME) + .collection(NAVAIDS_COLLECTION); + info!("Start adding navaid"); + let mut navaid_count = 0; + let mut page = 0; + loop { + let navaids = sqlite_be + .search_navaid(None, Some(page), None, None, None, None) + .await + .unwrap(); + + if navaids.len() == 0 { + break; + } + let result = navaids_collection.insert_many(navaids, None).await.unwrap(); + navaid_count += result.inserted_ids.len(); + page += 1; + } + info!("{} navaids added to MongoDB", navaid_count); } - pub async fn periodical_update(&self) {} - pub async fn get_airport_by_icao_code(&self, icao: String) -> Result> { - Ok(Airport::default()) + pub async fn periodical_update(&self) { + loop { + info!("Awake ! reloading data"); + self.load_database().await; + info!("Database fully reloaded"); + let _delay = sleep(Duration::from_secs(86400)).await; + } + } + + pub async fn get_airport_by_icao_code( + &self, + icao: String, + ) -> Result, Box> { + let coll: Collection = self + .client + .database(DATABASE_NAME) + .collection(AIRPORTS_COLLECTION); + let result = coll.find_one(doc! {"icao_code":icao}, None).await?; + Ok(result) } pub async fn get_navaids_by_icao_code( &self, icao: String, ) -> Result, Box> { - Ok(vec![]) - } - pub async fn get_navaid_by_id(&self, id: i64) -> Result> { - Ok(Navaid::default()) + let coll: Collection = self + .client + .database(DATABASE_NAME) + .collection(NAVAIDS_COLLECTION); + let mut result = coll.find(doc! {"icao_code":icao}, None).await?; + let mut navaids = vec![]; + while let Some(navaid) = result.try_next().await? { + navaids.push(navaid); + } + Ok(navaids) } pub async fn search_navaid( &self, @@ -125,8 +283,61 @@ impl MongoDbBackend { page: Option, country: Option, navaid_type: Option, + latitude: Option, + longitude: Option, ) -> Result, Box> { - Ok(vec![Navaid::default()]) + let coll: Collection = self + .client + .database(DATABASE_NAME) + .collection(NAVAIDS_COLLECTION); + + let mut ands = vec![]; + + if search.is_some() { + let search = search.unwrap(); + let search_filter = doc! {"$or": [ + {"icao_code":search.clone()}, + {"name":search.clone()}, + {"associated_airport":search.clone()} + ]}; + ands.push(search_filter); + } + + if latitude.is_some() && longitude.is_some() { + let geo_filter = doc! {"location":{ + "$nearSphere": { + "$geometry": { + "type" : "Point", + "coordinates" : [ longitude.unwrap(), latitude.unwrap() ] + }, + "$minDistance": 0, + "$maxDistance": 5000000 + } + }}; + ands.push(geo_filter); + } + + if country.is_some() { + let country_filter = doc! {"iso_country": country.unwrap()}; + ands.push(country_filter); + } + + if navaid_type.is_some() { + let type_filter = doc! {"type": navaid_type.unwrap()}; + ands.push(type_filter); + } + + let filter = doc! {"$and":ands}; + + let page = page.unwrap_or(0) as u64; + let options = FindOptions::builder().skip(page * 100).limit(100).build(); + + let mut result = coll.find(filter, options).await?; + let mut navaids = vec![]; + while let Some(navaid) = result.try_next().await? { + navaids.push(navaid); + } + Ok(navaids) } pub async fn search_airport( &self, @@ -134,7 +345,61 @@ impl MongoDbBackend { page: Option, country: Option, airport_type: Option, + latitude: Option, + longitude: Option, ) -> Result, Box> { - Ok(vec![Airport::default()]) + let coll: Collection = self + .client + .database(DATABASE_NAME) + .collection(AIRPORTS_COLLECTION); + + let mut ands = vec![]; + + if search.is_some() { + let search = search.unwrap(); + let search_filter = doc! {"$or": [ + {"icao_code":search.clone()}, + {"name":search.clone()}, + {"municipality":search.clone()}, + {"iata_code":search.clone()} + ]}; + ands.push(search_filter); + } + + if latitude.is_some() && longitude.is_some() { + let geo_filter = doc! {"location":{ + "$nearSphere": { + "$geometry": { + "type" : "Point", + "coordinates" : [ longitude.unwrap(), latitude.unwrap() ] + }, + "$minDistance": 0, + "$maxDistance": 5000000 + } + }}; + ands.push(geo_filter); + } + + if country.is_some() { + let country_filter = doc! {"iso_country": country.unwrap()}; + ands.push(country_filter); + } + + if airport_type.is_some() { + let type_filter = doc! {"type": airport_type.unwrap()}; + ands.push(type_filter); + } + + let filter = doc! {"$and":ands}; + + let page = page.unwrap_or(0) as u64; + let options = FindOptions::builder().skip(page * 100).limit(100).build(); + + let mut result = coll.find(filter, options).await?; + let mut airports = vec![]; + while let Some(airport) = result.try_next().await? { + airports.push(airport); + } + Ok(airports) } } diff --git a/src/app/db/sqlite.rs b/src/app/db/sqlite.rs index e88d030..07dbf52 100644 --- a/src/app/db/sqlite.rs +++ b/src/app/db/sqlite.rs @@ -128,7 +128,7 @@ impl SqliteBackend { Ok(()) } - async fn load_airports(&self) -> Result<(), Box> { + pub async fn load_airports(&self) -> Result<(), Box> { let result = reqwest::get(format!("{}{}", super::CSV_ROOT_URL, super::AIRPORT_CSV)).await?; let data = result.text().await?; let mut reader = csv::ReaderBuilder::new().from_reader(data.as_bytes()); @@ -163,7 +163,7 @@ impl SqliteBackend { Ok(()) } - async fn load_airport_frequencies(&self) -> Result<(), Box> { + pub async fn load_airport_frequencies(&self) -> Result<(), Box> { let result = reqwest::get(format!( "{}{}", super::CSV_ROOT_URL, @@ -203,7 +203,7 @@ impl SqliteBackend { Ok(()) } - async fn load_airport_runways(&self) -> Result<(), Box> { + pub async fn load_airport_runways(&self) -> Result<(), Box> { let result = reqwest::get(format!( "{}{}", super::CSV_ROOT_URL, @@ -243,7 +243,7 @@ impl SqliteBackend { Ok(()) } - async fn load_navaids(&self) -> Result<(), Box> { + pub async fn load_navaids(&self) -> Result<(), Box> { let result = reqwest::get(format!("{}{}", super::CSV_ROOT_URL, super::NAVAID_CSV)).await?; let data = result.text().await?; let mut reader = csv::ReaderBuilder::new().from_reader(data.as_bytes()); @@ -475,6 +475,7 @@ impl SqliteBackend { freq_statement.read::("type")?.as_str(), ) .unwrap(), + raw_type: freq_statement.read::("type")?, }; frequencies.push(frequency); } @@ -507,7 +508,10 @@ impl SqliteBackend { Ok(navaids) } - pub async fn get_airport_by_icao_code(&self, icao: String) -> Result> { + pub async fn get_airport_by_icao_code( + &self, + icao: String, + ) -> Result, Box> { let query = "SELECT * FROM airports WHERE icao_code=?"; let icao = icao.to_uppercase(); @@ -547,7 +551,7 @@ impl SqliteBackend { airport.runways = self.get_runways_by_icao_code(icao.clone()).await?; airport.frequencies = self.get_frequencies_by_icao_code(icao.clone()).await?; airport.navaids = self.get_navaids_by_airport_icao_code(icao.clone()).await?; - Ok(airport) + Ok(Some(airport)) } pub async fn get_navaids_by_icao_code( @@ -602,7 +606,7 @@ impl SqliteBackend { Ok(navaids) } - pub async fn get_navaid_by_id(&self, id: i64) -> Result> { + async fn get_navaid_by_id(&self, id: i64) -> Result> { let query = "SELECT * FROM navaids WHERE id=?"; let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); @@ -650,6 +654,8 @@ impl SqliteBackend { page: Option, country: Option, navaid_type: Option, + _latitude: Option, + _longitude: Option, ) -> Result, Box> { let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); @@ -752,6 +758,8 @@ impl SqliteBackend { page: Option, country: Option, airport_type: Option, + _latitude: Option, + _longitude: Option, ) -> Result, Box> { let codes = { let con = self.connection.lock().expect(ERROR_SQLITE_ACCESS); @@ -820,7 +828,9 @@ impl SqliteBackend { let mut airports = vec![]; for code in codes { let airport = self.get_airport_by_icao_code(code).await?; - airports.push(airport); + if airport.is_some() { + airports.push(airport.unwrap()); + } } Ok(airports) diff --git a/src/app/navdata/airport.rs b/src/app/navdata/airport.rs index 5c3052e..eb18d7b 100644 --- a/src/app/navdata/airport.rs +++ b/src/app/navdata/airport.rs @@ -3,6 +3,7 @@ use actix_web::{get, web, HttpResponse, Responder}; use log::{error, info}; use serde::Deserialize; use serde_json::json; +use uuid::Uuid; pub fn register_routes(cfg: &mut web::ServiceConfig) { cfg.service(airport_by_icao_code); @@ -17,6 +18,8 @@ struct FormData { search: Option, country: Option, airport_type: Option, + latitude: Option, + longitude: Option, } #[get("/airport")] @@ -29,13 +32,19 @@ async fn airport(param: web::Query, app_state: web::Data) -> param.page, param.country.clone(), param.airport_type.clone(), + param.latitude, + param.longitude, ) .await; match data { Ok(data) => HttpResponse::Ok().json(json!({"status": "success", "airports" : data})), Err(err) => { - error!("Error while answering request /airport: {}", err); - HttpResponse::Ok().json(json!({"status": "error"})) + let error_id = Uuid::new_v4(); + error!( + "[{}] Error while answering request /airport : {}", + error_id, err + ); + HttpResponse::Ok().json(json!({"status": "error", "description" : format!("Error {} : contact your administrator", error_id)})) } } } @@ -46,15 +55,31 @@ async fn airport_by_icao_code( app_state: web::Data, ) -> impl Responder { info!("Request received : /airport/{}", icao); + + if icao.len() != 4 { + return HttpResponse::Ok().json( + json!({"status": "error", "description":"Airport ICAO codes must be 4 letter long"}), + ); + } + let data = app_state .database .get_airport_by_icao_code(icao.to_string()) .await; match data { - Ok(data) => HttpResponse::Ok().json(json!({"status": "success", "airport" : data})), + Ok(data) => match data { + Some(data) => { + HttpResponse::Ok().json(json!({"status": "success", "airport" : data, "count" : 1})) + } + None => HttpResponse::Ok().json(json!({"status": "success", "count" : 0})), + }, Err(err) => { - error!("Error while answering request /airport/{} : {}", icao, err); - HttpResponse::Ok().json(json!({"status": "error"})) + let error_id = Uuid::new_v4(); + error!( + "[{}] Error while answering request /airport/{} : {}", + error_id, icao, err + ); + HttpResponse::Ok().json(json!({"status": "error", "description" : format!("Error {} : contact your administrator", error_id)})) } } } diff --git a/src/app/navdata/navaid.rs b/src/app/navdata/navaid.rs index e5d706c..b482547 100644 --- a/src/app/navdata/navaid.rs +++ b/src/app/navdata/navaid.rs @@ -3,6 +3,7 @@ use actix_web::{get, web, HttpResponse, Responder}; use log::{error, info}; use serde::Deserialize; use serde_json::json; +use uuid::Uuid; pub fn register_routes(cfg: &mut web::ServiceConfig) { cfg.service(navaid); @@ -17,6 +18,8 @@ struct FormData { search: Option, country: Option, navaid_type: Option, + latitude: Option, + longitude: Option, } #[get("/navaid")] @@ -29,13 +32,19 @@ async fn navaid(param: web::Query, app_state: web::Data) -> param.page, param.country.clone(), param.navaid_type.clone(), + param.latitude, + param.longitude, ) .await; match data { Ok(data) => HttpResponse::Ok().json(json!({"status": "success", "navaid" : data})), Err(err) => { - error!("Error while answering request /navaid: {}", err); - HttpResponse::Ok().json(json!({"status": "error"})) + let error_id = Uuid::new_v4(); + error!( + "[{}] Error while answering request /navaid : {}", + error_id, err + ); + HttpResponse::Ok().json(json!({"status": "error", "description" : format!("Error {} : contact your administrator", error_id)})) } } } @@ -46,6 +55,13 @@ async fn navaid_by_icao_code( app_state: web::Data, ) -> impl Responder { info!("Request received : /navaid/{}", icao); + + if icao.len() != 3 { + return HttpResponse::Ok().json( + json!({"status": "error", "description":"Navaid ICAO codes must be 3 letter long"}), + ); + } + let data = app_state .database .get_navaids_by_icao_code(icao.to_string()) @@ -53,8 +69,12 @@ async fn navaid_by_icao_code( match data { Ok(data) => HttpResponse::Ok().json(json!({"status": "success", "navaid" : data})), Err(err) => { - error!("Error while answering request /navaid/{} : {}", icao, err); - HttpResponse::Ok().json(json!({"status": "error"})) + let error_id = Uuid::new_v4(); + error!( + "[{}] Error while answering request /navaid/{} : {}", + error_id, icao, err + ); + HttpResponse::Ok().json(json!({"status": "error", "description" : format!("Error {} : contact your administrator", error_id)})) } } } From 253adab7896e59ad31b554efd3d7622156bd84fa Mon Sep 17 00:00:00 2001 From: Leirn Date: Mon, 22 Jan 2024 14:58:58 +0100 Subject: [PATCH 3/7] Move from u32 to u64 to avoid cast --- src/app/db/mod.rs | 4 ++-- src/app/db/mongodb.rs | 8 ++++---- src/app/db/sqlite.rs | 4 ++-- src/app/navdata/airport.rs | 2 +- src/app/navdata/navaid.rs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/db/mod.rs b/src/app/db/mod.rs index 5b6dde9..60fdc97 100644 --- a/src/app/db/mod.rs +++ b/src/app/db/mod.rs @@ -504,7 +504,7 @@ impl DatabaseBackend { pub async fn search_navaid( self: &Self, search: Option, - page: Option, + page: Option, country: Option, navaid_type: Option, latitude: Option, @@ -530,7 +530,7 @@ impl DatabaseBackend { pub async fn search_airport( self: &Self, search: Option, - page: Option, + page: Option, country: Option, airport_type: Option, latitude: Option, diff --git a/src/app/db/mongodb.rs b/src/app/db/mongodb.rs index 834c09d..9e26d51 100644 --- a/src/app/db/mongodb.rs +++ b/src/app/db/mongodb.rs @@ -280,7 +280,7 @@ impl MongoDbBackend { pub async fn search_navaid( &self, search: Option, - page: Option, + page: Option, country: Option, navaid_type: Option, latitude: Option, @@ -329,7 +329,7 @@ impl MongoDbBackend { let filter = doc! {"$and":ands}; - let page = page.unwrap_or(0) as u64; + let page = page.unwrap_or(0); let options = FindOptions::builder().skip(page * 100).limit(100).build(); let mut result = coll.find(filter, options).await?; @@ -342,7 +342,7 @@ impl MongoDbBackend { pub async fn search_airport( &self, search: Option, - page: Option, + page: Option, country: Option, airport_type: Option, latitude: Option, @@ -392,7 +392,7 @@ impl MongoDbBackend { let filter = doc! {"$and":ands}; - let page = page.unwrap_or(0) as u64; + let page = page.unwrap_or(0); let options = FindOptions::builder().skip(page * 100).limit(100).build(); let mut result = coll.find(filter, options).await?; diff --git a/src/app/db/sqlite.rs b/src/app/db/sqlite.rs index 07dbf52..ddb703e 100644 --- a/src/app/db/sqlite.rs +++ b/src/app/db/sqlite.rs @@ -651,7 +651,7 @@ impl SqliteBackend { pub async fn search_navaid( &self, search: Option, - page: Option, + page: Option, country: Option, navaid_type: Option, _latitude: Option, @@ -755,7 +755,7 @@ impl SqliteBackend { pub async fn search_airport( &self, search: Option, - page: Option, + page: Option, country: Option, airport_type: Option, _latitude: Option, diff --git a/src/app/navdata/airport.rs b/src/app/navdata/airport.rs index eb18d7b..372a953 100644 --- a/src/app/navdata/airport.rs +++ b/src/app/navdata/airport.rs @@ -14,7 +14,7 @@ pub fn register_routes(cfg: &mut web::ServiceConfig) { #[derive(Deserialize)] struct FormData { - page: Option, + page: Option, search: Option, country: Option, airport_type: Option, diff --git a/src/app/navdata/navaid.rs b/src/app/navdata/navaid.rs index b482547..aad5074 100644 --- a/src/app/navdata/navaid.rs +++ b/src/app/navdata/navaid.rs @@ -14,7 +14,7 @@ pub fn register_routes(cfg: &mut web::ServiceConfig) { #[derive(Deserialize)] struct FormData { - page: Option, + page: Option, search: Option, country: Option, navaid_type: Option, From 57463056a97d41216ae711db5c62e8d7e317d4ab Mon Sep 17 00:00:00 2001 From: Leirn Date: Mon, 22 Jan 2024 18:02:57 +0100 Subject: [PATCH 4/7] Remove unused code --- src/app/db/mongodb.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/app/db/mongodb.rs b/src/app/db/mongodb.rs index 9e26d51..a1f9dc5 100644 --- a/src/app/db/mongodb.rs +++ b/src/app/db/mongodb.rs @@ -197,11 +197,6 @@ impl MongoDbBackend { break; } - // let result = airports_collection - // .insert_many(airports, None) - // .await - // .unwrap(); - for airport in airports { if airport.icao_code.len() < 4 { continue; @@ -213,7 +208,6 @@ impl MongoDbBackend { .unwrap(); airport_count += 1; } - // airport_count += result.inserted_ids.len(); page += 1 } info!("{} airports added to MongoDB", airport_count); From 1d7a1d45764f35db37f457a83b0dfe61f6989df2 Mon Sep 17 00:00:00 2001 From: Leirn Date: Mon, 22 Jan 2024 18:19:33 +0100 Subject: [PATCH 5/7] Wildcard support for mongodb --- src/app/db/mongodb.rs | 94 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/src/app/db/mongodb.rs b/src/app/db/mongodb.rs index a1f9dc5..3d0febb 100644 --- a/src/app/db/mongodb.rs +++ b/src/app/db/mongodb.rs @@ -124,10 +124,26 @@ impl MongoDbBackend { info!("Index {} created for airports collection", index.index_name); // name, municipality, iata_code, iso_country and type are mandatory to speed_up searchs on airports + let index_model = IndexModel::builder().keys(doc! { "name.$**": 1 }).build(); + match airports_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for airports collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } + let index_model = IndexModel::builder() + .keys(doc! { "municipality.$**": 1 }) + .build(); + match airports_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for airports collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } let index_model = IndexModel::builder() - .keys( - doc! { "name": 1, "municipality": 1, "iata_code": 1, "iso_country": 1, "type": 1 }, - ) + .keys(doc! { "iata_code.$**": 1 }) .build(); match airports_collection .create_index(index_model.clone(), None) @@ -136,12 +152,64 @@ impl MongoDbBackend { Ok(index) => info!("Index {} created for airports collection", index.index_name), Err(err) => info!("Index not created, may alreay exists : {}", err), } + let index_model = IndexModel::builder() + .keys(doc! { "iso_country.$**": 1 }) + .build(); + match airports_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for airports collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } + let index_model = IndexModel::builder().keys(doc! { "type.$**": 1 }).build(); + match airports_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for airports collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } // name, filename, associated_airport, iso_country and type are mandatory to speed_up searchs on navaids + let index_model = IndexModel::builder().keys(doc! { "name.$**": 1 }).build(); + match navaids_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for navaids collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } + let index_model = IndexModel::builder() + .keys(doc! { "filename.$**": 1 }) + .build(); + match navaids_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for navaids collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } + let index_model = IndexModel::builder() + .keys(doc! { "associated_airport.$**": 1,"type.$**": 1 }) + .build(); + match navaids_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for navaids collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } + let index_model = IndexModel::builder().keys(doc! {"type.$**": 1 }).build(); + match navaids_collection + .create_index(index_model.clone(), None) + .await + { + Ok(index) => info!("Index {} created for navaids collection", index.index_name), + Err(err) => info!("Index not created, may alreay exists : {}", err), + } let index_model = IndexModel::builder() - .keys( - doc! { "name": 1,"filename": 1,"associated_airport": 1,"type": 1,"iso_country": 1 }, - ) + .keys(doc! {"iso_country.$**": 1 }) .build(); match navaids_collection .create_index(index_model.clone(), None) @@ -290,9 +358,9 @@ impl MongoDbBackend { if search.is_some() { let search = search.unwrap(); let search_filter = doc! {"$or": [ - {"icao_code":search.clone()}, - {"name":search.clone()}, - {"associated_airport":search.clone()} + {"icao_code":{"$regex" : search.clone(), "$options" : "i"}}, + {"name":{"$regex" : search.clone(), "$options" : "i"}}, + {"associated_airport":{"$regex" : search.clone(), "$options" : "i"}} ]}; ands.push(search_filter); } @@ -352,10 +420,10 @@ impl MongoDbBackend { if search.is_some() { let search = search.unwrap(); let search_filter = doc! {"$or": [ - {"icao_code":search.clone()}, - {"name":search.clone()}, - {"municipality":search.clone()}, - {"iata_code":search.clone()} + {"icao_code":{"$regex" : search.clone(), "$options" : "i"}}, + {"name":{"$regex" : search.clone(), "$options" : "i"}}, + {"municipality":{"$regex" : search.clone(), "$options" : "i"}}, + {"iata_code":{"$regex" : search.clone(), "$options" : "i"}} ]}; ands.push(search_filter); } From 4b9a22a18bf0eda305108eb310831ead205a8b1f Mon Sep 17 00:00:00 2001 From: Leirn Date: Mon, 22 Jan 2024 18:22:32 +0100 Subject: [PATCH 6/7] Support for empty search filters --- src/app/db/mongodb.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/db/mongodb.rs b/src/app/db/mongodb.rs index 3d0febb..08ddf00 100644 --- a/src/app/db/mongodb.rs +++ b/src/app/db/mongodb.rs @@ -389,7 +389,10 @@ impl MongoDbBackend { ands.push(type_filter); } - let filter = doc! {"$and":ands}; + let filter = match ands.len() { + 0 => None, + _ => Some(doc! {"$and":ands}), + }; let page = page.unwrap_or(0); let options = FindOptions::builder().skip(page * 100).limit(100).build(); @@ -452,7 +455,10 @@ impl MongoDbBackend { ands.push(type_filter); } - let filter = doc! {"$and":ands}; + let filter = match ands.len() { + 0 => None, + _ => Some(doc! {"$and":ands}), + }; let page = page.unwrap_or(0); let options = FindOptions::builder().skip(page * 100).limit(100).build(); From 25d07492be70dfe4d3af8a2ea670a1c4f2221178 Mon Sep 17 00:00:00 2001 From: Leirn Date: Mon, 22 Jan 2024 19:13:09 +0100 Subject: [PATCH 7/7] Add config file option --- Cargo.lock | 22 +++++++++++++- Cargo.toml | 5 ++-- Dockerfile | 17 +++-------- README.md | 22 +++++++++++++- navdata_config.yaml | 11 +++++++ src/app/config/mod.rs | 27 ++++++++++++++++++ src/app/db/mod.rs | 5 +++- src/app/messages.rs | 1 - src/app/mod.rs | 1 + src/app/security/simple_token.rs | 19 ++++++------- src/main.rs | 49 ++++++++++++++++++++++++-------- 11 files changed, 137 insertions(+), 42 deletions(-) create mode 100644 navdata_config.yaml create mode 100644 src/app/config/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e1db698..f4d9a75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1376,7 +1376,7 @@ dependencies = [ [[package]] name = "nav_data" -version = "0.1.0" +version = "0.1.5" dependencies = [ "actix", "actix-cors", @@ -1394,6 +1394,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_yaml", "sqlite", "tokio", "uuid", @@ -1853,6 +1854,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serde_yaml" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +dependencies = [ + "indexmap 2.1.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha-1" version = "0.10.1" @@ -2278,6 +2292,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index b11ed2f..1ae3f79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nav_data" -version = "0.1.0" +version = "0.1.5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -25,6 +25,7 @@ bson = "2.8.1" clap = { version = "4.4.18", features = ["derive"] } clap_derive = "4.4.7" futures = "0.3.30" +serde_yaml = "0.9.30" [dependencies.uuid] version = "1.7.0" @@ -32,4 +33,4 @@ features = [ "v4", # Lets you generate random UUIDs "fast-rng", # Use a faster (but still sufficiently random) RNG "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs -] \ No newline at end of file +] diff --git a/Dockerfile b/Dockerfile index bad27fa..2bdbae0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ LABEL maintainer="Laurent " \ org.opencontainers.image.vendor="Laurent Vromman" \ org.opencontainers.image.documentation="https://github.com/leirn/navdata/README.md" \ org.opencontainers.image.licenses="MIT" \ - org.opencontainers.image.version="0.1.3" \ + org.opencontainers.image.version="0.1.5" \ org.opencontainers.image.url="https://github.com/leirn/navdata/" \ org.opencontainers.image.source="https://github.com/leirn/navdata/" \ org.opencontainers.image.revision=$VCS_REF \ @@ -20,21 +20,12 @@ COPY . /app RUN apt-get update && apt-get -y install sqlite3 RUN cargo build --release -FROM gcr.io/distroless/cc-debian12 +FROM gcr.io/distroless/cc-debian12:lastest ENV DATABASE_FOLDER=/data VOLUME "/data" - -ENV HOST=0.0.0.0 - -ENV PORT=8080 - -ARG DATABASE_PATH=":memory:" -ENV DATABASE_PATH=${DATABASE_PATH} - -ARG TOKEN_LIST="" -ENV TOKEN_LIST=${TOKEN_LIST} +VOLUME "/config" ARG RUST_LOG="warn" ENV RUST_LOG=${RUST_LOG} @@ -46,4 +37,4 @@ EXPOSE 8080 COPY --from=build-env /app/target/release/nav_data / COPY --from=build-env /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 -CMD ["./nav_data"] \ No newline at end of file +CMD ["./nav_data --config /config/config.yaml"] \ No newline at end of file diff --git a/README.md b/README.md index 5160cd2..d4542f2 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,24 @@ This data MUST NOT be used to plan real life flights. - ```GET /airport?search={query}``` : look for an airport based on ```query``` string. Answer first 100 results - ```GET /airport/{icao}``` : look for an airport based on its ICAO code - ```GET /navaid?search={query}``` : look for a navaid (VOR, DME, ADF...) based on ```query``` string. Answer first 100 results -- ```GET /navaid/{icao}``` : look for an navaid based on its ICAO code \ No newline at end of file +- ```GET /navaid/{icao}``` : look for an navaid based on its ICAO code + +### Config file + +Config files must be given for docker as ```/config/config.yaml```. + +Format is: + +```yaml +http: + host: 127.0.0.1 + port: 8080 +security: + auth_tokens: + - aaaa + - bbbb + - cccc +database: + backend : MONGODB # can be either SQLITE or MONGODB + path : mongodb://localhost:27017 # Mongo URI if mongo (mandatory. Path to sqlite file if sqlite. If sqlite and no path, memory is used +``` diff --git a/navdata_config.yaml b/navdata_config.yaml new file mode 100644 index 0000000..a380d8e --- /dev/null +++ b/navdata_config.yaml @@ -0,0 +1,11 @@ +http: + host: 127.0.0.1 + port: 8080 +security: + auth_tokens: + - aaaa + - bbbb + - cccc +database: + backend : MONGODB # can be either SQLITE or MONGODB + path : mongodb://localhost:27017 # Mongo URI if mongo (mandatory. Path to sqlite file if sqlite. If sqlite and no path, memory is used diff --git a/src/app/config/mod.rs b/src/app/config/mod.rs new file mode 100644 index 0000000..ddb04a4 --- /dev/null +++ b/src/app/config/mod.rs @@ -0,0 +1,27 @@ +use serde::Deserialize; + +use super::db::BackendType; + +#[derive(Debug, Deserialize, Default)] +pub struct Config { + pub http: HttpConfig, + pub security: SecurityConfig, + pub database: DatabaseConfig, +} + +#[derive(Debug, Deserialize, Default)] +pub struct HttpConfig { + pub host: String, + pub port: u16, +} + +#[derive(Debug, Deserialize, Default)] +pub struct SecurityConfig { + pub auth_tokens: Vec, +} + +#[derive(Debug, Deserialize, Default)] +pub struct DatabaseConfig { + pub backend: BackendType, + pub path: Option, +} diff --git a/src/app/db/mod.rs b/src/app/db/mod.rs index 60fdc97..9c3600f 100644 --- a/src/app/db/mod.rs +++ b/src/app/db/mod.rs @@ -3,6 +3,7 @@ pub mod mongodb; pub mod sqlite; use self::mongodb::MongoDbBackend; use self::sqlite::SqliteBackend; +use crate::app::config::Config; use log::error; use serde::{Deserialize, Serialize}; use std::error::Error; @@ -414,6 +415,7 @@ pub struct Navaid { pub struct AppState { pub database: DatabaseBackend, + pub config: Config, } pub async fn periodical_update(app_state: web::Data) { @@ -421,8 +423,9 @@ pub async fn periodical_update(app_state: web::Data) { state.database.periodical_update().await } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, Default, Deserialize)] pub enum BackendType { + #[default] SQLITE, MONGODB, } diff --git a/src/app/messages.rs b/src/app/messages.rs index da63230..10a3e51 100644 --- a/src/app/messages.rs +++ b/src/app/messages.rs @@ -3,7 +3,6 @@ pub const ERROR_SQLITE_ACCESS: &str = "Error while accessing SQLite connection"; pub const CSV_FORMAT_ERROR: &str = "CSV file does not have the right format"; // Parameters -pub const PARAM_TOKEN_LIST: &str = "TOKEN_LIST"; pub const TOKEN_COOKIE: &str = "navaid_auth_token"; // HTTP diff --git a/src/app/mod.rs b/src/app/mod.rs index d2b2bbd..2ecf635 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod db; pub mod messages; pub mod navdata; diff --git a/src/app/security/simple_token.rs b/src/app/security/simple_token.rs index 3de6807..59bea74 100644 --- a/src/app/security/simple_token.rs +++ b/src/app/security/simple_token.rs @@ -1,9 +1,8 @@ use super::error::AuthorizationError; +use crate::app::db::AppState; +use crate::app::messages::TOKEN_COOKIE; use actix_web::dev::{forward_ready, Service, ServiceResponse, Transform}; use actix_web::{dev::ServiceRequest, Error}; - -use crate::app::messages::{PARAM_TOKEN_LIST, TOKEN_COOKIE}; -use std::env; use std::{ future::{ready, Future, Ready}, pin::Pin, @@ -74,22 +73,20 @@ where let conn_info = req.connection_info().clone(); let real_remote_addr = conn_info.realip_remote_addr().unwrap_or("unknown"); - let tokens = env::var(PARAM_TOKEN_LIST); - - let success = match tokens { - Ok(tokens) => { - let tokens: Vec<&str> = tokens.split(",").collect(); + let app_data = req.app_data::().unwrap(); + let success = match app_data.config.security.auth_tokens.len() { + // If no token set + 0 => true, + _ => { let token = match req.cookie(TOKEN_COOKIE) { Some(cookie) => cookie.value().to_owned(), None => { return Err(Error::from(AuthorizationError::NoToken).into()); } }; - tokens.len() == 0 || tokens.contains(&token.as_str()) + app_data.config.security.auth_tokens.contains(&token) } - // If not set, bypass auth - Err(_) => true, }; match success { diff --git a/src/main.rs b/src/main.rs index 9f86f56..580baac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ mod app; use actix_cors::Cors; use actix_web::{middleware::Logger, web, App, HttpServer}; +use app::config::Config; use app::db::{periodical_update, AppState, BackendType, DatabaseBackend}; use app::security::simple_token::SimpleToken; use clap::Parser; +use std::path::PathBuf; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -31,6 +33,10 @@ struct Args { /// loglevel. 0 for error, 1 for warn, 2 for info, 3 for debug #[arg(short, long, default_value_t = 2)] loglevel: u8, + + /// path to YAML config file + #[arg(short, long)] + config: PathBuf, } #[actix_web::main] @@ -39,21 +45,40 @@ async fn main() -> std::io::Result<()> { let args = Args::parse(); - let host = args.address; - let port = args.port; + let mut config = Config::default(); + if args.config.exists() { + let f = std::fs::File::open(args.config).expect("Could not open file."); + config = serde_yaml::from_reader(f).expect("Could not read values."); + } else { + config.http.host = args.address.clone(); + config.http.port = args.port; - let backend_type = { - if args.mongodb { - BackendType::MONGODB - } else { - BackendType::SQLITE - } - }; - let database_path = args.db_path; + config.database.backend = { + if args.mongodb { + BackendType::MONGODB + } else { + BackendType::SQLITE + } + }; + config.database.path = Some(args.db_path.clone()); + } + let backend = DatabaseBackend::new( + config.database.backend, + config + .database + .path + .clone() + .unwrap_or(":memory:".to_string()), + ) + .await; - let backend = DatabaseBackend::new(backend_type, database_path).await; + let host = config.http.host.clone(); + let port = config.http.port; - let app_state = web::Data::new(AppState { database: backend }); + let app_state: web::Data = web::Data::new(AppState { + database: backend, + config: config, + }); actix_rt::spawn(periodical_update(app_state.clone()));