aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2025-01-31 22:28:53 +0100
committerJoris2025-01-31 22:28:53 +0100
commit0adf5a093494bdb7f5d5c0f12913133e333ddfad (patch)
treeada6df0f3480647bec99429819f1bfffd36194ce
parent24eeb54a6b7159964e8887ade7fa5173b50feb3a (diff)
Migrate to tokio_rusqlite
-rw-r--r--Cargo.lock900
-rw-r--r--Cargo.toml4
-rw-r--r--src/controller/balance.rs8
-rw-r--r--src/controller/categories.rs13
-rw-r--r--src/controller/incomes.rs25
-rw-r--r--src/controller/login.rs11
-rw-r--r--src/controller/payments.rs30
-rw-r--r--src/controller/statistics.rs6
-rw-r--r--src/controller/wallet.rs4
-rw-r--r--src/db/categories.rs174
-rw-r--r--src/db/incomes.rs656
-rw-r--r--src/db/jobs.rs42
-rw-r--r--src/db/payments.rs827
-rw-r--r--src/db/users.rs180
-rw-r--r--src/db/utils.rs24
-rw-r--r--src/jobs/mod.rs16
-rw-r--r--src/jobs/weekly_report.rs22
-rw-r--r--src/main.rs18
-rw-r--r--src/model/action.rs1
-rw-r--r--src/model/category.rs4
-rw-r--r--src/model/frequency.rs20
-rw-r--r--src/model/income.rs7
-rw-r--r--src/model/job.rs10
-rw-r--r--src/model/payment.rs9
-rw-r--r--src/model/report.rs30
-rw-r--r--src/model/user.rs4
-rw-r--r--src/queries.rs9
-rw-r--r--src/routes.rs14
-rw-r--r--src/templates.rs3
29 files changed, 1194 insertions, 1877 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1455ebf..613768f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index d206b30..6e01dcf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,