diff options
author | Joris | 2025-01-31 22:28:53 +0100 |
---|---|---|
committer | Joris | 2025-01-31 22:28:53 +0100 |
commit | 0adf5a093494bdb7f5d5c0f12913133e333ddfad (patch) | |
tree | ada6df0f3480647bec99429819f1bfffd36194ce | |
parent | 24eeb54a6b7159964e8887ade7fa5173b50feb3a (diff) |
Migrate to tokio_rusqlite
-rw-r--r-- | Cargo.lock | 900 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/controller/balance.rs | 8 | ||||
-rw-r--r-- | src/controller/categories.rs | 13 | ||||
-rw-r--r-- | src/controller/incomes.rs | 25 | ||||
-rw-r--r-- | src/controller/login.rs | 11 | ||||
-rw-r--r-- | src/controller/payments.rs | 30 | ||||
-rw-r--r-- | src/controller/statistics.rs | 6 | ||||
-rw-r--r-- | src/controller/wallet.rs | 4 | ||||
-rw-r--r-- | src/db/categories.rs | 174 | ||||
-rw-r--r-- | src/db/incomes.rs | 656 | ||||
-rw-r--r-- | src/db/jobs.rs | 42 | ||||
-rw-r--r-- | src/db/payments.rs | 827 | ||||
-rw-r--r-- | src/db/users.rs | 180 | ||||
-rw-r--r-- | src/db/utils.rs | 24 | ||||
-rw-r--r-- | src/jobs/mod.rs | 16 | ||||
-rw-r--r-- | src/jobs/weekly_report.rs | 22 | ||||
-rw-r--r-- | src/main.rs | 18 | ||||
-rw-r--r-- | src/model/action.rs | 1 | ||||
-rw-r--r-- | src/model/category.rs | 4 | ||||
-rw-r--r-- | src/model/frequency.rs | 20 | ||||
-rw-r--r-- | src/model/income.rs | 7 | ||||
-rw-r--r-- | src/model/job.rs | 10 | ||||
-rw-r--r-- | src/model/payment.rs | 9 | ||||
-rw-r--r-- | src/model/report.rs | 30 | ||||
-rw-r--r-- | src/model/user.rs | 4 | ||||
-rw-r--r-- | src/queries.rs | 9 | ||||
-rw-r--r-- | src/routes.rs | 14 | ||||
-rw-r--r-- | src/templates.rs | 3 |
29 files changed, 1194 insertions, 1877 deletions
@@ -18,6 +18,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -27,12 +39,6 @@ dependencies = [ ] [[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -98,15 +104,6 @@ dependencies = [ ] [[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -130,7 +127,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -140,12 +137,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] name = "bcrypt" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -163,9 +154,6 @@ name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" -dependencies = [ - "serde", -] [[package]] name = "block-buffer" @@ -202,13 +190,13 @@ dependencies = [ "minijinja", "rand", "rand_core", + "rusqlite", "serde", "serde_json", "serde_urlencoded", "sha2", - "sqlx", - "sqlx-core", "tokio", + "tokio-rusqlite", "tokio-util", "url", ] @@ -257,7 +245,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -277,21 +265,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -307,25 +280,10 @@ dependencies = [ ] [[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -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.12" +name = "crossbeam-channel" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] @@ -347,24 +305,12 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -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", ] @@ -381,21 +327,6 @@ dependencies = [ ] [[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -dependencies = [ - "serde", -] - -[[package]] name = "env_filter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -425,53 +356,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "etcetera" -version = "0.8.0" +name = "fallible-iterator" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "fastrand" -version = "2.3.0" +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fnv" @@ -480,12 +374,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "foldhash" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" - -[[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -501,7 +389,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -511,34 +398,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -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.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -557,13 +416,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", ] [[package]] @@ -614,29 +469,27 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "ahash", ] [[package]] -name = "hashlink" -version = "0.10.0" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown", -] +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] -name = "heck" -version = "0.5.0" +name = "hashlink" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] [[package]] name = "hex" @@ -645,15 +498,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -663,15 +507,6 @@ dependencies = [ ] [[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] name = "http" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -932,7 +767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -967,44 +802,22 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] -name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - -[[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ - "cc", "pkg-config", "vcpkg", ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1027,16 +840,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1080,50 +883,12 @@ dependencies = [ ] [[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.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1142,12 +907,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1167,16 +926,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", + "windows-targets", ] [[package]] @@ -1198,27 +948,6 @@ 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.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1320,38 +1049,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "ring" -version = "0.17.8" +name = "rusqlite" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", - "subtle", - "zeroize", + "bitflags", + "chrono", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", ] [[package]] @@ -1361,59 +1070,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "rustix" -version = "0.38.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1482,17 +1138,6 @@ dependencies = [ ] [[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" @@ -1519,16 +1164,6 @@ dependencies = [ ] [[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - -[[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1542,9 +1177,6 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] [[package]] name = "socket2" @@ -1557,236 +1189,12 @@ dependencies = [ ] [[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" -dependencies = [ - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "url", - "webpki-roots", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" -dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn", - "tempfile", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" -dependencies = [ - "atoi", - "base64", - "bitflags", - "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.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "tracing", - "url", -] - -[[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1815,40 +1223,6 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" -dependencies = [ - "cfg-if", - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "thiserror" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1859,21 +1233,6 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" -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.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1903,13 +1262,13 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.17" +name = "tokio-rusqlite" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "b65501378eb676f400c57991f42cbd0986827ab5c5200c53f206d710fb32a945" dependencies = [ - "futures-core", - "pin-project-lite", + "crossbeam-channel", + "rusqlite", "tokio", ] @@ -1938,24 +1297,11 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1977,39 +1323,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2066,12 +1385,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2130,40 +1443,12 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.26.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" -dependencies = [ - "redox_syscall", - "wasite", -] - -[[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -2172,7 +1457,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2181,22 +1466,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -2205,48 +1475,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" @@ -2259,48 +1511,24 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" @@ -17,12 +17,12 @@ log = "0.4" minijinja = { version = "2.6", features = ["loader"] } rand = { version = "0.8", features = ["getrandom"] } rand_core = "0.6" +rusqlite = { version = "0.32", features = ["chrono"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" sha2 = "0.10" -sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite", "chrono"] } -sqlx-core = "0.8" tokio = { version = "1", features = ["full"] } +tokio-rusqlite = "0.6" tokio-util = { version = "0.7", features = ["codec"] } url = "2.5" diff --git a/src/controller/balance.rs b/src/controller/balance.rs index efe5af6..ae6d810 100644 --- a/src/controller/balance.rs +++ b/src/controller/balance.rs @@ -11,18 +11,18 @@ use crate::payer; use crate::templates; pub async fn get(wallet: &Wallet) -> Response<Full<Bytes>> { - let users = db::users::list(&wallet.pool).await; + let users = db::users::list(&wallet.db_conn).await; - let incomes_from = db::incomes::defined_for_all(&wallet.pool).await; + let incomes_from = db::incomes::defined_for_all(&wallet.db_conn).await; let user_incomes = match incomes_from { - Some(from) => db::incomes::cumulative(&wallet.pool, from).await, + Some(from) => db::incomes::cumulative(&wallet.db_conn, from).await, None => HashMap::new(), }; let template_user_incomes = get_template_user_incomes(&users, &user_incomes); let total_income: i64 = user_incomes.values().sum(); - let user_payments = db::payments::repartition(&wallet.pool).await; + let user_payments = db::payments::repartition(&wallet.db_conn).await; let template_user_payments = get_template_user_payments(&users, &user_payments); let total_payments: i64 = user_payments.iter().map(|p| p.1).sum(); diff --git a/src/controller/categories.rs b/src/controller/categories.rs index fbbd309..8772e38 100644 --- a/src/controller/categories.rs +++ b/src/controller/categories.rs @@ -14,7 +14,7 @@ pub async fn table( wallet: &Wallet, query: queries::Categories, ) -> Response<Full<Bytes>> { - let categories = db::categories::list(&wallet.pool).await; + let categories = db::categories::list(&wallet.db_conn).await; let context = minijinja::context!( header => templates::Header::Categories, @@ -65,7 +65,7 @@ pub async fn create( match validation::category::create(&form) { Some(category) => { - match db::categories::create(&wallet.pool, &category).await { + match db::categories::create(&wallet.db_conn, category).await { Some(id) => { utils::redirect(&format!("/categories?highlight={}", id)) } @@ -86,9 +86,9 @@ async fn update_form_feedback( form: HashMap<String, String>, error: Option<String>, ) -> Response<Full<Bytes>> { - let category = db::categories::get(&wallet.pool, id).await; + let category = db::categories::get(&wallet.db_conn, id).await; let is_category_used = - db::payments::is_category_used(&wallet.pool, id).await; + db::payments::is_category_used(&wallet.db_conn, id).await; let context = minijinja::context!( header => templates::Header::Categories, @@ -119,7 +119,8 @@ pub async fn update( match validation::category::update(&form) { Some(update_category) => { - if db::categories::update(&wallet.pool, id, &update_category).await + if db::categories::update(&wallet.db_conn, id, update_category) + .await { utils::redirect(&format!("/categories?highlight={}", id)) } else { @@ -131,7 +132,7 @@ pub async fn update( } pub async fn delete(id: i64, wallet: &Wallet) -> Response<Full<Bytes>> { - if db::categories::delete(&wallet.pool, id).await { + if db::categories::delete(&wallet.db_conn, id).await { utils::redirect("/categories") } else { update_form_feedback( diff --git a/src/controller/incomes.rs b/src/controller/incomes.rs index ac3a332..09d483e 100644 --- a/src/controller/incomes.rs +++ b/src/controller/incomes.rs @@ -19,8 +19,8 @@ pub async fn table( query: queries::Incomes, ) -> Response<Full<Bytes>> { let page = query.page.unwrap_or(1); - let count = db::incomes::count(&wallet.pool).await; - let incomes = db::incomes::list(&wallet.pool, page, PER_PAGE).await; + let count = db::incomes::count(&wallet.db_conn).await; + let incomes = db::incomes::list(&wallet.db_conn, page, PER_PAGE).await; let max_page = (count as f32 / PER_PAGE as f32).ceil() as i64; let context = minijinja::context!( @@ -68,7 +68,7 @@ async fn create_form_feedback( form: HashMap<String, String>, error: Option<String>, ) -> Response<Full<Bytes>> { - let users = db::users::list(&wallet.pool).await; + let users = db::users::list(&wallet.db_conn).await; let context = minijinja::context!( header => templates::Header::Incomes, @@ -101,7 +101,7 @@ pub async fn create( match validation::income::create(&form) { Some(income) => { if !db::incomes::defined_at( - &wallet.pool, + &wallet.db_conn, income.user_id, income.date, ) @@ -110,9 +110,10 @@ pub async fn create( { error("Un revenu est déjà défini à cette date.").await } else { - match db::incomes::create(&wallet.pool, &income).await { + match db::incomes::create(&wallet.db_conn, income).await { Some(id) => { - let row = db::incomes::get_row(&wallet.pool, id).await; + let row = + db::incomes::get_row(&wallet.db_conn, id).await; let page = (row - 1) / PER_PAGE + 1; utils::redirect(&format!( "/incomes?page={}&highlight={}", @@ -142,8 +143,8 @@ async fn update_form_feedback( form: HashMap<String, String>, error: Option<String>, ) -> Response<Full<Bytes>> { - let users = db::users::list(&wallet.pool).await; - let income = db::incomes::get(&wallet.pool, id).await; + let users = db::users::list(&wallet.db_conn).await; + let income = db::incomes::get(&wallet.db_conn, id).await; let context = minijinja::context!( header => &templates::Header::Incomes, @@ -184,15 +185,15 @@ pub async fn update( match validation::income::update(&form) { Some(income) => { let existing_incomes = db::incomes::defined_at( - &wallet.pool, + &wallet.db_conn, income.user_id, income.date, ) .await; if existing_incomes.into_iter().any(|eid| eid != id) { error("Un revenu est déjà défini à cette date.").await - } else if db::incomes::update(&wallet.pool, id, &income).await { - let row = db::incomes::get_row(&wallet.pool, id).await; + } else if db::incomes::update(&wallet.db_conn, id, income).await { + let row = db::incomes::get_row(&wallet.db_conn, id).await; let page = (row - 1) / PER_PAGE + 1; utils::redirect(&format!( "/incomes?page={}&highlight={}", @@ -211,7 +212,7 @@ pub async fn delete( wallet: &Wallet, query: queries::Incomes, ) -> Response<Full<Bytes>> { - if db::incomes::delete(&wallet.pool, id).await { + if db::incomes::delete(&wallet.db_conn, id).await { utils::redirect(&format!("/incomes?page={}", query.page.unwrap_or(1))) } else { update_form_feedback( diff --git a/src/controller/login.rs b/src/controller/login.rs index 31370cc..d01f799 100644 --- a/src/controller/login.rs +++ b/src/controller/login.rs @@ -3,8 +3,8 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::header::SET_COOKIE; use hyper::Response; -use sqlx::sqlite::SqlitePool; use std::collections::HashMap; +use tokio_rusqlite::Connection; use crate::controller::utils::with_headers; use crate::controller::wallet::Wallet; @@ -35,18 +35,19 @@ pub async fn login( assets: &HashMap<String, String>, templates: &minijinja::Environment<'_>, form: HashMap<String, String>, - pool: SqlitePool, + db_conn: Connection, ) -> Response<Full<Bytes>> { match validation::login::login(&form) { Some(login) => { - match db::users::get_password_hash(&pool, login.email.clone()).await + match db::users::get_password_hash(&db_conn, login.email.clone()) + .await { Some(hash) => match bcrypt::verify(login.password, &hash) { Ok(true) => { let login_token = cookie::generate_token(); if db::users::set_login_token( - &pool, + &db_conn, login.email, login_token.clone().to_string(), ) @@ -110,7 +111,7 @@ async fn not_authorized( } pub async fn logout(config: &Config, wallet: &Wallet) -> Response<Full<Bytes>> { - if db::users::remove_login_token(&wallet.pool, wallet.user.id).await { + if db::users::remove_login_token(&wallet.db_conn, wallet.user.id).await { with_headers( utils::redirect("/"), vec![(SET_COOKIE, &cookie::logout(config))], diff --git a/src/controller/payments.rs b/src/controller/payments.rs index b5c0256..1ffa09f 100644 --- a/src/controller/payments.rs +++ b/src/controller/payments.rs @@ -19,12 +19,12 @@ pub async fn table( query: queries::Payments, ) -> Response<Full<Bytes>> { let page = query.page.unwrap_or(1); - let count = db::payments::count(&wallet.pool, &query).await; + let count = db::payments::count(&wallet.db_conn, &query).await; let payments = - db::payments::list_for_table(&wallet.pool, &query, PER_PAGE).await; + db::payments::list_for_table(&wallet.db_conn, &query, PER_PAGE).await; let max_page = (count.count as f32 / PER_PAGE as f32).ceil() as i64; - let users = db::users::list(&wallet.pool).await; - let categories = db::categories::list(&wallet.pool).await; + let users = db::users::list(&wallet.db_conn).await; + let categories = db::categories::list(&wallet.db_conn).await; let context = minijinja::context!( header => templates::Header::Payments, @@ -60,8 +60,8 @@ async fn create_form_feedback( form: HashMap<String, String>, error: Option<String>, ) -> Response<Full<Bytes>> { - let users = db::users::list(&wallet.pool).await; - let categories = db::categories::list(&wallet.pool).await; + let users = db::users::list(&wallet.db_conn).await; + let categories = db::categories::list(&wallet.db_conn).await; let context = minijinja::context!( header => templates::Header::Payments, @@ -92,10 +92,10 @@ pub async fn create( match validation::payment::create(&form) { Some(create_payment) => { - match db::payments::create(&wallet.pool, &create_payment).await { + match db::payments::create(&wallet.db_conn, create_payment.clone()).await { Some(id) => { let row = db::payments::get_row( - &wallet.pool, + &wallet.db_conn, id, create_payment.frequency, ) @@ -139,9 +139,9 @@ async fn update_form_feedback( form: HashMap<String, String>, error: Option<String>, ) -> Response<Full<Bytes>> { - let payment = db::payments::get_for_form(&wallet.pool, id).await; - let users = db::users::list(&wallet.pool).await; - let categories = db::categories::list(&wallet.pool).await; + let payment = db::payments::get_for_form(&wallet.db_conn, id).await; + let users = db::users::list(&wallet.db_conn).await; + let categories = db::categories::list(&wallet.db_conn).await; let context = minijinja::context!( header => templates::Header::Payments, @@ -181,10 +181,10 @@ pub async fn update( match validation::payment::update(&form) { Some(update_payment) => { - if db::payments::update(&wallet.pool, id, &update_payment).await { + if db::payments::update(&wallet.db_conn, id, update_payment).await { let frequency = query.frequency.unwrap_or(Frequency::Punctual); let row = - db::payments::get_row(&wallet.pool, id, frequency).await; + db::payments::get_row(&wallet.db_conn, id, frequency).await; let page = (row - 1) / PER_PAGE + 1; // TODO: keep name, cost, user and category when updating a line let query = queries::Payments { @@ -212,7 +212,7 @@ pub async fn delete( wallet: &Wallet, query: queries::Payments, ) -> Response<Full<Bytes>> { - if db::payments::delete(&wallet.pool, id).await { + if db::payments::delete(&wallet.db_conn, id).await { let query = queries::Payments { highlight: None, ..query @@ -234,7 +234,7 @@ pub async fn search_category( wallet: &Wallet, query: queries::PaymentCategory, ) -> Response<Full<Bytes>> { - match db::payments::search_category(&wallet.pool, query.payment_name).await + match db::payments::search_category(&wallet.db_conn, query.payment_name).await { Some(category_id) => utils::with_headers( Response::new(format!("{}", category_id).into()), diff --git a/src/controller/statistics.rs b/src/controller/statistics.rs index e57e2be..a546c67 100644 --- a/src/controller/statistics.rs +++ b/src/controller/statistics.rs @@ -8,9 +8,9 @@ use crate::db; use crate::templates; pub async fn get(wallet: &Wallet) -> Response<Full<Bytes>> { - let categories = db::categories::list(&wallet.pool).await; - let payments = db::payments::list_for_stats(&wallet.pool).await; - let incomes = db::incomes::total_each_month(&wallet.pool).await; + let categories = db::categories::list(&wallet.db_conn).await; + let payments = db::payments::list_for_stats(&wallet.db_conn).await; + let incomes = db::incomes::total_each_month(&wallet.db_conn).await; let context = minijinja::context!( header => templates::Header::Statistics, diff --git a/src/controller/wallet.rs b/src/controller/wallet.rs index 7537406..edc773d 100644 --- a/src/controller/wallet.rs +++ b/src/controller/wallet.rs @@ -1,11 +1,11 @@ -use sqlx::sqlite::SqlitePool; use std::collections::HashMap; +use tokio_rusqlite::Connection; use crate::model::user::User; #[derive(Clone)] pub struct Wallet { - pub pool: SqlitePool, + pub db_conn: Connection, pub assets: HashMap<String, String>, pub templates: minijinja::Environment<'static>, pub user: User, diff --git a/src/db/categories.rs b/src/db/categories.rs index fafe459..31cb3d0 100644 --- a/src/db/categories.rs +++ b/src/db/categories.rs @@ -1,24 +1,38 @@ -use sqlx::sqlite::SqlitePool; +use tokio_rusqlite::{named_params, Connection, Row}; +use crate::db::utils; use crate::model::category::{Category, Create, Update}; -pub async fn list(pool: &SqlitePool) -> Vec<Category> { - let res = sqlx::query_as::<_, Category>( - r#" -SELECT - id, - name, - color -FROM - categories -WHERE - deleted_at IS NULL -ORDER BY - name - "#, - ) - .fetch_all(pool) - .await; +fn row_to_category(row: &Row) -> Result<Category, rusqlite::Error> { + Ok(Category { + id: row.get(0)?, + name: row.get(1)?, + color: row.get(2)?, + }) +} + +pub async fn list(conn: &Connection) -> Vec<Category> { + let query = r#" + SELECT + id, + name, + color + FROM categories + WHERE deleted_at IS NULL + ORDER BY name + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + + let users = stmt + .query_map([], row_to_category)? + .collect::<Result<Vec<Category>, _>>()?; + + Ok(users) + }) + .await; match res { Ok(categories) => categories, @@ -29,26 +43,29 @@ ORDER BY } } -pub async fn get(pool: &SqlitePool, id: i64) -> Option<Category> { +pub async fn get(conn: &Connection, id: i64) -> Option<Category> { let query = r#" -SELECT - id, - name, - color -FROM - categories -WHERE - id = ? - AND deleted_at IS NULL + SELECT + id, + name, + color + FROM categories + WHERE + id = :id + AND deleted_at IS NULL "#; - let res = sqlx::query_as::<_, Category>(query) - .bind(id) - .fetch_one(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = + stmt.query_map(named_params![":id": id], row_to_category)?; + utils::one(&mut iter) + }) .await; match res { - Ok(p) => Some(p), + Ok(category) => Some(category), Err(err) => { log::error!("Error looking for category {}: {:?}", id, err); None @@ -56,22 +73,21 @@ WHERE } } -pub async fn create(pool: &SqlitePool, c: &Create) -> Option<i64> { - let res = sqlx::query( - r#" -INSERT INTO - categories(name, color) -VALUES - (?, ?) - "#, - ) - .bind(c.name.clone()) - .bind(c.color.clone()) - .execute(pool) - .await; +pub async fn create(conn: &Connection, c: Create) -> Option<i64> { + let query = r#"INSERT INTO categories(name, color) VALUES (:name, :color)"#; + + let res = conn + .call(move |conn| { + conn.execute( + query, + named_params![":name": c.name, ":color": c.color], + )?; + Ok(conn.last_insert_rowid()) + }) + .await; match res { - Ok(x) => Some(x.last_insert_rowid()), + Ok(category_id) => Some(category_id), Err(err) => { log::error!("Error creating category: {:?}", err); None @@ -79,24 +95,24 @@ VALUES } } -pub async fn update(pool: &SqlitePool, id: i64, c: &Update) -> bool { - let res = sqlx::query( - r#" -UPDATE - categories -SET - name = ?, - color = ?, - updated_at = datetime() -WHERE - id = ? - "#, - ) - .bind(c.name.clone()) - .bind(c.color.clone()) - .bind(id) - .execute(pool) - .await; +pub async fn update(conn: &Connection, id: i64, c: Update) -> bool { + let query = r#" + UPDATE categories + SET + name = :name, + color = :color, + updated_at = datetime() + WHERE id = :id + "#; + + let res = conn + .call(move |conn| { + Ok(conn.execute( + query, + named_params![":name": c.name, ":color": c.color, ":id": id], + )?) + }) + .await; match res { Ok(_) => true, @@ -107,20 +123,22 @@ WHERE } } -pub async fn delete(pool: &SqlitePool, id: i64) -> bool { - let res = sqlx::query( - r#" -UPDATE - categories -SET - deleted_at = datetime() -WHERE - id = ? - "#, - ) - .bind(id) - .execute(pool) - .await; +pub async fn delete(conn: &Connection, id: i64) -> bool { + let res = conn + .call(move |conn| { + Ok(conn.execute( + r#" + UPDATE + categories + SET + deleted_at = datetime() + WHERE + id = :id + "#, + named_params![":id": id], + )?) + }) + .await; match res { Ok(_) => true, diff --git a/src/db/incomes.rs b/src/db/incomes.rs index 97cb2b7..90282c0 100644 --- a/src/db/incomes.rs +++ b/src/db/incomes.rs @@ -1,26 +1,51 @@ use chrono::NaiveDate; -use sqlx::error::Error; -use sqlx::sqlite::{SqlitePool, SqliteRow}; -use sqlx_core::row::Row; use std::collections::HashMap; use std::iter::FromIterator; +use tokio_rusqlite::{named_params, Connection, Row}; +use crate::db::utils; use crate::model::income::{Create, Form, Stat, Table, Update}; use crate::model::report::Report; -pub async fn count(pool: &SqlitePool) -> i64 { +fn row_to_table(row: &Row) -> Result<Table, rusqlite::Error> { + Ok(Table { + id: row.get(0)?, + date: row.get(1)?, + user: row.get(2)?, + amount: row.get(3)?, + }) +} + +fn row_to_form(row: &Row) -> Result<Form, rusqlite::Error> { + Ok(Form { + id: row.get(0)?, + amount: row.get(1)?, + user_id: row.get(2)?, + month: row.get(3)?, + year: row.get(4)?, + }) +} + +fn row_to_stat(row: &Row) -> Result<Stat, rusqlite::Error> { + Ok(Stat { + date: row.get(0)?, + amount: row.get(1)?, + }) +} + +pub async fn count(conn: &Connection) -> i64 { let query = r#" -SELECT - COUNT(*) AS count -FROM - incomes -WHERE - incomes.deleted_at IS NULL - "#; - - let res = sqlx::query(query) - .map(|row: SqliteRow| row.get("count")) - .fetch_one(pool) + SELECT COUNT(*) + FROM incomes + WHERE incomes.deleted_at IS NULL + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = stmt.query_map([], |row| row.get(0))?; + utils::one::<i64, _>(&mut iter) + }) .await; match res { @@ -32,32 +57,35 @@ WHERE } } -pub async fn list(pool: &SqlitePool, page: i64, per_page: i64) -> Vec<Table> { +pub async fn list(conn: &Connection, page: i64, per_page: i64) -> Vec<Table> { let query = r#" -SELECT - incomes.id, - users.name AS user, - strftime('%m/%Y', incomes.date) AS date, - incomes.amount -FROM - incomes -INNER JOIN - users -ON - incomes.user_id = users.id -WHERE - incomes.deleted_at IS NULL -ORDER BY - incomes.date DESC -LIMIT ? -OFFSET ? + SELECT + incomes.id, + users.name AS user, + strftime('%m/%Y', incomes.date) AS date, + incomes.amount + FROM incomes + INNER JOIN users + ON incomes.user_id = users.id + WHERE incomes.deleted_at IS NULL + ORDER BY incomes.date DESC + LIMIT :limit + OFFSET :offset "#; - let res = sqlx::query_as::<_, Table>(query) - .bind(per_page) - .bind((page - 1) * per_page) - .fetch_all(pool) - .await; + let res = conn.call(move |conn| { + let mut stmt = conn.prepare(query)?; + + let incomes = stmt + .query_map( + named_params![":limit": per_page, ":offset": (page - 1) * per_page], + row_to_table + )? + .collect::<Result<Vec<Table>, _>>()?; + + Ok(incomes) + }) + .await; match res { Ok(incomes) => incomes, @@ -68,31 +96,30 @@ OFFSET ? } } -pub async fn get_row(pool: &SqlitePool, id: i64) -> i64 { +pub async fn get_row(conn: &Connection, id: i64) -> i64 { let query = r#" -SELECT - row -FROM ( - SELECT - ROW_NUMBER () OVER (ORDER BY date DESC) AS row, - id - FROM - incomes - WHERE - deleted_at IS NULL -) -WHERE - id = ? + SELECT row + FROM ( + SELECT + ROW_NUMBER () OVER (ORDER BY date DESC) AS row, + id + FROM incomes + WHERE deleted_at IS NULL + ) + WHERE id = :id "#; - let res = sqlx::query(query) - .bind(id) - .map(|row: SqliteRow| row.get("row")) - .fetch_one(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = + stmt.query_map(named_params![":id": id], |row| row.get(0))?; + utils::one::<i64, _>(&mut iter) + }) .await; match res { - Ok(count) => count, + Ok(row) => row, Err(err) => { log::error!("Error getting income row: {:?}", err); 1 @@ -100,28 +127,31 @@ WHERE } } -pub async fn get(pool: &SqlitePool, id: i64) -> Option<Form> { +pub async fn get(conn: &Connection, id: i64) -> Option<Form> { let query = r#" -SELECT - id, - amount, - user_id, - CAST(strftime('%m', date) AS INTEGER) as month, - CAST(strftime('%Y', date) AS INTEGER) as year -FROM - incomes -WHERE - id = ? - AND deleted_at IS NULL + SELECT + id, + amount, + user_id, + CAST(strftime('%m', date) AS INTEGER) as month, + CAST(strftime('%Y', date) AS INTEGER) as year + FROM incomes + WHERE + id = :id + AND deleted_at IS NULL "#; - let res = sqlx::query_as::<_, Form>(query) - .bind(id) - .fetch_one(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = + stmt.query_map(named_params![":id": id], row_to_form)?; + utils::one(&mut iter) + }) .await; match res { - Ok(p) => Some(p), + Ok(income) => Some(income), Err(err) => { log::error!("Error looking for income {}: {:?}", id, err); None @@ -129,23 +159,28 @@ WHERE } } -pub async fn create(pool: &SqlitePool, i: &Create) -> Option<i64> { - let res = sqlx::query( - r#" -INSERT INTO - incomes(user_id, date, amount) -VALUES - (?, ?, ?) - "#, - ) - .bind(i.user_id) - .bind(i.date) - .bind(i.amount) - .execute(pool) - .await; +pub async fn create(conn: &Connection, i: Create) -> Option<i64> { + let query = r#" + INSERT INTO incomes(user_id, date, amount) + VALUES (:user_id, :date, :amount) + "#; + + let res = conn + .call(move |conn| { + conn.execute( + query, + named_params![ + ":user_id": i.user_id, + ":date": i.date, + ":amount": i.amount + ], + )?; + Ok(conn.last_insert_rowid()) + }) + .await; match res { - Ok(x) => Some(x.last_insert_rowid()), + Ok(income_id) => Some(income_id), Err(err) => { log::error!("Error creating income: {:?}", err); None @@ -154,31 +189,36 @@ VALUES } pub async fn defined_at( - pool: &SqlitePool, + conn: &Connection, user_id: i64, date: NaiveDate, ) -> Vec<i64> { let query = r#" -SELECT - id -FROM - incomes -WHERE - user_id = ? - AND date = ? - AND deleted_at IS NULL + SELECT id + FROM incomes + WHERE + user_id = :user_id + AND date = :date + AND deleted_at IS NULL "#; - let res = sqlx::query(query) - .bind(user_id) - .bind(date) - .map(|row: SqliteRow| row.get("id")) - .fetch_all(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + + let incomes = stmt + .query_map( + named_params![":user_id": user_id, ":date": date], + |row| row.get(0), + )? + .collect::<Result<Vec<i64>, _>>()?; + + Ok(incomes) + }) .await; match res { Ok(ids) => ids, - Err(Error::RowNotFound) => vec![], Err(err) => { log::error!("Error looking if income is defined: {:?}", err); vec![] @@ -186,26 +226,30 @@ WHERE } } -pub async fn update(pool: &SqlitePool, id: i64, i: &Update) -> bool { - let res = sqlx::query( - r#" -UPDATE - incomes -SET - user_id = ?, - date = ?, - amount = ?, - updated_at = datetime() -WHERE - id = ? - "#, - ) - .bind(i.user_id) - .bind(i.date) - .bind(i.amount) - .bind(id) - .execute(pool) - .await; +pub async fn update(conn: &Connection, id: i64, i: Update) -> bool { + let query = r#" + UPDATE incomes + SET + user_id = :user_id, + date = :date, + amount = :amount, + updated_at = datetime() + WHERE id = :id + "#; + + let res = conn + .call(move |conn| { + Ok(conn.execute( + query, + named_params![ + ":user_id": i.user_id, + ":date": i.date, + ":amount": i.amount, + ":id": id + ], + )?) + }) + .await; match res { Ok(_) => true, @@ -216,20 +260,12 @@ WHERE } } -pub async fn delete(pool: &SqlitePool, id: i64) -> bool { - let res = sqlx::query( - r#" -UPDATE - incomes -SET - deleted_at = datetime() -WHERE - id = ? - "#, - ) - .bind(id) - .execute(pool) - .await; +pub async fn delete(conn: &Connection, id: i64) -> bool { + let query = r#"UPDATE incomes SET deleted_at = datetime() WHERE id = :id"#; + + let res = conn + .call(move |conn| Ok(conn.execute(query, named_params![":id": id])?)) + .await; match res { Ok(_) => true, @@ -240,37 +276,34 @@ WHERE } } -pub async fn defined_for_all(pool: &SqlitePool) -> Option<NaiveDate> { - let res = sqlx::query( - r#" -SELECT - (CASE COUNT(users.id) == COUNT(min_income.date) - WHEN 1 THEN MIN(min_income.date) - ELSE NULL - END) AS date -FROM - users -LEFT OUTER JOIN ( - SELECT - user_id, - MIN(date) AS date - FROM - incomes - WHERE - deleted_at IS NULL - GROUP BY - user_id -) min_income -ON - users.id = min_income.user_id; - "#, - ) - .map(|row: SqliteRow| row.get("date")) - .fetch_one(pool) - .await; +pub async fn defined_for_all(conn: &Connection) -> Option<NaiveDate> { + let query = r#" + SELECT + (CASE COUNT(users.id) == COUNT(min_income.date) + WHEN 1 THEN MIN(min_income.date) + ELSE NULL + END) AS date + FROM users + LEFT OUTER JOIN + (SELECT + user_id, + MIN(date) AS date + FROM incomes + WHERE deleted_at IS NULL + GROUP BY user_id) min_income + ON users.id = min_income.user_id; + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = stmt.query_map([], |row| row.get(0))?; + utils::one::<NaiveDate, _>(&mut iter) + }) + .await; match res { - Ok(d) => d, + Ok(d) => Some(d), Err(err) => { log::error!("Error looking for incomes defined for all: {:?}", err); None @@ -279,12 +312,17 @@ ON } pub async fn cumulative( - pool: &SqlitePool, + conn: &Connection, from: NaiveDate, ) -> HashMap<i64, i64> { - let res = sqlx::query(&cumulative_query(from)) - .map(|row: SqliteRow| (row.get("user_id"), row.get("income"))) - .fetch_all(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(&cumulative_query(from))?; + let incomes = stmt + .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? + .collect::<Result<Vec<(i64, i64)>, _>>()?; + Ok(incomes) + }) .await; match res { @@ -306,34 +344,34 @@ pub async fn cumulative( fn cumulative_query(from: NaiveDate) -> String { format!( r#" -SELECT - users.id AS user_id, - COALESCE(incomes.income, 0) AS income -FROM - users -LEFT OUTER JOIN ( - SELECT - user_id, - CAST(ROUND(SUM(count)) AS INTEGER) AS income - FROM ( SELECT - I1.user_id, - ((JULIANDAY(MIN(I2.date)) - JULIANDAY(I1.date)) * I1.amount * 12 / 365) AS count + users.id AS user_id, + COALESCE(incomes.income, 0) AS income FROM - ({}) AS I1 - INNER JOIN - ({}) AS I2 + users + LEFT OUTER JOIN ( + SELECT + user_id, + CAST(ROUND(SUM(count)) AS INTEGER) AS income + FROM ( + SELECT + I1.user_id, + ((JULIANDAY(MIN(I2.date)) - JULIANDAY(I1.date)) * I1.amount * 12 / 365) AS count + FROM + ({}) AS I1 + INNER JOIN + ({}) AS I2 + ON + I2.date > I1.date + AND I2.user_id == I1.user_id + GROUP BY + I1.date, I1.user_id + ) + GROUP BY + user_id + ) incomes ON - I2.date > I1.date - AND I2.user_id == I1.user_id - GROUP BY - I1.date, I1.user_id - ) - GROUP BY - user_id -) incomes -ON - users.id = incomes.user_id + users.id = incomes.user_id "#, bounded_query(">".to_string(), from.format("%Y-%m-%d").to_string()), bounded_query("<".to_string(), "date()".to_string()) @@ -347,33 +385,33 @@ ON fn bounded_query(op: String, date: String) -> String { format!( r#" -SELECT - user_id, - date, - amount -FROM ( - SELECT - user_id, - {} AS date, - amount, - MAX(date) AS max_date - FROM - incomes - WHERE - date <= {} - AND deleted_at IS NULL - GROUP BY - user_id -) UNION -SELECT - user_id, - date, - amount -FROM - incomes -WHERE - date {} {} - AND deleted_at IS NULL + SELECT + user_id, + date, + amount + FROM ( + SELECT + user_id, + {} AS date, + amount, + MAX(date) AS max_date + FROM + incomes + WHERE + date <= {} + AND deleted_at IS NULL + GROUP BY + user_id + ) UNION + SELECT + user_id, + date, + amount + FROM + incomes + WHERE + date {} {} + AND deleted_at IS NULL "#, date, date, op, date ) @@ -383,54 +421,63 @@ WHERE /// /// For each month, from the first defined income and until now, /// compute the total income of the users. -pub async fn total_each_month(pool: &SqlitePool) -> Vec<Stat> { +pub async fn total_each_month(conn: &Connection) -> Vec<Stat> { let query = r#" -WITH RECURSIVE dates(date) AS ( - VALUES(( - SELECT - strftime('%Y-%m-01', MIN(date)) - FROM - incomes - WHERE - deleted_at IS NULL - )) - UNION ALL - SELECT - date(date, '+1 month') - FROM - dates - WHERE - date < date(date(), '-1 month') -) -SELECT - strftime('%Y-%m-01', dates.date) AS date, - ( - SELECT - SUM(amount) AS amount - FROM ( - SELECT ( + WITH RECURSIVE dates(date) AS ( + VALUES(( SELECT - amount + strftime('%Y-%m-01', MIN(date)) FROM incomes WHERE - user_id = users.id - AND date < date(dates.date, '+1 month') - AND deleted_at IS NULL - ORDER BY - date DESC - LIMIT - 1 - ) AS amount + deleted_at IS NULL + )) + UNION ALL + SELECT + date(date, '+1 month') FROM - users + dates + WHERE + date < date(date(), '-1 month') ) - ) AS amount -FROM - dates; + SELECT + strftime('%Y-%m-01', dates.date) AS date, + ( + SELECT + SUM(amount) AS amount + FROM ( + SELECT ( + SELECT + amount + FROM + incomes + WHERE + user_id = users.id + AND date < date(dates.date, '+1 month') + AND deleted_at IS NULL + ORDER BY + date DESC + LIMIT + 1 + ) AS amount + FROM + users + ) + ) AS amount + FROM + dates; "#; - let res = sqlx::query_as::<_, Stat>(query).fetch_all(pool).await; + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let stats = stmt + .query_map([], row_to_stat)? + .collect::<Result<Vec<Stat>, _>>()?; + + Ok(stats) + }) + .await; match res { Ok(xs) => xs, @@ -441,50 +488,59 @@ FROM } } -pub async fn last_week(pool: &SqlitePool) -> Vec<Report> { +pub async fn last_week(conn: &Connection) -> Vec<Report> { let query = r#" -SELECT - strftime('%m/%Y', incomes.date) AS date, - users.name AS name, - incomes.amount AS amount, - (CASE - WHEN - incomes.deleted_at IS NOT NULL - THEN - 'Deleted' - WHEN - incomes.updated_at IS NOT NULL - AND incomes.created_at < date('now', 'weekday 0', '-13 days') - THEN - 'Updated' - ELSE - 'Created' - END) AS action -FROM - incomes -INNER JOIN - users -ON - incomes.user_id = users.id -WHERE - ( - incomes.created_at >= date('now', 'weekday 0', '-13 days') - AND incomes.created_at < date('now', 'weekday 0', '-6 days') - ) OR ( - incomes.updated_at >= date('now', 'weekday 0', '-13 days') - AND incomes.updated_at < date('now', 'weekday 0', '-6 days') - ) OR ( - incomes.deleted_at >= date('now', 'weekday 0', '-13 days') - AND incomes.deleted_at < date('now', 'weekday 0', '-6 days') - ) -ORDER BY - incomes.date + SELECT + strftime('%m/%Y', incomes.date) AS date, + users.name AS name, + incomes.amount AS amount, + (CASE + WHEN + incomes.deleted_at IS NOT NULL + THEN + 'Deleted' + WHEN + incomes.updated_at IS NOT NULL + AND incomes.created_at < date('now', 'weekday 0', '-13 days') + THEN + 'Updated' + ELSE + 'Created' + END) AS action + FROM + incomes + INNER JOIN + users + ON + incomes.user_id = users.id + WHERE + ( + incomes.created_at >= date('now', 'weekday 0', '-13 days') + AND incomes.created_at < date('now', 'weekday 0', '-6 days') + ) OR ( + incomes.updated_at >= date('now', 'weekday 0', '-13 days') + AND incomes.updated_at < date('now', 'weekday 0', '-6 days') + ) OR ( + incomes.deleted_at >= date('now', 'weekday 0', '-13 days') + AND incomes.deleted_at < date('now', 'weekday 0', '-6 days') + ) + ORDER BY + incomes.date "#; - let res = sqlx::query_as::<_, Report>(query).fetch_all(pool).await; + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let xs = stmt + .query_map([], utils::row_to_report)? + .collect::<Result<Vec<Report>, _>>()?; + + Ok(xs) + }) + .await; match res { - Ok(payments) => payments, + Ok(xs) => xs, Err(err) => { log::error!("Error listing payments for report: {:?}", err); vec![] diff --git a/src/db/jobs.rs b/src/db/jobs.rs index 7d9386a..1d00408 100644 --- a/src/db/jobs.rs +++ b/src/db/jobs.rs @@ -1,32 +1,27 @@ -use sqlx::error::Error; -use sqlx::sqlite::SqlitePool; +use tokio_rusqlite::{named_params, Connection}; use crate::model::job::Job; -pub async fn should_run(pool: &SqlitePool, job: Job) -> bool { +pub async fn should_run(conn: &Connection, job: Job) -> bool { let run_from = match job { Job::WeeklyReport => "date('now', 'weekday 0', '-6 days')", Job::MonthlyPayment => "date('now', 'start of month')", }; let query = format!( - r#" -SELECT - 1 -FROM - jobs -WHERE - name = ? - AND last_execution < {} - "#, + r#"SELECT 1 FROM jobs WHERE name = ? AND last_execution < {}"#, run_from ); - let res = sqlx::query(&query).bind(job).fetch_one(pool).await; + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(&query)?; + Ok(stmt.exists([job.to_string()])?) + }) + .await; match res { - Ok(_) => true, - Err(Error::RowNotFound) => false, + Ok(r) => r, Err(err) => { log::error!("Error looking if job should run: {:?}", err); false @@ -34,17 +29,18 @@ WHERE } } -pub async fn actualize_last_execution(pool: &SqlitePool, job: Job) { +pub async fn actualize_last_execution(conn: &Connection, job: Job) { let query = r#" -UPDATE - jobs -SET - last_execution = datetime() -WHERE - name = ? + UPDATE jobs + SET last_execution = datetime() + WHERE name = :name "#; - let res = sqlx::query(query).bind(job).execute(pool).await; + let res = conn + .call(move |conn| { + Ok(conn.execute(query, named_params![":name": job.to_string()])?) + }) + .await; match res { Ok(_) => (), diff --git a/src/db/payments.rs b/src/db/payments.rs index 25b10f4..540a006 100644 --- a/src/db/payments.rs +++ b/src/db/payments.rs @@ -1,10 +1,6 @@ -use sqlx::error::Error; -use sqlx::sqlite::{Sqlite, SqliteArguments}; -use sqlx::sqlite::{SqlitePool, SqliteRow}; -use sqlx::FromRow; -use sqlx_core::row::Row; use std::collections::HashMap; use std::iter::FromIterator; +use tokio_rusqlite::{Connection, Row, params_from_iter, types::ToSql, named_params}; use crate::db::utils; use crate::model::frequency::Frequency; @@ -13,71 +9,75 @@ use crate::model::report::Report; use crate::queries; use crate::utils::text; -#[derive(FromRow)] pub struct Count { pub count: i64, pub total_cost: i64, } +fn row_to_count(row: &Row) -> Result<Count, rusqlite::Error> { + Ok(Count { + count: row.get(0)?, + total_cost: row.get(1)?, + }) +} + +fn row_to_table(row: &Row) -> Result<payment::Table, rusqlite::Error> { + Ok(payment::Table { + id: row.get(0)?, + name: row.get(1)?, + cost: row.get(2)?, + user: row.get(3)?, + category_name: row.get(4)?, + category_color: row.get(5)?, + date: row.get(6)?, + frequency: row.get(7)? + }) +} + +fn row_to_stat(row: &Row) -> Result<payment::Stat, rusqlite::Error> { + Ok(payment::Stat { + start_date: row.get(0)?, + cost: row.get(1)?, + category_id: row.get(2)?, + }) +} + +fn row_to_form(row: &Row) -> Result<payment::Form, rusqlite::Error> { + Ok(payment::Form { + id: row.get(0)?, + name: row.get(1)?, + cost: row.get(2)?, + user_id: row.get(3)?, + category_id: row.get(4)?, + date: row.get(5)?, + frequency: row.get(6)? + }) +} + pub async fn count( - pool: &SqlitePool, + conn: &Connection, payment_query: &queries::Payments, ) -> Count { - let query = format!( - r#" -SELECT - COUNT(*) AS count, - SUM(payments.cost) AS total_cost -FROM - payments -INNER JOIN - users ON users.id = payments.user_id -INNER JOIN - categories ON categories.id = payments.category_id -WHERE - payments.deleted_at IS NULL - AND payments.frequency = ? - {} {} {} {} {} {} - "#, - name_query(payment_query.name.clone()), - cost_query(payment_query.cost.clone()), - user_query(payment_query.user), - category_query(payment_query.category), - date_query( - "payments.date >=".to_string(), - payment_query.start_date.clone() - ), - date_query( - "payments.date <=".to_string(), - payment_query.end_date.clone() - ) - ); - - let res = bind_date( - bind_date( - bind_category( - bind_user( - bind_cost( - bind_name( - sqlx::query_as::<_, Count>(&query).bind( - payment_query - .frequency - .unwrap_or(Frequency::Punctual), - ), - payment_query.name.clone(), - ), - payment_query.cost.clone(), - ), - payment_query.user, - ), - payment_query.category, - ), - payment_query.start_date.clone(), - ), - payment_query.end_date.clone(), - ) - .fetch_one(pool) - .await; + let mut query = format!(r#" + SELECT + COUNT(*) AS count, + SUM(payments.cost) AS total_cost + FROM payments + INNER JOIN users ON users.id = payments.user_id + INNER JOIN categories ON categories.id = payments.category_id + WHERE payments.deleted_at IS NULL + "#); + + let mut params = Vec::<Box<dyn ToSql + Send>>::new(); + complete_search_query(payment_query, &mut query, &mut params); + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(&query)?; + let mut iter = stmt.query_map(params_from_iter(params), row_to_count)?; + utils::one(&mut iter) + }) + .await; match res { Ok(count) => count, @@ -92,79 +92,42 @@ WHERE } pub async fn list_for_table( - pool: &SqlitePool, + conn: &Connection, payment_query: &queries::Payments, per_page: i64, ) -> Vec<payment::Table> { - let offset = (payment_query.page.unwrap_or(1) - 1) * per_page; - - let query = format!( - r#" -SELECT - payments.id, - payments.name, - payments.cost, - users.name AS user, - categories.name AS category_name, - categories.color AS category_color, - strftime('%d/%m/%Y', date) AS date, - payments.frequency AS frequency -FROM - payments -INNER JOIN - users ON users.id = payments.user_id -INNER JOIN - categories ON categories.id = payments.category_id -WHERE - payments.deleted_at IS NULL - AND payments.frequency = ? - {} {} {} {} {} {} -ORDER BY - payments.date DESC -LIMIT ? -OFFSET ? - "#, - name_query(payment_query.name.clone()), - cost_query(payment_query.cost.clone()), - user_query(payment_query.user), - category_query(payment_query.category), - date_query( - "payments.date >=".to_string(), - payment_query.start_date.clone() - ), - date_query( - "payments.date <=".to_string(), - payment_query.end_date.clone() - ) - ); - - let res = bind_date( - bind_date( - bind_category( - bind_user( - bind_cost( - bind_name( - sqlx::query_as::<_, payment::Table>(&query).bind( - payment_query - .frequency - .unwrap_or(Frequency::Punctual), - ), - payment_query.name.clone(), - ), - payment_query.cost.clone(), - ), - payment_query.user, - ), - payment_query.category, - ), - payment_query.start_date.clone(), - ), - payment_query.end_date.clone(), - ) - .bind(per_page) - .bind(offset) - .fetch_all(pool) - .await; + let mut query = format!(r#" + SELECT + payments.id, + payments.name, + payments.cost, + users.name AS user, + categories.name AS category_name, + categories.color AS category_color, + strftime('%d/%m/%Y', date) AS date, + payments.frequency AS frequency + FROM payments + INNER JOIN users ON users.id = payments.user_id + INNER JOIN categories ON categories.id = payments.category_id + WHERE payments.deleted_at IS NULL + "#); + + let mut params = Vec::<Box<dyn ToSql + Send>>::new(); + complete_search_query(payment_query, &mut query, &mut params); + + query.push_str("ORDER BY payments.date DESC LIMIT ? OFFSET ?"); + params.push(Box::new(per_page)); + params.push(Box::new((payment_query.page.unwrap_or(1) - 1) * per_page)); + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(&query)?; + let payments = stmt + .query_map(params_from_iter(params), row_to_table)? + .collect::<Result<Vec<payment::Table>, _>>()?; + Ok(payments) + }) + .await; match res { Ok(payments) => payments, @@ -175,137 +138,101 @@ OFFSET ? } } -fn name_query(name: Option<String>) -> String { - if name.map_or_else(|| false, |str| !str.is_empty()) { - format!( - "AND {} LIKE ?", - utils::format_key_for_search("payments.name") - ) - } else { - "".to_string() - } -} - -fn bind_name<'a, Row: FromRow<'a, SqliteRow>>( - query: sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>>, - name: Option<String>, -) -> sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>> { - match name { - Some(str) => { - if str.is_empty() { - query - } else { - query.bind(text::format_search(&str)) - } - } - _ => query, - } +fn complete_search_query( + q: &queries::Payments, + query: &mut String, + params: &mut Vec<Box<dyn ToSql + Send>> +) { + complete_frequency(q.frequency, query, params); + complete_name(q.name.clone(), query, params); + complete_cost(q.cost.clone(), query, params); + complete_user(q.user, query, params); + complete_category(q.category, query, params); + complete_date("payments.date >=".to_string(), q.start_date.clone(), query, params); + complete_date("payments.date <=".to_string(), q.end_date.clone(), query, params); } -fn cost_query(cost: Option<String>) -> String { - if cost.map_or_else(|| false, |str| !str.is_empty()) { - "AND payments.cost = ?".to_string() - } else { - "".to_string() - } +fn complete_frequency(frequency: Option<Frequency>, query: &mut String, params: &mut Vec<Box<dyn ToSql + Send>>) { + query.push_str("AND payments.frequency = ?"); + params.push(Box::new(frequency.unwrap_or(Frequency::Punctual).to_string())); } -fn bind_cost<'a, Row: FromRow<'a, SqliteRow>>( - query: sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>>, - cost: Option<String>, -) -> sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>> { - match cost { - Some(str) => { - if str.is_empty() { - query - } else { - query.bind(str) - } +fn complete_name(name: Option<String>, query: &mut String, params: &mut Vec<Box<dyn ToSql + Send>>) { + if let Some(name) = name { + if !name.is_empty() { + query.push_str(format!( + "AND {} LIKE ?", + utils::format_key_for_search("payments.name") + ).as_str()); + params.push(Box::new(name)); } - _ => query, - } -} - -fn user_query(user: Option<i64>) -> String { - if user.is_some() { - "AND payments.user_id = ?".to_string() - } else { - "".to_string() } } -fn bind_user<'a, Row: FromRow<'a, SqliteRow>>( - query: sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>>, - user: Option<i64>, -) -> sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>> { - match user { - Some(id) => query.bind(id), - _ => query, - } -} - -fn category_query(category: Option<i64>) -> String { - if category.is_some() { - "AND payments.category_id = ?".to_string() - } else { - "".to_string() +fn complete_cost(cost: Option<String>, query: &mut String, params: &mut Vec<Box<dyn ToSql + Send>>) { + if let Some(cost) = cost { + if !cost.is_empty() { + query.push_str("AND payments.cost = ?"); + params.push(Box::new(cost)) + } } } -fn bind_category<'a, Row: FromRow<'a, SqliteRow>>( - query: sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>>, - category: Option<i64>, -) -> sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>> { - match category { - Some(id) => query.bind(id), - _ => query, +fn complete_user(user: Option<i64>, query: &mut String, params: &mut Vec<Box<dyn ToSql + Send>>) { + if let Some(user) = user { + query.push_str("AND payments.user_id = ?"); + params.push(Box::new(user)) } } -fn date_query(name_and_op: String, date: Option<String>) -> String { - if date.map_or_else(|| false, |str| !str.is_empty()) { - format!("AND {} ?", name_and_op) - } else { - "".to_string() +fn complete_category(category: Option<i64>, query: &mut String, params: &mut Vec<Box<dyn ToSql + Send>>) { + if let Some(category) = category { + query.push_str("AND payments.category_id = ?"); + params.push(Box::new(category)); } } -fn bind_date<'a, Row: FromRow<'a, SqliteRow>>( - query: sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>>, +fn complete_date( + name_and_op: String, date: Option<String>, -) -> sqlx::query::QueryAs<'a, Sqlite, Row, SqliteArguments<'a>> { - match date { - Some(d) => { - if d.is_empty() { - query - } else { - query.bind(d) - } + query: &mut String, + params: &mut Vec<Box<dyn ToSql + Send>> +) { + if let Some(date) = date { + if !date.is_empty() { + query.push_str(format!("AND {name_and_op} ?").as_str()); + params.push(Box::new(date)); } - _ => query, } } -pub async fn list_for_stats(pool: &SqlitePool) -> Vec<payment::Stat> { + +pub async fn list_for_stats(conn: &Connection) -> Vec<payment::Stat> { let query = r#" -SELECT - strftime('%Y-%m-01', payments.date) AS start_date, - SUM(payments.cost) AS cost, - payments.category_id AS category_id -FROM - payments -WHERE - payments.deleted_at IS NULL - AND payments.frequency = 'Punctual' -GROUP BY - start_date, - payments.category_id; + SELECT + strftime('%Y-%m-01', payments.date) AS start_date, + SUM(payments.cost) AS cost, + payments.category_id AS category_id + FROM + payments + WHERE + payments.deleted_at IS NULL + AND payments.frequency = 'Punctual' + GROUP BY + start_date, + payments.category_id; "#; - let result = sqlx::query_as::<_, payment::Stat>(query) - .fetch_all(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(&query)?; + let payments = stmt + .query_map([], row_to_stat)? + .collect::<Result<Vec<payment::Stat>, _>>()?; + Ok(payments) + }) .await; - match result { + match res { Ok(payments) => payments, Err(err) => { log::error!("Error listing payments for statistics: {:?}", err); @@ -314,29 +241,30 @@ GROUP BY } } -pub async fn get_row(pool: &SqlitePool, id: i64, frequency: Frequency) -> i64 { +pub async fn get_row(conn: &Connection, id: i64, frequency: Frequency) -> i64 { let query = r#" -SELECT - row -FROM ( - SELECT - ROW_NUMBER () OVER (ORDER BY date DESC) AS row, - id - FROM - payments - WHERE - deleted_at IS NULL - AND frequency = ? -) -WHERE - id = ? + SELECT row + FROM ( + SELECT + ROW_NUMBER () OVER (ORDER BY date DESC) AS row, + id + FROM payments + WHERE + deleted_at IS NULL + AND frequency = :frequency + ) + WHERE id = :id "#; - let res = sqlx::query(query) - .bind(frequency) - .bind(id) - .map(|row: SqliteRow| row.get("row")) - .fetch_one(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = stmt.query_map( + named_params![":id": id, ":frequency": frequency.to_string()], + |row| row.get(0) + )?; + utils::one::<i64, _>(&mut iter) + }) .await; match res { @@ -348,26 +276,28 @@ WHERE } } -pub async fn get_for_form(pool: &SqlitePool, id: i64) -> Option<payment::Form> { +pub async fn get_for_form(conn: &Connection, id: i64) -> Option<payment::Form> { let query = r#" -SELECT - id, - name, - cost, - user_id, - category_id, - strftime('%Y-%m-%d', date) AS date, - frequency AS frequency -FROM - payments -WHERE - id = ? - AND deleted_at IS NULL + SELECT + id, + name, + cost, + user_id, + category_id, + strftime('%Y-%m-%d', date) AS date, + frequency AS frequency + FROM payments + WHERE + id = :id + AND deleted_at IS NULL "#; - let res = sqlx::query_as::<_, payment::Form>(query) - .bind(id) - .fetch_one(pool) + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = stmt.query_map(named_params![":id": id], row_to_form)?; + utils::one(&mut iter) + }) .await; match res { @@ -379,26 +309,31 @@ WHERE } } -pub async fn create(pool: &SqlitePool, p: &payment::Create) -> Option<i64> { - let res = sqlx::query( - r#" -INSERT INTO - payments(name, cost, user_id, category_id, date, frequency) -VALUES - (?, ?, ?, ?, ?, ?) - "#, - ) - .bind(p.name.clone()) - .bind(p.cost) - .bind(p.user_id) - .bind(p.category_id) - .bind(p.date) - .bind(p.frequency) - .execute(pool) - .await; +pub async fn create(conn: &Connection, p: payment::Create) -> Option<i64> { + let query = r#" + INSERT INTO payments(name, cost, user_id, category_id, date, frequency) + VALUES (:name, :cost, :user_id, :category_id, :date, :frequency) + "#; + + let res = conn + .call(move |conn| { + conn.execute( + query, + named_params![ + ":name": p.name, + ":cost": p.cost, + ":user_id": p.user_id, + ":category_id": p.category_id, + ":date": p.date, + ":frequency": p.frequency.to_string() + ], + )?; + Ok(conn.last_insert_rowid()) + }) + .await; match res { - Ok(x) => Some(x.last_insert_rowid()), + Ok(payment_id) => Some(payment_id), Err(err) => { log::error!("Error creating payment: {:?}", err); None @@ -406,30 +341,35 @@ VALUES } } -pub async fn update(pool: &SqlitePool, id: i64, p: &payment::Update) -> bool { - let res = sqlx::query( - r#" -UPDATE - payments -SET - name = ?, - cost = ?, - user_id = ?, - category_id = ?, - date = ?, - updated_at = datetime() -WHERE - id = ? - "#, - ) - .bind(p.name.clone()) - .bind(p.cost) - .bind(p.user_id) - .bind(p.category_id) - .bind(p.date) - .bind(id) - .execute(pool) - .await; +pub async fn update(conn: &Connection, id: i64, p: payment::Update) -> bool { + let query = r#" + UPDATE payments + SET + name = :name, + cost = :cost, + user_id = :user_id, + category_id = :category_id, + date = :date, + updated_at = datetime() + WHERE id = :id + "#; + + let res = conn + .call(move |conn| { + Ok(conn.execute( + query, + named_params![ + ":name": p.name, + ":cost": p.cost, + ":user_id": p.user_id, + ":category_id": p.category_id, + ":date": p.date, + ":id": id + ], + )?) + }) + .await; + match res { Ok(_) => true, @@ -440,20 +380,12 @@ WHERE } } -pub async fn delete(pool: &SqlitePool, id: i64) -> bool { - let res = sqlx::query( - r#" -UPDATE - payments -SET - deleted_at = datetime() -WHERE - id = ? - "#, - ) - .bind(id) - .execute(pool) - .await; +pub async fn delete(conn: &Connection, id: i64) -> bool { + let query = r#"UPDATE payments SET deleted_at = datetime() WHERE id = :id"#; + + let res = conn + .call(move |conn| Ok(conn.execute(query, named_params![":id": id])?)) + .await; match res { Ok(_) => true, @@ -465,33 +397,34 @@ WHERE } pub async fn search_category( - pool: &SqlitePool, + conn: &Connection, payment_name: String, ) -> Option<i64> { - let query = format!( - r#" -SELECT - category_id -FROM - payments -WHERE - deleted_at IS NULL - AND {} LIKE ? -ORDER BY - updated_at, created_at - "#, - utils::format_key_for_search("name") - ); - - let res = sqlx::query(&query) - .bind(text::format_search(&payment_name)) - .map(|row: SqliteRow| row.get("category_id")) - .fetch_one(pool) + let query = format!(r#" + SELECT category_id + FROM payments + WHERE + deleted_at IS NULL + AND {} LIKE :name + ORDER BY updated_at, created_at + "#, utils::format_key_for_search("name")); + + let payment_name_closure = payment_name.clone(); + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(&query)?; + let mut iter = stmt.query_map( + named_params![":name": text::format_search(&payment_name_closure)], + |row| row.get(0) + )?; + utils::one::<i64, _>(&mut iter) + }) .await; match res { Ok(category) => Some(category), - Err(Error::RowNotFound) => None, + Err(tokio_rusqlite::Error::Rusqlite(rusqlite::Error::QueryReturnedNoRows)) => None, Err(err) => { log::error!( "Error looking for the category of {}: {:?}", @@ -503,24 +436,25 @@ ORDER BY } } -pub async fn is_category_used(pool: &SqlitePool, category_id: i64) -> bool { +pub async fn is_category_used(conn: &Connection, category_id: i64) -> bool { let query = r#" -SELECT - 1 -FROM - payments -WHERE - category_id = ? - AND deleted_at IS NULL -LIMIT - 1 + SELECT 1 + FROM payments + WHERE + category_id = :category_id + AND deleted_at IS NULL + LIMIT 1 "#; - let res = sqlx::query(query).bind(category_id).fetch_one(pool).await; + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(&query)?; + Ok(stmt.exists(named_params![":category_id": category_id])?) + }) + .await; match res { - Ok(_) => true, - Err(Error::RowNotFound) => false, + Ok(is_used) => is_used, Err(err) => { log::error!( "Error looking if category {} is used: {:?}", @@ -532,31 +466,33 @@ LIMIT } } -pub async fn repartition(pool: &SqlitePool) -> HashMap<i64, i64> { +pub async fn repartition(conn: &Connection) -> HashMap<i64, i64> { let query = r#" -SELECT - users.id AS user_id, - COALESCE(payments.sum, 0) AS sum -FROM - users -LEFT OUTER JOIN ( - SELECT - user_id, - SUM(cost) AS sum - FROM - payments - WHERE - deleted_at IS NULL - AND frequency = 'Punctual' - GROUP BY - user_id -) payments -ON - users.id = payments.user_id"#; - - let res = sqlx::query(query) - .map(|row: SqliteRow| (row.get("user_id"), row.get("sum"))) - .fetch_all(pool) + SELECT + users.id, + COALESCE(payments.sum, 0) + FROM users + LEFT OUTER JOIN ( + SELECT + user_id, + SUM(cost) AS sum + FROM payments + WHERE + deleted_at IS NULL + AND frequency = 'Punctual' + GROUP BY user_id + ) payments + ON users.id = payments.user_id + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let payments = stmt + .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? + .collect::<Result<Vec<(i64, i64)>, _>>()?; + Ok(payments) + }) .await; match res { @@ -568,25 +504,25 @@ ON } } -pub async fn create_monthly_payments(pool: &SqlitePool) { +pub async fn create_monthly_payments(conn: &Connection) { let query = r#" -INSERT INTO - payments(name, cost, user_id, category_id, date, frequency) -SELECT - name, - cost, - user_id, - category_id, - date() AS date, - 'Punctual' AS frequency -FROM - payments -WHERE - frequency = 'Monthly' - AND deleted_at IS NULL + INSERT INTO payments(name, cost, user_id, category_id, date, frequency) + SELECT + name, + cost, + user_id, + category_id, + date() AS date, + 'Punctual' AS frequency + FROM payments + WHERE + frequency = 'Monthly' + AND deleted_at IS NULL "#; - let res = sqlx::query(query).execute(pool).await; + let res = conn + .call(move |conn| Ok(conn.execute(query, [])?)) + .await; match res { Ok(_) => (), @@ -594,50 +530,51 @@ WHERE } } -pub async fn last_week(pool: &SqlitePool) -> Vec<Report> { +pub async fn last_week(conn: &Connection) -> Vec<Report> { let query = r#" -SELECT - strftime('%d/%m/%Y', payments.date) AS date, - (payments.name || ' (' || users.name || ')') AS name, - payments.cost AS amount, - (CASE - WHEN - payments.deleted_at IS NOT NULL - THEN - 'Deleted' - WHEN - payments.updated_at IS NOT NULL - AND payments.created_at < date('now', 'weekday 0', '-13 days') - THEN - 'Updated' - ELSE - 'Created' - END) AS action -FROM - payments -INNER JOIN - users -ON - payments.user_id = users.id -WHERE - payments.frequency = 'Punctual' - AND ( - ( - payments.created_at >= date('now', 'weekday 0', '-13 days') - AND payments.created_at < date('now', 'weekday 0', '-6 days') - ) OR ( - payments.updated_at >= date('now', 'weekday 0', '-13 days') - AND payments.updated_at < date('now', 'weekday 0', '-6 days') - ) OR ( - payments.deleted_at >= date('now', 'weekday 0', '-13 days') - AND payments.deleted_at < date('now', 'weekday 0', '-6 days') - ) - ) -ORDER BY - payments.date + SELECT + strftime('%d/%m/%Y', payments.date) AS date, + (payments.name || ' (' || users.name || ')') AS name, + payments.cost AS amount, + (CASE + WHEN payments.deleted_at IS NOT NULL + THEN 'Deleted' + WHEN + payments.updated_at IS NOT NULL + AND payments.created_at < date('now', 'weekday 0', '-13 days') + THEN 'Updated' + ELSE 'Created' + END) AS action + FROM payments + INNER JOIN users + ON payments.user_id = users.id + WHERE + payments.frequency = 'Punctual' + AND ( + ( + payments.created_at >= date('now', 'weekday 0', '-13 days') + AND payments.created_at < date('now', 'weekday 0', '-6 days') + ) OR ( + payments.updated_at >= date('now', 'weekday 0', '-13 days') + AND payments.updated_at < date('now', 'weekday 0', '-6 days') + ) OR ( + payments.deleted_at >= date('now', 'weekday 0', '-13 days') + AND payments.deleted_at < date('now', 'weekday 0', '-6 days') + ) + ) + ORDER BY payments.date "#; - let res = sqlx::query_as::<_, Report>(query).fetch_all(pool).await; + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let xs = stmt + .query_map([], utils::row_to_report)? + .collect::<Result<Vec<Report>, _>>()?; + + Ok(xs) + }) + .await; match res { Ok(payments) => payments, diff --git a/src/db/users.rs b/src/db/users.rs index f463421..8b21ff4 100644 --- a/src/db/users.rs +++ b/src/db/users.rs @@ -1,24 +1,30 @@ -use sqlx::error::Error; -use sqlx::sqlite::{SqlitePool, SqliteRow}; -use sqlx_core::row::Row; +use tokio_rusqlite::{named_params, Connection, Row}; +use crate::db::utils; use crate::model::user::User; -pub async fn list(pool: &SqlitePool) -> Vec<User> { - let res = sqlx::query_as::<_, User>( - r#" -SELECT - id, - name, - email -FROM - users -ORDER BY - name - "#, - ) - .fetch_all(pool) - .await; +fn row_to_user(row: &Row) -> Result<User, rusqlite::Error> { + Ok(User { + id: row.get(0)?, + name: row.get(1)?, + email: row.get(2)?, + }) +} + +pub async fn list(conn: &Connection) -> Vec<User> { + let query = r#"SELECT id, name, email FROM users ORDER BY name"#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + + let users = stmt + .query_map([], row_to_user)? + .collect::<Result<Vec<User>, _>>()?; + + Ok(users) + }) + .await; match res { Ok(users) => users, @@ -30,25 +36,30 @@ ORDER BY } pub async fn set_login_token( - pool: &SqlitePool, + conn: &Connection, email: String, login_token: String, ) -> bool { - let res = sqlx::query( - r#" -UPDATE - users -SET - login_token = ?, - updated_at = datetime() -WHERE - email = ? - "#, - ) - .bind(login_token) - .bind(email) - .execute(pool) - .await; + let query = r#" + UPDATE users + SET + login_token = :login_token, + updated_at = datetime() + WHERE + email = :email + "#; + + let res = conn + .call(move |conn| { + Ok(conn.execute( + query, + named_params![ + ":login_token": login_token, + ":email": email + ], + )?) + }) + .await; match res { Ok(_) => true, @@ -59,21 +70,18 @@ WHERE } } -pub async fn remove_login_token(pool: &SqlitePool, id: i64) -> bool { - let res = sqlx::query( - r#" -UPDATE - users -SET - login_token = NULL, - updated_at = datetime() -WHERE - id = ? - "#, - ) - .bind(id) - .execute(pool) - .await; +pub async fn remove_login_token(conn: &Connection, id: i64) -> bool { + let query = r#" + UPDATE users + SET + login_token = NULL, + updated_at = datetime() + WHERE id = :id + "#; + + let res = conn + .call(move |conn| Ok(conn.execute(query, named_params![":id": id])?)) + .await; match res { Ok(_) => true, @@ -85,59 +93,61 @@ WHERE } pub async fn get_by_login_token( - pool: &SqlitePool, + conn: &Connection, login_token: String, ) -> Option<User> { - let res = sqlx::query_as::<_, User>( - r#" -SELECT - id, - name, - email -FROM - users -WHERE - login_token = ? - "#, - ) - .bind(login_token) - .fetch_one(pool) - .await; + let query = r#" + SELECT + id, + name, + email + FROM users + WHERE login_token = :login_token + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = stmt.query_map( + named_params![":login_token": login_token], + row_to_user, + )?; + utils::one(&mut iter) + }) + .await; match res { Ok(user) => Some(user), - Err(Error::RowNotFound) => None, Err(err) => { - log::error!("Error getting user from login token: {:?}", err); + log::error!("Error getting user from login_token: {err:?}"); None } } } pub async fn get_password_hash( - pool: &SqlitePool, + conn: &Connection, email: String, ) -> Option<String> { - let res = sqlx::query( - r#" -SELECT - password -FROM - users -WHERE - email = ? - "#, - ) - .bind(email) - .map(|row: SqliteRow| row.get("password")) - .fetch_one(pool) - .await; + let query = r#" + SELECT password + FROM users + WHERE email = :email + "#; + + let res = conn + .call(move |conn| { + let mut stmt = conn.prepare(query)?; + let mut iter = stmt + .query_map(named_params![":email": email], |row| row.get(0))?; + utils::one::<String, _>(&mut iter) + }) + .await; match res { - Ok(hash) => Some(hash), - Err(Error::RowNotFound) => None, + Ok(hash) => Some(hash.clone()), Err(err) => { - log::error!("Error getting password hash: {:?}", err); + log::error!("Error listing users: {:?}", err); None } } diff --git a/src/db/utils.rs b/src/db/utils.rs index 621a69c..f61d20a 100644 --- a/src/db/utils.rs +++ b/src/db/utils.rs @@ -1,3 +1,27 @@ +use crate::model::report::Report; +use tokio_rusqlite::Row; + pub fn format_key_for_search(value: &str) -> String { format!("replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(lower({}), 'à', 'a'), 'â', 'a'), 'ç', 'c'), 'è', 'e'), 'é', 'e'), 'ê', 'e'), 'ë', 'e'), 'î', 'i'), 'ï', 'i'), 'ô', 'o'), 'ù', 'u'), 'û', 'u'), 'ü', 'u')", value) } + +pub fn one<A, I: Iterator<Item = Result<A, rusqlite::Error>>>( + mut iter: I, +) -> Result<A, tokio_rusqlite::Error> { + match iter.next() { + Some(Ok(user)) => Ok(user), + Some(Err(err)) => Err(tokio_rusqlite::Error::Rusqlite(err)), + None => Err(tokio_rusqlite::Error::Rusqlite( + rusqlite::Error::QueryReturnedNoRows, + )), + } +} + +pub fn row_to_report(row: &Row) -> Result<Report, rusqlite::Error> { + Ok(Report { + date: row.get(0)?, + name: row.get(1)?, + amount: row.get(2)?, + action: row.get(3)?, + }) +} diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index a718d93..3bfca71 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -1,7 +1,7 @@ mod weekly_report; -use sqlx::sqlite::SqlitePool; use tokio::time::{sleep, Duration}; +use tokio_rusqlite::Connection; use crate::db; use crate::model::config::Config; @@ -9,21 +9,21 @@ use crate::model::job::Job; pub async fn start( config: Config, - pool: SqlitePool, + db_conn: Connection, templates: minijinja::Environment<'_>, ) { loop { - if db::jobs::should_run(&pool, Job::WeeklyReport).await { + if db::jobs::should_run(&db_conn, Job::WeeklyReport).await { log::info!("Starting weekly report job"); - if weekly_report::send(&config, &pool, &templates).await { - db::jobs::actualize_last_execution(&pool, Job::WeeklyReport) + if weekly_report::send(&config, &db_conn, &templates).await { + db::jobs::actualize_last_execution(&db_conn, Job::WeeklyReport) .await; } } - if db::jobs::should_run(&pool, Job::MonthlyPayment).await { + if db::jobs::should_run(&db_conn, Job::MonthlyPayment).await { log::info!("Starting monthly payment job"); - db::payments::create_monthly_payments(&pool).await; - db::jobs::actualize_last_execution(&pool, Job::MonthlyPayment) + db::payments::create_monthly_payments(&db_conn).await; + db::jobs::actualize_last_execution(&db_conn, Job::MonthlyPayment) .await; } // Sleeping 8 hours diff --git a/src/jobs/weekly_report.rs b/src/jobs/weekly_report.rs index 5058c52..a91a3fb 100644 --- a/src/jobs/weekly_report.rs +++ b/src/jobs/weekly_report.rs @@ -1,5 +1,5 @@ -use sqlx::sqlite::SqlitePool; use std::collections::HashMap; +use tokio_rusqlite::Connection; use crate::db; use crate::mail; @@ -8,12 +8,12 @@ use crate::payer; pub async fn send( config: &Config, - pool: &SqlitePool, + db_conn: &Connection, env: &minijinja::Environment<'_>, ) -> bool { - match get_weekly_report(pool, env).await { + match get_weekly_report(db_conn, env).await { Ok(report) => { - let users = db::users::list(pool).await; + let users = db::users::list(db_conn).await; mail::send( config, users @@ -39,21 +39,21 @@ pub async fn send( } async fn get_weekly_report( - pool: &SqlitePool, + db_conn: &Connection, env: &minijinja::Environment<'_>, ) -> Result<String, minijinja::Error> { - let users = db::users::list(pool).await; - let incomes_from = db::incomes::defined_for_all(pool).await; + let users = db::users::list(db_conn).await; + let incomes_from = db::incomes::defined_for_all(db_conn).await; let user_incomes = match incomes_from { - Some(from) => db::incomes::cumulative(pool, from).await, + Some(from) => db::incomes::cumulative(db_conn, from).await, None => HashMap::new(), }; - let user_payments = db::payments::repartition(pool).await; + let user_payments = db::payments::repartition(db_conn).await; let exceeding_payers = payer::exceeding(&users, &user_incomes, &user_payments); - let last_week_payments = db::payments::last_week(pool).await; - let last_week_incomes = db::incomes::last_week(pool).await; + let last_week_payments = db::payments::last_week(db_conn).await; + let last_week_incomes = db::incomes::last_week(db_conn).await; let template = env.get_template("report/report.j2")?; template.render(minijinja::context!( diff --git a/src/main.rs b/src/main.rs index 5fe8a94..0786f46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use hyper::server::conn::http1; use hyper::service::service_fn; use hyper_util::rt::TokioIo; -use sqlx::sqlite::SqlitePool; use tokio::net::TcpListener; +use tokio_rusqlite::Connection; mod assets; mod controller; @@ -27,22 +27,28 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let config = config::from_env() .unwrap_or_else(|err| panic!("Error reading config: {err}")); - let pool = SqlitePool::connect(&format!("sqlite:{}", config.db_path)) + let db_conn = Connection::open(config.db_path.clone()) .await - .unwrap(); + .unwrap_or_else(|_| { + panic!("Error while openning DB: {}", config.db_path) + }); let assets = assets::get(); let templates = templates::get()?; - tokio::spawn(jobs::start(config.clone(), pool.clone(), templates.clone())); + tokio::spawn(jobs::start( + config.clone(), + db_conn.clone(), + templates.clone(), + )); let listener = TcpListener::bind(config.socket_address).await?; log::info!("Starting server at {}", config.socket_address); loop { let config = config.clone(); - let pool = pool.clone(); + let db_conn = db_conn.clone(); let assets = assets.clone(); let templates = templates.clone(); let (stream, _) = listener.accept().await?; @@ -56,7 +62,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { service_fn(move |req| { routes::routes( config.clone(), - pool.clone(), + db_conn.clone(), assets.clone(), templates.clone(), req, diff --git a/src/model/action.rs b/src/model/action.rs deleted file mode 100644 index a77543a..0000000 --- a/src/model/action.rs +++ /dev/null @@ -1 +0,0 @@ -use serde::Serialize; diff --git a/src/model/category.rs b/src/model/category.rs index de08dea..ecece96 100644 --- a/src/model/category.rs +++ b/src/model/category.rs @@ -1,6 +1,4 @@ -use serde::Serialize; - -#[derive(sqlx::FromRow, Serialize)] +#[derive(serde::Serialize, Clone)] pub struct Category { pub id: i64, pub name: String, diff --git a/src/model/frequency.rs b/src/model/frequency.rs index bb83e27..91aab89 100644 --- a/src/model/frequency.rs +++ b/src/model/frequency.rs @@ -1,7 +1,7 @@ -use serde::{Deserialize, Serialize}; +use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef}; use std::{fmt, str}; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, sqlx::Type)] +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)] pub enum Frequency { Punctual, Monthly, @@ -29,3 +29,19 @@ impl str::FromStr for Frequency { } } } + +impl FromSql for Frequency { + fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { + match value { + ValueRef::Text(text) => match std::str::from_utf8(text) { + Ok("Punctual") => Ok(Frequency::Punctual), + Ok("Monthly") => Ok(Frequency::Monthly), + Ok(str) => Err(FromSqlError::Other( + format!("Unknown frequency: {str}").into(), + )), + Err(err) => Err(FromSqlError::Other(err.into())), + }, + _ => Err(FromSqlError::InvalidType), + } + } +} diff --git a/src/model/income.rs b/src/model/income.rs index ef97b56..c953251 100644 --- a/src/model/income.rs +++ b/src/model/income.rs @@ -1,13 +1,12 @@ use chrono::NaiveDate; -use serde::Serialize; -#[derive(Debug, Clone, sqlx::FromRow, Serialize)] +#[derive(Debug, Clone, serde::Serialize)] pub struct Stat { pub date: String, pub amount: i64, } -#[derive(Debug, Clone, sqlx::FromRow, Serialize)] +#[derive(Debug, Clone, serde::Serialize)] pub struct Table { pub id: i64, pub date: String, @@ -15,7 +14,7 @@ pub struct Table { pub amount: i64, } -#[derive(Debug, Clone, sqlx::FromRow, Serialize)] +#[derive(Debug, Clone, serde::Serialize)] pub struct Form { pub id: i64, pub amount: i64, diff --git a/src/model/job.rs b/src/model/job.rs index 74151ae..f31cfa0 100644 --- a/src/model/job.rs +++ b/src/model/job.rs @@ -1,5 +1,13 @@ -#[derive(Debug, sqlx::Type)] +use std::fmt; + +#[derive(Debug)] pub enum Job { MonthlyPayment, WeeklyReport, } + +impl fmt::Display for Job { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/src/model/payment.rs b/src/model/payment.rs index 5ce6bb9..d732528 100644 --- a/src/model/payment.rs +++ b/src/model/payment.rs @@ -1,9 +1,8 @@ use chrono::NaiveDate; -use serde::Serialize; use crate::model::frequency::Frequency; -#[derive(Debug, sqlx::FromRow, Serialize)] +#[derive(Debug, serde::Serialize)] pub struct Table { pub id: i64, pub name: String, @@ -15,7 +14,7 @@ pub struct Table { pub frequency: Frequency, } -#[derive(Debug, sqlx::FromRow, Serialize)] +#[derive(Debug, serde::Serialize)] pub struct Form { pub id: i64, pub name: String, @@ -26,14 +25,14 @@ pub struct Form { pub frequency: Frequency, } -#[derive(Debug, sqlx::FromRow, Serialize)] +#[derive(Debug, serde::Serialize)] pub struct Stat { pub start_date: String, pub cost: i64, pub category_id: i64, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Create { pub name: String, pub cost: i64, diff --git a/src/model/report.rs b/src/model/report.rs index 4858402..e944745 100644 --- a/src/model/report.rs +++ b/src/model/report.rs @@ -1,6 +1,7 @@ -use serde::Serialize; +use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef}; +use std::fmt; -#[derive(Debug, sqlx::FromRow, Serialize)] +#[derive(Debug, serde::Serialize)] pub struct Report { pub date: String, pub name: String, @@ -8,9 +9,32 @@ pub struct Report { pub action: Action, } -#[derive(Debug, PartialEq, Serialize, sqlx::Type)] +#[derive(Debug, PartialEq, serde::Serialize)] pub enum Action { Created, Updated, Deleted, } + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl FromSql for Action { + fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { + match value { + ValueRef::Text(text) => match std::str::from_utf8(text) { + Ok("Created") => Ok(Action::Created), + Ok("Updated") => Ok(Action::Updated), + Ok("Deleted") => Ok(Action::Deleted), + Ok(str) => Err(FromSqlError::Other( + format!("Unknown action: {str}").into(), + )), + Err(err) => Err(FromSqlError::Other(err.into())), + }, + _ => Err(FromSqlError::InvalidType), + } + } +} diff --git a/src/model/user.rs b/src/model/user.rs index e8a61bf..06165dd 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,6 +1,4 @@ -use serde::Serialize; - -#[derive(Debug, sqlx::FromRow, Clone, Serialize)] +#[derive(Debug, Clone, serde::Serialize)] pub struct User { pub id: i64, pub name: String, diff --git a/src/queries.rs b/src/queries.rs index 8ecc4fe..9699d57 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -1,7 +1,6 @@ use crate::model::frequency::Frequency; -use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize, Clone)] +#[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct Payments { pub page: Option<i64>, pub name: Option<String>, @@ -69,18 +68,18 @@ pub fn payments_url(q: Payments) -> String { } } -#[derive(Deserialize, Serialize, Clone)] +#[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct Incomes { pub page: Option<i64>, pub highlight: Option<i64>, } -#[derive(Deserialize, Serialize, Clone)] +#[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct Categories { pub highlight: Option<i64>, } -#[derive(Deserialize, Serialize)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct PaymentCategory { pub payment_name: String, } diff --git a/src/routes.rs b/src/routes.rs index 5f17ca5..aca4284 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -2,9 +2,9 @@ use http_body_util::{BodyExt, Full}; use hyper::body::{Bytes, Incoming}; use hyper::{Method, Request, Response}; use serde::Deserialize; -use sqlx::sqlite::SqlitePool; use std::collections::HashMap; use std::convert::Infallible; +use tokio_rusqlite::Connection; use url::form_urlencoded; use crate::controller; @@ -17,7 +17,7 @@ use crate::utils::cookie; pub async fn routes( config: Config, - pool: SqlitePool, + db_conn: Connection, assets: HashMap<String, String>, templates: minijinja::Environment<'static>, request: Request<Incoming>, @@ -38,7 +38,7 @@ pub async fn routes( &assets, &templates, body_form(request).await, - pool, + db_conn, ) .await } @@ -49,10 +49,10 @@ pub async fn routes( "icon.png" => file("assets/icon.png", "image/png").await, _ => controller::utils::not_found(), }, - _ => match connected_user(&config, &pool, &request).await { + _ => match connected_user(&config, &db_conn, &request).await { Some(user) => { let wallet = Wallet { - pool, + db_conn, assets, templates, user, @@ -68,12 +68,12 @@ pub async fn routes( async fn connected_user( config: &Config, - pool: &SqlitePool, + db_conn: &Connection, request: &Request<Incoming>, ) -> Option<User> { let cookie = request.headers().get("COOKIE")?.to_str().ok()?; let login_token = cookie::extract_token(config, cookie).ok()?; - db::users::get_by_login_token(pool, login_token.to_string()).await + db::users::get_by_login_token(db_conn, login_token.to_string()).await } async fn authenticated_routes( diff --git a/src/templates.rs b/src/templates.rs index c9a750b..8f160dc 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,9 +1,8 @@ -use serde::Serialize; use std::fs; use crate::queries; -#[derive(Debug, Serialize)] +#[derive(Debug, serde::Serialize)] pub enum Header { Payments, Categories, |