diff --git a/.containerignore b/.containerignore index faf4558..bc6f674 100644 --- a/.containerignore +++ b/.containerignore @@ -1,5 +1,6 @@ * !Cargo.* +!.sqlx/ !js/ !src/ !templates/ diff --git a/.editorconfig b/.editorconfig index 512c6a5..0397624 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,3 +20,7 @@ indent_size = 4 [*.json] indent_style = space indent_size = 2 + +[*.sql] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index 43665b1..f79f24b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /Rocket.toml /firefly.token /target +/.postgresql diff --git a/.sqlx/query-16d204cf990ebd5f78cfe21a5a9cbcfc9b0f084b755d0d9f065dbfe31c53679d.json b/.sqlx/query-16d204cf990ebd5f78cfe21a5a9cbcfc9b0f084b755d0d9f065dbfe31c53679d.json new file mode 100644 index 0000000..fe4859e --- /dev/null +++ b/.sqlx/query-16d204cf990ebd5f78cfe21a5a9cbcfc9b0f084b755d0d9f065dbfe31c53679d.json @@ -0,0 +1,27 @@ +{ + "db_name": "PostgreSQL", + "query": "\nINSERT INTO receipts (\n vendor, date, amount, notes, filename, image\n) VALUES (\n$1, $2, $3, $4, $5, $6\n)\nRETURNING id\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Timestamptz", + "Numeric", + "Text", + "Varchar", + "Bytea" + ] + }, + "nullable": [ + false + ] + }, + "hash": "16d204cf990ebd5f78cfe21a5a9cbcfc9b0f084b755d0d9f065dbfe31c53679d" +} diff --git a/.sqlx/query-9fe465619a9818b201fbf091018a75eea38e846468d1877062db8c5606550a13.json b/.sqlx/query-9fe465619a9818b201fbf091018a75eea38e846468d1877062db8c5606550a13.json new file mode 100644 index 0000000..8cfee24 --- /dev/null +++ b/.sqlx/query-9fe465619a9818b201fbf091018a75eea38e846468d1877062db8c5606550a13.json @@ -0,0 +1,52 @@ +{ + "db_name": "PostgreSQL", + "query": "\nSELECT\n id, vendor, date, amount, notes, filename\nFROM\n receipts\nWHERE\n id = $1\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "vendor", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "date", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "amount", + "type_info": "Numeric" + }, + { + "ordinal": 4, + "name": "notes", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "filename", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false, + true, + false + ] + }, + "hash": "9fe465619a9818b201fbf091018a75eea38e846468d1877062db8c5606550a13" +} diff --git a/.sqlx/query-da76776380d89783df7270592d03afbd07c627342af3d03dc83932b152402941.json b/.sqlx/query-da76776380d89783df7270592d03afbd07c627342af3d03dc83932b152402941.json new file mode 100644 index 0000000..b217c55 --- /dev/null +++ b/.sqlx/query-da76776380d89783df7270592d03afbd07c627342af3d03dc83932b152402941.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT filename, image FROM receipts WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "filename", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "image", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "da76776380d89783df7270592d03afbd07c627342af3d03dc83932b152402941" +} diff --git a/.sqlx/query-e62b8704ebb897be63463427ba9e808ddb9c7fb5d3326b388d5e7f1066732aa9.json b/.sqlx/query-e62b8704ebb897be63463427ba9e808ddb9c7fb5d3326b388d5e7f1066732aa9.json new file mode 100644 index 0000000..71dbf92 --- /dev/null +++ b/.sqlx/query-e62b8704ebb897be63463427ba9e808ddb9c7fb5d3326b388d5e7f1066732aa9.json @@ -0,0 +1,50 @@ +{ + "db_name": "PostgreSQL", + "query": "\nSELECT\n id, vendor, date, amount, notes, filename\nFROM\n receipts\nORDER BY date\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "vendor", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "date", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "amount", + "type_info": "Numeric" + }, + { + "ordinal": 4, + "name": "notes", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "filename", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + true, + false + ] + }, + "hash": "e62b8704ebb897be63463427ba9e808ddb9c7fb5d3326b388d5e7f1066732aa9" +} diff --git a/Cargo.lock b/Cargo.lock index 1040768..8264c79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,30 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,6 +50,12 @@ dependencies = [ "memchr", ] +[[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" @@ -41,6 +71,12 @@ dependencies = [ "libc", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-stream" version = "0.3.6" @@ -60,7 +96,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -71,7 +107,16 @@ checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", ] [[package]] @@ -116,12 +161,24 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" 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 = "binascii" version = "0.1.4" @@ -139,6 +196,21 @@ name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "block-buffer" @@ -149,6 +221,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "bstr" version = "1.11.3" @@ -165,6 +260,28 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.22.0" @@ -198,6 +315,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.40" @@ -233,6 +356,12 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cookie" version = "0.18.1" @@ -269,6 +398,21 @@ dependencies = [ "libc", ] +[[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-channel" version = "0.5.14" @@ -297,6 +441,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -313,6 +466,17 @@ dependencies = [ "typenum", ] +[[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 = "deranged" version = "0.3.11" @@ -358,7 +522,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -368,7 +532,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -379,14 +545,23 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" @@ -413,6 +588,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fastrand" version = "2.3.0" @@ -445,6 +637,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[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", +] + [[package]] name = "fnv" version = "1.0.7" @@ -484,6 +687,12 @@ dependencies = [ "libc", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -514,6 +723,28 @@ version = "0.3.31" 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" @@ -650,12 +881,49 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -668,6 +936,39 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.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" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[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 = "0.2.12" @@ -798,7 +1099,7 @@ dependencies = [ "http 1.2.0", "hyper 1.6.0", "hyper-util", - "rustls", + "rustls 0.23.23", "rustls-pki-types", "tokio", "tokio-rustls", @@ -978,7 +1279,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -1025,7 +1326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", "serde", ] @@ -1113,6 +1414,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1137,6 +1441,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.9.2" @@ -1189,6 +1504,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[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" @@ -1201,6 +1526,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.5" @@ -1269,6 +1600,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "normpath" version = "1.3.0" @@ -1307,12 +1648,49 @@ dependencies = [ "winapi", ] +[[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-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[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" @@ -1320,6 +1698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1370,7 +1749,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -1429,6 +1808,12 @@ dependencies = [ "regex", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pear" version = "0.2.9" @@ -1449,7 +1834,16 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.99", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] @@ -1465,7 +1859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.12", "ucd-trie", ] @@ -1489,7 +1883,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -1553,6 +1947,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1574,6 +1989,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -1591,11 +2015,31 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", "version_check", "yansi", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" version = "1.0.39" @@ -1605,6 +2049,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1642,9 +2092,12 @@ dependencies = [ "chrono", "reqwest", "rocket", + "rocket_db_pools", "rocket_dyn_templates", + "rust_decimal", "serde", - "thiserror", + "sqlx", + "thiserror 2.0.12", "tracing", "tracing-subscriber", ] @@ -1675,7 +2128,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -1722,13 +2175,22 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -1749,7 +2211,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", @@ -1780,6 +2242,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rocket" version = "0.5.1" @@ -1830,11 +2321,33 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn", + "syn 2.0.99", "unicode-xid", "version_check", ] +[[package]] +name = "rocket_db_pools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6578b2740ceee3e78bff63fe9299d964b7e68318446cdcb9af3b9cab46e1e9d" +dependencies = [ + "rocket", + "rocket_db_pools_codegen", + "sqlx", + "version_check", +] + +[[package]] +name = "rocket_db_pools_codegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842e859f2e87a23efc0f81e25756c0fb43f18726e62daf99da7ea19fbc56cebd" +dependencies = [ + "devise", + "quote", +] + [[package]] name = "rocket_dyn_templates" version = "0.2.0" @@ -1875,6 +2388,42 @@ dependencies = [ "uncased", ] +[[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", +] + +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1894,6 +2443,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.23" @@ -1902,11 +2462,20 @@ checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -1922,6 +2491,16 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -1975,6 +2554,22 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" version = "2.11.1" @@ -2015,7 +2610,7 @@ checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2051,6 +2646,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2086,6 +2692,22 @@ dependencies = [ "libc", ] +[[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 = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.1" @@ -2132,6 +2754,237 @@ 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 = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash 0.8.11", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rust_decimal", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror 1.0.69", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.9.0", + "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", + "rust_decimal", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.9.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "time", + "tracing", + "url", + "urlencoding", +] [[package]] name = "stable-pattern" @@ -2157,12 +3010,34 @@ dependencies = [ "loom", ] +[[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" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.99" @@ -2191,7 +3066,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2215,6 +3090,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.18.0" @@ -2251,13 +3132,33 @@ dependencies = [ "unic-segment", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", ] [[package]] @@ -2268,7 +3169,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2322,6 +3223,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +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.44.0" @@ -2347,7 +3263,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2366,7 +3282,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls", + "rustls 0.23.23", "tokio", ] @@ -2461,6 +3377,7 @@ 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", @@ -2474,7 +3391,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2603,18 +3520,51 @@ dependencies = [ "unic-common", ] +[[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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[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 = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "untrusted" version = "0.9.0" @@ -2632,6 +3582,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -2644,6 +3600,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" + [[package]] name = "valuable" version = "0.1.1" @@ -2696,6 +3658,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[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" @@ -2718,7 +3686,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.99", "wasm-bindgen-shared", ] @@ -2753,7 +3721,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2777,6 +3745,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[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 = "winapi" version = "0.3.9" @@ -3040,6 +4024,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yansi" version = "1.0.1" @@ -3069,7 +4062,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", "synstructure", ] @@ -3091,7 +4084,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -3111,7 +4104,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", "synstructure", ] @@ -3140,5 +4133,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] diff --git a/Cargo.toml b/Cargo.toml index 3ae29fe..7880e0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,11 @@ keywords = ["personal-finance", "receipts"] chrono = { version = "0.4.40", default-features = false, features = ["serde"] } reqwest = { version = "0.12.12", features = ["json"] } rocket = { version = "0.5.1", default-features = false, features = ["json"] } +rocket_db_pools = { version = "0.2.0", features = ["sqlx_macros", "sqlx_postgres"] } rocket_dyn_templates = { version = "0.2.0", features = ["tera"] } +rust_decimal = { version = "1.36.0", features = ["serde-with-str"] } serde = { version = "1.0.218", default-features = false, features = ["derive"] } +sqlx = { version = "~0.7.4", default-features = false, features = ["chrono", "macros", "postgres", "rust_decimal", "time"] } thiserror = "2.0.12" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } diff --git a/Containerfile b/Containerfile index eda3ad3..f368222 100644 --- a/Containerfile +++ b/Containerfile @@ -11,8 +11,8 @@ RUN --mount=type=cache,target=/var/cache \ WORKDIR /build COPY Cargo.* . - COPY src src +COPY .sqlx .sqlx RUN --mount=type=cache,target=/root/.cargo \ cargo build --release --locked diff --git a/createdb.sh b/createdb.sh new file mode 100644 index 0000000..5e05821 --- /dev/null +++ b/createdb.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +set -e + +C() { + podman exec -u postgres postgresql "$@" +} + +until C pg_isready; do sleep 1; done + +if + ! C psql -At -c 'SELECT 1 FROM pg_user WHERE usename = '\'receipts\' \ + | grep -q . +then + C createuser -DERS receipts +fi +if + ! C psql -At -c 'SELECT 1 FROM pg_database WHERE datname = '\'receipts\' \ + | grep -q . +then + C createdb -O receipts receipts +fi diff --git a/js/alert.ts b/js/alert.ts index bb1855b..b557003 100644 --- a/js/alert.ts +++ b/js/alert.ts @@ -26,3 +26,9 @@ export function notify( alert.toast(); } +export function notifyError( + message: string, + duration: number | null = null, +) { + notify(message, "danger", "exclamation-octagon", duration); +} diff --git a/js/build.js b/js/build.js index aa4742f..d6c94dd 100644 --- a/js/build.js +++ b/js/build.js @@ -11,6 +11,7 @@ const context = await esbuild.context({ ], outdir: "./dist", bundle: true, + sourcemap: true, platform: "node", target: "esnext", format: "esm", diff --git a/js/camera.ts b/js/camera.ts new file mode 100644 index 0000000..de56874 --- /dev/null +++ b/js/camera.ts @@ -0,0 +1,177 @@ +import "@shoelace-style/shoelace/dist/components/icon-button/icon-button.js"; +import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; + +import SlIconButton from "@shoelace-style/shoelace/dist/components/icon-button/icon-button.js"; + +import { notifyError } from "./alert.js"; + +const STYLESHEET = ` +.camera-input { + text-align: center; +} + +.camera-input video, +.camera-input canvas { + width: 100%; + height: auto; + object-fit: contain; +} + +.camera-buttons sl-icon-button { + font-size: 3em; + margin: 0 0.5em; +} + +.hidden { + display: none; +} +`; + +export default class CameraInput extends HTMLElement { + elmVideo: HTMLVideoElement; + btnShutter: SlIconButton; + btnClear: SlIconButton; + + constructor() { + super(); + } + + connectedCallback() { + this.innerHTML = ""; + const shadow = this.attachShadow({ mode: "open" }); + shadow.append( + Object.assign(document.createElement("style"), { + textContent: STYLESHEET, + }), + ); + const wrapper = document.createElement("div"); + wrapper.className = "camera-input"; + shadow.appendChild(wrapper); + + this.elmVideo = Object.assign(document.createElement("video"), { + className: "hidden", + }); + this.elmVideo.addEventListener("canplay", () => { + this.btnShutter.disabled = false; + }); + wrapper.appendChild(this.elmVideo); + + const buttons = document.createElement("div"); + buttons.classList.add("camera-buttons"); + wrapper.appendChild(buttons); + this.btnShutter = Object.assign( + document.createElement("sl-icon-button"), + { + name: "camera", + label: "Take Photo", + disabled: true, + }, + ); + this.btnShutter.addEventListener("click", () => { + this.takePhoto(); + }); + const ttShutter = Object.assign(document.createElement("sl-tooltip"), { + content: "Take Photo", + }); + ttShutter.appendChild(this.btnShutter); + buttons.appendChild(ttShutter); + this.btnClear = Object.assign( + document.createElement("sl-icon-button"), + { + name: "trash", + label: "Start Over", + disabled: true, + className: "hidden", + }, + ); + this.btnClear.addEventListener("click", () => { + this.clearCamera(); + this.startCamera(); + }); + const ttClear = Object.assign(document.createElement("sl-tooltip"), { + content: "Start Over", + }); + ttClear.appendChild(this.btnClear); + buttons.appendChild(ttClear); + } + + public async getBlob(): Promise { + const canvas = this.shadowRoot!.querySelector("canvas"); + return await new Promise((resolve) => { + if (canvas) { + canvas.toBlob((blob) => { + resolve(blob); + }, "image/jpeg"); + } else { + resolve(null); + } + }); + } + + async clearCamera() { + this.elmVideo.pause(); + this.elmVideo.srcObject = null; + this.elmVideo.classList.add("hidden"); + this.elmVideo.parentNode + ?.querySelectorAll("canvas") + .forEach((e) => e.remove()); + this.btnShutter.disabled = true; + this.btnShutter.classList.add("hidden"); + this.btnClear.disabled = true; + this.btnClear.classList.add("hidden"); + this.sendReady(false); + } + + sendReady(hasPhoto: boolean) { + this.dispatchEvent(new CustomEvent("ready", { detail: { hasPhoto } })); + } + + async startCamera() { + let stream: MediaStream; + try { + stream = await navigator.mediaDevices.getUserMedia({ + video: { + facingMode: { + ideal: "environment", + }, + width: { ideal: 1280 }, + height: { ideal: 720 }, + }, + audio: false, + }); + } catch (ex) { + console.error(ex); + notifyError(`${ex}`); + return; + } + this.btnShutter.classList.remove("hidden"); + this.elmVideo.classList.remove("hidden"); + this.elmVideo.srcObject = stream; + this.elmVideo.play(); + } + + takePhoto() { + this.btnShutter.disabled = true; + this.btnShutter.classList.add("hidden"); + this.elmVideo.pause(); + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + if (!context) { + notifyError("Failed to get canvas 2D rendering context"); + return; + } + const width = this.elmVideo.videoWidth; + const height = this.elmVideo.videoHeight; + canvas.width = width; + canvas.height = height; + context.drawImage(this.elmVideo, 0, 0, width, height); + this.elmVideo.srcObject = null; + this.elmVideo.classList.add("hidden"); + this.elmVideo.after(canvas); + this.btnClear.disabled = false; + this.btnClear.classList.remove("hidden"); + this.sendReady(true); + } +} + +customElements.define("camera-input", CameraInput); diff --git a/js/common.css b/js/common.css index 8765e5d..32816e7 100644 --- a/js/common.css +++ b/js/common.css @@ -1,5 +1,7 @@ -:host, body { +:host, +body { font-family: var(--sl-font-sans); + color: var(--sl-color-neutral-900); } body:has(div#page-loading) main { @@ -24,9 +26,15 @@ main { } @keyframes pulse { - 0% { opacity: 0; } - 50% { opacity: 1; } - 100% { opacity: 0; } + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } } @media screen and (min-width: 900px) { @@ -53,6 +61,30 @@ tr { border-bottom: 1px solid var(--sl-color-neutral-200); } -table td, table th { +table td, +table th { padding: 1rem; } + +col.shrink { + text-align: center; + width: 1px; + white-space: nowrap; +} + +.table-actions { + text-align: right; +} + +sl-input[data-user-invalid]::part(base), +sl-select[data-user-invalid]::part(combobox), +sl-checkbox[data-user-invalid]::part(control) { + border-color: var(--sl-color-danger-600); +} + +sl-input:focus-within[data-user-invalid]::part(base), +sl-select:focus-within[data-user-invalid]::part(combobox), +sl-checkbox:focus-within[data-user-invalid]::part(control) { + border-color: var(--sl-color-danger-600); + box-shadow: 0 0 0 var(--sl-focus-ring-width) var(--sl-color-danger-300); +} diff --git a/js/receipt-form.ts b/js/receipt-form.ts new file mode 100644 index 0000000..6ce5a9e --- /dev/null +++ b/js/receipt-form.ts @@ -0,0 +1,100 @@ +import "@shoelace-style/shoelace/dist/components/button/button.js"; +import "@shoelace-style/shoelace/dist/components/details/details.js"; +import "@shoelace-style/shoelace/dist/components/icon/icon.js"; +import "@shoelace-style/shoelace/dist/components/input/input.js"; +import "@shoelace-style/shoelace/dist/components/textarea/textarea.js"; + +import "./shoelace.js"; + +import "./camera.ts"; + +import CameraInput from "./camera.ts"; +import SlButton from "@shoelace-style/shoelace/dist/components/button/button.js"; + +import { notify, notifyError } from "./alert"; + +const form = document.forms[0]; +const cameraInput = form.querySelector("camera-input") as CameraInput; +const btnSubmit = form.querySelector("sl-button[type='submit']") as SlButton; +const btnUpload = form.querySelector( + "sl-button[class='choose-file']", +) as SlButton; +const inpImage = form.photo as HTMLInputElement; +const imgPreview = document.getElementById("upload-preview") as HTMLImageElement; + +form.addEventListener("submit", async (evt) => { + evt.preventDefault(); + btnSubmit.loading = true; + const data = new FormData(form); + const blob = await cameraInput.getBlob(); + if (blob) { + data.append("photo", blob, "photo.jpg"); + } + let r: Response; + try { + r = await fetch("", { + method: "POST", + body: data, + }); + } catch (e) { + notifyError(`Failed to submit form: ${e}`); + return; + } finally { + btnSubmit.loading = false; + } + if (r.ok) { + notify("Successfully uploaded receipt", undefined, undefined, null); + window.location.href = "/receipts"; + } else { + let ct = r.headers.get("Content-Type"); + if (ct && ct.indexOf("json") > -1) { + const json = await r.json(); + if (json.error) { + notifyError(json.error); + return; + } + } + const html = await r.text(); + if (html) { + const doc = new DOMParser().parseFromString(html, "text/html"); + notifyError(doc.body.textContent ?? ""); + } else { + notifyError(r.statusText); + } + } +}); + +cameraInput.addEventListener("ready", ((evt: CustomEvent) => { + btnSubmit.disabled = !evt.detail.hasPhoto; + btnUpload.disabled = !!evt.detail.hasPhoto; + if (!!evt.detail.hasPhoto) { + inpImage.value = ""; + imgPreview.src = ""; + } +}) as EventListener); + +const cameraDetails = document.querySelector( + "sl-details[summary='Take Photo']", +)!; +let cameraInitialized = false; +cameraDetails.addEventListener("sl-show", () => { + if (!cameraInitialized) { + cameraInitialized = true; + cameraInput.startCamera(); + } +}); + +btnUpload.addEventListener("click", (evt) => { + evt.preventDefault(); + form.photo.showPicker(); +}); + +inpImage.addEventListener("change", () => { + if (inpImage.files) { + const file = inpImage.files[0]; + if (file) { + btnSubmit.disabled = false; + imgPreview.src = URL.createObjectURL(file); + } + } +}); diff --git a/js/receipt-list.ts b/js/receipt-list.ts new file mode 100644 index 0000000..18efa4b --- /dev/null +++ b/js/receipt-list.ts @@ -0,0 +1,5 @@ +import "@shoelace-style/shoelace/dist/components/button/button.js"; +import "@shoelace-style/shoelace/dist/components/card/card.js"; +import "@shoelace-style/shoelace/dist/components/icon/icon.js"; + +import "./shoelace.js"; diff --git a/js/receipt.ts b/js/receipt.ts new file mode 100644 index 0000000..ef82950 --- /dev/null +++ b/js/receipt.ts @@ -0,0 +1,6 @@ +import "@shoelace-style/shoelace/dist/components/button/button.js"; +import "@shoelace-style/shoelace/dist/components/details/details.js"; +import "@shoelace-style/shoelace/dist/components/input/input.js"; +import "@shoelace-style/shoelace/dist/components/textarea/textarea.js"; + +import "./shoelace.js"; diff --git a/js/shoelace.ts b/js/shoelace.ts new file mode 100644 index 0000000..c172a2c --- /dev/null +++ b/js/shoelace.ts @@ -0,0 +1,2 @@ +import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path.js"; +setBasePath("/static/shoelace/"); diff --git a/migrations/20250309144224_Initial.sql b/migrations/20250309144224_Initial.sql new file mode 100644 index 0000000..4c7293b --- /dev/null +++ b/migrations/20250309144224_Initial.sql @@ -0,0 +1,9 @@ +CREATE TABLE receipts ( + id serial PRIMARY KEY, + date timestamp with time zone NOT NULL, + vendor varchar(99) NOT NULL, + amount decimal NOT NULL, + notes text, + filename varchar(199) NOT NULL, + image bytea NOT NULL +); diff --git a/src/main.rs b/src/main.rs index 4883d97..ddb93bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod config; mod firefly; +mod receipts; use rocket::form::Form; use rocket::fs::{FileServer, TempFile}; @@ -7,6 +8,7 @@ use rocket::http::Status; use rocket::response::Redirect; use rocket::tokio::io::{AsyncReadExt, BufReader}; use rocket::State; +use rocket_db_pools::Database as RocketDatabase; use rocket_dyn_templates::{context, Template}; use serde::Serialize; use tracing::{debug, error}; @@ -37,6 +39,10 @@ impl Context { } } +#[derive(RocketDatabase)] +#[database("receipts")] +struct Database(rocket_db_pools::sqlx::PgPool); + #[derive(Serialize)] pub struct Transaction { pub id: String, @@ -255,6 +261,8 @@ async fn rocket() -> _ { update_transaction ], ) + .mount("/receipts", receipts::routes()) .mount("/static", FileServer::from("static")) .attach(Template::fairing()) + .attach(Database::init()) } diff --git a/src/receipts.rs b/src/receipts.rs new file mode 100644 index 0000000..c38c4ee --- /dev/null +++ b/src/receipts.rs @@ -0,0 +1,268 @@ +use chrono::{DateTime, FixedOffset}; +use rocket::form::Form; +use rocket::fs::TempFile; +use rocket::http::{ContentType, MediaType, Status}; +use rocket::serde::json::Json; +use rocket::tokio::io::{AsyncReadExt, BufReader}; +use rocket::Route; +use rocket_db_pools::Connection as DatabaseConnection; +use rocket_dyn_templates::{context, Template}; +use serde::Serialize; +use sqlx::types::Decimal; +use tracing::{error, info}; + +use super::Database; + +#[derive(Debug, Serialize)] +pub struct Receipt { + pub id: i32, + pub date: DateTime, + pub vendor: String, + pub amount: Decimal, + pub notes: Option, + pub filename: String, + pub image: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ReceiptJson { + pub id: i32, + pub date: DateTime, + pub vendor: String, + pub amount: Decimal, + pub notes: Option, + pub filename: String, +} + +#[derive(rocket::FromForm)] +pub struct ReceiptPostForm<'r> { + pub date: String, + pub vendor: String, + pub amount: String, + pub notes: String, + pub photo: TempFile<'r>, +} + +struct ReceiptPostData { + pub date: DateTime, + pub vendor: String, + pub amount: Decimal, + pub notes: String, + pub filename: String, + pub photo: Vec, +} + +#[derive(Debug, thiserror::Error)] +enum ReceiptPostFormError { + #[error("Invalid date: {0}")] + Date(#[from] chrono::format::ParseError), + #[error("Invalid amount: {0}")] + Amount(#[from] rust_decimal::Error), + #[error("Error reading photo: {0}")] + Photo(#[from] std::io::Error), +} + +#[derive(Serialize)] +#[serde(rename_all = "lowercase")] +pub enum AddReceiptResponse { + Success(ReceiptJson), + Error(String), +} + +impl ReceiptPostData { + async fn from_form( + form: &ReceiptPostForm<'_>, + ) -> Result { + let date = DateTime::parse_from_str( + &format!("{} 00:00:00 +0000", form.date), + "%Y-%m-%d %H:%M:%S %z", + )?; + let vendor = form.vendor.clone(); + use rust_decimal::prelude::FromStr; + let amount = Decimal::from_str(&form.amount)?; + let notes = form.notes.clone(); + let filename = form + .photo + .raw_name() + .map(|n| n.dangerous_unsafe_unsanitized_raw().as_str()) + .unwrap_or("photo.jpg") + .into(); + let stream = form.photo.open().await?; + let mut reader = BufReader::new(stream); + let mut photo = Vec::new(); + reader.read_to_end(&mut photo).await?; + Ok(Self { + date, + vendor, + amount, + notes, + filename, + photo, + }) + } +} + +#[rocket::get("/")] +pub async fn list_receipts( + mut db: DatabaseConnection, +) -> (Status, Template) { + let result = rocket_db_pools::sqlx::query_as!( + ReceiptJson, + r##" +SELECT + id, vendor, date, amount, notes, filename +FROM + receipts +ORDER BY date +"##, + ) + .fetch_all(&mut **db) + .await; + match result { + Ok(r) => ( + Status::Ok, + Template::render( + "receipt-list", + context! { + receipts: r, + }, + ), + ), + Err(e) => ( + Status::InternalServerError, + Template::render( + "error", + context! { + error: e.to_string(), + }, + ), + ), + } +} + +#[rocket::get("/add")] +pub async fn receipt_form() -> Template { + Template::render("receipt-form", context! {}) +} + +#[rocket::post("/add", data = "
")] +pub async fn add_receipt( + form: Form>, + mut db: DatabaseConnection, +) -> (Status, Json) { + let data = match ReceiptPostData::from_form(&form).await { + Ok(d) => d, + Err(e) => { + return ( + Status::BadRequest, + Json(AddReceiptResponse::Error(e.to_string())), + ); + }, + }; + let result = rocket_db_pools::sqlx::query!( + r##" +INSERT INTO receipts ( + vendor, date, amount, notes, filename, image +) VALUES ( +$1, $2, $3, $4, $5, $6 +) +RETURNING id +"##, + data.vendor, + data.date, + data.amount, + data.notes, + data.filename, + data.photo, + ) + .fetch_one(&mut **db) + .await; + match result { + Ok(r) => { + info!("Created new receipt {}", r.id); + ( + Status::Ok, + Json(AddReceiptResponse::Success(ReceiptJson { + id: r.id, + vendor: data.vendor, + date: data.date, + amount: data.amount, + notes: Some(data.notes), + filename: data.filename, + })), + ) + }, + Err(e) => { + error!("Failed to insert new receipt record: {}", e); + ( + Status::InternalServerError, + Json(AddReceiptResponse::Error(e.to_string())), + ) + }, + } +} + +#[rocket::get("/")] +pub async fn get_receipt( + id: i32, + mut db: DatabaseConnection, +) -> Option