From 21080771bbd0ec82de25f0018e5ce1088a8d14c8 Mon Sep 17 00:00:00 2001 From: Joe Burnard Date: Sun, 7 Jan 2024 23:09:38 +0000 Subject: [PATCH] export nutracheck entries into mongodb --- .env.example | 4 + .gitignore | 5 +- Cargo.lock | 794 ++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 +- docker-compose.yml | 24 ++ src/dateiter.rs | 19 ++ src/entries.rs | 484 +++++++++++++++++++++++++++ src/main.rs | 49 +-- src/models.rs | 5 +- src/mongo.rs | 72 ++++ src/nutracheck.rs | 182 +++++++++++ tidy_dump.jq | 21 ++ 12 files changed, 1625 insertions(+), 40 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.yml create mode 100644 src/dateiter.rs create mode 100644 src/entries.rs create mode 100644 src/mongo.rs create mode 100644 src/nutracheck.rs create mode 100644 tidy_dump.jq diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0780a4d --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +NUTRACHECK_USERNAME=my@email.com +NUTRACHECK_PASSWORD=secretpassword +MONGODB_CONNECTION_URI="mongodb://localhost:27017/calories" +MONGODB_DATABASE_NAME=calories \ No newline at end of file diff --git a/.gitignore b/.gitignore index f29b8be..8d2a351 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ target/ -.envrc \ No newline at end of file +.envrc +grafana-storage/ +.env +nutracheck_token.json diff --git a/Cargo.lock b/Cargo.lock index e1b100d..a56984d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,19 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom 0.2.11", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -137,6 +150,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.5" @@ -182,6 +201,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" @@ -191,6 +222,27 @@ dependencies = [ "generic-array", ] +[[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 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -220,6 +272,10 @@ name = "calories" version = "0.1.0" dependencies = [ "axum", + "chrono", + "dotenv", + "futures", + "mongodb", "reqwest", "rxing", "serde", @@ -258,6 +314,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -308,6 +365,12 @@ dependencies = [ "custom_derive", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.4" @@ -416,6 +479,80 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -424,6 +561,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -446,6 +584,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "either" version = "1.9.0" @@ -525,6 +669,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 = "equivalent" version = "1.0.1" @@ -582,6 +738,12 @@ dependencies = [ "simd-adler32", ] +[[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" @@ -631,20 +793,70 @@ 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" +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.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +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.39", +] [[package]] name = "futures-sink" @@ -654,20 +866,26 @@ checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -730,7 +948,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -746,18 +964,56 @@ dependencies = [ "crunchy", ] +[[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +[[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" @@ -852,6 +1108,23 @@ 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.4.0" @@ -899,6 +1172,16 @@ dependencies = [ "rusttype", ] +[[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" @@ -906,10 +1189,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.2", "serde", ] +[[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]] name = "ipnet" version = "2.9.0" @@ -978,6 +1273,12 @@ dependencies = [ "redox_syscall", ] +[[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" @@ -1000,6 +1301,27 @@ 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 = "matchit" version = "0.7.3" @@ -1016,6 +1338,16 @@ dependencies = [ "rawpointer", ] +[[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.6.4" @@ -1059,6 +1391,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 0.8.5", + "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 = "multimap" version = "0.9.1" @@ -1309,6 +1688,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.0" @@ -1404,6 +1792,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1452,6 +1846,12 @@ dependencies = [ "bytemuck", ] +[[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.33" @@ -1461,6 +1861,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.7.3" @@ -1469,7 +1875,7 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", ] @@ -1480,6 +1886,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -1493,6 +1901,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -1507,6 +1925,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.11", +] [[package]] name = "rand_distr" @@ -1607,7 +2028,7 @@ version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -1639,6 +2060,30 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.11", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rust-embed" version = "8.0.0" @@ -1680,6 +2125,34 @@ 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 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]] name = "rustix" version = "0.38.28" @@ -1693,6 +2166,37 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rusttype" version = "0.9.3" @@ -1784,6 +2288,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -1807,6 +2321,27 @@ dependencies = [ "libc", ] +[[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.192" @@ -1816,6 +2351,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.192" @@ -1833,6 +2377,7 @@ version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -1860,6 +2405,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 = "sha2" version = "0.10.8" @@ -1967,6 +2545,29 @@ dependencies = [ "lock_api", ] +[[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" @@ -2016,6 +2617,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 = "tempfile" version = "3.9.0" @@ -2070,6 +2683,35 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2125,6 +2767,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -2133,6 +2785,7 @@ checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -2225,6 +2878,51 @@ dependencies = [ "tracing-log", ] +[[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 0.8.5", + "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.4" @@ -2237,6 +2935,17 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" +[[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" @@ -2279,6 +2988,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "uriparse" version = "0.6.4" @@ -2296,7 +3011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", - "idna", + "idna 0.4.0", "percent-encoding", ] @@ -2312,7 +3027,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ff05e3bac2c9428f57ade702667753ca3f5cf085e2011fe697de5bfd49aa72d" dependencies = [ - "indexmap", + "indexmap 2.1.0", "serde", "serde_json", "utoipa-gen", @@ -2371,6 +3086,16 @@ dependencies = [ "zip", ] +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom 0.2.11", + "serde", +] + [[package]] name = "valuable" version = "0.1.0" @@ -2496,6 +3221,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "weezl" version = "0.1.7" @@ -2512,6 +3243,12 @@ dependencies = [ "safe_arch", ] +[[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" @@ -2694,6 +3431,35 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/Cargo.toml b/Cargo.toml index 9f30cb9..819b571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,8 @@ utoipa-swagger-ui = { version = "4.0.0", features = ["axum"] } utoipa-redoc = { version = "1.0.0", features = ["axum"] } utoipa-rapidoc = { version = "1.0.0", features = ["axum"] } reqwest = {version = "0.11", features = ["json"]} -rxing = { version = "0.5.0", features = ["image", "serde"] } \ No newline at end of file +rxing = { version = "0.5.0", features = ["image", "serde"] } +mongodb = { version = "2.8.0", features = ["tokio-runtime"] } +futures = { version = "0.3.28" } +chrono = { version = "0.4.31", features = ["serde"] } +dotenv = "0.15.0" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6cb2aba --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.8' +services: + mongodb: + image: mongo:7-jammy + ports: + - '27017:27017' + volumes: + - mongodata:/data/db + grafana: + image: grafana/grafana-oss:latest + # image: ghcr.io/ajeje93/grafana-mongodb-docker:latest + container_name: grafana + restart: unless-stopped + ports: + - '3000:3000' + volumes: + # - grafana-storage:/var/lib/grafana + - $PWD/grafana-storage:/var/lib/grafana + environment: + - "GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=meln5674-mongodb-community" + - "GF_INSTALL_PLUGINS=https://github.com/meln5674/grafana-mongodb-community-plugin/releases/download/v0.2.0%2Brc4/meln5674-mongodb-community.zip;meln5674-mongodb-community" +volumes: + mongodata: + grafana-storage: diff --git a/src/dateiter.rs b/src/dateiter.rs new file mode 100644 index 0000000..c80774d --- /dev/null +++ b/src/dateiter.rs @@ -0,0 +1,19 @@ +// stolen from https://stackoverflow.com/questions/41679239/loop-over-date-range + +use std::mem; + +use chrono::{Duration, NaiveDate}; + +pub struct DateRange(pub NaiveDate, pub NaiveDate); + +impl Iterator for DateRange { + type Item = NaiveDate; + fn next(&mut self) -> Option { + if self.0 <= self.1 { + let next = self.0 + Duration::days(1); + Some(mem::replace(&mut self.0, next)) + } else { + None + } + } +} diff --git a/src/entries.rs b/src/entries.rs new file mode 100644 index 0000000..d67571c --- /dev/null +++ b/src/entries.rs @@ -0,0 +1,484 @@ +use serde::{Deserialize, Serialize, de::{DeserializeOwned, self, Error}, Deserializer}; +use serde_json::{Map, Value}; + +// Generated by https://jsonformatter.org/json-to-rust + +#[derive(Serialize, Deserialize, Debug)] +pub struct EntriesResponse { + #[serde(rename = "alcohol")] + alcohol: Option, + + #[serde(rename = "settings")] + settings: Option, + + #[serde(rename = "fiveADay")] + five_a_day: Option, + + #[serde(rename = "water")] + water: Option, + + #[serde(rename = "separateTotals")] + separate_totals: Option, + + #[serde(rename = "combinedTotals")] + combined_totals: Option, + + #[serde(rename = "accountStatus")] + account_status: Option, + + #[serde(rename = "entries")] + entries: Vec, + + #[serde(rename = "alcoholCalories")] + alcohol_calories: Option, + + #[serde(rename = "subTotals")] + sub_totals: Option, + + #[serde(rename = "five")] + five: Option, + + #[serde(rename = "healthKitTotals")] + health_kit_totals: Option, + + #[serde(rename = "status")] + status: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct CombinedTotals { + #[serde(rename = "goal")] + goal: Option, + + #[serde(rename = "left")] + left: Option, + + #[serde(rename = "exercise")] + exercise: Option, + + #[serde(rename = "food")] + food: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct FoodClass { + #[serde(rename = "carbohydrates")] + carbohydrates: Option, + + #[serde(rename = "kcal")] + kcal: Option, + + #[serde(rename = "saturatedFat")] + saturated_fat: Option, + + #[serde(rename = "sodium")] + sodium: Option, + + #[serde(rename = "salt")] + salt: Option, + + #[serde(rename = "sugars")] + sugars: Option, + + #[serde(rename = "protein")] + protein: Option, + + #[serde(rename = "fat")] + fat: Option, + + #[serde(rename = "fibre")] + fibre: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Carbohydrates { + #[serde(rename = "colour")] + colour: Option, + + #[serde(rename = "value")] + value: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Entry { + #[serde(rename = "productID")] + product_id: Option, + + #[serde(rename = "description")] + description: Option, + + #[serde(rename = "source")] + source: Option, + + #[serde(rename = "isCopyable")] + is_copyable: Option, + + #[serde(rename = "timeEaten")] + time_eaten: Option, + + #[serde(rename = "serving")] + serving: Option, + + #[serde(rename = "isDeletable")] + is_deletable: Option, + + #[serde(rename = "isEditable")] + is_editable: Option, + + #[serde(rename = "protein")] + protein: Option, + + #[serde(rename = "imageUrl")] + image_url: Option, + + #[serde(rename = "fat")] + fat: Option, + + #[serde(rename = "id")] + id: Option, + + #[serde(rename = "isFavourite")] + is_favourite: Option, + + #[serde(rename = "servingSize")] + serving_size: Option, + + #[serde(rename = "carbohydrates")] + carbohydrates: Option, + + #[serde(rename = "alcohol")] + alcohol: Option, + + #[serde(rename = "isFavoritable")] + is_favoritable: Option, + + #[serde(rename = "image")] + image: Option, + + #[serde(rename = "fiveADay")] + five_a_day: Option, + + #[serde(rename = "sugars")] + sugars: Option, + + #[serde(rename = "salt")] + salt: Option, + + #[serde(rename = "isExercise")] + is_exercise: Option, + + #[serde(rename = "saturatedFat")] + saturated_fat: Option, + + #[serde(rename = "sodium")] + sodium: Option, + + #[serde(rename = "kcal")] + kcal: Option, + + #[serde(rename = "occasionTag")] + occasion_tag: Option, + + #[serde(rename = "fibre")] + fibre: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Image { + #[serde(rename = "imageUrlLarge")] + image_url_large: Option, + + #[serde(rename = "imageUrl")] + image_url: Option, + + #[serde(rename = "imageUrlSmall")] + image_url_small: Option, + + #[serde(rename = "imageUrlSmallCropped")] + image_url_small_cropped: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ServingSize { + #[serde(rename = "measure")] + measure: Option, + + #[serde(rename = "size")] + size: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct HealthKitTotals { + #[serde(rename = "carbohydrates")] + carbohydrates: Option, + + #[serde(rename = "kcal")] + kcal: Option, + + #[serde(rename = "saturatedFat")] + saturated_fat: Option, + + #[serde(rename = "sodium")] + sodium: Option, + + #[serde(rename = "sugars")] + sugars: Option, + + #[serde(rename = "salt")] + salt: Option, + + #[serde(rename = "protein")] + protein: Option, + + #[serde(rename = "kcalsBurnt")] + kcals_burnt: Option, + + #[serde(rename = "fat")] + fat: Option, + + #[serde(rename = "fibre")] + fibre: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SeparateTotals { + #[serde(rename = "exercise")] + exercise: Option, + + #[serde(rename = "food")] + food: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SeparateTotalsExercise { + #[serde(rename = "added")] + added: Option, + + #[serde(rename = "left")] + left: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Added { + #[serde(rename = "kcal")] + kcal: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Food { + #[serde(rename = "added")] + added: Option, + + #[serde(rename = "left")] + left: FoodClass, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Settings { + #[serde(rename = "tabletLandscapeDiaryTrackers")] + tablet_landscape_diary_trackers: Option, + + #[serde(rename = "offers")] + offers: Vec>, + + #[serde(rename = "occasionTags")] + occasion_tags: Vec, + + #[serde(rename = "desktopColumns")] + desktop_columns: Option, + + #[serde(rename = "showOccasionView")] + show_occasion_view: Option, + + #[serde(rename = "phoneLandscapeColumns")] + phone_landscape_columns: Option, + + #[serde(rename = "phonePortraitColumns")] + phone_portrait_columns: Option, + + #[serde(rename = "phonePortraitMultiColumns")] + phone_portrait_multi_columns: Option, + + #[serde(rename = "tabletLandscapeColumns")] + tablet_landscape_columns: Option, + + #[serde(rename = "tabletPortraitColumns")] + tablet_portrait_columns: Option, + + #[serde(rename = "tabletPortraitDiaryTrackers")] + tablet_portrait_diary_trackers: Option, + + #[serde(rename = "contactable")] + contactable: Option, + + #[serde(rename = "weeklyViewStartDay")] + weekly_view_start_day: Option, + + #[serde(rename = "foodDatabase")] + food_database: Option, + + #[serde(rename = "phonePortraitDiaryTrackers")] + phone_portrait_diary_trackers: Option, + + #[serde(rename = "hasLinkedUserAccounts")] + has_linked_user_accounts: Option, + + #[serde(rename = "viewStyle")] + view_style: Option, + + #[serde(rename = "linkedAccounts")] + linked_accounts: Vec, + + #[serde(rename = "messages")] + messages: Vec, + + #[serde(rename = "currentDietaryProfile")] + current_dietary_profile: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Contactable { + #[serde(rename = "offers")] + offers: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Message { + #[serde(rename = "buttons")] + buttons: Option, + + #[serde(rename = "imageUrl")] + image_url: Option, + + #[serde(rename = "name")] + name: Option, + + #[serde(rename = "title")] + title: Option, + + #[serde(rename = "body")] + body: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct OccasionTag { + #[serde(rename = "name")] + name: Option, + + #[serde(rename = "iconGuid")] + icon_guid: Option, + + #[serde(rename = "enabled")] + enabled: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Status { + #[serde(rename = "accountStatus")] + account_status: Option, + + #[serde(rename = "accountStatusEnum")] + account_status_enum: Option, + + #[serde(rename = "dateExpiry")] + date_expiry: Option, + + #[serde(rename = "dateRegistration")] + date_registration: Option, + + #[serde(rename = "dateSinceLastUse")] + date_since_last_use: Option, + + #[serde(rename = "daysSinceLastUse")] + days_since_last_use: Option, + + #[serde(rename = "debugIdentifier")] + debug_identifier: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SubTotals { + #[serde(rename = "Breakfast")] + breakfast: Option, + + #[serde(rename = "Brunch")] + brunch: Option, + + #[serde(rename = "Dinner")] + dinner: Option, + + #[serde(rename = "Snacks")] + snacks: Option, + + #[serde(rename = "exercise")] + exercise: Option, + + #[serde(rename = "Drinks")] + drinks: Option, + + #[serde(rename = "Lunch")] + lunch: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SubTotalsExercise { + #[serde(rename = "kcal")] + kcal: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Colour { + #[serde(rename = "#a1a1a1")] + A1A1A1, + + #[serde(rename = "#da0716")] + Da0716, + + #[serde(rename = "#f77b00")] + F77B00, + + #[serde(rename = "#1791b5")] + The1791B5, + + #[serde(rename = "#73c01a")] + The73C01A, + + #[serde(rename = "#933fb7")] + The933Fb7, + + #[serde(rename = "#989898")] + The989898, +} + +#[derive(Serialize, Debug)] +pub enum Measure { + G, + Kg, + Ml, + UsFl, + L, +} + +impl<'de> Deserialize<'de> for Measure { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + let s = String::deserialize(deserializer)?; + let s = s.to_lowercase(); + let s2 = s.as_str(); + Ok(match s2 { + "g" => Measure::G, + "kg" => Measure::Kg, + "ml" => Measure::Ml, + "us fl" | "usfl" => Measure::UsFl, + "l" => Measure::L, + _ => return Err(D::Error::custom(format!("unhandled variant: {}", s2))), + }) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Source { + #[serde(rename = "nutracheck")] + Nutracheck, +} diff --git a/src/main.rs b/src/main.rs index c008469..823b989 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,43 @@ -use axum::{ - http::StatusCode, - routing::{get, post}, - Json, Router, -}; -use serde::{Deserialize, Serialize}; -use std::{net::SocketAddr, collections::HashMap}; +use chrono::{Duration, Utc, NaiveDate}; +use mongodb::bson::doc; + +pub mod entries; mod models; -use models::*; + +mod dateiter; +mod mongo; +mod nutracheck; #[tokio::main] async fn main() -> Result<(), Box> { + dotenv::dotenv().ok(); tracing_subscriber::fmt::init(); - let res = rxing::helpers::detect_in_file("orange.jpg", None).expect("detecting barcode"); - let barcode = res.getText(); + let mongo_client = mongo::Client::new_from_env().await?; + + let nc_client = nutracheck::Client::new_from_env().await?; + + let todays_date = Utc::now().date_naive(); - println!("barcode: {}", barcode); + for date in dateiter::DateRange(todays_date - Duration::weeks(52), todays_date) { + let entries = nc_client.entries(date).await.expect("getting entries"); - let resp = reqwest::get(format!("https://world.openfoodfacts.org/api/v2/product/{}.json", barcode)) - .await? - .json::() - .await?; + mongo_client.insert_entries(date, entries).await?; + } - println!("{:#?}", resp); + // let res = rxing::helpers::detect_in_file("orange.jpg", None).expect("detecting barcode"); + // let barcode = res.getText(); + + // println!("barcode: {}", barcode); + + // let resp = reqwest::get(format!("https://world.openfoodfacts.org/api/v2/product/{}.json", barcode)) + // .await? + // .json::() + // .await?; + + // println!("{:#?}", resp); // let app = Router::new() // .route("/", get(root)) @@ -40,7 +53,3 @@ async fn main() -> Result<(), Box> { Ok(()) } -// basic handler that responds with a static string -async fn root() -> &'static str { - "Hello, World!" -} diff --git a/src/models.rs b/src/models.rs index 200b5cd..b3b27f7 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,6 +1,6 @@ use serde::Deserialize; use serde::Serialize; -use serde_json::Value; + use std::fmt::{self, Display}; use std::iter::FromIterator; use std::marker::PhantomData as Phantom; @@ -23,12 +23,10 @@ pub struct Product { pub id: String, // #[serde(rename = "_keywords")] // pub keywords: Vec, - pub entry_dates_tags: Option>, #[serde(rename = "expiration_date")] pub expiration_date: String, - #[serde(rename = "product_name_en")] pub product_name_en: String, @@ -41,7 +39,6 @@ pub struct Product { pub nutriments: Nutriments, } - #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Nutriments { diff --git a/src/mongo.rs b/src/mongo.rs new file mode 100644 index 0000000..3e7433c --- /dev/null +++ b/src/mongo.rs @@ -0,0 +1,72 @@ +use chrono::NaiveDate; +use mongodb::{bson::doc, options::FindOneAndReplaceOptions, Collection}; +use serde::{Deserialize, Serialize}; + +use crate::entries::EntriesResponse; + +pub struct Client { + client: mongodb::Client, + database: mongodb::Database, +} + +impl Client { + pub async fn new_from_env() -> Result> { + let uri = std::env::var("MONGODB_CONNECTION_URI")?; + let database_name = std::env::var("MONGODB_DATABASE_NAME")?; + + let client = mongodb::Client::with_uri_str(uri).await?; + let database = client.database(&database_name); + + Ok(Self { client, database }) + } + + pub async fn insert_entries( + &self, + date: NaiveDate, + entries: EntriesResponse, + ) -> Result<(), Box> { + #[derive(Serialize, Deserialize)] + struct EntriesWrapper { + #[serde(rename = "_id")] + date: NaiveDate, + #[serde(rename = "data")] + entries: EntriesResponse, + } + + let collection: Collection = self.database.collection("nutracheck_entries"); + + // let _ = collection.update_one(EntriesWrapper { + // date, entries + // }, None).await?; + + let wrapped = EntriesWrapper { date, entries }; + + collection + .find_one_and_replace( + doc! { "_id": date.to_string(), }, + wrapped, + FindOneAndReplaceOptions::builder().upsert(true).build(), + ) + .await?; + + Ok(()) + } +} + +// let client = mongodb::Client::with_uri_str(uri).await?; + +// let database = client.database("calories"); + +// let collection: Collection = database.collection("nutracheck"); + +// let doc = doc! { +// "name": "bob", +// "age": 23, +// }; + +// let result = collection.insert_one(doc, None).await?; + +// println!("{:?}", result); + +// Ok(()) +// } diff --git a/src/nutracheck.rs b/src/nutracheck.rs new file mode 100644 index 0000000..2737aed --- /dev/null +++ b/src/nutracheck.rs @@ -0,0 +1,182 @@ +use std::sync::RwLock; + +use chrono::NaiveDate; +use reqwest::Method; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::json; +use tracing::info; + +use crate::entries::EntriesResponse; + +const TOKEN_CACHE_PATH: &str = "nutracheck_token.json"; +const APP_API_KEY: &str = "76ef7432-e7d8-437d-b5f6-2adf7aa8d224"; +const USER_AGENT: &str = "Nutracheck.iOS.phone"; + +pub struct Client { + client: reqwest::Client, + username: String, + password: String, + token: RwLock, +} + +impl Client { + pub async fn new_from_env() -> Result> { + let username = std::env::var("NUTRACHECK_USERNAME")?; + let password = std::env::var("NUTRACHECK_PASSWORD")?; + + let token = match get_token_file().await? { + Some(token_resp) => token_resp, + None => { + let token_resp = get_new_nc_token(&username, &password).await?; + write_token_file(token_resp.clone()).await?; + token_resp + } + }; + + Ok(Self { + client: reqwest::Client::new(), + username, + password, + token: RwLock::new(token), + }) + } + + async fn refresh_token_if_expired(&self) -> Result<(), Box> { + let expired = self.token.read()?.is_expired(); + if !expired { + return Ok(()); + } + + let mut guard = self.token.write()?; + + let token_resp = get_new_nc_token(&self.username, &self.password).await?; + + *guard = token_resp; + + Ok(()) + } + + pub async fn entries( + &self, + date: NaiveDate, + ) -> Result> { + let entries = self + .send_request::( + Method::GET, + &format!("v1.3/diary/entries?date={}", date.to_string()), + ) + .await?; + Ok(entries) + } + + async fn send_request( + &self, + method: Method, + path: &str, + ) -> Result> { + self.refresh_token_if_expired().await?; + + let token = self.token.read()?; + + let req = self + .client + .request(method, format!("https://api.nutracheck.com/api/{}", path)) + .header("Authorization", &token.token) + // .header("Content-Type", "application/json") + .header("X-Api-Key", APP_API_KEY) + .header("User-Agent", USER_AGENT); + + println!("{:?}", req); + + let response = req.send().await?; + + println!("resp code: {}", response.status().as_u16()); + + let resp_body = response.text().await?; + + println!("resp: {}", resp_body); + + let de_response: T = serde_json::from_str(&resp_body)?; + + Ok(de_response) + } +} + +async fn get_new_nc_token( + username: &str, + password: &str, +) -> Result> { + info!("getting new token"); + + let body = json!({ + "username": username, + "password": password, + }) + .to_string(); + + let client = reqwest::Client::new(); + let resp = client + .post("https://api.nutracheck.com/api/v1/users/authenticate") + .header("Content-Type", "application/json") + .header("X-Api-Key", APP_API_KEY) + .header("User-Agent", USER_AGENT) + .body(body) + .send() + .await? + .json::() + .await?; + + Ok(resp) +} + +async fn get_token_file() -> Result, Box> { + if !tokio::fs::try_exists(TOKEN_CACHE_PATH).await? { + return Ok(None); + } + + let data = tokio::fs::read(TOKEN_CACHE_PATH).await?; + + let token_response: NCTokenResponse = serde_json::from_slice(&data)?; + + if token_response.is_expired() { + return Ok(None); + } + + Ok(Some(token_response)) +} + +async fn write_token_file(resp: NCTokenResponse) -> Result<(), Box> { + let data = serde_json::to_vec(&resp)?; + + tokio::fs::write(TOKEN_CACHE_PATH, &data).await?; + + Ok(()) +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NCTokenResponse { + // #[serde(rename = "userID")] + // pub user_id: i64, + pub token: String, + pub status: NCTokenStatus, +} + +impl NCTokenResponse { + /// is_expired checks if the token has less than 5 minutes left before expiring + pub fn is_expired(&self) -> bool { + self.status.date_expiry < chrono::Utc::now() + chrono::Duration::minutes(5) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NCTokenStatus { + pub account_status: String, + pub account_status_enum: String, + pub date_expiry: chrono::DateTime, + pub date_registration: String, + pub date_since_last_use: String, + pub days_since_last_use: i64, + pub debug_identifier: String, +} diff --git a/tidy_dump.jq b/tidy_dump.jq new file mode 100644 index 0000000..1701d27 --- /dev/null +++ b/tidy_dump.jq @@ -0,0 +1,21 @@ +def try_fromjson: if . == null or .[0:1] != "{" then . else fromjson end; +def try_frombase64: if . == null then . else @base64d end; + +[.[] | { + method: .method, + host: .host, + path: .path, + query: .query, + request: { + # headers: .request.header.headers, + mimeType: .request.mimeType, + contentEncoding: .request.contentEncoding, + body: .request.body.text | try_fromjson + }, + response: { + mimeType: .response.mimeType, + encoding: .response.body.encoding, + headers: .response.header.headers, + body: (if .response.body.encoding == "base64" then (.response.body.encoded | try_frombase64) else (.response.body.text | try_fromjson) end), + } +}] \ No newline at end of file