From d2cb5b30310b18946bdc00cbdcedb729950f2f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Forc=C3=A9n=20Mu=C3=B1oz?= Date: Mon, 12 Feb 2024 14:04:03 +0100 Subject: [PATCH] Application able to CRUD --- .gitignore | 3 + Cargo.lock | 2058 ++++++++++++++++++++++++++ Cargo.toml | 28 + migrations/20231110161954_base.sql | 51 + src/lib.rs | 20 + src/models.rs | 8 + src/models/account.rs | 360 +++++ src/models/categories.rs | 41 + src/models/rules.rs | 54 + src/models/transaction.rs | 217 +++ src/models/users.rs | 35 + webserver/Cargo.toml | 16 + webserver/src/main.rs | 77 + webserver/src/routes.rs | 43 + webserver/src/routes/accounts.rs | 87 ++ webserver/src/routes/categories.rs | 33 + webserver/src/routes/rules.rs | 33 + webserver/src/routes/transactions.rs | 62 + webserver/src/users.rs | 42 + 19 files changed, 3268 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 migrations/20231110161954_base.sql create mode 100644 src/lib.rs create mode 100644 src/models.rs create mode 100644 src/models/account.rs create mode 100644 src/models/categories.rs create mode 100644 src/models/rules.rs create mode 100644 src/models/transaction.rs create mode 100644 src/models/users.rs create mode 100644 webserver/Cargo.toml create mode 100644 webserver/src/main.rs create mode 100644 webserver/src/routes.rs create mode 100644 webserver/src/routes/accounts.rs create mode 100644 webserver/src/routes/categories.rs create mode 100644 webserver/src/routes/rules.rs create mode 100644 webserver/src/routes/transactions.rs create mode 100644 webserver/src/users.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b5ca87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/test +/*.db* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..66ff798 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2058 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "accounters" +version = "0.1.0" +dependencies = [ + "chrono", + "futures", + "regex", + "serde", + "sqlx", + "tokio", +] + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[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 = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "bitflags 1.3.2", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +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", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[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 = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +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 = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rsa" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.1", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.1", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", +] + +[[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 = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys", +] + +[[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.39", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + +[[package]] +name = "webserver" +version = "0.1.0" +dependencies = [ + "accounters", + "axum", + "chrono", + "hyper", + "serde", + "serde_json", + "sqlx", + "tokio", +] + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zerocopy" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e84a74d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "accounters" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] + +[dependencies] +serde = { version = "1", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"] } +sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "chrono"] } +futures = "0.3" +regex = "1" + +[dev-dependencies] +tokio = { version = "1", features = ["full"] } + +[workspace] +members = [ + "webserver" +] + +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"]} +sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "chrono"] } diff --git a/migrations/20231110161954_base.sql b/migrations/20231110161954_base.sql new file mode 100644 index 0000000..6bc1c3f --- /dev/null +++ b/migrations/20231110161954_base.sql @@ -0,0 +1,51 @@ +-- Add migration script here + +CREATE TABLE IF NOT EXISTS users( + user_id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT, + pass TEXT, + UNIQUE(username) +); + +CREATE TABLE IF NOT EXISTS accounts( + account_id INTEGER PRIMARY KEY AUTOINCREMENT, + user INTEGER, + account_name TEXT, + FOREIGN KEY (user) REFERENCES users(user_id) +); + +CREATE TABLE IF NOT EXISTS account_snapshot( + account INTEGER, + datestamp DATE, + amount INT, + FOREIGN KEY (account) REFERENCES accounts(account_id), + PRIMARY KEY (account, datestamp) +); + +CREATE TABLE IF NOT EXISTS categories ( + category_id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + description TEXT +); + +CREATE TABLE IF NOT EXISTS rules( + rule_id INTEGER PRIMARY KEY AUTOINCREMENT, + user INTEGER, + regex TEXT, + category INTEGER, + FOREIGN KEY (user) REFERENCES users(user_id) + FOREIGN KEY (category) REFERENCES categories(category_id) +); + +CREATE TABLE IF NOT EXISTS transactions ( + transaction_id INTEGER PRIMARY KEY AUTOINCREMENT, + account INTEGER, + description TEXT, + transaction_timestamp DATETIME, + category INTEGER, + amount INTEGER, + FOREIGN KEY (account) REFERENCES accounts(account_id), + FOREIGN KEY (category) REFERENCES categories(category_id) +); + +CREATE INDEX idx_transactions_ts ON transactions(account, transaction_timestamp); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f7ab6d9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool}; + +pub mod models; + +pub async fn create_db(db_url: &str) -> sqlx::Result { + if !Sqlite::database_exists(db_url).await.unwrap_or(false) { + println!("Creating database {}", db_url); + match Sqlite::create_database(db_url).await { + Ok(_) => println!("create db success"), + Err(e) => panic!("Cannot create db: {e:?}"), + }; + } else { + println!("Database already exists") + } + + let db = SqlitePool::connect(db_url).await?; + sqlx::migrate!().run(&db).await?; + + Ok(db) +} diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..cc1ef55 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,8 @@ +mod account; +pub mod categories; +pub mod rules; +pub mod transaction; +pub mod users; + +pub use account::{Account, AccountSnapshot}; +pub use transaction::Transaction; diff --git a/src/models/account.rs b/src/models/account.rs new file mode 100644 index 0000000..b1ab72f --- /dev/null +++ b/src/models/account.rs @@ -0,0 +1,360 @@ +use chrono::{prelude::*, Duration, DurationRound}; +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, Result, SqlitePool}; + +use super::{rules::Rule, Transaction}; + +#[derive(FromRow, Serialize, Deserialize, Clone, Debug)] +pub struct AccountSnapshot { + account: i32, + datestamp: DateTime, + amount: i32, +} + +impl AccountSnapshot { + pub async fn get( + pool: &SqlitePool, + account: i32, + date: DateTime, + ) -> Result { + sqlx::query("SELECT * FROM account_snapshot WHERE account=? AND datestamp=?") + .bind(account) + .bind(date) + .fetch_one(pool) + .await + .and_then(|r| AccountSnapshot::from_row(&r)) + } + + pub async fn get_last( + pool: &SqlitePool, + account: i32, + date: DateTime, + ) -> Result { + sqlx::query("SELECT * FROM account_snapshot WHERE account=? AND datestamp<=? LIMIT 1") + .bind(account) + .bind(date) + .fetch_one(pool) + .await + .and_then(|r| AccountSnapshot::from_row(&r)) + } + + pub async fn list( + pool: &SqlitePool, + account: i32, + limit: Option, + offset: Option, + asc: bool, + ) -> sqlx::Result> { + let mut query = sqlx::QueryBuilder::new("SELECT * FROM account_snapshot WHERE account="); + query.push_bind(account); + + if let Some(limit) = limit { + query.push(" LIMIT "); + query.push_bind(limit); + } + + if let Some(offset) = offset { + query.push(" OFFSET "); + query.push_bind(offset); + } + + if asc { + query.push(" ORDER BY datestamp ASC"); + } else { + query.push(" ORDER BY datestamp DESC"); + } + + let rows = query.build().fetch_all(pool).await?; + + let mut res = Vec::new(); + for r in rows.iter() { + res.push(AccountSnapshot::from_row(r)?); + } + Ok(res) + } + + pub async fn list_by_date( + pool: &SqlitePool, + account: i32, + after: Option>, + before: Option>, + limit: Option, + asc: bool, + ) -> sqlx::Result> { + let mut query = sqlx::QueryBuilder::new("SELECT * FROM account_snapshot WHERE account="); + query.push_bind(account); + + if let Some(after) = after { + query.push(" AND datestamp >= "); + query.push_bind(after); + } + + if let Some(before) = before { + query.push(" AND datestamp < "); + query.push_bind(before); + } + + if let Some(limit) = limit { + query.push(" LIMIT "); + query.push_bind(limit); + } + + if asc { + query.push(" ORDER BY datestamp ASC"); + } else { + query.push(" ORDER BY datestamp DESC"); + } + + let rows = query.build().fetch_all(pool).await?; + + let mut res = Vec::new(); + for r in rows.iter() { + res.push(AccountSnapshot::from_row(r)?); + } + Ok(res) + } + + pub async fn delete_by_dates( + pool: &SqlitePool, + account: i32, + after: Option>, + before: Option>, + ) -> sqlx::Result<()> { + if after.is_none() && before.is_none() { + return Err(sqlx::Error::RowNotFound); + } + + let mut query = sqlx::QueryBuilder::new("DELETE FROM account_snapshot WHERE account="); + query.push_bind(account); + + if let Some(after) = after { + query.push(" AND datestamp >= "); + query.push_bind(after); + } + + if let Some(before) = before { + query.push(" AND datestamp < "); + query.push_bind(before); + } + + query.build().execute(pool).await?; + + Ok(()) + } + + pub async fn insert(&self, pool: &SqlitePool) -> sqlx::Result<()> { + sqlx::query("INSERT INTO account_snapshot(account, datestamp, amount) VALUES(?,?,?)") + .bind(self.account) + .bind(self.datestamp) + .bind(self.amount) + .execute(pool) + .await + .map(|_| ()) + } + + pub async fn get_next(&self, pool: &SqlitePool) -> sqlx::Result> { + let date_next = match Transaction::list_by_date( + pool, + self.account, + Some(self.datestamp + Duration::days(1)), + None, + Some(1), + true, + ) + .await? + .first() + { + Some(tx) => tx.get_timestamp(), + None => { + return Ok(None); + } + } + .duration_trunc(chrono::Duration::days(1)) + .unwrap(); + + println!( + "Starting date: {:?}, ending date: {:?}", + self.datestamp, date_next + ); + + let tx_list = Transaction::list_by_date( + pool, + self.account, + Some(self.datestamp), + Some(date_next), + None, + true, + ) + .await?; + + Ok(Some(AccountSnapshot { + datestamp: date_next, + account: self.account, + amount: self.amount + tx_list.iter().fold(0, |acc, tx| acc + tx.get_amount()), + })) + } +} + +#[derive(FromRow, Serialize, Deserialize)] +pub struct Account { + account_id: i32, + user: i32, + account_name: String, +} + +impl Account { + pub fn get_id(&self) -> i32 { + self.account_id + } + + pub fn get_user(&self) -> i32 { + self.user + } + + pub fn get_account_name(&self) -> &str { + self.account_name.as_str() + } + + pub async fn set_account_name(&mut self, pool: &SqlitePool, name: &str) -> Result<()> { + sqlx::query("UPDATE accounts SET account_name=? WHERE account_id=?") + .bind(name) + .bind(self.account_id) + .execute(pool) + .await?; + self.account_name = name.to_string(); + Ok(()) + } + + pub async fn get_by_id(pool: &SqlitePool, id: i32) -> Result { + sqlx::query("SELECT * FROM accounts WHERE account_id=?") + .bind(id) + .fetch_one(pool) + .await + .and_then(|r| Account::from_row(&r)) + } + + pub async fn new(pool: &SqlitePool, user: i32, name: &str) -> Result { + let row = sqlx::query("INSERT INTO accounts(user, account_name) VALUES (?,?) RETURNING *") + .bind(user) + .bind(name) + .fetch_one(pool) + .await?; + Self::from_row(&row) + } + + pub async fn list(pool: &SqlitePool, user: i32) -> Result> { + let rows = sqlx::query("SELECT * FROM accounts WHERE user=?") + .bind(user) + .fetch_all(pool) + .await?; + let mut res = Vec::new(); + for r in &rows { + res.push(Account::from_row(r)?) + } + Ok(res) + } + + pub async fn recalculate_snapshots( + &self, + pool: &SqlitePool, + from: Option>, + ) -> Result<()> { + let mut snap = match from { + Some(f) => { + let snapshot = AccountSnapshot::list_by_date( + pool, + self.get_id(), + None, + Some(f), + Some(1), + true, + ) + .await?; + + if snapshot.is_empty() { + AccountSnapshot { + account: self.account_id, + datestamp: Utc.timestamp_opt(0, 0).unwrap(), + amount: 0, + } + } else { + snapshot.first().unwrap().clone() + } + } + None => AccountSnapshot { + account: self.account_id, + datestamp: Utc.timestamp_opt(0, 0).unwrap(), + amount: 0, + }, + }; + + AccountSnapshot::delete_by_dates( + pool, + self.get_id(), + Some(snap.datestamp + Duration::hours(12)), + None, + ) + .await?; + + while let Some(next) = snap.get_next(pool).await? { + next.insert(pool).await?; + snap = next; + } + Ok(()) + } + + pub async fn recategorize_transactions( + &self, + pool: &SqlitePool, + from: Option>, + to: Option>, + ) -> Result<()> { + let rules = Rule::list_by_user(pool, self.user).await?; + let mut tx_list = + Transaction::list_by_date(pool, self.account_id, from, to, None, true).await?; + for tx in tx_list.iter_mut() { + println!("Checking {}", tx.get_description()); + if tx.recategorize(pool, &rules).await? { + println!( + "Tx {} updated with category {}", + tx.get_id(), + tx.get_category().unwrap_or(0) + ); + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::Account; + use crate::models::users::User; + use sqlx::SqlitePool; + + async fn get_db() -> SqlitePool { + crate::create_db("sqlite://account_test.db").await.unwrap() + } + + async fn remove_db(pool: SqlitePool) { + pool.close().await; + std::fs::remove_file("account_test.db").unwrap(); + } + + async fn new_user(pool: &SqlitePool) -> User { + User::create_user(pool, "account_test", "pass") + .await + .unwrap() + } + + #[tokio::test] + async fn create_test() { + let pool = get_db().await; + let user = new_user(&pool).await; + Account::new(&pool, user.get_id(), "account_test") + .await + .unwrap(); + remove_db(pool).await; + } +} diff --git a/src/models/categories.rs b/src/models/categories.rs new file mode 100644 index 0000000..19efbef --- /dev/null +++ b/src/models/categories.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, SqlitePool}; + +#[derive(FromRow, Serialize, Deserialize)] +pub struct Category { + pub category_id: i32, + pub name: String, + pub description: String, +} + +impl Category { + pub async fn get_by_id(pool: &SqlitePool, id: i32) -> sqlx::Result { + sqlx::query("SELECT * FROM categories WHERE category_id=?") + .bind(id) + .fetch_one(pool) + .await + .and_then(|r| Category::from_row(&r)) + } + + pub async fn list(pool: &SqlitePool) -> sqlx::Result> { + let mut res = Vec::new(); + for r in sqlx::query("SELECT * FROM categories") + .fetch_all(pool) + .await? + .iter() + { + res.push(Category::from_row(r)?) + } + + Ok(res) + } + + pub async fn new(pool: &SqlitePool, name: &str, description: &str) -> sqlx::Result { + sqlx::query("INSERT INTO categories(name, description) VALUES (?,?) RETURNING *") + .bind(name) + .bind(description) + .fetch_one(pool) + .await + .and_then(|r| Category::from_row(&r)) + } +} diff --git a/src/models/rules.rs b/src/models/rules.rs new file mode 100644 index 0000000..f9485da --- /dev/null +++ b/src/models/rules.rs @@ -0,0 +1,54 @@ +use regex::Regex; +use serde::Serialize; +use sqlx::{FromRow, SqlitePool}; + +#[derive(FromRow, Serialize)] +pub struct Rule { + pub rule_id: i32, + pub user: i32, + pub regex: String, + pub category: i32, +} + +impl Rule { + pub async fn get_by_id(pool: &SqlitePool, rule_id: i32) -> sqlx::Result { + sqlx::query("SELECT * FROM rules WHERE rule_id=?") + .bind(rule_id) + .fetch_one(pool) + .await + .and_then(|r| Rule::from_row(&r)) + } + + pub async fn list_by_user(pool: &SqlitePool, user: i32) -> sqlx::Result> { + let mut res = Vec::new(); + for r in sqlx::query("SELECT * FROM rules WHERE user=?") + .bind(user) + .fetch_all(pool) + .await? + .iter() + { + res.push(Rule::from_row(r)?); + } + Ok(res) + } + + pub async fn new( + pool: &SqlitePool, + user: i32, + regex: String, + category: i32, + ) -> sqlx::Result { + sqlx::query("INSERT INTO rules(user, regex, category) VALUES (?,?,?) RETURNING *") + .bind(user) + .bind(regex) + .bind(category) + .fetch_one(pool) + .await + .and_then(|r| Rule::from_row(&r)) + } + + pub fn matches(&self, description: &str) -> Result { + let re = Regex::new(&self.regex)?; + Ok(re.is_match(description)) + } +} diff --git a/src/models/transaction.rs b/src/models/transaction.rs new file mode 100644 index 0000000..e159ec1 --- /dev/null +++ b/src/models/transaction.rs @@ -0,0 +1,217 @@ +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, Result, Sqlite, SqlitePool}; + +use crate::models::rules::Rule; + +#[derive(FromRow, Serialize, Deserialize, Debug)] +pub struct Transaction { + transaction_id: i32, + account: i32, + description: String, + transaction_timestamp: DateTime, + category: Option, + amount: i32, +} + +impl Transaction { + pub async fn new( + pool: &SqlitePool, + account: i32, + desc: &str, + ts: &DateTime, + category: Option, + amount: i32, + ) -> Result { + let res = sqlx::query(concat!( + "INSERT INTO transactions(", + "account, description, transaction_timestamp, category, amount", + ") VALUES (?,?,?,?,?) RETURNING *" + )) + .bind(account) + .bind(desc) + .bind(ts) + .bind(category) + .bind(amount) + .fetch_one(pool) + .await?; + + Transaction::from_row(&res) + } + + pub async fn list( + pool: &SqlitePool, + account: i32, + limit: i32, + offset: i32, + asc: bool, + ) -> Result> { + let rows = sqlx::query( + if asc { + "SELECT * FROM transactions WHERE account=? ORDER BY transaction_timestamp ASC LIMIT ? OFFSET ?" + } else { + "SELECT * FROM transactions WHERE account=? ORDER BY transaction_timestamp DESC LIMIT ? OFFSET ?" + } + ).bind(account) + .bind(limit) + .bind(offset) + .fetch_all(pool) + .await?; + + let mut res = Vec::new(); + for r in &rows { + res.push(Transaction::from_row(r)?); + } + Ok(res) + } + + pub fn query_by_date<'a>( + account: i32, + after: Option>, + before: Option>, + limit: Option, + asc: bool, + ) -> sqlx::QueryBuilder<'a, Sqlite> { + let mut query = sqlx::QueryBuilder::new("SELECT * FROM TRANSACTIONS WHERE account="); + query.push_bind(account); + + if let Some(after) = after { + query.push(" AND transaction_timestamp >= "); + query.push_bind(after); + } + + if let Some(before) = before { + query.push(" AND transaction_timestamp < "); + query.push_bind(before); + } + + if asc { + query.push(" ORDER BY transaction_timestamp ASC"); + } else { + query.push(" ORDER BY transaction_timestamp DESC"); + } + + if let Some(lim) = limit { + query.push(" LIMIT "); + query.push_bind(lim); + } + + query + } + + pub async fn list_by_date( + pool: &SqlitePool, + account: i32, + after: Option>, + before: Option>, + limit: Option, + asc: bool, + ) -> Result> { + let mut query = Self::query_by_date(account, after, before, limit, asc); + + let rows = query.build().fetch_all(pool).await?; + + let mut res = Vec::new(); + for r in &rows { + res.push(Transaction::from_row(r)?); + } + Ok(res) + } + + pub fn get_id(&self) -> i32 { + self.transaction_id + } + + pub fn get_account(&self) -> i32 { + self.account + } + + pub fn get_description(&self) -> &str { + &self.description + } + + pub fn get_timestamp(&self) -> &DateTime { + &self.transaction_timestamp + } + + pub fn get_category(&self) -> Option { + self.category + } + + pub async fn set_category(&mut self, pool: &SqlitePool, new_category: i32) -> Result<()> { + sqlx::query("UPDATE transactions SET category=? WHERE transaction_id=?") + .bind(new_category) + .bind(self.transaction_id) + .execute(pool) + .await?; + self.category = Some(new_category); + Ok(()) + } + + pub async fn recategorize(&mut self, pool: &SqlitePool, rules: &Vec) -> Result { + for r in rules.iter() { + if r.matches(&self.description) + .map_err(|_| sqlx::Error::Protocol("RegexError".to_string()))? + { + self.set_category(pool, r.category).await?; + return Ok(true); + } + } + Ok(false) + } + + pub fn get_amount(&self) -> i32 { + self.amount + } + + pub async fn set_description(&mut self, pool: &SqlitePool, desc: &str) -> Result<()> { + sqlx::query("UPDATE transactions SET description=? WHERE transaction_id=?") + .bind(desc) + .bind(self.transaction_id) + .execute(pool) + .await?; + self.description = desc.to_string(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::Transaction; + use crate::models::{account::Account, users::User}; + use sqlx::SqlitePool; + + async fn get_db() -> SqlitePool { + crate::create_db("sqlite://tx_test.db").await.unwrap() + } + + async fn remove_db(pool: SqlitePool) { + pool.close().await; + std::fs::remove_file("tx_test.db").unwrap(); + } + + async fn new_user(pool: &SqlitePool) -> User { + User::create_user(pool, "testuser", "pass").await.unwrap() + } + + #[tokio::test] + async fn create_test() { + let pool = get_db().await; + let user = new_user(&pool).await; + let acc = Account::new(&pool, user.get_id(), "tx_test").await.unwrap(); + let tx = Transaction::new( + &pool, + acc.get_id(), + "Test transaction", + &chrono::Utc::now(), + None, + 100, + ) + .await + .unwrap(); + + println!("{tx:?}"); + + remove_db(pool).await; + } +} diff --git a/src/models/users.rs b/src/models/users.rs new file mode 100644 index 0000000..cafb726 --- /dev/null +++ b/src/models/users.rs @@ -0,0 +1,35 @@ +use sqlx::{FromRow, SqlitePool}; + +#[derive(Debug, FromRow)] +pub struct User { + user_id: i32, + username: String, + pass: String, +} + +impl User { + pub fn get_id(&self) -> i32 { + self.user_id + } + + pub fn check_pass(&self, pass: &str) -> bool { + &self.pass == pass + } + + pub async fn create_user(pool: &SqlitePool, user: &str, pass: &str) -> sqlx::Result { + sqlx::query("INSERT INTO users(username, pass) VALUES (?, ?) RETURNING *") + .bind(user) + .bind(pass) + .fetch_one(pool) + .await + .and_then(|r| User::from_row(&r)) + } + + pub async fn get_user(pool: &SqlitePool, user: &str) -> sqlx::Result { + sqlx::query("SELECT * FROM users WHERE username = ?") + .bind(user) + .fetch_one(pool) + .await + .and_then(|r| User::from_row(&r)) + } +} diff --git a/webserver/Cargo.toml b/webserver/Cargo.toml new file mode 100644 index 0000000..d31aa72 --- /dev/null +++ b/webserver/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "webserver" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { workspace = true, features = ["derive"] } +chrono = { workspace = true, features = ["serde"] } +sqlx = { workspace = true, features = ["runtime-tokio", "sqlite", "chrono"]} +tokio = { version = "1", features = ["full"] } +axum = { version = "0.6.20", features = ["macros", "headers"] } +hyper = "0.14.27" +serde_json = "1" +accounters = { path = ".." } diff --git a/webserver/src/main.rs b/webserver/src/main.rs new file mode 100644 index 0000000..35702a4 --- /dev/null +++ b/webserver/src/main.rs @@ -0,0 +1,77 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use sqlx::SqlitePool; + +use axum::{ + extract::FromRef, + routing::{get, post}, + Router, +}; +use hyper::StatusCode; + +mod routes; +mod users; + +const DB_URL: &str = "sqlite://sqlite.db"; + +#[tokio::main] +async fn main() { + let db = accounters::create_db(DB_URL).await.unwrap(); + + let state = AppState { db: Arc::new(db) }; + + let app = Router::new() + .route("/", get(index)) + .nest( + "/api/v1", + Router::new() + .route("/user", post(routes::create_user)) + .route("/login", post(routes::login)) + .route("/accounts", post(routes::accounts::account_create)) + .route("/accounts", get(routes::accounts::account_list)) + .route("/accounts/id/:id", get(routes::accounts::account_get)) + .route( + "/accounts/id/:id/transaction", + post(routes::transactions::create), + ) + .route( + "/accounts/id/:id/transaction", + get(routes::transactions::list), + ) + .route( + "/accounts/id/:id/update", + post(routes::accounts::snapshot_update), + ) + .route( + "/accounts/id/:id/recategorize", + post(routes::accounts::recategorize), + ) + .route("/categories", post(routes::categories::create)) + .route("/categories", get(routes::categories::list)) + .route("/rules", post(routes::rules::create)) + .route("/rules", get(routes::rules::list)), + ) + .with_state(state); + + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +#[derive(Clone)] +pub struct AppState { + db: Arc, +} + +impl FromRef for Arc { + fn from_ref(state: &AppState) -> Arc { + state.db.clone() + } +} + +async fn index() -> (StatusCode, String) { + (StatusCode::OK, String::from("Hello, World!")) +} diff --git a/webserver/src/routes.rs b/webserver/src/routes.rs new file mode 100644 index 0000000..fed8c50 --- /dev/null +++ b/webserver/src/routes.rs @@ -0,0 +1,43 @@ +use std::sync::Arc; + +use axum::extract::{Json, State}; +use hyper::StatusCode; +use serde::Deserialize; +use sqlx::SqlitePool; + +use accounters::models::users::User; + +pub mod accounts; +pub mod categories; +pub mod rules; +pub mod transactions; + +#[derive(Deserialize)] +pub struct CreateUserRequest { + user: String, + pass: String, +} + +pub async fn create_user( + State(db): State>, + Json(user_info): Json, +) -> (StatusCode, String) { + let exec = User::create_user(db.as_ref(), &user_info.user, &user_info.pass).await; + match exec { + Ok(e) => (StatusCode::OK, format!("{}", e.get_id())), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")), + } +} + +pub async fn login( + State(db): State>, + Json(user_info): Json, +) -> (StatusCode, String) { + let user = User::get_user(db.as_ref(), &user_info.user).await.unwrap(); + + if user.check_pass(&user_info.pass) { + (StatusCode::OK, format!("{}", user.get_id())) + } else { + (StatusCode::UNAUTHORIZED, String::new()) + } +} diff --git a/webserver/src/routes/accounts.rs b/webserver/src/routes/accounts.rs new file mode 100644 index 0000000..4fc115c --- /dev/null +++ b/webserver/src/routes/accounts.rs @@ -0,0 +1,87 @@ +use std::sync::Arc; + +use axum::extract::{Json, Path, State}; +use hyper::StatusCode; +use serde::Deserialize; +use sqlx::SqlitePool; + +use crate::users::UserToken; +use accounters::models::Account; + +pub async fn account_get( + State(db): State>, + uid: UserToken, + Path(id): Path, +) -> (StatusCode, String) { + match Account::get_by_id(db.as_ref(), id).await { + Ok(a) => { + if a.get_user() == uid.user_id { + (StatusCode::OK, serde_json::to_string(&a).unwrap()) + } else { + (StatusCode::UNAUTHORIZED, String::new()) + } + } + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + } +} + +#[derive(Deserialize)] +pub struct AccountRequestCreate { + pub name: String, +} + +pub async fn account_create( + State(db): State>, + uid: UserToken, + Json(account): Json, +) -> (StatusCode, String) { + match Account::new(db.as_ref(), uid.user_id, &account.name).await { + Ok(a) => (StatusCode::OK, serde_json::to_string(&a).unwrap()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + } +} + +pub async fn account_list( + State(db): State>, + uid: UserToken, +) -> (StatusCode, String) { + match Account::list(db.as_ref(), uid.user_id).await { + Ok(a) => (StatusCode::OK, serde_json::to_string(&a).unwrap()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + } +} + +pub async fn snapshot_update( + State(db): State>, + uid: UserToken, + Path(account): Path, +) -> (StatusCode, String) { + let account = Account::get_by_id(db.as_ref(), account).await.unwrap(); + if account.get_user() != uid.user_id { + return (StatusCode::UNAUTHORIZED, String::new()); + } + + match account.recalculate_snapshots(db.as_ref(), None).await { + Ok(_) => (StatusCode::OK, String::new()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + } +} + +pub async fn recategorize( + State(db): State>, + uid: UserToken, + Path(account): Path, +) -> (StatusCode, String) { + let account = Account::get_by_id(db.as_ref(), account).await.unwrap(); + if account.get_user() != uid.user_id { + return (StatusCode::UNAUTHORIZED, String::new()); + } + + match account + .recategorize_transactions(db.as_ref(), None, None) + .await + { + Ok(_) => (StatusCode::OK, String::new()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + } +} diff --git a/webserver/src/routes/categories.rs b/webserver/src/routes/categories.rs new file mode 100644 index 0000000..c0b37be --- /dev/null +++ b/webserver/src/routes/categories.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; + +use axum::{extract::State, Json}; +use hyper::StatusCode; +use serde::Deserialize; +use sqlx::SqlitePool; + +use crate::users::UserToken; +use accounters::models::categories::Category; + +#[derive(Deserialize)] +pub struct CategoryCreateRequest { + name: String, + description: String, +} + +pub async fn create( + State(db): State>, + uid: UserToken, + Json(new_category): Json, +) -> (StatusCode, String) { + match Category::new(db.as_ref(), &new_category.name, &new_category.description).await { + Ok(_) => (StatusCode::OK, String::new()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")), + } +} + +pub async fn list(State(db): State>, uid: UserToken) -> (StatusCode, String) { + match Category::list(db.as_ref()).await { + Ok(c) => (StatusCode::OK, serde_json::to_string(&c).unwrap()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")), + } +} diff --git a/webserver/src/routes/rules.rs b/webserver/src/routes/rules.rs new file mode 100644 index 0000000..303c675 --- /dev/null +++ b/webserver/src/routes/rules.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; + +use axum::extract::{Json, State}; +use hyper::StatusCode; +use serde::Deserialize; +use sqlx::SqlitePool; + +use crate::users::UserToken; +use accounters::models::rules::Rule; + +#[derive(Deserialize)] +pub struct RuleCreateRequest { + regex: String, + category: i32, +} + +pub async fn create( + State(db): State>, + uid: UserToken, + Json(rule): Json, +) -> (StatusCode, String) { + match Rule::new(db.as_ref(), uid.user_id, rule.regex, rule.category).await { + Ok(r) => (StatusCode::OK, serde_json::to_string(&r).unwrap()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")), + } +} + +pub async fn list(State(db): State>, uid: UserToken) -> (StatusCode, String) { + match Rule::list_by_user(db.as_ref(), uid.user_id).await { + Ok(rule_list) => (StatusCode::OK, serde_json::to_string(&rule_list).unwrap()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")), + } +} diff --git a/webserver/src/routes/transactions.rs b/webserver/src/routes/transactions.rs new file mode 100644 index 0000000..133f594 --- /dev/null +++ b/webserver/src/routes/transactions.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use axum::extract::{Json, Path, Query, State}; +use chrono::{offset::Utc, DateTime}; +use hyper::StatusCode; +use serde::Deserialize; +use sqlx::SqlitePool; + +use accounters::models::Transaction; + +#[derive(Deserialize)] +pub struct TransactionContent { + description: String, + timestamp: DateTime, + category: Option, + amount: i32, +} + +pub async fn create( + State(db): State>, + Path(account): Path, + Json(txcnt): Json, +) -> (StatusCode, String) { + match Transaction::new( + db.as_ref(), + account, + &txcnt.description, + &txcnt.timestamp, + None, + txcnt.amount, + ) + .await + { + Ok(tx) => (StatusCode::OK, serde_json::to_string(&tx).unwrap()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + } +} + +#[derive(Deserialize)] +pub struct PaginationOptions { + pub limit: Option, + pub offset: Option, +} + +pub async fn list( + State(db): State>, + Path(account): Path, + Query(pagination): Query, +) -> (StatusCode, String) { + match Transaction::list( + db.as_ref(), + account, + pagination.limit.unwrap_or(100), + pagination.offset.unwrap_or(0), + true, + ) + .await + { + Ok(txs) => (StatusCode::OK, serde_json::to_string(&txs).unwrap()), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")), + } +} diff --git a/webserver/src/users.rs b/webserver/src/users.rs new file mode 100644 index 0000000..14008da --- /dev/null +++ b/webserver/src/users.rs @@ -0,0 +1,42 @@ +use axum::{ + async_trait, + extract::FromRequestParts, + headers::authorization::Bearer, + headers::Authorization, + http::request::Parts, + response::{IntoResponse, Redirect}, + RequestPartsExt, TypedHeader, +}; + +pub struct AuthRedirect; + +impl IntoResponse for AuthRedirect { + fn into_response(self) -> axum::response::Response { + Redirect::temporary("/login").into_response() + } +} + +pub struct UserToken { + pub user_id: i32, +} + +#[async_trait] +impl FromRequestParts for UserToken +where + S: Send + Sync, +{ + type Rejection = AuthRedirect; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let auth = parts + .extract::>>() + .await + .map_err(|e| panic!("Could not get cookies: {e}")) + .unwrap(); + let ut = UserToken { + user_id: auth.0 .0.token().parse().unwrap(), + }; + Ok(ut) + } +} +