diff --git a/.env b/.env index 9359f40..ec5f23e 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -DATABASE_FILE=data/demo.db +DATABASE_URL=data/demo.db diff --git a/.gitattributes b/.gitattributes index 7560d6d..f4a7800 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ Cargo.lock diff=nodiff +package-lock.json diff=nodiff diff --git a/Cargo.lock b/Cargo.lock index 94824cd..21e631f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,17 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "1.1.2" @@ -52,12 +41,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - [[package]] name = "anyhow" version = "1.0.75" @@ -154,19 +137,17 @@ dependencies = [ "axum", "convert_case", "currency_rs", + "diesel", + "diesel_migrations", "dotenvy", "hotwire-turbo", "hotwire-turbo-axum", "itertools", + "libsqlite3-sys", "markup", "mime_guess", - "nanoid", "once_cell", - "r2d2", - "r2d2_sqlite", "regex", - "rusqlite", - "rusqlite_migration", "rust-embed", "serde", "serde_json", @@ -175,6 +156,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "ulid", "validator", ] @@ -195,9 +177,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bit-set" @@ -248,9 +230,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -289,9 +271,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -318,9 +300,9 @@ dependencies = [ [[package]] name = "currency_rs" version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee50374562062a2b38294fd4ac424acba5c587cc9b361b2dfcf3b8bb6f32a" +source = "git+https://github.com/johnbcodes/currency_rs?branch=feature/db-diesel2-sqlite#74cbbca4d98ec9dbf6faceb4a4a18a6fcdb84469" dependencies = [ + "diesel", "fancy-regex", "lazy_static", ] @@ -335,6 +317,50 @@ dependencies = [ "serde", ] +[[package]] +name = "diesel" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2268a214a6f118fce1838edba3d1561cf0e78d8de785475957a580a7f8c69d33" +dependencies = [ + "diesel_derives", + "libsqlite3-sys", + "r2d2", + "time", +] + +[[package]] +name = "diesel_derives" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44" +dependencies = [ + "diesel_table_macro_syntax", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "diesel_migrations" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" +dependencies = [ + "syn 2.0.38", +] + [[package]] name = "digest" version = "0.10.7" @@ -378,16 +404,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "fallible-iterator" -version = "0.2.0" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "fancy-regex" @@ -426,36 +446,36 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", "futures-task", @@ -496,19 +516,6 @@ name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashlink" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" -dependencies = [ - "hashbrown", -] [[package]] name = "hermit-abi" @@ -599,7 +606,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -622,6 +629,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "iri-string" version = "0.7.0" @@ -737,6 +754,27 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "migrations_internals" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "migrations_macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + [[package]] name = "mime" version = "0.3.17" @@ -764,24 +802,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", "windows-sys", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -955,17 +984,6 @@ dependencies = [ "scheduled-thread-pool", ] -[[package]] -name = "r2d2_sqlite" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99f31323d6161385f385046738df520e0e8694fa74852d35891fc0be08348ddc" -dependencies = [ - "r2d2", - "rusqlite", - "uuid", -] - [[package]] name = "rand" version = "0.8.5" @@ -1069,31 +1087,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "rusqlite" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" -dependencies = [ - "bitflags 2.4.1", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "serde_json", - "smallvec", -] - -[[package]] -name = "rusqlite_migration" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef7dd29a4426624704d5966416682fb7ab3682f724986e9e3893eaca44accabc" -dependencies = [ - "log", - "rusqlite", -] - [[package]] name = "rust-embed" version = "8.0.0" @@ -1173,18 +1166,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -1193,9 +1186,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1212,6 +1205,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1279,9 +1281,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1289,9 +1291,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -1413,7 +1415,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] @@ -1431,9 +1433,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1442,6 +1444,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -1535,12 +1571,12 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -1574,6 +1610,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ulid" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e37c4b6cbcc59a8dcd09a6429fbc7890286bcbb79215cea7b38a3c4c0921d93" +dependencies = [ + "rand", +] + [[package]] name = "unicase" version = "2.7.0" @@ -1634,7 +1679,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", - "rand", ] [[package]] @@ -1819,6 +1863,15 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "winnow" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32" +dependencies = [ + "memchr", +] + [[package]] name = "zstd" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index dfa626f..e07746b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,20 +16,18 @@ lto = true anyhow = "1.0" axum = "0.6" convert_case = "0.6" -currency_rs = "1.1" +currency_rs = { git = "https://github.com/johnbcodes/currency_rs", branch = "feature/db-diesel2-sqlite", version = "1.1", features = [ "db-diesel2-sqlite" ] } +diesel = { version = "2.1", features = ["r2d2", "sqlite", "time"] } +diesel_migrations = "2.1" dotenvy = "0.15" hotwire-turbo = "0.1" hotwire-turbo-axum = "0.1" itertools = "0.11" +libsqlite3-sys = { version = "0.26", features = ["bundled"] } markup = "0.13" mime_guess = "2" -nanoid = "0.4" once_cell = "1" -r2d2 = "0.8" -r2d2_sqlite = "0.22" regex = "1" -rusqlite = { version = "0.29", features = ["bundled", "serde_json"] } -rusqlite_migration = "1" rust-embed = { version = "8", features = ["interpolate-folder-path"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -38,4 +36,5 @@ tokio = { version = "1", features = ["full"] } tower-http = { version = "0.4", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } +ulid = "1.1" validator = { version = "0.16", features = ["derive"] } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..299a110 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +# Usage: +# +# Do any modifications to `diesel.toml` and/or run migrations and then re-make the schema: +# +# $ make src/schema.rs +# +# Do any modifications to `src/schema.rs` and then re-make the patch: +# +# $ make src/schema.rs.patch + +SHELL = /bin/bash + +.PHONY: src/schema.rs +src/schema.rs: + # patch file must exist if it's defined in diesel.toml + touch src/schema.rs.patch + # print the schema + diesel print-schema > src/schema.rs + # make unpached version + make src/schema.rs.unpatched + # make the new patch file + make src/schema.rs.patch + +.PHONY: src/schema.rs.patch +src/schema.rs.patch: + diff -U6 src/schema.rs.unpatched src/schema.rs > src/schema.rs.patch || true + +.PHONY: src/schema.rs.unpatched +src/schema.rs.unpatched: + # if patch isn't empty, create schema.rs.unpatched from the existing patch + [ ! -s src/schema.rs.patch ] || \ + patch -p0 -R -o src/schema.rs.unpatched < src/schema.rs.patch + # otherwise create schema.rs.unpatched by copying schema.rs + [ -f src/schema.rs.unpatched ] || \ + cp -a src/schema.rs src/schema.rs.unpatched \ No newline at end of file diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..79c3eb6 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,10 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId"] +patch_file = "src/schema.rs.patch" + +[migrations_directory] +dir = "migrations" diff --git a/migrations/.keep b/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/2023-10-31-123932_quotes/down.sql b/migrations/2023-10-31-123932_quotes/down.sql new file mode 100644 index 0000000..e2dd0ce --- /dev/null +++ b/migrations/2023-10-31-123932_quotes/down.sql @@ -0,0 +1 @@ +drop table quotes; diff --git a/migrations/2023-10-31-123932_quotes/up.sql b/migrations/2023-10-31-123932_quotes/up.sql new file mode 100644 index 0000000..a1c0420 --- /dev/null +++ b/migrations/2023-10-31-123932_quotes/up.sql @@ -0,0 +1,13 @@ +create table quotes ( + id text not null primary key, + name text not null, + created_at text not null, + updated_at text not null +); + +insert into quotes + (id, name, created_at, updated_at) +values + ('01HE2X4FKPDTVHHB6C2HZD5Z53','First quote',strftime('%Y-%m-%dT%H:%M:%fZ'),strftime('%Y-%m-%dT%H:%M:%fZ')), + ('01HE2X51WKBYSJ7ZPETRB9STCQ','Second quote',strftime('%Y-%m-%dT%H:%M:%fZ'),strftime('%Y-%m-%dT%H:%M:%fZ')), + ('01HE2X5BB7JNHNXP17H9HM7H9C','Third quote',strftime('%Y-%m-%dT%H:%M:%fZ'),strftime('%Y-%m-%dT%H:%M:%fZ')); \ No newline at end of file diff --git a/migrations/2023-10-31-123938_line_item_dates/down.sql b/migrations/2023-10-31-123938_line_item_dates/down.sql new file mode 100644 index 0000000..1b6bb4f --- /dev/null +++ b/migrations/2023-10-31-123938_line_item_dates/down.sql @@ -0,0 +1 @@ +drop table line_item_dates; diff --git a/migrations/20230413082629_line_item_dates.up.sql b/migrations/2023-10-31-123938_line_item_dates/up.sql similarity index 86% rename from migrations/20230413082629_line_item_dates.up.sql rename to migrations/2023-10-31-123938_line_item_dates/up.sql index 305ca87..00f83bf 100644 --- a/migrations/20230413082629_line_item_dates.up.sql +++ b/migrations/2023-10-31-123938_line_item_dates/up.sql @@ -9,4 +9,4 @@ create table line_item_dates ( create unique index idx_quote_id_and_date on line_item_dates (quote_id, "date"); create index idx_date on line_item_dates ("date"); -create index idx_quote_id on line_item_dates (quote_id); \ No newline at end of file +create index idx_quote_id on line_item_dates (quote_id); diff --git a/migrations/2023-10-31-123940_line_items/down.sql b/migrations/2023-10-31-123940_line_items/down.sql new file mode 100644 index 0000000..d3e3ff7 --- /dev/null +++ b/migrations/2023-10-31-123940_line_items/down.sql @@ -0,0 +1 @@ +drop table line_items; diff --git a/migrations/20230413082726_line_items.up.sql b/migrations/2023-10-31-123940_line_items/up.sql similarity index 89% rename from migrations/20230413082726_line_items.up.sql rename to migrations/2023-10-31-123940_line_items/up.sql index 0d7919f..f962e90 100644 --- a/migrations/20230413082726_line_items.up.sql +++ b/migrations/2023-10-31-123940_line_items/up.sql @@ -1,6 +1,6 @@ create table line_items ( id text not null primary key, - line_item_date_id integer not null, + line_item_date_id text not null, name text not null, description text, quantity integer not null, @@ -10,4 +10,4 @@ create table line_items ( foreign key(line_item_date_id) references line_item_dates(id) ); -create index idx_line_item_date_id on line_items (line_item_date_id); \ No newline at end of file +create index idx_line_item_date_id on line_items (line_item_date_id); diff --git a/migrations/20230408081854_quotes.down.sql b/migrations/20230408081854_quotes.down.sql deleted file mode 100644 index 76b1dc7..0000000 --- a/migrations/20230408081854_quotes.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table quotes; \ No newline at end of file diff --git a/migrations/20230408081854_quotes.up.sql b/migrations/20230408081854_quotes.up.sql deleted file mode 100644 index b89596c..0000000 --- a/migrations/20230408081854_quotes.up.sql +++ /dev/null @@ -1,13 +0,0 @@ -create table quotes ( - id text not null primary key, - name text not null, - created_at text not null, - updated_at text not null -); - -insert into quotes - (id, name, created_at, updated_at) -values - ('HLB99rYWEnVeZmzTpACXW','First quote',strftime('%Y-%m-%dT%H:%M:%fZ'),strftime('%Y-%m-%dT%H:%M:%fZ')), - ('2iCNgm7s44hn-V4ofYbMY','Second quote',strftime('%Y-%m-%dT%H:%M:%fZ'),strftime('%Y-%m-%dT%H:%M:%fZ')), - ('ez-f61kn7U5-YqpoGAdvU','Third quote',strftime('%Y-%m-%dT%H:%M:%fZ'),strftime('%Y-%m-%dT%H:%M:%fZ')); \ No newline at end of file diff --git a/migrations/20230413082629_line_item_dates.down.sql b/migrations/20230413082629_line_item_dates.down.sql deleted file mode 100644 index adbca82..0000000 --- a/migrations/20230413082629_line_item_dates.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table line_item_dates; \ No newline at end of file diff --git a/migrations/20230413082726_line_items.down.sql b/migrations/20230413082726_line_items.down.sql deleted file mode 100644 index d16b2ed..0000000 --- a/migrations/20230413082726_line_items.down.sql +++ /dev/null @@ -1 +0,0 @@ -drop table line_items; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dcdedb1..3622b8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "@hotwired/stimulus": "3.2.2", "@hotwired/turbo": "7.3.0", "npm-run-all": "4.1.5", - "parcel": "2.10.0", - "tailwindcss": "3.3.3" + "parcel": "2.10.2", + "tailwindcss": "3.3.5" } }, "node_modules/@alloc/quick-lru": { @@ -133,9 +133,9 @@ "dev": true }, "node_modules/@lezer/lr": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.13.tgz", - "integrity": "sha512-RLAbau/4uSzKgIKj96mI5WUtG1qtiR0Frn0Ei9zhPj8YOkHM+1Bb8SgdVvmR/aWJCFIzjo2KFnDiRZ75Xf5NdQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.14.tgz", + "integrity": "sha512-z5mY4LStlA3yL7aHT/rqgG614cfcvklS+8oFRFBYrs4YaWLJyKKM4+nN6KopToX0o9Hj6zmH6M5kinOYuy06ug==", "dev": true, "dependencies": { "@lezer/common": "^1.0.0" @@ -347,21 +347,21 @@ } }, "node_modules/@parcel/bundler-default": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.10.0.tgz", - "integrity": "sha512-pn8McDCuS02/D9jSo9QUTIv3tBQLlJl0PD4FvrndORXLAIFGoQR7jO4lxTSJB/eVBXwqKNVIR7WpB4sjsnBFyg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.10.2.tgz", + "integrity": "sha512-XlVGsScK5PgIFXNJ0Yx/+nHu1RFCuslCbrb8MIs0yqS790yzvyJF2QHX5WAr7Qc5powij/+2tfBHiViauWwVpA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/graph": "3.0.0", - "@parcel/plugin": "2.10.0", - "@parcel/rust": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/graph": "3.0.2", + "@parcel/plugin": "2.10.2", + "@parcel/rust": "2.10.2", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -369,14 +369,14 @@ } }, "node_modules/@parcel/cache": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.10.0.tgz", - "integrity": "sha512-4FzZpMTAAEFE65+O+Cf7f5kLLWRCiA+04IJdDYyQG5YzW1WujKXzrbh8B6tSSlw712dsQ/cUqNW4O0Q3FFKrfw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.10.2.tgz", + "integrity": "sha512-B69e5n+bBzYoaJdUOviYeUT7N1iXI3IC5G8dAxKNZ9Zgn+pjZ5BwltbfmP47+NTfQ7LqM8Ea4UJxysQsLdwb+Q==", "dev": true, "dependencies": { - "@parcel/fs": "2.10.0", - "@parcel/logger": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/fs": "2.10.2", + "@parcel/logger": "2.10.2", + "@parcel/utils": "2.10.2", "lmdb": "2.8.5" }, "engines": { @@ -387,13 +387,13 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@parcel/codeframe": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.10.0.tgz", - "integrity": "sha512-hHp457tddXEWrOgHHaA/NtkOhOAyt4mpBUzhnPbWDONLu5xeg1mu1Jffiu2rlw5xajhphrUFDWyJW0/xq1815g==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.10.2.tgz", + "integrity": "sha512-EZrYSIlVg4qiBLHRRqC/BGN2MLG0SKnw4u7kpviwz63I+v36ghqmHGOomwfn4x13nDL+EgOFz4/+Q7QpbMTKug==", "dev": true, "dependencies": { "chalk": "^4.1.0" @@ -477,16 +477,16 @@ } }, "node_modules/@parcel/compressor-raw": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.10.0.tgz", - "integrity": "sha512-TXjosh5+kNN4lxENeIZ/2ZFQKWXpXlOoHhJbW4cGPXBMHxm0eimVpnFpD8xbWxg7VCcWzbEaUTp20GQ153X+9A==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.10.2.tgz", + "integrity": "sha512-zIbtmL7vGfWkvBwD29zVdDosFR1eKHa29SpPOQXYLmDO0EVdwzYcTQq2OrlZM07o759QUqwXJfuAYxwcBNRTYg==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0" + "@parcel/plugin": "2.10.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -494,72 +494,72 @@ } }, "node_modules/@parcel/config-default": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.10.0.tgz", - "integrity": "sha512-7Ucd+KNEC08To2NrduC/yIHBN5ayedBoiuI5OVrDeKvyqUnI1S1HRqPF3DsrM8pC2PIdnwJgoOviRXwJemW28A==", - "dev": true, - "dependencies": { - "@parcel/bundler-default": "2.10.0", - "@parcel/compressor-raw": "2.10.0", - "@parcel/namer-default": "2.10.0", - "@parcel/optimizer-css": "2.10.0", - "@parcel/optimizer-htmlnano": "2.10.0", - "@parcel/optimizer-image": "2.10.0", - "@parcel/optimizer-svgo": "2.10.0", - "@parcel/optimizer-swc": "2.10.0", - "@parcel/packager-css": "2.10.0", - "@parcel/packager-html": "2.10.0", - "@parcel/packager-js": "2.10.0", - "@parcel/packager-raw": "2.10.0", - "@parcel/packager-svg": "2.10.0", - "@parcel/packager-wasm": "2.10.0", - "@parcel/reporter-dev-server": "2.10.0", - "@parcel/resolver-default": "2.10.0", - "@parcel/runtime-browser-hmr": "2.10.0", - "@parcel/runtime-js": "2.10.0", - "@parcel/runtime-react-refresh": "2.10.0", - "@parcel/runtime-service-worker": "2.10.0", - "@parcel/transformer-babel": "2.10.0", - "@parcel/transformer-css": "2.10.0", - "@parcel/transformer-html": "2.10.0", - "@parcel/transformer-image": "2.10.0", - "@parcel/transformer-js": "2.10.0", - "@parcel/transformer-json": "2.10.0", - "@parcel/transformer-postcss": "2.10.0", - "@parcel/transformer-posthtml": "2.10.0", - "@parcel/transformer-raw": "2.10.0", - "@parcel/transformer-react-refresh-wrap": "2.10.0", - "@parcel/transformer-svg": "2.10.0" + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.10.2.tgz", + "integrity": "sha512-BGn7G5MT6VXpnI5Rj8fzHT1ij0YElge3l2KVGSOJ5crho2Fmz7UKmm8kJ9kdcLrzHWOIH07T100YoQuAwKVQaA==", + "dev": true, + "dependencies": { + "@parcel/bundler-default": "2.10.2", + "@parcel/compressor-raw": "2.10.2", + "@parcel/namer-default": "2.10.2", + "@parcel/optimizer-css": "2.10.2", + "@parcel/optimizer-htmlnano": "2.10.2", + "@parcel/optimizer-image": "2.10.2", + "@parcel/optimizer-svgo": "2.10.2", + "@parcel/optimizer-swc": "2.10.2", + "@parcel/packager-css": "2.10.2", + "@parcel/packager-html": "2.10.2", + "@parcel/packager-js": "2.10.2", + "@parcel/packager-raw": "2.10.2", + "@parcel/packager-svg": "2.10.2", + "@parcel/packager-wasm": "2.10.2", + "@parcel/reporter-dev-server": "2.10.2", + "@parcel/resolver-default": "2.10.2", + "@parcel/runtime-browser-hmr": "2.10.2", + "@parcel/runtime-js": "2.10.2", + "@parcel/runtime-react-refresh": "2.10.2", + "@parcel/runtime-service-worker": "2.10.2", + "@parcel/transformer-babel": "2.10.2", + "@parcel/transformer-css": "2.10.2", + "@parcel/transformer-html": "2.10.2", + "@parcel/transformer-image": "2.10.2", + "@parcel/transformer-js": "2.10.2", + "@parcel/transformer-json": "2.10.2", + "@parcel/transformer-postcss": "2.10.2", + "@parcel/transformer-posthtml": "2.10.2", + "@parcel/transformer-raw": "2.10.2", + "@parcel/transformer-react-refresh-wrap": "2.10.2", + "@parcel/transformer-svg": "2.10.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@parcel/core": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.10.0.tgz", - "integrity": "sha512-8jvLhLC2503HIBphJe/C1qL3bfiTSw6WgIDH0e7B8EL0v7v2JYnlTZ8o9myf+bMAxzwNLiZ2uEDCri9EWbi4tQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.10.2.tgz", + "integrity": "sha512-c6hh13oYk9w5creiQ9yCz9GLQ17ZRMonULhJ46J0yoFArynVhNTJ9B5xVst7rS/chOTY8jU0jSdJuxQCR4fjkg==", "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/cache": "2.10.0", - "@parcel/diagnostic": "2.10.0", - "@parcel/events": "2.10.0", - "@parcel/fs": "2.10.0", - "@parcel/graph": "3.0.0", - "@parcel/logger": "2.10.0", - "@parcel/package-manager": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/profiler": "2.10.0", - "@parcel/rust": "2.10.0", + "@parcel/cache": "2.10.2", + "@parcel/diagnostic": "2.10.2", + "@parcel/events": "2.10.2", + "@parcel/fs": "2.10.2", + "@parcel/graph": "3.0.2", + "@parcel/logger": "2.10.2", + "@parcel/package-manager": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/profiler": "2.10.2", + "@parcel/rust": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", - "@parcel/workers": "2.10.0", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", + "@parcel/workers": "2.10.2", "abortcontroller-polyfill": "^1.1.9", "base-x": "^3.0.8", "browserslist": "^4.6.6", @@ -595,9 +595,9 @@ } }, "node_modules/@parcel/diagnostic": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.10.0.tgz", - "integrity": "sha512-ibr+sUZLc0MW75b+nThOa6YEi9QXTNYbUNCo067mtMIfhKNYTx24DaiGzDWgy1Yv49eucBaQ4u7gFI2Qa98uIA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.10.2.tgz", + "integrity": "sha512-FwtphyiV/TJEiYIRYXBOloXp7XhTW37ifRSLr7RdLbDVyn/P9q/7l0+ORlnOL+WuKwbDQtY+dXYLh/ijTsq7qQ==", "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", @@ -612,9 +612,9 @@ } }, "node_modules/@parcel/events": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.10.0.tgz", - "integrity": "sha512-mhykJBnP3BPMI6A9hLZmTtmNHZuE+HGzsF6vzmA2YBuU3/BGlQUmxdObsmwQ1O24eq0EfJVwTM+R/bdu+/nFrA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.10.2.tgz", + "integrity": "sha512-Dp8Oqh5UvSuIASfiHP8jrEtdtzzmTKiOG/RkSL3mtp2tK3mu6dZLJZbcdJXrvBTg7smtRiznkrIOJCawALC7AQ==", "dev": true, "engines": { "node": ">= 12.0.0" @@ -625,16 +625,16 @@ } }, "node_modules/@parcel/fs": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.10.0.tgz", - "integrity": "sha512-so39KdZ4o7tDekeuuQfQdbfTUvldUtzvIsuUtJMqxVOVJRZr9VjieR9GbeFhqRmi9fM5oYdzQn4lbduKdAtANA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.10.2.tgz", + "integrity": "sha512-80SXdFGDJtil9tTbWrYiZRfQ5ehMAT/dq6eY4EYcFg+MvSiwBL/4GfYMfqXn6AamuSVeQlsFCPpunFLNl9YDDA==", "dev": true, "dependencies": { - "@parcel/rust": "2.10.0", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/rust": "2.10.2", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", "@parcel/watcher": "^2.0.7", - "@parcel/workers": "2.10.0" + "@parcel/workers": "2.10.2" }, "engines": { "node": ">= 12.0.0" @@ -644,13 +644,13 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@parcel/graph": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.0.0.tgz", - "integrity": "sha512-8Lussud6gWRM3Mysu+veBRsBdSlWgkM8y7PvF8AiRwEY2eiVxZ3Rgh8o9KJau3B8R8q+lyCaUElYpbnUT6Bkiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.0.2.tgz", + "integrity": "sha512-cPxCN3+QF+5l4BJ0wnLeb3DPJarWQoD3W984CfuEYy/8Zgo2oayd31soZzkevyTYtp7H4tJKo+I79i2TJdNq5Q==", "dev": true, "dependencies": { "nullthrows": "^1.1.1" @@ -664,13 +664,13 @@ } }, "node_modules/@parcel/logger": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.10.0.tgz", - "integrity": "sha512-rDa48czGBZA313scvSEkuHSOQiGdoYLaWqInZBKtl0zke3qrgTFNG4b173H6IFdNq5KmKjafBxaV5jG87i4Gww==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.10.2.tgz", + "integrity": "sha512-5lufBuBnXDs3hjAaptmeEAxpH0eHe0+2hJvlVv5lE/RwHR7vDjh+FDwzPfCLWNM3TQhPQdZPdHcDsuA539GHcw==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/events": "2.10.0" + "@parcel/diagnostic": "2.10.2", + "@parcel/events": "2.10.2" }, "engines": { "node": ">= 12.0.0" @@ -681,9 +681,9 @@ } }, "node_modules/@parcel/markdown-ansi": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.10.0.tgz", - "integrity": "sha512-fuOuFglNANegE2nqVURwOJ/HzKM28O0hqy120Gl0NTbCAFbG34WCFxfkmVio8fondD4NcZcDj5GGv5P5TWcTIg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.10.2.tgz", + "integrity": "sha512-uZrysHjJ+0vbQNK2bhKy8yoVso8KnoW6O/SW8MiGQ4lpDJdqHShkW08wZUKr4sjl7h/WVFdNsDdgvi2/ANwoRQ==", "dev": true, "dependencies": { "chalk": "^4.1.0" @@ -767,18 +767,18 @@ } }, "node_modules/@parcel/namer-default": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.10.0.tgz", - "integrity": "sha512-N1IF6A8Y2fYz0BteU9IkhPQGezLA3cKkoSxoIOTiUf2LZPpUIuCGEAB1IgaUVNdAeMVsGw3UrdEYZg4xdMovEg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.10.2.tgz", + "integrity": "sha512-wjn3MCus0w9IOjCtQsp5fgb8hgITyxMr0OPF9cBVAhVJI1X9vvd4RurHuLJ3MjvlCqrP1en09yg3ME7VO1kPuA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -786,16 +786,16 @@ } }, "node_modules/@parcel/node-resolver-core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.1.0.tgz", - "integrity": "sha512-0KBdWIXCpnDzjoZgc1qHhgxtNe5CZ4r4+Iht+LExacXwG1A1O5qKLQE1bBAgcjqkgv1iglVuMx0kVe9G8oob8A==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.1.2.tgz", + "integrity": "sha512-xvIBgYBRQGmCkfwK/yxVSDtPEvWDVH9poQcGpKHT1jqstYju5crXro0acni5nYF0hWZu7Kttrp9G9fXJQWBksw==", "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", - "@parcel/diagnostic": "2.10.0", - "@parcel/fs": "2.10.0", - "@parcel/rust": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/fs": "2.10.2", + "@parcel/rust": "2.10.2", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1", "semver": "^7.5.2" }, @@ -823,22 +823,22 @@ } }, "node_modules/@parcel/optimizer-css": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.10.0.tgz", - "integrity": "sha512-D7hFYjJpudlbSgMlzwSbSguLehwGe4vJrQ4s83jj7z0UlHd74Vir9fd9LQTPuUErgs2SOQIPxFwLGqR6Hi+mOg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.10.2.tgz", + "integrity": "sha512-05H/Ng90TErSFZkNaUwi7gNCf2gLWi3/w07oIzHu1wjRjjKjZidqaQqZtHTEYoO9ffmhK14Xwh9q4IpOTa0sbQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.10.0", + "@parcel/utils": "2.10.2", "browserslist": "^4.6.6", "lightningcss": "^1.16.1", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -846,12 +846,12 @@ } }, "node_modules/@parcel/optimizer-htmlnano": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.10.0.tgz", - "integrity": "sha512-uYmluYpyyVumY7d/aHkGnFVLzOHoGqzVF9os/PkqVOnGEQ3qQibO3Hl9neTtm2GEUgeOfq1lrXLEfpW/k3qG1w==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.10.2.tgz", + "integrity": "sha512-9Sg2xLsfX7CPLd1AO3uVa/Kh9EROKVNHMnmNxlzmO2+LEOU/M1OHalvt4bhC7I+cNFPLN5BePdBv3QMYpO0yyA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", + "@parcel/plugin": "2.10.2", "htmlnano": "^2.0.0", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", @@ -859,7 +859,7 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -935,43 +935,43 @@ } }, "node_modules/@parcel/optimizer-image": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.10.0.tgz", - "integrity": "sha512-uR/nd3kRxiQuPxB0nP5WLlydTUwRHcpFPAY0iV12cyjCQs+MaZHrhwoDO8kWVaT7jN7WXKYcSIHeP1kvR0HEQw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.10.2.tgz", + "integrity": "sha512-X8q7mvWJEIXsEMYHYKbwIRUJvI0W41YWCEW7Ohmn0SSi+KuiO8BW5JEPKs7HboO9bX+i6Yxa/T1h9HgRXhdUug==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/rust": "2.10.0", - "@parcel/utils": "2.10.0", - "@parcel/workers": "2.10.0" + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/rust": "2.10.2", + "@parcel/utils": "2.10.2", + "@parcel/workers": "2.10.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@parcel/optimizer-svgo": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.10.0.tgz", - "integrity": "sha512-2IXClEpjlafidKAiOh/+amdDWOHGtA4Sil/3flmhLkjNFh7z2bGTYodO5xvC3Umw6N11fPNL1Wch1jn54fMO1g==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.10.2.tgz", + "integrity": "sha512-Ws+xd6nbetMCZHmRj54tIF8wYuu/JwkEvn5BotLE69l3naf2ELtsQ+PHg9G5jUa+PnSNMHhykIhBOqjxhTeq/w==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", "svgo": "^2.4.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1047,21 +1047,21 @@ } }, "node_modules/@parcel/optimizer-swc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.10.0.tgz", - "integrity": "sha512-yq17TG6uyzIbiouK57AngJa6rVwfJ8hPzgc2lqZ9LJxDX07t/5Z+k/+aq4Izy+7kQNR8kH+4asWaMXReSsXmNQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.10.2.tgz", + "integrity": "sha512-/4yMgMgLvF4yCHh0QnZlTUTpKobuFK/lNhB1i5yrtiipRaYcS+OgtakB83grfK+x1KwTbYjzXZBILwqu6GKJDQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.10.0", + "@parcel/utils": "2.10.2", "@swc/core": "^1.3.36", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1069,18 +1069,18 @@ } }, "node_modules/@parcel/package-manager": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.10.0.tgz", - "integrity": "sha512-BBUhwgX2Rz92SqGCyYp5Du4UEzm/bjrSSoeLtuRRevWKTVXhgHGbqcAlZmICoxb1lZGpn8x+pEivWd3w+5M7iA==", - "dev": true, - "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/fs": "2.10.0", - "@parcel/logger": "2.10.0", - "@parcel/node-resolver-core": "3.1.0", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", - "@parcel/workers": "2.10.0", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.10.2.tgz", + "integrity": "sha512-c91YYsIxjX3YhMvtPT7v2MpDOn/Qyw13bi1+0Ftd2JNjUZPlm8+xKizlmgvdi75dgs7dGIUVpvrGLU9LoKthCA==", + "dev": true, + "dependencies": { + "@parcel/diagnostic": "2.10.2", + "@parcel/fs": "2.10.2", + "@parcel/logger": "2.10.2", + "@parcel/node-resolver-core": "3.1.2", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", + "@parcel/workers": "2.10.2", "semver": "^7.5.2" }, "engines": { @@ -1091,7 +1091,7 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@parcel/package-manager/node_modules/semver": { @@ -1110,20 +1110,20 @@ } }, "node_modules/@parcel/packager-css": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.10.0.tgz", - "integrity": "sha512-BY1PoPPOngiJ6gFD+mUQ6YZvwDxlth8oCU9328T8kFwhmA4qL6pfIxNPI1I53Ig5f38tf1nhFkHACDCbs4MxaQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.10.2.tgz", + "integrity": "sha512-+X4dV7mBdOhXSHeg5gAkk0Qju6A1oezYIancqDC17zoFzbHUfD13nHNDOXrEfMNFVWy93lB8vLJwchH54MDMwQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.10.0", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1131,20 +1131,20 @@ } }, "node_modules/@parcel/packager-html": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.10.0.tgz", - "integrity": "sha512-EtxQwuQXQ6zrPRG9/pIdIcvuDCzBEsAnjN9kZ+XuxEYGoReX7weN4oALA6gCnw3w7U4cq6+VR1R08F6Cd8T2MQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.10.2.tgz", + "integrity": "sha512-GonfLzuzEkelJde89sq9P9LowLJrFNkuEt33nRokc1Q5TPNOWfTYb6difjuVIMr/j0c4nWlOzUrkGJsyo++F7w==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1", "posthtml": "^0.16.5" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1152,23 +1152,23 @@ } }, "node_modules/@parcel/packager-js": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.10.0.tgz", - "integrity": "sha512-9r1pv8GScZzgGempexikym9d1aehTAp0DxK71LUxBT0os9Br+nJOtV4wmJWnHapt4r108d75DcgtytdVM5nuqA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.10.2.tgz", + "integrity": "sha512-SgKJqIvMt+UJM0x3F21yBVsgdHbTnOnBrNJ7VoY3nujQX5fa+pxTf0emWuX1vSUDbBaJOmO/pC9rKwWP5enqfQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/rust": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/rust": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", "globals": "^13.2.0", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1176,16 +1176,16 @@ } }, "node_modules/@parcel/packager-raw": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.10.0.tgz", - "integrity": "sha512-fk1XGqMP38uyWC1Jqg8/Mp1x0dLxfd9GnmLHQCUZ0OSQLwF9Nqpow1WR4tC8juxYNK5haGqKyL9X5pVN4KLNYQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.10.2.tgz", + "integrity": "sha512-+/O2DeMIB9d+1+zCPOkaf2aTl2rN5TFod/UcMzG/HGFlDVqhkV9xgfwV4rV+Vso5TlyHA4p53BFgvGWQBQJAQw==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0" + "@parcel/plugin": "2.10.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1193,19 +1193,19 @@ } }, "node_modules/@parcel/packager-svg": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.10.0.tgz", - "integrity": "sha512-+vXXZwENinz/N2m04tH5BDSc8Zv7XNd/fsXZ3BAcEWmYpiTHBYMgbIy+fsdQb1tpFwku7CezthHFDsXejyNtrg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.10.2.tgz", + "integrity": "sha512-eQx3VJpuuDcen+DcLxlPn95txlnbpEH8TES+Ezym/LFyD8oQQfok/VFHy/iGoG4r1CtH0/c7lFUJE8+LZdwYmQ==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", "posthtml": "^0.16.4" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1213,16 +1213,16 @@ } }, "node_modules/@parcel/packager-wasm": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.10.0.tgz", - "integrity": "sha512-G/OsV9Xpyu1D/mTwazw4FkWlFotcFMaRmejmc6km3+qjaFxMubRBLCNMCvGw2lDIhA40qz/DpZS/kblB/FGSPA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.10.2.tgz", + "integrity": "sha512-Y/UyyOePb3WmWy2WtmXn4QLLrb7wjWL/ZhVgvhFiQft4lCbdGBGz1BiKEzhFkkN2IGdX06XZolmKCQieAM6zlQ==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0" + "@parcel/plugin": "2.10.2" }, "engines": { "node": ">=12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1230,12 +1230,12 @@ } }, "node_modules/@parcel/plugin": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.10.0.tgz", - "integrity": "sha512-FaWchkYJxLOohNNb3ah9R/9gckew+iGOzcGZ1bUtLGc/Dwz1mTVeaAanqOjlZ6C5FCe9lMctkH7h0eQsJ0mlVQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.10.2.tgz", + "integrity": "sha512-1u+GJhuqqlYjMAQLBbMExfFCbsbtuSAm6wXmMmTse5cBpFqxgsMumMeztAhcTy0oMnMhbZg2AKZV0XVSMrIgng==", "dev": true, "dependencies": { - "@parcel/types": "2.10.0" + "@parcel/types": "2.10.2" }, "engines": { "node": ">= 12.0.0" @@ -1246,13 +1246,13 @@ } }, "node_modules/@parcel/profiler": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.10.0.tgz", - "integrity": "sha512-SGkslseYA5TQOb8Z7gepi7YiIv3uH4BYAM9nwduMZrRZENcICbgTh1Pb+dp10y+6k9hFFH748eHtxJqSWARDBw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.10.2.tgz", + "integrity": "sha512-YQugGhf12u83O0RJLWbhkPV772nePPxNZjvFJmV++7buPUpgJW2m1lVOrut/s/8ZZIPqcxJe8dyxSSOtvdG7OQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/events": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/events": "2.10.2", "chrome-trace-event": "^1.0.2" }, "engines": { @@ -1264,20 +1264,20 @@ } }, "node_modules/@parcel/reporter-cli": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.10.0.tgz", - "integrity": "sha512-+OtZUdmHFgNY8+w3/U7dEZKMTtIFh7EiFw5VelKIGdvJrZNa9j7vbFuZziK6zUW2uopCk4qsDinn6Rfi7M16KA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.10.2.tgz", + "integrity": "sha512-6/cLuiGfMh1ny8ULNOXJkugIvJRVo4tV4XA3vJXH96SYqFSfiWxtHqb6MAVndBy8MezEAv0EsLqc7yR7ygdZJw==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", "chalk": "^4.1.0", "term-size": "^2.2.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1355,17 +1355,17 @@ } }, "node_modules/@parcel/reporter-dev-server": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.10.0.tgz", - "integrity": "sha512-1dMkVgbfx+AxRVjzX5on3LOY8Vhsr4wuwQdLhmN1kAveTNWUYBPSVzIt5ZPVj3Cmpwpaonj7tHkZ2YujaNWHQg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.10.2.tgz", + "integrity": "sha512-mLEcZFPpw0ixlvbT846NwmPEVv1ej7H5dwCQ3r1Ca1nQjyXkmQMM06rdb5M+/gk12WVEDOuienWqBL44Xsz3NA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0" + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1373,19 +1373,19 @@ } }, "node_modules/@parcel/reporter-tracer": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.10.0.tgz", - "integrity": "sha512-mlxF3ozH6Kys4hewG1Bze1q8wHJL1ue276Qek9xPJly8ed08wU7rPGZF0vz8fJfKT8vx+nGvnKFXYiHjF+w6bg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.10.2.tgz", + "integrity": "sha512-oreu3vIdN5u9ONSNhqypcK3nR91NoreR4B4vwD/1Rqod1ud2Vb9awJZv7QIrkdnEMmGcr5DQ/R872s7XYWeZnA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", "chrome-trace-event": "^1.0.3", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1393,17 +1393,17 @@ } }, "node_modules/@parcel/resolver-default": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.10.0.tgz", - "integrity": "sha512-KWtKrmjf/CAyZkk+SSwHhMMwN6cjJJRtUSLCvwbrlevd0onRl3erUdVYrJrNB5X+N8ylCO6Vb0wCyMegOo/OwQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.10.2.tgz", + "integrity": "sha512-ENEq8f4wRQlU7p3tCelXWK6xIsL+57q9hQ+b4eRJOEctjfN1/BguxZDh+P+fIlJ1lkqiX4UB/PUkK97uSI5XTQ==", "dev": true, "dependencies": { - "@parcel/node-resolver-core": "3.1.0", - "@parcel/plugin": "2.10.0" + "@parcel/node-resolver-core": "3.1.2", + "@parcel/plugin": "2.10.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1411,17 +1411,17 @@ } }, "node_modules/@parcel/runtime-browser-hmr": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.10.0.tgz", - "integrity": "sha512-x22HHUAFuhycE/NGowkEaR7zeZsp8PcViHkmuNkSvLboe8PJvq4BFpnd+RUj+o8EjN31p+8K2pFqS1hYAmtdwg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.10.2.tgz", + "integrity": "sha512-ABlCzDYI16lAZLTTL2g3JZasU/dWuSzRGK5paC6JhIJJwQwPeTwu4PaUoEPKeyk0iE9PzVuXjkBbGuSLXQFmmA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0" + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1429,19 +1429,19 @@ } }, "node_modules/@parcel/runtime-js": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.10.0.tgz", - "integrity": "sha512-AyDY+tQ9jiip6YsDGbaw7Azj60qG4fWNniUMIRMsywKQZOySLpfMNGHUcwDkV8j1NTve87Cwr2EzMOMnQHaUsQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.10.2.tgz", + "integrity": "sha512-a6TaMVg1Xgy+WJJ0a3sC/Taw5hkN4hmLnz00jg7G6LwoGbBpvjJn8pm4eovkMFJz13RCjmS9q0K+qZnvXh1WYA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1449,19 +1449,19 @@ } }, "node_modules/@parcel/runtime-react-refresh": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.10.0.tgz", - "integrity": "sha512-hmiK9i6iitdjfcCaI0888+pecQHA0dzf6wMKnwtJsYQxCv2TrwXPsSOMHjkKr1K3ALXi8vlauG4K0Rm7c+vfdw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.10.2.tgz", + "integrity": "sha512-9xW3g4FH9iizHWscHD2yEWJOCfYkIYMbWsZoj0EOMILqrRd1OZxHH8FbLYBQKT6swRbZI2mM19veVVBBfxco/Q==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", "react-error-overlay": "6.0.9", "react-refresh": "^0.9.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1469,18 +1469,18 @@ } }, "node_modules/@parcel/runtime-service-worker": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.10.0.tgz", - "integrity": "sha512-vi84PwAsyPI1P/5FTt1uNKjH1NGizQRdS4CmjBMz+VBT6GVuXMgZ9iQy3OYC8MsiyHlyG7mScftI74RWqw1DDg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.10.2.tgz", + "integrity": "sha512-XY1GrY4r+zu0b/pZiTflZHdk9+I3XoxpExgPcZzep5hnq2UdyXbS4yDhmen7pTcqay5U9NmRw/62YrKL+yPang==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1488,9 +1488,9 @@ } }, "node_modules/@parcel/rust": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.10.0.tgz", - "integrity": "sha512-9J7riqPI8mVlFSDphK9kVUH8nFQgeMbO/95Ycf4vaEOVE1ICQo1h18WHAy2DndmL1uSd/UTimirrP6yLt/I3KA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.10.2.tgz", + "integrity": "sha512-v/Cyf3iXlzSc6vgvPiEZzqdKAZ1jJ/aZX7y1YSupDh3RoqJI2bZ93kAOyEi+S7P3kshJkQM0px3YveJFOAMUOA==", "dev": true, "engines": { "node": ">= 12.0.0" @@ -1513,15 +1513,15 @@ } }, "node_modules/@parcel/transformer-babel": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.10.0.tgz", - "integrity": "sha512-XwlzHt7WPfueFlwl/bXItopgZ6ILSPzl5OmPeytHrM2TanymeLjJ1y3vxwY1C1BhNlrTwPHcf9U8aiuVSpE8RQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.10.2.tgz", + "integrity": "sha512-lmuksSzEBdPL1nVTznsQi5hQ+4mJ7GP+jvOv/Tvx3MjnzIu1G6Fs5MvNpAwBRXmG/F1+0aw/Wa8J38HYfN05dA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.10.0", + "@parcel/utils": "2.10.2", "browserslist": "^4.6.6", "json5": "^2.2.0", "nullthrows": "^1.1.1", @@ -1529,7 +1529,7 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1552,22 +1552,22 @@ } }, "node_modules/@parcel/transformer-css": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.10.0.tgz", - "integrity": "sha512-hITticpUE/qilpsTc7HQP04qhXwyUSKGZKgcFnvf8+BJO/LoclbVK1nzbR61eYl5Jhj1XB67p3tCt5fSvPhOsQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.10.2.tgz", + "integrity": "sha512-WxKe1YherQrX0vEfxAsBALEIsztGStmfXF0GAMeynE4q/w1iHQdTzu29tqLrJY7x532Ric8TxnwO8zR0r89DJg==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.10.0", + "@parcel/utils": "2.10.2", "browserslist": "^4.6.6", "lightningcss": "^1.16.1", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1575,14 +1575,14 @@ } }, "node_modules/@parcel/transformer-html": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.10.0.tgz", - "integrity": "sha512-rc8YKjB+bE7yGHOf674CSzW8ii+m5caBo4akdRIUdhEHJS4FnSwxYIZlMcfV9pZM4Tj5PFMZyrlAHad6YrO8aA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.10.2.tgz", + "integrity": "sha512-Zkg1HHdYp14ecdtNF+s4d/e1lr8/PAQgBTYhyEVLVC1N7uivjjZ9XClxZlHuZImbQvX3q3PgZS+PocIizhY4rQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/rust": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/rust": "2.10.2", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", "posthtml-parser": "^0.10.1", @@ -1592,7 +1592,7 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1615,36 +1615,36 @@ } }, "node_modules/@parcel/transformer-image": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.10.0.tgz", - "integrity": "sha512-qbNyAJvzqdO/OnHhCOoPAZN5aBD/xphyXvDNI0Fb3UPEr5MQtAnzv2lS1I63s4rKpphBntWj7nEIAio6s7c5bw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.10.2.tgz", + "integrity": "sha512-sR2kTsPykYRujKR7ISn0d6Fhem1pMQoqm0cFTrtC9Te5pfIjZ72NfM9clP7jPK660Gd2DYudhUa48y+qKBfCAw==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", - "@parcel/workers": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", + "@parcel/workers": "2.10.2", "nullthrows": "^1.1.1" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@parcel/transformer-js": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.10.0.tgz", - "integrity": "sha512-39ZNnje8dlmME1ipjFyAFHyhHaGCwZZpXYN9SCTl/+AnjZLamnmVFkesgBbrRSBRQixRG1VwCvrWsjLLeLkTUg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.10.2.tgz", + "integrity": "sha512-qcVLyikhSVf3oHhzReECkKdPU5uHVH4L0TC5O9ahlsq2IUTqR8Swq+9wUgUN0S2aYFTWreH05bQwBCNrLzF/eQ==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/rust": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/rust": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/utils": "2.10.0", - "@parcel/workers": "2.10.0", + "@parcel/utils": "2.10.2", + "@parcel/workers": "2.10.2", "@swc/helpers": "^0.5.0", "browserslist": "^4.6.6", "nullthrows": "^1.1.1", @@ -1653,14 +1653,14 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@parcel/transformer-js/node_modules/semver": { @@ -1679,17 +1679,17 @@ } }, "node_modules/@parcel/transformer-json": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.10.0.tgz", - "integrity": "sha512-4G6ZIt7IYu1l3BlsL55Hi3869X6KHE0CHybWf364h5ZUmzo3Xpc5i7cziQX+IhWDo1qn1jiziOPGY85LXlo8ug==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.10.2.tgz", + "integrity": "sha512-iVgwuaLNqH3jgoBzMds63zd9FULvYb/s/5Hq9JZJ6pCZrOQoPruurgAW8A/t2IE4CSFkDDNoFvRpjsq1WBsSvA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", + "@parcel/plugin": "2.10.2", "json5": "^2.2.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1697,15 +1697,15 @@ } }, "node_modules/@parcel/transformer-postcss": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.10.0.tgz", - "integrity": "sha512-Xhz+MHr9Q31d3u3hsBOtmFGEQx7FsNbTumGpqIqaGkDDq4IIMKbEwyrpkmf7/02kyxcbwr6uaBqnMHm55j10sQ==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.10.2.tgz", + "integrity": "sha512-2/ehCZgj5TOmsAIeGiLwrm6gO/M+X4fZ/O71MhpmXd8zr08j25T0VdSdw5UyopsBvtPYM7DI/FJCviZc7AigCg==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/rust": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/rust": "2.10.2", + "@parcel/utils": "2.10.2", "clone": "^2.1.1", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", @@ -1713,7 +1713,7 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1736,13 +1736,13 @@ } }, "node_modules/@parcel/transformer-posthtml": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.10.0.tgz", - "integrity": "sha512-kmz8Yip5hh2y3bfA76mC2QtI9VHdS7k5dV96/yjar0CkLHJnr33Jh7MTfuCN+01nVU20Tn3YMqEMQ/ErPVJwlg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.10.2.tgz", + "integrity": "sha512-0jvqqXfrLqPYBD62aWIMldDnZ9hO/esX6TGKNhAO+85ljeaS2+QZ5XLLb8uPJq8UXB4olhsoEGyGtJSByigndg==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", "posthtml-parser": "^0.10.1", @@ -1751,7 +1751,7 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1774,16 +1774,16 @@ } }, "node_modules/@parcel/transformer-raw": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.10.0.tgz", - "integrity": "sha512-1tR58kqzTh4baLq/++bp84H2lhOoAz8cJeJykgsYImva7aRWcjlTppNKjBF6Ef8etIRMPZOozTdbS53VdQ9IbA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.10.2.tgz", + "integrity": "sha512-h6SoIZ3u+Lq8z8SEEAVsHg4IQbUtkBWCln5SG4qfjGiclUDDA2hcG7grsP06Wb6/U7oEc8n0ksTtaG4dekYIxw==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0" + "@parcel/plugin": "2.10.2" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1791,18 +1791,18 @@ } }, "node_modules/@parcel/transformer-react-refresh-wrap": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.10.0.tgz", - "integrity": "sha512-4ab1tiwUA2XznTh/eb/IVKEA+Ynkbqc5sgNuobf1MLKF82FXTUT5szVshff/ODpwublvVBD3YbXlapxV5xyFvA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.10.2.tgz", + "integrity": "sha512-1jpzaEbKwJnDUmF8Kgf3/XvT9BnUWIQ7FWkg5EL5kEx6tq2KLKdzD17nFigNj8fr2V+faX0Qa63h+e3OOpnMAA==", "dev": true, "dependencies": { - "@parcel/plugin": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/plugin": "2.10.2", + "@parcel/utils": "2.10.2", "react-refresh": "^0.9.0" }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1810,14 +1810,14 @@ } }, "node_modules/@parcel/transformer-svg": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.10.0.tgz", - "integrity": "sha512-qEZFk4gxyVNhm2V8R3YLo9qCyYNVBySWmZLjmwuhLLmAE+r0qGebc9oXyo7C6ML5d/4Tfj6NriCOeX+HMhPVxw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.10.2.tgz", + "integrity": "sha512-SsCjiM9LZwGne3LUn+GuwhyqklAnr7CER6D0ozdpw+tPOeODsXZXNSktvtpE1Qbia61c/zdlU0yOEuhkeXz29w==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/plugin": "2.10.0", - "@parcel/rust": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/plugin": "2.10.2", + "@parcel/rust": "2.10.2", "nullthrows": "^1.1.1", "posthtml": "^0.16.5", "posthtml-parser": "^0.10.1", @@ -1826,7 +1826,7 @@ }, "engines": { "node": ">= 12.0.0", - "parcel": "^2.10.0" + "parcel": "^2.10.2" }, "funding": { "type": "opencollective", @@ -1849,31 +1849,31 @@ } }, "node_modules/@parcel/types": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.10.0.tgz", - "integrity": "sha512-iDFVvgN+jK02GY++V+WY3WuNTM6CGDPToGfL31/Sgf6/1PzT7kL6uXJ6+859u8wkTIrtkWD2XyTNkKJJ8jPwgg==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.10.2.tgz", + "integrity": "sha512-fwHJu03ROcc4/Kr/00VfOQUD6aV+6FBLN5bDW1+Xblgrpkb1MSUGTWRuz0YH5X6xhkVigC1llCIR2uHSwA+YBg==", "dev": true, "dependencies": { - "@parcel/cache": "2.10.0", - "@parcel/diagnostic": "2.10.0", - "@parcel/fs": "2.10.0", - "@parcel/package-manager": "2.10.0", + "@parcel/cache": "2.10.2", + "@parcel/diagnostic": "2.10.2", + "@parcel/fs": "2.10.2", + "@parcel/package-manager": "2.10.2", "@parcel/source-map": "^2.1.1", - "@parcel/workers": "2.10.0", + "@parcel/workers": "2.10.2", "utility-types": "^3.10.0" } }, "node_modules/@parcel/utils": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.10.0.tgz", - "integrity": "sha512-8qx9caJTjli6UKpKlcPjdSBblkwTc+BnIsSK3/7fX7kbtHLmEkQH/RWZbbOJItHbnzlsmaDJTfS7j6rrcFw2Pw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.10.2.tgz", + "integrity": "sha512-XLUhTh0UkPB5n8r7agX9iIz9f+3JsBIVsmqltsJYX7n/GAa6EQtqrIYyZu8cEFeZlZw3zaf7wTmf9xJppdlj7Q==", "dev": true, "dependencies": { - "@parcel/codeframe": "2.10.0", - "@parcel/diagnostic": "2.10.0", - "@parcel/logger": "2.10.0", - "@parcel/markdown-ansi": "2.10.0", - "@parcel/rust": "2.10.0", + "@parcel/codeframe": "2.10.2", + "@parcel/diagnostic": "2.10.2", + "@parcel/logger": "2.10.2", + "@parcel/markdown-ansi": "2.10.2", + "@parcel/rust": "2.10.2", "@parcel/source-map": "^2.1.1", "chalk": "^4.1.0", "nullthrows": "^1.1.1" @@ -2231,16 +2231,16 @@ } }, "node_modules/@parcel/workers": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.10.0.tgz", - "integrity": "sha512-PILDag4aW7G9w2AvYvBsMHe/NRCoOt+L7HJzp6UIvy6ssbafH/8fzdGjSpA99GXzC5AXpAHVt8RXhGMXmMP6QA==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.10.2.tgz", + "integrity": "sha512-LvifdeORXKGGyhwOwnYxn1AsJ5u6Ihk2RJUxsVA4WYEjz2PSsmLAUDdp48ovssSMnTb9P2g4RrbEG1mJjYtBGA==", "dev": true, "dependencies": { - "@parcel/diagnostic": "2.10.0", - "@parcel/logger": "2.10.0", - "@parcel/profiler": "2.10.0", - "@parcel/types": "2.10.0", - "@parcel/utils": "2.10.0", + "@parcel/diagnostic": "2.10.2", + "@parcel/logger": "2.10.2", + "@parcel/profiler": "2.10.2", + "@parcel/types": "2.10.2", + "@parcel/utils": "2.10.2", "nullthrows": "^1.1.1" }, "engines": { @@ -2251,13 +2251,13 @@ "url": "https://opencollective.com/parcel" }, "peerDependencies": { - "@parcel/core": "^2.10.0" + "@parcel/core": "^2.10.2" } }, "node_modules/@swc/core": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.93.tgz", - "integrity": "sha512-690GRr1wUGmGYZHk7fUduX/JUwViMF2o74mnZYIWEcJaCcd9MQfkhsxPBtjeg6tF+h266/Cf3RPYhsFBzzxXcA==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.95.tgz", + "integrity": "sha512-PMrNeuqIusq9DPDooV3FfNEbZuTu5jKAc04N3Hm6Uk2Fl49cqElLFQ4xvl4qDmVDz97n3n/C1RE0/f6WyGPEiA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2272,16 +2272,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.93", - "@swc/core-darwin-x64": "1.3.93", - "@swc/core-linux-arm-gnueabihf": "1.3.93", - "@swc/core-linux-arm64-gnu": "1.3.93", - "@swc/core-linux-arm64-musl": "1.3.93", - "@swc/core-linux-x64-gnu": "1.3.93", - "@swc/core-linux-x64-musl": "1.3.93", - "@swc/core-win32-arm64-msvc": "1.3.93", - "@swc/core-win32-ia32-msvc": "1.3.93", - "@swc/core-win32-x64-msvc": "1.3.93" + "@swc/core-darwin-arm64": "1.3.95", + "@swc/core-darwin-x64": "1.3.95", + "@swc/core-linux-arm-gnueabihf": "1.3.95", + "@swc/core-linux-arm64-gnu": "1.3.95", + "@swc/core-linux-arm64-musl": "1.3.95", + "@swc/core-linux-x64-gnu": "1.3.95", + "@swc/core-linux-x64-musl": "1.3.95", + "@swc/core-win32-arm64-msvc": "1.3.95", + "@swc/core-win32-ia32-msvc": "1.3.95", + "@swc/core-win32-x64-msvc": "1.3.95" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -2293,9 +2293,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.93.tgz", - "integrity": "sha512-gEKgk7FVIgltnIfDO6GntyuQBBlAYg5imHpRgLxB1zSI27ijVVkksc6QwISzFZAhKYaBWIsFSVeL9AYSziAF7A==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.95.tgz", + "integrity": "sha512-VAuBAP3MNetO/yBIBzvorUXq7lUBwhfpJxYViSxyluMwtoQDhE/XWN598TWMwMl1ZuImb56d7eUsuFdjgY7pJw==", "cpu": [ "arm64" ], @@ -2309,9 +2309,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.93.tgz", - "integrity": "sha512-ZQPxm/fXdDQtn3yrYSL/gFfA8OfZ5jTi33yFQq6vcg/Y8talpZ+MgdSlYM0FkLrZdMTYYTNFiuBQuuvkA+av+Q==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.95.tgz", + "integrity": "sha512-20vF2rvUsN98zGLZc+dsEdHvLoCuiYq/1B+TDeE4oolgTFDmI1jKO+m44PzWjYtKGU9QR95sZ6r/uec0QC5O4Q==", "cpu": [ "x64" ], @@ -2325,9 +2325,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.93.tgz", - "integrity": "sha512-OYFMMI2yV+aNe3wMgYhODxHdqUB/jrK0SEMHHS44GZpk8MuBXEF+Mcz4qjkY5Q1EH7KVQqXb/gVWwdgTHpjM2A==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.95.tgz", + "integrity": "sha512-oEudEM8PST1MRNGs+zu0cx5i9uP8TsLE4/L9HHrS07Ck0RJ3DCj3O2fU832nmLe2QxnAGPwBpSO9FntLfOiWEQ==", "cpu": [ "arm" ], @@ -2341,9 +2341,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.93.tgz", - "integrity": "sha512-BT4dT78odKnJMNiq5HdjBsv29CiIdcCcImAPxeFqAeFw1LL6gh9nzI8E96oWc+0lVT5lfhoesCk4Qm7J6bty8w==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.95.tgz", + "integrity": "sha512-pIhFI+cuC1aYg+0NAPxwT/VRb32f2ia8oGxUjQR6aJg65gLkUYQzdwuUmpMtFR2WVf7WVFYxUnjo4UyMuyh3ng==", "cpu": [ "arm64" ], @@ -2357,9 +2357,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.93.tgz", - "integrity": "sha512-yH5fWEl1bktouC0mhh0Chuxp7HEO4uCtS/ly1Vmf18gs6wZ8DOOkgAEVv2dNKIryy+Na++ljx4Ym7C8tSJTrLw==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.95.tgz", + "integrity": "sha512-ZpbTr+QZDT4OPJfjPAmScqdKKaT+wGurvMU5AhxLaf85DuL8HwUwwlL0n1oLieLc47DwIJEMuKQkYhXMqmJHlg==", "cpu": [ "arm64" ], @@ -2373,9 +2373,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.93.tgz", - "integrity": "sha512-OFUdx64qvrGJhXKEyxosHxgoUVgba2ztYh7BnMiU5hP8lbI8G13W40J0SN3CmFQwPP30+3oEbW7LWzhKEaYjlg==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.95.tgz", + "integrity": "sha512-n9SuHEFtdfSJ+sHdNXNRuIOVprB8nbsz+08apKfdo4lEKq6IIPBBAk5kVhPhkjmg2dFVHVo4Tr/OHXM1tzWCCw==", "cpu": [ "x64" ], @@ -2389,9 +2389,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.93.tgz", - "integrity": "sha512-4B8lSRwEq1XYm6xhxHhvHmKAS7pUp1Q7E33NQ2TlmFhfKvCOh86qvThcjAOo57x8DRwmpvEVrqvpXtYagMN6Ig==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.95.tgz", + "integrity": "sha512-L1JrVlsXU3LC0WwmVnMK9HrOT2uhHahAoPNMJnZQpc18a0paO9fqifPG8M/HjNRffMUXR199G/phJsf326UvVg==", "cpu": [ "x64" ], @@ -2405,9 +2405,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.93.tgz", - "integrity": "sha512-BHShlxtkven8ZjjvZ5QR6sC5fZCJ9bMujEkiha6W4cBUTY7ce7qGFyHmQd+iPC85d9kD/0cCiX/Xez8u0BhO7w==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.95.tgz", + "integrity": "sha512-YaP4x/aZbUyNdqCBpC2zL8b8n58MEpOUpmOIZK6G1SxGi+2ENht7gs7+iXpWPc0sy7X3YPKmSWMAuui0h8lgAA==", "cpu": [ "arm64" ], @@ -2421,9 +2421,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.93.tgz", - "integrity": "sha512-nEwNWnz4JzYAK6asVvb92yeylfxMYih7eMQOnT7ZVlZN5ba9WF29xJ6kcQKs9HRH6MvWhz9+wRgv3FcjlU6HYA==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.95.tgz", + "integrity": "sha512-w0u3HI916zT4BC/57gOd+AwAEjXeUlQbGJ9H4p/gzs1zkSHtoDQghVUNy3n/ZKp9KFod/95cA8mbVF9t1+6epQ==", "cpu": [ "ia32" ], @@ -2437,9 +2437,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.93", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.93.tgz", - "integrity": "sha512-jibQ0zUr4kwJaQVwgmH+svS04bYTPnPw/ZkNInzxS+wFAtzINBYcU8s2PMWbDb2NGYiRSEeoSGyAvS9H+24JFA==", + "version": "1.3.95", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.95.tgz", + "integrity": "sha512-5RGnMt0S6gg4Gc6QtPUJ3Qs9Un4sKqccEzgH/tj7V/DVTJwKdnBKxFZfgQ34OR2Zpz7zGOn889xwsFVXspVWNA==", "cpu": [ "x64" ], @@ -2672,9 +2672,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001551", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz", - "integrity": "sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg==", + "version": "1.0.30001559", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001559.tgz", + "integrity": "sha512-cPiMKZgqgkg5LY3/ntGeLFUpi6tzddBNS58A4tnTgQw1zON7u2sZMU7SzOeVH4tj20++9ggL+V6FDOFMTaFFYA==", "dev": true, "funding": [ { @@ -3113,9 +3113,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.559", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz", - "integrity": "sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==", + "version": "1.4.574", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz", + "integrity": "sha512-bg1m8L0n02xRzx4LsTTMbBPiUd9yIR+74iPtS/Ao65CuXvhVZHP0ym1kSdDG3yHFDXqHQQBKujlN1AQ8qZnyFg==", "dev": true }, "node_modules/entities": { @@ -3237,9 +3237,9 @@ } }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3554,9 +3554,9 @@ "dev": true }, "node_modules/htmlnano": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.0.4.tgz", - "integrity": "sha512-WGCkyGFwjKW1GeCBsPYacMvaMnZtFJ0zIRnC2NCddkA+IOEhTqskXrS7lep+3yYZw/nQ3dW1UAX4yA/GJyR8BA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.0.tgz", + "integrity": "sha512-jVGRE0Ep9byMBKEu0Vxgl8dhXYOUk0iNQ2pjsG+BcRB0u0oDF5A9p/iBGMg/PGKYUyMD0OAGu8dVT5Lzj8S58g==", "dev": true, "dependencies": { "cosmiconfig": "^8.0.0", @@ -3923,9 +3923,9 @@ "dev": true }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -4564,22 +4564,22 @@ "dev": true }, "node_modules/parcel": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.10.0.tgz", - "integrity": "sha512-YJmWEsiv1ClpPcJiWkr3gFj40sRvfeK89GGGwJjpzQMQsBmN6h6OHrSkByx0jrsPIvdsOIccU702upYpRAypuw==", - "dev": true, - "dependencies": { - "@parcel/config-default": "2.10.0", - "@parcel/core": "2.10.0", - "@parcel/diagnostic": "2.10.0", - "@parcel/events": "2.10.0", - "@parcel/fs": "2.10.0", - "@parcel/logger": "2.10.0", - "@parcel/package-manager": "2.10.0", - "@parcel/reporter-cli": "2.10.0", - "@parcel/reporter-dev-server": "2.10.0", - "@parcel/reporter-tracer": "2.10.0", - "@parcel/utils": "2.10.0", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.10.2.tgz", + "integrity": "sha512-wRvsK9v12Nt2/EIjLp/uvxd3UeRSN9DRoSofDn21Ot+rEw4e98ODvbdSHi6dYr82s4oo6mF823ACmOp1hXd4wg==", + "dev": true, + "dependencies": { + "@parcel/config-default": "2.10.2", + "@parcel/core": "2.10.2", + "@parcel/diagnostic": "2.10.2", + "@parcel/events": "2.10.2", + "@parcel/fs": "2.10.2", + "@parcel/logger": "2.10.2", + "@parcel/package-manager": "2.10.2", + "@parcel/reporter-cli": "2.10.2", + "@parcel/reporter-dev-server": "2.10.2", + "@parcel/reporter-tracer": "2.10.2", + "@parcel/utils": "2.10.2", "chalk": "^4.1.0", "commander": "^7.0.0", "get-port": "^4.2.0" @@ -5438,9 +5438,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", - "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz", + "integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5448,10 +5448,10 @@ "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.12", + "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.18.2", + "jiti": "^1.19.1", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", diff --git a/package.json b/package.json index ad3b28b..6dd388c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@hotwired/turbo": "7.3.0", "@hotwired/stimulus": "3.2.2", "npm-run-all": "4.1.5", - "parcel": "2.10.0", - "tailwindcss": "3.3.3" + "parcel": "2.10.2", + "tailwindcss": "3.3.5" } } diff --git a/src/currency.rs b/src/currency.rs index e9fc195..a2fcc61 100644 --- a/src/currency.rs +++ b/src/currency.rs @@ -1,28 +1,5 @@ -use currency_rs::Currency; use once_cell::sync::Lazy; use regex::Regex; -use rusqlite::{ - types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}, - Result, -}; pub(crate) static FORM_CURRENCY_REGEX: Lazy = Lazy::new(|| Regex::new(r"^\d*(\.\d{2})?$").unwrap()); - -pub(crate) struct CurrencySql(pub(crate) Currency); - -impl FromSql for CurrencySql { - fn column_result(value: ValueRef) -> FromSqlResult { - match value { - ValueRef::Integer(x) => Ok(CurrencySql(Currency::new_float(x as f64, None))), - ValueRef::Real(x) => Ok(CurrencySql(Currency::new_float(x, None))), - _ => Err(FromSqlError::InvalidType), - } - } -} - -impl ToSql for CurrencySql { - fn to_sql(&self) -> Result { - Ok(ToSqlOutput::from(self.0.value())) - } -} diff --git a/src/line_item_dates/controller.rs b/src/line_item_dates/controller.rs index e7b0094..3b7a68c 100644 --- a/src/line_item_dates/controller.rs +++ b/src/line_item_dates/controller.rs @@ -12,22 +12,22 @@ use axum::{ http::StatusCode, response::{Html, IntoResponse}, }; +use diesel::prelude::SqliteConnection; +use diesel::r2d2::{ConnectionManager, Pool}; use hotwire_turbo_axum::TurboStream; -use r2d2::Pool; -use r2d2_sqlite::SqliteConnectionManager; use std::time::Instant; use tracing::info; use validator::Validate; pub(crate) async fn new( - State(pool): State>, + State(pool): State>>, Path(quote_id): Path, ) -> Result { let start = Instant::now(); let quote = quotes::query::read(&pool, quote_id).await?; let duration = start.elapsed().as_micros(); info!("quo - read duration: {duration} μs"); - let line_item_date = LineItemDatePresenter::from_quote(quote); + let line_item_date = LineItemDatePresenter::from_quote_with_total(quote); let duration = start.elapsed().as_micros(); info!("lid - read duration: {duration} μs"); Ok(Html( @@ -42,7 +42,7 @@ pub(crate) async fn new( } pub(crate) async fn create( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let result = form.validate(); @@ -84,7 +84,7 @@ pub(crate) async fn create( } pub(crate) async fn edit( - State(pool): State>, + State(pool): State>>, Path(id): Path, ) -> Result { let start = Instant::now(); @@ -104,7 +104,7 @@ pub(crate) async fn edit( } pub(crate) async fn update( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let result = form.validate(); @@ -154,7 +154,7 @@ pub(crate) async fn update( } pub(crate) async fn delete( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let start = Instant::now(); diff --git a/src/line_item_dates/model.rs b/src/line_item_dates/model.rs index 0a50d20..ddcf5e7 100644 --- a/src/line_item_dates/model.rs +++ b/src/line_item_dates/model.rs @@ -1,13 +1,15 @@ use crate::{ - quotes::model::Quote, + quotes::model::QuoteWithTotal, + schema::line_item_dates, time::{long_form, parse_date, short_form, DATE_REGEX}, }; -use nanoid::nanoid; +use diesel::prelude::*; use serde::Deserialize; use time::{Date, OffsetDateTime}; +use ulid::Ulid; use validator::Validate; -#[derive(Debug)] +#[derive(Debug, Insertable, Queryable)] pub struct LineItemDate { pub id: String, pub quote_id: String, @@ -20,7 +22,7 @@ impl From<&LineItemDateForm> for LineItemDate { fn from(value: &LineItemDateForm) -> Self { let date = parse_date(&value.date); LineItemDate { - id: value.id.clone().unwrap_or(nanoid!()), + id: value.id.clone().unwrap_or(Ulid::new().to_string()), quote_id: value.quote_id.clone(), date, created_at: OffsetDateTime::now_utc(), @@ -47,7 +49,7 @@ pub struct LineItemDatePresenter { } impl LineItemDatePresenter { - pub fn from_quote(quote: Quote) -> LineItemDatePresenter { + pub fn from_quote_with_total(quote: QuoteWithTotal) -> LineItemDatePresenter { LineItemDatePresenter { quote_id: quote.id, ..Default::default() diff --git a/src/line_item_dates/query.rs b/src/line_item_dates/query.rs index 1dca840..e8ae1e2 100644 --- a/src/line_item_dates/query.rs +++ b/src/line_item_dates/query.rs @@ -1,159 +1,102 @@ use crate::{ line_item_dates::model::{LineItemDate, LineItemDateForm}, line_items, - time::{DateSql, DateTimeSql}, + schema::line_item_dates, Result, }; -use r2d2::{Pool, PooledConnection}; -use r2d2_sqlite::SqliteConnectionManager; -use rusqlite::{Row, Transaction}; +use diesel::prelude::*; +use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; pub(crate) async fn all>( - pool: &Pool, - quote_id: S, + pool: &Pool>, + id: S, ) -> Result> { - // language=SQL - let sql = r#" - select - id, - quote_id, - date, - created_at, - updated_at - from line_item_dates - where quote_id = ? - order by date - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - let records = statement - .query_map(["e_id.as_ref()], map_result) - .unwrap() - .map(|result| result.unwrap()) - .collect(); + let mut connection = pool.get()?; + let records = line_item_dates::table + .filter(line_item_dates::quote_id.eq(&id.as_ref())) + .get_results(&mut connection)?; Ok(records) } pub(crate) async fn read>( - pool: &Pool, + pool: &Pool>, id: S, ) -> Result { - let connection = pool.get()?; - read_from_connection(&connection, id) + let mut connection = pool.get()?; + read_from_connection(&mut connection, id) } fn read_from_connection>( - connection: &PooledConnection, + connection: &mut PooledConnection>, id: S, ) -> Result { - // language=SQL - let sql = r#" - select - id, - quote_id, - date, - created_at, - updated_at - from line_item_dates - where id = ? - "#; - let mut statement = connection.prepare_cached(sql)?; - let record = statement.query_row([id.as_ref()], map_result)?; + let record = line_item_dates::table + .filter(line_item_dates::id.eq(&id.as_ref())) + .get_result(connection)?; Ok(record) } pub(crate) async fn insert( - pool: &Pool, + pool: &Pool>, form: &LineItemDateForm, ) -> Result { let record: LineItemDate = form.into(); - // language=SQL - let sql = r#" - insert into line_item_dates - (id, quote_id, "date", created_at, updated_at) - values - (?, ?, ?, ?, ?); - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - statement.execute(( - &record.id, - &record.quote_id, - &DateSql(record.date), - &DateTimeSql(record.created_at), - &DateTimeSql(record.updated_at), - ))?; + + let mut connection = pool.get()?; + diesel::dsl::insert_into(line_item_dates::table) + .values(&record) + .execute(&mut connection)?; Ok(record) } pub(crate) async fn update( - pool: &Pool, + pool: &Pool>, form: &LineItemDateForm, ) -> Result { let record: LineItemDate = form.into(); - // language=SQL - let sql = r#" - update line_item_dates - set "date" = ?, - updated_at = ? - where id = ?; - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - statement.execute(( - &DateSql(record.date), - &DateTimeSql(record.updated_at), - &record.id, - ))?; - read_from_connection(&connection, &record.id) + + let mut connection = pool.get()?; + diesel::dsl::update(line_item_dates::table) + .set(( + line_item_dates::date.eq(&record.date), + line_item_dates::updated_at.eq(&record.updated_at), + )) + .filter(line_item_dates::id.eq(&record.id)) + .execute(&mut connection)?; + + read_from_connection(&mut connection, &record.id) } pub(crate) async fn delete>( - pool: &Pool, + pool: &Pool>, id: S, ) -> Result { - let record = read(pool, &id).await?; let mut connection = pool.get()?; - let mut tx = connection.transaction()?; - line_items::query::delete_all_for_date(&mut tx, &id)?; - delete_line_item_date(&mut tx, &id)?; - tx.commit()?; - Ok(record) -} -fn delete_line_item_date>(tx: &mut Transaction<'_>, id: S) -> Result { - // language=SQL - let sql = r#"delete from line_item_dates where id = ?"#; - let mut statement = tx.prepare_cached(sql)?; - statement.execute([&id.as_ref()])?; - Ok(()) + let record = read_from_connection(&mut connection, &id)?; + _ = connection.transaction::<_, _, _>(|tx| { + line_items::query::delete_all_for_date(tx, &id)?; + + _ = diesel::dsl::delete(line_item_dates::table) + .filter(line_item_dates::id.eq(&id.as_ref())) + .execute(tx)?; + + Ok::<(), crate::error::AppError>(()) + }); + + Ok(record) } -pub(crate) fn delete_all_for_quote>(tx: &mut Transaction<'_>, id: S) -> Result { - // language=SQL - let sql = r#" - delete from line_item_dates - where id in ( - select - id - from line_item_dates - where quote_id = ? - ); - "#; +pub(crate) fn delete_all_for_quote>( + tx: &mut PooledConnection>, + id: S, +) -> Result { line_items::query::delete_all_for_quote(tx, &id)?; - let mut statement = tx.prepare_cached(sql)?; - statement.execute([&id.as_ref()])?; - Ok(()) -} -#[inline] -fn map_result(row: &Row<'_>) -> rusqlite::Result { - Ok(LineItemDate { - id: row.get(0)?, - quote_id: row.get(1)?, - date: row.get::<_, DateSql>(2)?.0, - created_at: row.get::<_, DateTimeSql>(3)?.0, - updated_at: row.get::<_, DateTimeSql>(4)?.0, - }) + _ = diesel::dsl::delete(line_item_dates::table) + .filter(line_item_dates::quote_id.eq(&id.as_ref())) + .execute(tx)?; + + Ok(()) } diff --git a/src/line_items/controller.rs b/src/line_items/controller.rs index e484714..85d6190 100644 --- a/src/line_items/controller.rs +++ b/src/line_items/controller.rs @@ -12,15 +12,15 @@ use axum::{ http::StatusCode, response::{Html, IntoResponse}, }; +use diesel::prelude::SqliteConnection; +use diesel::r2d2::{ConnectionManager, Pool}; use hotwire_turbo_axum::TurboStream; -use r2d2::Pool; -use r2d2_sqlite::SqliteConnectionManager; use std::time::Instant; use tracing::info; use validator::Validate; pub(crate) async fn new( - State(pool): State>, + State(pool): State>>, Path(line_item_date_id): Path, ) -> Result { let start = Instant::now(); @@ -39,7 +39,7 @@ pub(crate) async fn new( } pub(crate) async fn create( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let result = form.validate(); @@ -88,7 +88,7 @@ pub(crate) async fn create( } pub(crate) async fn edit( - State(pool): State>, + State(pool): State>>, Path(id): Path, ) -> Result { let start = Instant::now(); @@ -115,7 +115,7 @@ pub(crate) async fn edit( } pub(crate) async fn update( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let result = form.validate(); @@ -128,6 +128,7 @@ pub(crate) async fn update( let start = Instant::now(); let quote = quotes::query::from_line_item_date_id(&pool, &form.line_item_date_id).await?; + info!("Quote total after update: {}", quote.total); let duration = start.elapsed().as_micros(); info!("quo - read duration: {duration} μs"); Ok(TurboStream( @@ -166,7 +167,7 @@ pub(crate) async fn update( } pub(crate) async fn delete( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let start = Instant::now(); diff --git a/src/line_items/model.rs b/src/line_items/model.rs index 98862d0..289554b 100644 --- a/src/line_items/model.rs +++ b/src/line_items/model.rs @@ -1,17 +1,19 @@ use crate::currency::FORM_CURRENCY_REGEX; +use crate::schema::line_items; use currency_rs::Currency; -use nanoid::nanoid; +use diesel::prelude::*; use serde::Deserialize; use time::OffsetDateTime; +use ulid::Ulid; use validator::Validate; -#[derive(Debug)] +#[derive(Debug, Insertable, Queryable, Selectable)] pub(crate) struct LineItem { pub(crate) id: String, pub(crate) line_item_date_id: String, pub(crate) name: String, pub(crate) description: Option, - pub(crate) quantity: u32, + pub(crate) quantity: i32, pub(crate) unit_price: Currency, pub(crate) created_at: OffsetDateTime, pub(crate) updated_at: OffsetDateTime, @@ -26,7 +28,7 @@ impl From<&LineItemForm> for LineItem { Some(description) }; LineItem { - id: value.id.clone().unwrap_or(nanoid!()), + id: value.id.clone().unwrap_or(Ulid::new().to_string()), line_item_date_id: value.line_item_date_id.clone(), name: value.name.clone(), description, @@ -50,7 +52,7 @@ pub(crate) struct LineItemForm { #[validate(length(min = 1, message = "can't be blank"))] pub(crate) name: String, pub(crate) description: Option, - pub(crate) quantity: u32, + pub(crate) quantity: i32, #[validate(regex(path = "FORM_CURRENCY_REGEX"))] pub(crate) unit_price: String, } diff --git a/src/line_items/query.rs b/src/line_items/query.rs index 0783291..04f2d5f 100644 --- a/src/line_items/query.rs +++ b/src/line_items/query.rs @@ -1,217 +1,130 @@ use crate::{ - currency::CurrencySql, line_items::model::{LineItem, LineItemForm}, - time::DateTimeSql, + schema::{line_item_dates, line_items}, Result, }; -use r2d2::{Pool, PooledConnection}; -use r2d2_sqlite::SqliteConnectionManager; -use rusqlite::{Row, Transaction}; -use tracing::info; +use diesel::prelude::*; +use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; pub(crate) async fn all_for_quote>( - pool: &Pool, + pool: &Pool>, quote_id: S, ) -> Result> { - // language=SQL - let sql = r#" - select - li.id, - li.line_item_date_id, - li.name, - li.description, - li.quantity, - li.unit_price, - li.created_at, - li.updated_at - from line_items li - inner join line_item_dates lid on lid.id = li.line_item_date_id - where lid.quote_id = ? - order by li.rowid - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - let records = statement - .query_map(["e_id.as_ref()], map_result) - .unwrap() - .map(|result| result.unwrap()) - .collect(); + let mut connection = pool.get()?; + let records = line_items::table + .inner_join(line_item_dates::table) + .select(LineItem::as_select()) + .filter(line_item_dates::quote_id.eq("e_id.as_ref())) + .get_results(&mut connection)?; + Ok(records) } pub(crate) async fn all_for_line_item_date>( - pool: &Pool, + pool: &Pool>, line_item_date_id: S, ) -> Result> { - // language=SQL - let sql = r#" - select - id, - line_item_date_id, - name, - description, - quantity, - unit_price, - created_at, - updated_at - from line_items - where line_item_date_id = ? - order by rowid - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - let records = statement - .query_map([&line_item_date_id.as_ref()], map_result) - .unwrap() - .map(|result| result.unwrap()) - .collect(); + let mut connection = pool.get()?; + let records = line_items::table + .filter(line_items::line_item_date_id.eq(&line_item_date_id.as_ref())) + .get_results(&mut connection)?; Ok(records) } pub(crate) async fn read>( - pool: &Pool, + pool: &Pool>, id: S, ) -> Result { - let connection = pool.get()?; - read_from_connection(&connection, id) + let mut connection = pool.get()?; + read_from_connection(&mut connection, id) } fn read_from_connection>( - connection: &PooledConnection, + connection: &mut PooledConnection>, id: S, ) -> Result { - // language=SQL - let sql = r#" - select - id, - line_item_date_id, - name, - description, - quantity, - unit_price, - created_at, - updated_at - from line_items - where id = ? - "#; - let mut statement = connection.prepare_cached(sql)?; - let record = statement.query_row([id.as_ref()], map_result)?; + let record = line_items::table + .filter(line_items::id.eq(&id.as_ref())) + .get_result(connection)?; Ok(record) } pub(crate) async fn insert( - pool: &Pool, + pool: &Pool>, form: &LineItemForm, ) -> Result { let record: LineItem = form.into(); - // language=SQL - let sql = r#" - insert into line_items - (id, line_item_date_id, name, description, quantity, unit_price, created_at, updated_at) - values - (?, ?, ?, ?, ?, ?, ?, ?); - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - statement.execute(( - &record.id, - &record.line_item_date_id, - &record.name, - &record.description, - &record.quantity, - &CurrencySql(record.unit_price.clone()), - &DateTimeSql(record.created_at), - &DateTimeSql(record.updated_at), - ))?; + let mut connection = pool.get()?; + diesel::dsl::insert_into(line_items::table) + .values(&record) + .execute(&mut connection)?; Ok(record) } pub(crate) async fn update( - pool: &Pool, + pool: &Pool>, form: &LineItemForm, ) -> Result { let record: LineItem = form.into(); - info!("LineItem:\n{:?}", &record); - - // language=SQL - let sql = r#" - update line_items - set name = ?, - description = ?, - quantity = ?, - unit_price = ?, - updated_at = ? - where id = ?; - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - statement.execute(( - &record.name, - &record.description, - &record.quantity, - &CurrencySql(record.unit_price), - &DateTimeSql(record.updated_at), - &record.id, - ))?; - read_from_connection(&connection, &record.id) + + let mut connection = pool.get()?; + diesel::dsl::update(line_items::table) + .set(( + line_items::name.eq(&record.name), + line_items::description.eq(&record.description), + line_items::quantity.eq(&record.quantity), + line_items::unit_price.eq(&record.unit_price), + line_items::updated_at.eq(&record.updated_at), + )) + .filter(line_items::id.eq(&record.id)) + .execute(&mut connection)?; + + read_from_connection(&mut connection, &record.id) } pub(crate) async fn delete>( - pool: &Pool, + pool: &Pool>, id: S, ) -> Result { - let record = read(pool, &id).await?; - // language=SQL - let sql = r#"delete from line_items where id = ?"#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - statement.execute([&id.as_ref()])?; + let mut connection = pool.get()?; + let record = read_from_connection(&mut connection, &id)?; + + _ = diesel::dsl::delete(line_items::table) + .filter(line_items::id.eq(&id.as_ref())) + .execute(&mut connection)?; + Ok(record) } -pub(crate) fn delete_all_for_quote>(tx: &mut Transaction<'_>, id: S) -> Result { - // language=SQL - let sql = r#" - delete from line_items - where id in ( - select - li.id - from line_items li - inner join line_item_dates lid on lid.id = li.line_item_date_id - where lid.quote_id = ? - );"#; - let mut statement = tx.prepare_cached(sql)?; - statement.execute([&id.as_ref()])?; - Ok(()) -} +pub(crate) fn delete_all_for_quote>( + tx: &mut PooledConnection>, + quote_id: S, +) -> Result { + let line_items1 = diesel::alias!(line_items as line_items1); + + _ = diesel::dsl::delete(line_items::table) + .filter( + line_items::id.eq_any( + line_items1 + .inner_join(line_item_dates::table) + .select(line_items1.field(line_items::id)) + .filter(line_item_dates::quote_id.eq("e_id.as_ref())), + ), + ) + .execute(tx)?; -pub(crate) fn delete_all_for_date>(tx: &mut Transaction<'_>, id: S) -> Result { - // language=SQL - let sql = r#" - delete from line_items - where id in ( - select - id - from line_items - where line_item_date_id = ? - );"#; - let mut statement = tx.prepare_cached(sql)?; - statement.execute([&id.as_ref()])?; Ok(()) } -#[inline] -fn map_result(row: &Row<'_>) -> rusqlite::Result { - Ok(LineItem { - id: row.get(0)?, - line_item_date_id: row.get(1)?, - name: row.get(2)?, - description: row.get(3)?, - quantity: row.get(4)?, - unit_price: row.get::<_, CurrencySql>(5)?.0, - created_at: row.get::<_, DateTimeSql>(6)?.0, - updated_at: row.get::<_, DateTimeSql>(7)?.0, - }) +pub(crate) fn delete_all_for_date>( + tx: &mut PooledConnection>, + id: S, +) -> Result { + _ = diesel::dsl::delete(line_items::table) + .filter(line_items::line_item_date_id.eq(&id.as_ref())) + .execute(tx)?; + + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 3f9fcf5..1157b55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ mod error; mod layout; pub mod line_item_dates; pub mod line_items; -mod migrations; pub mod quotes; +mod schema; mod time; use assets::asset_handler; @@ -18,16 +18,19 @@ use axum::{ response::Redirect, routing::{get, post, Router}, }; +use diesel::connection::SimpleConnection; +use diesel::prelude::*; +use diesel::r2d2::{ConnectionManager, Pool}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use dotenvy::dotenv; -use r2d2::Pool; -use r2d2_sqlite::SqliteConnectionManager; -use rusqlite::OpenFlags as of; use std::env; use tower_http::trace::{DefaultOnResponse, TraceLayer}; use tower_http::LatencyUnit; use tracing::{info, Level}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; +const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + pub(crate) type Result = std::result::Result; #[tokio::main] @@ -46,21 +49,27 @@ async fn main() { .with(fmt::layer()) .init(); - let db_url = env::var("DATABASE_FILE").unwrap(); - println!("DATABASE_FILE={db_url}"); + let database_url = env::var("DATABASE_URL").unwrap(); + println!("DATABASE_URL={database_url}"); - let manager = SqliteConnectionManager::file(db_url.as_str()) - .with_flags(of::SQLITE_OPEN_URI | of::SQLITE_OPEN_CREATE | of::SQLITE_OPEN_READ_WRITE) - .with_init(|conn| conn.pragma_update(None, "journal_mode", "wal")) - .with_init(|conn| conn.pragma_update(None, "synchronous", "normal")) - .with_init(|conn| conn.pragma_update(None, "foreign_keys", "on")); + let manager = ConnectionManager::::new(database_url); let pool = Pool::builder() .max_size(10) .build(manager) - .expect("unable to build pool"); + .expect("Could not build connection pool"); let mut conn = pool.get().unwrap(); - migrations::MIGRATIONS.to_latest(&mut conn).unwrap(); + + conn.batch_execute(" + PRAGMA journal_mode = WAL; -- better write-concurrency + PRAGMA synchronous = NORMAL; -- fsync only in critical moments + PRAGMA wal_autocheckpoint = 1000; -- write WAL changes back every 1000 pages, for an in average 1MB WAL file. May affect readers if number is increased + PRAGMA wal_checkpoint(TRUNCATE); -- free some space by truncating possibly massive WAL files from the last run. + PRAGMA busy_timeout = 250; -- sleep if the database is busy + PRAGMA foreign_keys = ON; -- enforce foreign keys + ").unwrap(); + + conn.run_pending_migrations(MIGRATIONS).unwrap(); drop(conn); let trace_layer = TraceLayer::new_for_http().on_response( diff --git a/src/migrations.rs b/src/migrations.rs deleted file mode 100644 index 467797f..0000000 --- a/src/migrations.rs +++ /dev/null @@ -1,21 +0,0 @@ -use once_cell::sync::Lazy; -use rusqlite_migration::{Migrations, M}; - -pub(crate) static MIGRATIONS: Lazy> = Lazy::new(|| { - Migrations::new(vec![ - M::up(include_str!("../migrations/20230408081854_quotes.up.sql")) - .down(include_str!("../migrations/20230408081854_quotes.down.sql")), - M::up(include_str!( - "../migrations/20230413082629_line_item_dates.up.sql" - )) - .down(include_str!( - "../migrations/20230413082629_line_item_dates.down.sql" - )), - M::up(include_str!( - "../migrations/20230413082726_line_items.up.sql" - )) - .down(include_str!( - "../migrations/20230413082726_line_items.down.sql" - )), - ]) -}); diff --git a/src/quotes/controller.rs b/src/quotes/controller.rs index de2a9ca..20840ea 100644 --- a/src/quotes/controller.rs +++ b/src/quotes/controller.rs @@ -14,16 +14,16 @@ use axum::{ http::StatusCode, response::{Html, IntoResponse}, }; +use diesel::prelude::SqliteConnection; +use diesel::r2d2::{ConnectionManager, Pool}; use hotwire_turbo_axum::TurboStream; use itertools::Itertools; -use r2d2::Pool; -use r2d2_sqlite::SqliteConnectionManager; use std::time::Instant; use tracing::info; use validator::Validate; pub(crate) async fn index( - State(pool): State>, + State(pool): State>>, ) -> Result> { let start = Instant::now(); let quotes = quotes::query::all(&pool) @@ -45,7 +45,7 @@ pub(crate) async fn index( } pub(crate) async fn show( - State(pool): State>, + State(pool): State>>, Path(id): Path, ) -> Result { let start = Instant::now(); @@ -98,7 +98,7 @@ pub(crate) async fn new() -> impl IntoResponse { } pub(crate) async fn create( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let result = form.validate(); @@ -137,7 +137,7 @@ pub(crate) async fn create( } pub(crate) async fn edit( - State(pool): State>, + State(pool): State>>, Path(id): Path, ) -> Result { let start = Instant::now(); @@ -155,7 +155,7 @@ pub(crate) async fn edit( } pub(crate) async fn update( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let result = form.validate(); @@ -194,7 +194,7 @@ pub(crate) async fn update( } pub(crate) async fn delete( - State(pool): State>, + State(pool): State>>, axum::Form(form): axum::Form, ) -> Result { let start = Instant::now(); diff --git a/src/quotes/model.rs b/src/quotes/model.rs index 066b4f9..7798b1e 100644 --- a/src/quotes/model.rs +++ b/src/quotes/model.rs @@ -1,14 +1,31 @@ +use crate::schema::quotes; use currency_rs::Currency; -use nanoid::nanoid; +use diesel::prelude::*; +use diesel::sql_types::*; use serde::Deserialize; use time::OffsetDateTime; +use ulid::Ulid; use validator::Validate; -#[derive(Debug)] -pub struct Quote { +#[derive(Debug, QueryableByName)] +pub struct QuoteWithTotal { + #[diesel(sql_type = Text)] pub id: String, + #[diesel(sql_type = Text)] pub name: String, + #[diesel(sql_type = currency_rs::diesel2::sqlite::sql_types::Currency)] pub total: Currency, + #[diesel(sql_type = TimestamptzSqlite)] + pub created_at: OffsetDateTime, + #[diesel(sql_type = TimestamptzSqlite)] + pub updated_at: OffsetDateTime, +} + +#[derive(Debug, Insertable, Queryable)] +#[diesel(table_name = quotes)] +pub struct Quote { + pub id: String, + pub name: String, pub created_at: OffsetDateTime, pub updated_at: OffsetDateTime, } @@ -16,9 +33,8 @@ pub struct Quote { impl From<&QuoteForm> for Quote { fn from(value: &QuoteForm) -> Self { Quote { - id: value.id.clone().unwrap_or(nanoid!()), + id: value.id.clone().unwrap_or(Ulid::new().to_string()), name: value.name.clone(), - total: Currency::new_float(0f64, None), created_at: OffsetDateTime::now_utc(), updated_at: OffsetDateTime::now_utc(), } @@ -69,6 +85,16 @@ impl Default for QuotePresenter { impl From for QuotePresenter { fn from(value: Quote) -> Self { + QuotePresenter { + id: Some(value.id), + name: value.name, + total: Currency::new_float(0f64, None), + } + } +} + +impl From for QuotePresenter { + fn from(value: QuoteWithTotal) -> Self { QuotePresenter { id: Some(value.id), name: value.name, diff --git a/src/quotes/query.rs b/src/quotes/query.rs index f24ecc7..f9f8477 100644 --- a/src/quotes/query.rs +++ b/src/quotes/query.rs @@ -1,48 +1,32 @@ use crate::{ - currency::CurrencySql, line_item_dates, - quotes::model::{Quote, QuoteForm}, - time::DateTimeSql, + quotes::model::{Quote, QuoteForm, QuoteWithTotal}, + schema::quotes, Result, }; -use currency_rs::Currency; -use r2d2::{Pool, PooledConnection}; -use r2d2_sqlite::SqliteConnectionManager; -use rusqlite::Row; +use diesel::prelude::*; +use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; -pub(crate) async fn all(pool: &Pool) -> Result> { - // language=SQL - let sql = r#" - select - id, - name, - created_at, - updated_at - from quotes - order by rowid desc - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - let records = statement - .query_map([], map_all) - .unwrap() - .map(|result| result.unwrap()) - .collect(); +pub(crate) async fn all(pool: &Pool>) -> Result> { + let mut connection = pool.get()?; + let records = quotes::table + .order_by(quotes::id) + .get_results(&mut connection)?; Ok(records) } pub(crate) async fn read>( - pool: &Pool, + pool: &Pool>, id: S, -) -> Result { - let connection = pool.get()?; - read_from_connection(&connection, id) +) -> Result { + let mut connection = pool.get()?; + read_from_connection(&mut connection, id) } fn read_from_connection>( - connection: &PooledConnection, + connection: &mut PooledConnection>, id: S, -) -> Result { +) -> Result { // language=SQL let sql = r#" select @@ -58,22 +42,23 @@ fn read_from_connection>( from quotes q where q.id = ? "#; - let mut statement = connection.prepare_cached(sql)?; - let record = statement.query_row([id.as_ref()], map_read)?; + let record = diesel::dsl::sql_query(sql) + .bind::(id.as_ref()) + .get_result(connection)?; Ok(record) } pub(crate) async fn from_line_item_date_id>( - pool: &Pool, + pool: &Pool>, id: S, -) -> Result { +) -> Result { // language=SQL let sql = r#" select q.id, q.name, (select - coalesce(sum(quantity * li.unit_price), 0) + coalesce(sum(li.quantity * li.unit_price), 0) from line_items li inner join line_item_dates lid2 on li.line_item_date_id = lid2.id where lid2.quote_id = q.id) as total, @@ -83,91 +68,60 @@ pub(crate) async fn from_line_item_date_id>( inner join quotes q on lid.quote_id = q.id where lid.id = ? "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - let record = statement.query_row([id.as_ref()], map_read)?; + let mut connection = pool.get()?; + let record = diesel::dsl::sql_query(sql) + .bind::(id.as_ref()) + .get_result(&mut connection)?; Ok(record) } pub(crate) async fn insert( - pool: &Pool, + pool: &Pool>, form: &QuoteForm, ) -> Result { let record: Quote = form.into(); - // language=SQL - let sql = r#" - insert into quotes - (id, name, created_at, updated_at) - values - (?, ?, ?, ?); - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - statement.execute(( - &record.id, - &record.name, - &DateTimeSql(record.created_at), - &DateTimeSql(record.updated_at), - ))?; + let mut connection = pool.get()?; + diesel::dsl::insert_into(quotes::table) + .values(&record) + .execute(&mut connection)?; Ok(record) } pub(crate) async fn update( - pool: &Pool, + pool: &Pool>, form: &QuoteForm, -) -> Result { +) -> Result { let record: Quote = form.into(); - // language=SQL - let sql = r#" - update quotes - set name = ?, - updated_at = ? - where id = ?; - "#; - let connection = pool.get()?; - let mut statement = connection.prepare_cached(sql)?; - statement.execute((&record.name, &DateTimeSql(record.updated_at), &record.id))?; - read_from_connection(&connection, &record.id) + + let mut connection = pool.get()?; + diesel::dsl::update(quotes::table) + .set(( + quotes::name.eq(&record.name), + quotes::updated_at.eq(&record.updated_at), + )) + .filter(quotes::id.eq(&record.id)) + .execute(&mut connection)?; + + read_from_connection(&mut connection, &record.id) } pub(crate) async fn delete>( - pool: &Pool, + pool: &Pool>, id: S, -) -> Result { - let record = read(pool, &id).await?; - // language=SQL - let sql = r#"delete from quotes where id = ?"#; +) -> Result { let mut connection = pool.get()?; - let mut tx = connection.transaction()?; - line_item_dates::query::delete_all_for_quote(&mut tx, &id)?; - { - let mut statement = tx.prepare_cached(sql)?; - statement.execute([&id.as_ref()])?; - } - tx.commit()?; - Ok(record) -} + let record = read_from_connection(&mut connection, &id)?; -#[inline] -fn map_all(row: &Row<'_>) -> rusqlite::Result { - Ok(Quote { - id: row.get(0)?, - name: row.get(1)?, - total: Currency::new_float(0f64, None), - created_at: row.get::<_, DateTimeSql>(2)?.0, - updated_at: row.get::<_, DateTimeSql>(3)?.0, - }) -} + _ = connection.transaction::<_, _, _>(|tx| { + line_item_dates::query::delete_all_for_quote(tx, &id)?; + + _ = diesel::dsl::delete(quotes::table) + .filter(quotes::id.eq(&id.as_ref())) + .execute(tx)?; -#[inline] -fn map_read(row: &Row<'_>) -> rusqlite::Result { - Ok(Quote { - id: row.get(0)?, - name: row.get(1)?, - total: row.get::<_, CurrencySql>(2)?.0, - created_at: row.get::<_, DateTimeSql>(3)?.0, - updated_at: row.get::<_, DateTimeSql>(4)?.0, - }) + Ok::<(), crate::error::AppError>(()) + }); + Ok(record) } diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..c69127e --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,41 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + line_item_dates (id) { + id -> Text, + quote_id -> Text, + date -> Date, + created_at -> TimestamptzSqlite, + updated_at -> TimestamptzSqlite, + } +} + +diesel::table! { + use currency_rs::diesel2::sqlite::sql_types::Currency; + use diesel::sql_types::*; + + line_items (id) { + id -> Text, + line_item_date_id -> Text, + name -> Text, + description -> Nullable, + quantity -> Integer, + unit_price -> Currency, + created_at -> TimestamptzSqlite, + updated_at -> TimestamptzSqlite, + } +} + +diesel::table! { + quotes (id) { + id -> Text, + name -> Text, + created_at -> TimestamptzSqlite, + updated_at -> TimestamptzSqlite, + } +} + +diesel::joinable!(line_item_dates -> quotes (quote_id)); +diesel::joinable!(line_items -> line_item_dates (line_item_date_id)); + +diesel::allow_tables_to_appear_in_same_query!(line_item_dates, line_items, quotes); diff --git a/src/schema.rs.patch b/src/schema.rs.patch new file mode 100644 index 0000000..15a2465 --- /dev/null +++ b/src/schema.rs.patch @@ -0,0 +1,57 @@ +--- src/schema.rs.unpatched 2023-11-01 20:17:20 ++++ src/schema.rs 2023-11-01 20:19:24 +@@ -1,42 +1,41 @@ + // @generated automatically by Diesel CLI. + + diesel::table! { + line_item_dates (id) { + id -> Text, + quote_id -> Text, +- date -> Text, +- created_at -> Text, +- updated_at -> Text, ++ date -> Date, ++ created_at -> TimestamptzSqlite, ++ updated_at -> TimestamptzSqlite, + } + } + + diesel::table! { ++ use currency_rs::diesel2::sqlite::sql_types::Currency; ++ use diesel::sql_types::*; ++ + line_items (id) { + id -> Text, + line_item_date_id -> Text, + name -> Text, + description -> Nullable, + quantity -> Integer, +- unit_price -> Double, +- created_at -> Text, +- updated_at -> Text, ++ unit_price -> Currency, ++ created_at -> TimestamptzSqlite, ++ updated_at -> TimestamptzSqlite, + } + } + + diesel::table! { + quotes (id) { + id -> Text, + name -> Text, +- created_at -> Text, +- updated_at -> Text, ++ created_at -> TimestamptzSqlite, ++ updated_at -> TimestamptzSqlite, + } + } + + diesel::joinable!(line_item_dates -> quotes (quote_id)); + diesel::joinable!(line_items -> line_item_dates (line_item_date_id)); + +-diesel::allow_tables_to_appear_in_same_query!( +- line_item_dates, +- line_items, +- quotes, +-); ++diesel::allow_tables_to_appear_in_same_query!(line_item_dates, line_items, quotes); diff --git a/src/schema.rs.unpatched b/src/schema.rs.unpatched new file mode 100644 index 0000000..2ba02ff --- /dev/null +++ b/src/schema.rs.unpatched @@ -0,0 +1,42 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + line_item_dates (id) { + id -> Text, + quote_id -> Text, + date -> Text, + created_at -> Text, + updated_at -> Text, + } +} + +diesel::table! { + line_items (id) { + id -> Text, + line_item_date_id -> Text, + name -> Text, + description -> Nullable, + quantity -> Integer, + unit_price -> Double, + created_at -> Text, + updated_at -> Text, + } +} + +diesel::table! { + quotes (id) { + id -> Text, + name -> Text, + created_at -> Text, + updated_at -> Text, + } +} + +diesel::joinable!(line_item_dates -> quotes (quote_id)); +diesel::joinable!(line_items -> line_item_dates (line_item_date_id)); + +diesel::allow_tables_to_appear_in_same_query!( + line_item_dates, + line_items, + quotes, +); diff --git a/src/time.rs b/src/time.rs index 2a9841a..92fb12a 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,63 +1,11 @@ use once_cell::sync::Lazy; use regex::Regex; -use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; -use rusqlite::Result; -use time::{ - format_description::{well_known::Rfc3339, FormatItem}, - macros::format_description, - Date, OffsetDateTime, -}; +use time::{format_description::FormatItem, macros::format_description, Date}; pub(crate) static DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]"); pub(crate) static DATE_REGEX: Lazy = Lazy::new(|| Regex::new(r"[0-9]{4}-[0-9]{2}-[0-9]{2}$").unwrap()); -pub(crate) struct DateTimeSql(pub(crate) OffsetDateTime); - -impl FromSql for DateTimeSql { - fn column_result(value: ValueRef) -> FromSqlResult { - String::column_result(value).and_then(|as_string| { - OffsetDateTime::parse(as_string.as_ref(), &Rfc3339) - .map(DateTimeSql) - .map_err(|err| FromSqlError::Other(Box::new(err))) - }) - } -} - -impl ToSql for DateTimeSql { - //noinspection DuplicatedCode - fn to_sql(&self) -> Result { - let time_string = self - .0 - .format(&Rfc3339) - .map_err(|err| FromSqlError::Other(Box::new(err)))?; - Ok(ToSqlOutput::from(time_string)) - } -} - -pub(crate) struct DateSql(pub(crate) Date); - -impl FromSql for DateSql { - fn column_result(value: ValueRef) -> FromSqlResult { - String::column_result(value).and_then(|as_string| { - Date::parse(as_string.as_ref(), &DATE_FORMAT) - .map(DateSql) - .map_err(|err| FromSqlError::Other(Box::new(err))) - }) - } -} - -impl ToSql for DateSql { - //noinspection DuplicatedCode - fn to_sql(&self) -> Result { - let date_string = self - .0 - .format(&DATE_FORMAT) - .map_err(|err| FromSqlError::Other(Box::new(err)))?; - Ok(ToSqlOutput::from(date_string)) - } -} - pub(crate) fn long_form(date: Date) -> String { let mut result = String::with_capacity(18); result.push_str(date.month().to_string().as_str());