diff --git a/Cargo.lock b/Cargo.lock index ca159cf..a41b48b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,8 @@ dependencies = [ "academy_models", "academy_persistence_contracts", "academy_persistence_postgres", + "academy_render_contracts", + "academy_render_impl", "academy_shared_contracts", "academy_shared_impl", "academy_templates_impl", @@ -156,6 +158,7 @@ dependencies = [ "anyhow", "config", "regex", + "rust_decimal", "serde", "serde_json", ] @@ -167,7 +170,7 @@ dependencies = [ "academy_models", "anyhow", "mockall", - "thiserror 2.0.0", + "thiserror 2.0.2", ] [[package]] @@ -343,7 +346,7 @@ dependencies = [ "academy_models", "anyhow", "mockall", - "thiserror 2.0.0", + "thiserror 2.0.2", ] [[package]] @@ -354,12 +357,17 @@ dependencies = [ "academy_core_paypal_contracts", "academy_demo", "academy_di", + "academy_email_contracts", "academy_extern_contracts", "academy_models", "academy_persistence_contracts", + "academy_render_contracts", "academy_shared_contracts", + "academy_templates_contracts", "academy_utils", "anyhow", + "rust_decimal", + "rust_decimal_macros", "tokio", "tracing", ] @@ -461,7 +469,7 @@ version = "0.0.0" dependencies = [ "darling", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -478,6 +486,7 @@ dependencies = [ name = "academy_email_impl" version = "0.0.0" dependencies = [ + "academy_assets", "academy_config", "academy_di", "academy_email_contracts", @@ -576,6 +585,27 @@ dependencies = [ "uuid", ] +[[package]] +name = "academy_render_contracts" +version = "0.0.0" +dependencies = [ + "anyhow", + "mockall", +] + +[[package]] +name = "academy_render_impl" +version = "0.0.0" +dependencies = [ + "academy_di", + "academy_render_contracts", + "academy_utils", + "anyhow", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "academy_shared_contracts" version = "0.0.0" @@ -621,7 +651,10 @@ version = "0.0.0" dependencies = [ "academy_assets", "anyhow", + "base64 0.22.1", + "chrono", "mockall", + "rust_decimal", "serde", ] @@ -674,7 +707,7 @@ version = "0.0.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -692,6 +725,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -838,6 +882,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-trait" version = "0.1.83" @@ -846,7 +896,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1037,6 +1087,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1055,6 +1117,30 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.87", + "syn_derive", +] + [[package]] name = "bstr" version = "1.10.0" @@ -1071,6 +1157,28 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -1119,6 +1227,28 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "chumsky" version = "0.9.3" @@ -1169,7 +1299,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1319,7 +1449,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.87", ] [[package]] @@ -1330,7 +1460,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1352,6 +1482,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + [[package]] name = "diff" version = "0.1.13" @@ -1377,7 +1513,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1435,6 +1571,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1480,6 +1626,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -1536,7 +1688,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1665,6 +1817,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -1672,7 +1827,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", ] @@ -1818,6 +1973,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "0.14.31" @@ -2052,7 +2216,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2211,7 +2375,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2259,6 +2423,18 @@ version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "litemap" version = "0.7.3" @@ -2368,7 +2544,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2451,7 +2627,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn", + "syn 2.0.87", "urlencoding", ] @@ -2523,7 +2699,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2555,6 +2731,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -2615,7 +2800,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2638,6 +2823,26 @@ dependencies = [ "phf_shared", ] +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + [[package]] name = "phf_shared" version = "0.11.2" @@ -2747,6 +2952,38 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -2764,7 +3001,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", "version_check", "yansi", ] @@ -2778,6 +3015,26 @@ dependencies = [ "cc", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quinn" version = "0.11.5" @@ -2842,6 +3099,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2946,6 +3209,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.11.27" @@ -3046,6 +3318,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rmp" version = "0.8.14" @@ -3068,6 +3369,32 @@ dependencies = [ "serde", ] +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da991f231869f34268415a49724c6578e740ad697ba0999199d6f22b3949332c" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3089,6 +3416,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -3221,7 +3561,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.87", ] [[package]] @@ -3240,6 +3580,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "semver" version = "1.0.23" @@ -3384,7 +3730,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -3395,7 +3741,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -3491,6 +3837,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "0.3.11" @@ -3506,6 +3867,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -3576,6 +3947,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.87" @@ -3587,6 +3969,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -3610,7 +4004,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -3634,19 +4028,44 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "tera" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ + "chrono", + "chrono-tz", "globwalk", + "humansize", "lazy_static", + "percent-encoding", "pest", "pest_derive", + "rand", "regex", "serde", "serde_json", + "slug", "unic-segment", ] @@ -3682,7 +4101,7 @@ checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -3693,7 +4112,7 @@ checksum = "ea4778c7e8ff768bdb32a58a2349903859fe719a320300d7d4ce8636f19a1e69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -3774,6 +4193,7 @@ dependencies = [ "mio", "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -3787,7 +4207,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -3959,7 +4379,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -4236,7 +4656,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -4270,7 +4690,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4576,6 +4996,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yansi" version = "1.0.1" @@ -4602,7 +5031,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", "synstructure", ] @@ -4624,7 +5053,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -4644,7 +5073,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", "synstructure", ] @@ -4673,5 +5102,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] diff --git a/Cargo.nix b/Cargo.nix index ebfed70..4eedaa4 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -417,6 +417,26 @@ rec { # File a bug if you depend on any for non-debug work! debug = internal.debugCrate { inherit packageId; }; }; + "academy_render_contracts" = rec { + packageId = "academy_render_contracts"; + build = internal.buildRustCrateWithFeatures { + packageId = "academy_render_contracts"; + }; + + # Debug support which might change between releases. + # File a bug if you depend on any for non-debug work! + debug = internal.debugCrate { inherit packageId; }; + }; + "academy_render_impl" = rec { + packageId = "academy_render_impl"; + build = internal.buildRustCrateWithFeatures { + packageId = "academy_render_impl"; + }; + + # Debug support which might change between releases. + # File a bug if you depend on any for non-debug work! + debug = internal.debugCrate { inherit packageId; }; + }; "academy_shared_contracts" = rec { packageId = "academy_shared_contracts"; build = internal.buildRustCrateWithFeatures { @@ -622,6 +642,14 @@ rec { name = "academy_persistence_postgres"; packageId = "academy_persistence_postgres"; } + { + name = "academy_render_contracts"; + packageId = "academy_render_contracts"; + } + { + name = "academy_render_impl"; + packageId = "academy_render_impl"; + } { name = "academy_shared_contracts"; packageId = "academy_shared_contracts"; @@ -702,7 +730,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } { name = "tracing"; @@ -875,7 +903,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } { name = "tower-http"; @@ -1038,7 +1066,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -1139,7 +1167,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; features = { @@ -1177,6 +1205,12 @@ rec { packageId = "regex"; usesDefaultFeatures = false; } + { + name = "rust_decimal"; + packageId = "rust_decimal"; + usesDefaultFeatures = false; + features = [ "std" "serde-str" ]; + } { name = "serde"; packageId = "serde"; @@ -1218,7 +1252,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.0"; + packageId = "thiserror 2.0.2"; usesDefaultFeatures = false; } ]; @@ -1283,7 +1317,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -1440,7 +1474,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -1496,7 +1530,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } { name = "tracing"; @@ -1630,7 +1664,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -1744,7 +1778,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -1885,7 +1919,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -1914,7 +1948,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 2.0.0"; + packageId = "thiserror 2.0.2"; usesDefaultFeatures = false; } ]; @@ -1941,6 +1975,10 @@ rec { name = "academy_di"; packageId = "academy_di"; } + { + name = "academy_email_contracts"; + packageId = "academy_email_contracts"; + } { name = "academy_extern_contracts"; packageId = "academy_extern_contracts"; @@ -1953,10 +1991,18 @@ rec { name = "academy_persistence_contracts"; packageId = "academy_persistence_contracts"; } + { + name = "academy_render_contracts"; + packageId = "academy_render_contracts"; + } { name = "academy_shared_contracts"; packageId = "academy_shared_contracts"; } + { + name = "academy_templates_contracts"; + packageId = "academy_templates_contracts"; + } { name = "academy_utils"; packageId = "academy_utils"; @@ -1967,6 +2013,17 @@ rec { usesDefaultFeatures = false; features = [ "std" ]; } + { + name = "rust_decimal"; + packageId = "rust_decimal"; + usesDefaultFeatures = false; + features = [ "std" "serde-str" ]; + } + { + name = "rust_decimal_macros"; + packageId = "rust_decimal_macros"; + usesDefaultFeatures = false; + } { name = "tracing"; packageId = "tracing"; @@ -1989,6 +2046,11 @@ rec { name = "academy_demo"; packageId = "academy_demo"; } + { + name = "academy_email_contracts"; + packageId = "academy_email_contracts"; + features = [ "mock" ]; + } { name = "academy_extern_contracts"; packageId = "academy_extern_contracts"; @@ -1999,16 +2061,26 @@ rec { packageId = "academy_persistence_contracts"; features = [ "mock" ]; } + { + name = "academy_render_contracts"; + packageId = "academy_render_contracts"; + features = [ "mock" ]; + } { name = "academy_shared_contracts"; packageId = "academy_shared_contracts"; features = [ "mock" ]; } + { + name = "academy_templates_contracts"; + packageId = "academy_templates_contracts"; + features = [ "mock" ]; + } { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -2155,7 +2227,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -2353,7 +2425,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -2444,7 +2516,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "derive" "printing" ]; } @@ -2489,6 +2561,10 @@ rec { edition = "2021"; src = lib.cleanSourceWith { filter = sourceFilter; src = ./academy_email/impl; }; dependencies = [ + { + name = "academy_assets"; + packageId = "academy_assets"; + } { name = "academy_di"; packageId = "academy_di"; @@ -2549,7 +2625,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } { name = "uuid"; @@ -2680,7 +2756,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; @@ -2896,13 +2972,81 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } ]; features = { }; resolvedDefaultFeatures = [ "dummy" ]; }; + "academy_render_contracts" = rec { + crateName = "academy_render_contracts"; + version = "0.0.0"; + edition = "2021"; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./academy_render/contracts; }; + dependencies = [ + { + name = "anyhow"; + packageId = "anyhow"; + usesDefaultFeatures = false; + features = [ "std" ]; + } + { + name = "mockall"; + packageId = "mockall"; + optional = true; + usesDefaultFeatures = false; + } + ]; + features = { + "mock" = [ "dep:mockall" ]; + }; + resolvedDefaultFeatures = [ "mock" ]; + }; + "academy_render_impl" = rec { + crateName = "academy_render_impl"; + version = "0.0.0"; + edition = "2021"; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./academy_render/impl; }; + dependencies = [ + { + name = "academy_di"; + packageId = "academy_di"; + } + { + name = "academy_render_contracts"; + packageId = "academy_render_contracts"; + } + { + name = "academy_utils"; + packageId = "academy_utils"; + } + { + name = "anyhow"; + packageId = "anyhow"; + usesDefaultFeatures = false; + features = [ "std" ]; + } + { + name = "tempfile"; + packageId = "tempfile"; + usesDefaultFeatures = false; + } + { + name = "tokio"; + packageId = "tokio"; + usesDefaultFeatures = false; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; + } + { + name = "tracing"; + packageId = "tracing"; + usesDefaultFeatures = false; + features = [ "attributes" ]; + } + ]; + + }; "academy_shared_contracts" = rec { crateName = "academy_shared_contracts"; version = "0.0.0"; @@ -3039,7 +3183,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } { name = "totp-rs"; @@ -3108,12 +3252,29 @@ rec { usesDefaultFeatures = false; features = [ "std" ]; } + { + name = "base64"; + packageId = "base64 0.22.1"; + usesDefaultFeatures = false; + } + { + name = "chrono"; + packageId = "chrono"; + usesDefaultFeatures = false; + features = [ "serde" "clock" ]; + } { name = "mockall"; packageId = "mockall"; optional = true; usesDefaultFeatures = false; } + { + name = "rust_decimal"; + packageId = "rust_decimal"; + usesDefaultFeatures = false; + features = [ "std" "serde-str" ]; + } { name = "serde"; packageId = "serde"; @@ -3164,6 +3325,7 @@ rec { name = "tera"; packageId = "tera"; usesDefaultFeatures = false; + features = [ "builtins" ]; } { name = "tracing"; @@ -3237,7 +3399,7 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - features = [ "rt-multi-thread" "macros" "sync" ]; + features = [ "rt-multi-thread" "macros" "sync" "fs" "process" ]; } { name = "tracing"; @@ -3320,7 +3482,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; usesDefaultFeatures = false; features = [ "parsing" "proc-macro" "derive" "printing" ]; } @@ -3373,7 +3535,43 @@ rec { "rustc-dep-of-std" = [ "core" "compiler_builtins" ]; }; }; - "ahash" = rec { + "ahash 0.7.8" = rec { + crateName = "ahash"; + version = "0.7.8"; + edition = "2018"; + sha256 = "1y9014qsy6gs9xld4ch7a6xi9bpki8vaciawxq4p75d8qvh7f549"; + authors = [ + "Tom Kaitchuck " + ]; + dependencies = [ + { + name = "getrandom"; + packageId = "getrandom"; + target = { target, features }: (("linux" == target."os" or null) || ("android" == target."os" or null) || ("windows" == target."os" or null) || ("macos" == target."os" or null) || ("ios" == target."os" or null) || ("freebsd" == target."os" or null) || ("openbsd" == target."os" or null) || ("netbsd" == target."os" or null) || ("dragonfly" == target."os" or null) || ("solaris" == target."os" or null) || ("illumos" == target."os" or null) || ("fuchsia" == target."os" or null) || ("redox" == target."os" or null) || ("cloudabi" == target."os" or null) || ("haiku" == target."os" or null) || ("vxworks" == target."os" or null) || ("emscripten" == target."os" or null) || ("wasi" == target."os" or null)); + } + { + name = "once_cell"; + packageId = "once_cell"; + usesDefaultFeatures = false; + target = { target, features }: (!(("arm" == target."arch" or null) && ("none" == target."os" or null))); + features = [ "alloc" ]; + } + ]; + buildDependencies = [ + { + name = "version_check"; + packageId = "version_check"; + } + ]; + features = { + "atomic-polyfill" = [ "dep:atomic-polyfill" "once_cell/atomic-polyfill" ]; + "compile-time-rng" = [ "const-random" ]; + "const-random" = [ "dep:const-random" ]; + "default" = [ "std" ]; + "serde" = [ "dep:serde" ]; + }; + }; + "ahash 0.8.11" = rec { crateName = "ahash"; version = "0.8.11"; edition = "2018"; @@ -3794,6 +3992,22 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "password-hash" "std" ]; }; + "arrayvec" = rec { + crateName = "arrayvec"; + version = "0.7.6"; + edition = "2018"; + sha256 = "0l1fz4ccgv6pm609rif37sl5nv5k6lbzi7kkppgzqzh1vwix20kw"; + authors = [ + "bluss" + ]; + features = { + "borsh" = [ "dep:borsh" ]; + "default" = [ "std" ]; + "serde" = [ "dep:serde" ]; + "zeroize" = [ "dep:zeroize" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; "async-trait" = rec { crateName = "async-trait"; version = "0.1.83"; @@ -3815,7 +4029,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; usesDefaultFeatures = false; features = [ "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } @@ -4483,6 +4697,39 @@ rec { "rustc-dep-of-std" = [ "core" "compiler_builtins" ]; "serde" = [ "dep:serde" ]; }; + resolvedDefaultFeatures = [ "std" ]; + }; + "bitvec" = rec { + crateName = "bitvec"; + version = "1.0.1"; + edition = "2021"; + sha256 = "173ydyj2q5vwj88k6xgjnfsshs4x9wbvjjv7sm0h36r34hn87hhv"; + dependencies = [ + { + name = "funty"; + packageId = "funty"; + usesDefaultFeatures = false; + } + { + name = "radium"; + packageId = "radium"; + } + { + name = "tap"; + packageId = "tap"; + } + { + name = "wyz"; + packageId = "wyz"; + usesDefaultFeatures = false; + } + ]; + features = { + "default" = [ "atomic" "std" ]; + "serde" = [ "dep:serde" ]; + "std" = [ "alloc" ]; + }; + resolvedDefaultFeatures = [ "alloc" ]; }; "blake2" = rec { crateName = "blake2"; @@ -4530,6 +4777,88 @@ rec { ]; }; + "borsh" = rec { + crateName = "borsh"; + version = "1.5.1"; + edition = "2018"; + crateBin = []; + sha256 = "1vgq96r3k9srkr9xww1pf63vdmslhnk4ciqaqzfjqqpgbpajwdm6"; + authors = [ + "Near Inc " + ]; + dependencies = [ + { + name = "borsh-derive"; + packageId = "borsh-derive"; + optional = true; + } + ]; + buildDependencies = [ + { + name = "cfg_aliases"; + packageId = "cfg_aliases"; + } + ]; + features = { + "ascii" = [ "dep:ascii" ]; + "borsh-derive" = [ "dep:borsh-derive" ]; + "bson" = [ "dep:bson" ]; + "bytes" = [ "dep:bytes" ]; + "default" = [ "std" ]; + "derive" = [ "borsh-derive" ]; + "hashbrown" = [ "dep:hashbrown" ]; + "unstable__schema" = [ "derive" "borsh-derive/schema" ]; + }; + resolvedDefaultFeatures = [ "borsh-derive" "derive" "std" "unstable__schema" ]; + }; + "borsh-derive" = rec { + crateName = "borsh-derive"; + version = "1.5.1"; + edition = "2018"; + sha256 = "02ych16fa7fqwhjww3m5mm6ndm5g9kv5p7v1r96wslsgfq2q1vy3"; + procMacro = true; + libName = "borsh_derive"; + authors = [ + "Near Inc " + ]; + dependencies = [ + { + name = "once_cell"; + packageId = "once_cell"; + } + { + name = "proc-macro-crate"; + packageId = "proc-macro-crate"; + } + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 2.0.87"; + features = [ "full" "fold" ]; + } + { + name = "syn_derive"; + packageId = "syn_derive"; + } + ]; + devDependencies = [ + { + name = "syn"; + packageId = "syn 2.0.87"; + features = [ "full" "fold" "parsing" ]; + } + ]; + features = { + }; + resolvedDefaultFeatures = [ "default" "schema" ]; + }; "bstr" = rec { crateName = "bstr"; version = "1.10.0"; @@ -4574,15 +4903,77 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; - "byteorder" = rec { - crateName = "byteorder"; - version = "1.5.0"; + "bytecheck" = rec { + crateName = "bytecheck"; + version = "0.6.12"; edition = "2021"; - sha256 = "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z"; + sha256 = "1hmipv4yyxgbamcbw5r65wagv9khs033v9483s9kri9sw9ycbk93"; authors = [ - "Andrew Gallant " + "David Koloski " ]; - features = { + dependencies = [ + { + name = "bytecheck_derive"; + packageId = "bytecheck_derive"; + usesDefaultFeatures = false; + } + { + name = "ptr_meta"; + packageId = "ptr_meta"; + usesDefaultFeatures = false; + } + { + name = "simdutf8"; + packageId = "simdutf8"; + optional = true; + usesDefaultFeatures = false; + } + ]; + features = { + "default" = [ "simdutf8" "std" ]; + "simdutf8" = [ "dep:simdutf8" ]; + "std" = [ "ptr_meta/std" "bytecheck_derive/std" "simdutf8/std" ]; + "uuid" = [ "dep:uuid" ]; + }; + resolvedDefaultFeatures = [ "simdutf8" "std" ]; + }; + "bytecheck_derive" = rec { + crateName = "bytecheck_derive"; + version = "0.6.12"; + edition = "2021"; + sha256 = "0ng6230brd0hvqpbgcx83inn74mdv3abwn95x515bndwkz90dd1x"; + procMacro = true; + authors = [ + "David Koloski " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 1.0.109"; + } + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; + "byteorder" = rec { + crateName = "byteorder"; + version = "1.5.0"; + edition = "2021"; + sha256 = "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z"; + authors = [ + "Andrew Gallant " + ]; + features = { "default" = [ "std" ]; }; resolvedDefaultFeatures = [ "default" "std" ]; @@ -4718,6 +5109,76 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "android-tzdata" "clock" "iana-time-zone" "js-sys" "now" "serde" "std" "wasm-bindgen" "wasmbind" "winapi" "windows-targets" ]; }; + "chrono-tz" = rec { + crateName = "chrono-tz"; + version = "0.9.0"; + edition = "2021"; + sha256 = "1fvicqrlmdsjkrgxr7bxfd62i9w2qi2b6iv4w85av5syvqlqnsck"; + libName = "chrono_tz"; + dependencies = [ + { + name = "chrono"; + packageId = "chrono"; + usesDefaultFeatures = false; + } + { + name = "phf"; + packageId = "phf"; + usesDefaultFeatures = false; + } + ]; + buildDependencies = [ + { + name = "chrono-tz-build"; + packageId = "chrono-tz-build"; + } + ]; + devDependencies = [ + { + name = "chrono"; + packageId = "chrono"; + usesDefaultFeatures = false; + features = [ "alloc" ]; + } + ]; + features = { + "arbitrary" = [ "dep:arbitrary" ]; + "case-insensitive" = [ "dep:uncased" "chrono-tz-build/case-insensitive" "phf/uncased" ]; + "default" = [ "std" ]; + "filter-by-regex" = [ "chrono-tz-build/filter-by-regex" ]; + "serde" = [ "dep:serde" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; + "chrono-tz-build" = rec { + crateName = "chrono-tz-build"; + version = "0.3.0"; + edition = "2021"; + sha256 = "1c8ixwwwsn9kgs1dr5mz963p0fgw9j9p7fzb3w2c7y8xhkp8l20c"; + libName = "chrono_tz_build"; + dependencies = [ + { + name = "parse-zoneinfo"; + packageId = "parse-zoneinfo"; + } + { + name = "phf"; + packageId = "phf"; + usesDefaultFeatures = false; + } + { + name = "phf_codegen"; + packageId = "phf_codegen"; + usesDefaultFeatures = false; + } + ]; + features = { + "case-insensitive" = [ "uncased" "phf/uncased" ]; + "filter-by-regex" = [ "regex" ]; + "regex" = [ "dep:regex" ]; + "uncased" = [ "dep:uncased" ]; + }; + }; "chumsky" = rec { crateName = "chumsky"; version = "0.9.3"; @@ -4873,7 +5334,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "full" ]; } ]; @@ -5311,7 +5772,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "full" "extra-traits" ]; } ]; @@ -5341,7 +5802,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } ]; @@ -5397,6 +5858,20 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "powerfmt" "std" ]; }; + "deunicode" = rec { + crateName = "deunicode"; + version = "1.6.0"; + edition = "2021"; + sha256 = "006gnml4jy3m03yqma8qvx7kl9i2bw667za9f7yc6k9ckv64959k"; + authors = [ + "Kornel Lesinski " + "Amit Chowdhury " + ]; + features = { + "default" = [ "alloc" ]; + }; + resolvedDefaultFeatures = [ "alloc" "default" ]; + }; "diff" = rec { crateName = "diff"; version = "0.1.13"; @@ -5467,7 +5942,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } ]; features = { @@ -5590,6 +6065,46 @@ rec { sha256 = "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl"; }; + "errno" = rec { + crateName = "errno"; + version = "0.3.9"; + edition = "2018"; + sha256 = "1fi0m0493maq1jygcf1bya9cymz2pc1mqxj26bdv7yjd37v5qk2k"; + authors = [ + "Chris Wong " + ]; + dependencies = [ + { + name = "libc"; + packageId = "libc"; + usesDefaultFeatures = false; + target = { target, features }: ("hermit" == target."os" or null); + } + { + name = "libc"; + packageId = "libc"; + usesDefaultFeatures = false; + target = { target, features }: ("wasi" == target."os" or null); + } + { + name = "libc"; + packageId = "libc"; + usesDefaultFeatures = false; + target = { target, features }: (target."unix" or false); + } + { + name = "windows-sys"; + packageId = "windows-sys 0.52.0"; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Foundation" "Win32_System_Diagnostics_Debug" ]; + } + ]; + features = { + "default" = [ "std" ]; + "std" = [ "libc/std" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; "fallible-iterator" = rec { crateName = "fallible-iterator"; version = "0.2.0"; @@ -5698,6 +6213,18 @@ rec { "slab" = [ "dep:slab" ]; }; }; + "funty" = rec { + crateName = "funty"; + version = "2.0.0"; + edition = "2018"; + sha256 = "177w048bm0046qlzvp33ag3ghqkqw4ncpzcm5lq36gxf2lla7mg6"; + authors = [ + "myrrlyn " + ]; + features = { + "default" = [ "std" ]; + }; + }; "futures" = rec { crateName = "futures"; version = "0.3.31"; @@ -5860,7 +6387,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "full" ]; } ]; @@ -6292,6 +6819,14 @@ rec { authors = [ "Amanieu d'Antras " ]; + dependencies = [ + { + name = "ahash"; + packageId = "ahash 0.7.8"; + optional = true; + usesDefaultFeatures = false; + } + ]; features = { "ahash" = [ "dep:ahash" ]; "ahash-compile-time-rng" = [ "ahash/compile-time-rng" ]; @@ -6304,7 +6839,7 @@ rec { "rustc-dep-of-std" = [ "nightly" "core" "compiler_builtins" "alloc" "rustc-internal-api" ]; "serde" = [ "dep:serde" ]; }; - resolvedDefaultFeatures = [ "raw" ]; + resolvedDefaultFeatures = [ "ahash" "default" "inline-more" "raw" ]; }; "hashbrown 0.14.5" = rec { crateName = "hashbrown"; @@ -6317,7 +6852,7 @@ rec { dependencies = [ { name = "ahash"; - packageId = "ahash"; + packageId = "ahash 0.8.11"; optional = true; usesDefaultFeatures = false; } @@ -6700,6 +7235,23 @@ rec { ]; }; + "humansize" = rec { + crateName = "humansize"; + version = "2.1.3"; + edition = "2021"; + sha256 = "1msxd1akb3dydsa8qs461sds9krwnn31szvqgaq93p4x0ad1rdbc"; + authors = [ + "Leopold Arkham " + ]; + dependencies = [ + { + name = "libm"; + packageId = "libm"; + } + ]; + features = { + }; + }; "hyper 0.14.31" = rec { crateName = "hyper"; version = "0.14.31"; @@ -7633,7 +8185,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } ]; @@ -8045,7 +8597,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "extra-traits" "full" ]; } ]; @@ -8263,7 +8815,36 @@ rec { "rustc-std-workspace-core" = [ "dep:rustc-std-workspace-core" ]; "use_std" = [ "std" ]; }; - resolvedDefaultFeatures = [ "default" "std" ]; + resolvedDefaultFeatures = [ "default" "extra_traits" "std" ]; + }; + "libm" = rec { + crateName = "libm"; + version = "0.2.11"; + edition = "2021"; + sha256 = "1yjgk18rk71rjbqcw9l1zaqna89p9s603k7n327nqs8dn88vwmc3"; + authors = [ + "Jorge Aparicio " + ]; + features = { + }; + resolvedDefaultFeatures = [ "default" ]; + }; + "linux-raw-sys" = rec { + crateName = "linux-raw-sys"; + version = "0.4.14"; + edition = "2021"; + sha256 = "12gsjgbhhjwywpqcrizv80vrp7p7grsz5laqq773i33wphjsxcvq"; + libName = "linux_raw_sys"; + authors = [ + "Dan Gohman " + ]; + features = { + "compiler_builtins" = [ "dep:compiler_builtins" ]; + "core" = [ "dep:core" ]; + "default" = [ "std" "general" "errno" ]; + "rustc-dep-of-std" = [ "core" "compiler_builtins" "no_std" ]; + }; + resolvedDefaultFeatures = [ "elf" "errno" "general" "ioctl" "no_std" ]; }; "litemap" = rec { crateName = "litemap"; @@ -8575,7 +9156,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "extra-traits" "full" ]; } ]; @@ -8797,7 +9378,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "extra-traits" "full" ]; } { @@ -9034,7 +9615,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "full" ]; } ]; @@ -9118,6 +9699,22 @@ rec { "thread-id" = [ "dep:thread-id" ]; }; }; + "parse-zoneinfo" = rec { + crateName = "parse-zoneinfo"; + version = "0.3.1"; + edition = "2021"; + sha256 = "093cs8slbd6kyfi6h12isz0mnaayf5ha8szri1xrbqj4inqhaahz"; + libName = "parse_zoneinfo"; + dependencies = [ + { + name = "regex"; + packageId = "regex"; + usesDefaultFeatures = false; + features = [ "std" "unicode-perl" ]; + } + ]; + + }; "password-hash" = rec { crateName = "password-hash"; version = "0.5.0"; @@ -9282,7 +9879,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } ]; features = { @@ -9349,6 +9946,52 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "phf_codegen" = rec { + crateName = "phf_codegen"; + version = "0.11.2"; + edition = "2021"; + sha256 = "0nia6h4qfwaypvfch3pnq1nd2qj64dif4a6kai3b7rjrsf49dlz8"; + authors = [ + "Steven Fackler " + ]; + dependencies = [ + { + name = "phf_generator"; + packageId = "phf_generator"; + } + { + name = "phf_shared"; + packageId = "phf_shared"; + } + ]; + + }; + "phf_generator" = rec { + crateName = "phf_generator"; + version = "0.11.2"; + edition = "2021"; + crateBin = []; + sha256 = "1c14pjyxbcpwkdgw109f7581cc5fa3fnkzdq1ikvx7mdq9jcrr28"; + authors = [ + "Steven Fackler " + ]; + dependencies = [ + { + name = "phf_shared"; + packageId = "phf_shared"; + usesDefaultFeatures = false; + } + { + name = "rand"; + packageId = "rand"; + usesDefaultFeatures = false; + features = [ "small_rng" ]; + } + ]; + features = { + "criterion" = [ "dep:criterion" ]; + }; + }; "phf_shared" = rec { crateName = "phf_shared"; version = "0.11.2"; @@ -9368,7 +10011,7 @@ rec { "uncased" = [ "dep:uncased" ]; "unicase" = [ "dep:unicase" ]; }; - resolvedDefaultFeatures = [ "std" ]; + resolvedDefaultFeatures = [ "default" "std" ]; }; "pin-project-lite" = rec { crateName = "pin-project-lite"; @@ -9652,33 +10295,113 @@ rec { }; resolvedDefaultFeatures = [ "std" ]; }; - "proc-macro2" = rec { - crateName = "proc-macro2"; - version = "1.0.89"; + "proc-macro-crate" = rec { + crateName = "proc-macro-crate"; + version = "3.2.0"; edition = "2021"; - sha256 = "0vlq56v41dsj69pnk7lil7fxvbfid50jnzdn3xnr31g05mkb0fgi"; - libName = "proc_macro2"; + sha256 = "0yzsqnavb3lmrcsmbrdjfrky9vcbl46v59xi9avn0796rb3likwf"; + libName = "proc_macro_crate"; authors = [ - "David Tolnay " - "Alex Crichton " + "Bastian Köcher " ]; dependencies = [ { - name = "unicode-ident"; - packageId = "unicode-ident"; + name = "toml_edit"; + packageId = "toml_edit"; } ]; - features = { - "default" = [ "proc-macro" ]; - }; - resolvedDefaultFeatures = [ "default" "proc-macro" ]; + }; - "proc-macro2-diagnostics" = rec { - crateName = "proc-macro2-diagnostics"; - version = "0.10.1"; + "proc-macro-error" = rec { + crateName = "proc-macro-error"; + version = "1.0.4"; edition = "2018"; - sha256 = "1j48ipc80pykvhx6yhndfa774s58ax1h6sm6mlhf09ls76f6l1mg"; - libName = "proc_macro2_diagnostics"; + sha256 = "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs"; + libName = "proc_macro_error"; + authors = [ + "CreepySkeleton " + ]; + dependencies = [ + { + name = "proc-macro-error-attr"; + packageId = "proc-macro-error-attr"; + } + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + ]; + buildDependencies = [ + { + name = "version_check"; + packageId = "version_check"; + } + ]; + features = { + "default" = [ "syn-error" ]; + "syn" = [ "dep:syn" ]; + "syn-error" = [ "syn" ]; + }; + }; + "proc-macro-error-attr" = rec { + crateName = "proc-macro-error-attr"; + version = "1.0.4"; + edition = "2018"; + sha256 = "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1"; + procMacro = true; + libName = "proc_macro_error_attr"; + authors = [ + "CreepySkeleton " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + ]; + buildDependencies = [ + { + name = "version_check"; + packageId = "version_check"; + } + ]; + + }; + "proc-macro2" = rec { + crateName = "proc-macro2"; + version = "1.0.89"; + edition = "2021"; + sha256 = "0vlq56v41dsj69pnk7lil7fxvbfid50jnzdn3xnr31g05mkb0fgi"; + libName = "proc_macro2"; + authors = [ + "David Tolnay " + "Alex Crichton " + ]; + dependencies = [ + { + name = "unicode-ident"; + packageId = "unicode-ident"; + } + ]; + features = { + "default" = [ "proc-macro" ]; + }; + resolvedDefaultFeatures = [ "default" "proc-macro" ]; + }; + "proc-macro2-diagnostics" = rec { + crateName = "proc-macro2-diagnostics"; + version = "0.10.1"; + edition = "2018"; + sha256 = "1j48ipc80pykvhx6yhndfa774s58ax1h6sm6mlhf09ls76f6l1mg"; + libName = "proc_macro2_diagnostics"; authors = [ "Sergio Benitez " ]; @@ -9693,7 +10416,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } { name = "yansi"; @@ -9729,6 +10452,51 @@ rec { } ]; + }; + "ptr_meta" = rec { + crateName = "ptr_meta"; + version = "0.1.4"; + edition = "2018"; + sha256 = "1wd4wy0wxrcays4f1gy8gwcmxg7mskmivcv40p0hidh6xbvwqf07"; + authors = [ + "David Koloski " + ]; + dependencies = [ + { + name = "ptr_meta_derive"; + packageId = "ptr_meta_derive"; + } + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; + "ptr_meta_derive" = rec { + crateName = "ptr_meta_derive"; + version = "0.1.4"; + edition = "2018"; + sha256 = "1b69cav9wn67cixshizii0q5mlbl0lihx706vcrzm259zkdlbf0n"; + procMacro = true; + authors = [ + "David Koloski " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 1.0.109"; + features = [ "full" ]; + } + ]; + }; "quinn" = rec { crateName = "quinn"; @@ -9954,6 +10722,17 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "radium" = rec { + crateName = "radium"; + version = "0.7.0"; + edition = "2018"; + sha256 = "02cxfi3ky3c4yhyqx9axqwhyaca804ws46nn4gc1imbk94nzycyw"; + authors = [ + "Nika Layzell " + "myrrlyn " + ]; + + }; "rand" = rec { crateName = "rand"; version = "0.8.5"; @@ -9996,7 +10775,7 @@ rec { "std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ]; "std_rng" = [ "rand_chacha" ]; }; - resolvedDefaultFeatures = [ "alloc" "default" "getrandom" "libc" "rand_chacha" "std" "std_rng" ]; + resolvedDefaultFeatures = [ "alloc" "default" "getrandom" "libc" "rand_chacha" "small_rng" "std" "std_rng" ]; }; "rand_chacha" = rec { crateName = "rand_chacha"; @@ -10383,6 +11162,31 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ]; }; + "rend" = rec { + crateName = "rend"; + version = "0.4.2"; + edition = "2018"; + sha256 = "0z4rrkycva0lcw0hxq479h4amxj9syn5vq4vb2qid5v2ylj3izki"; + authors = [ + "David Koloski " + ]; + dependencies = [ + { + name = "bytecheck"; + packageId = "bytecheck"; + optional = true; + usesDefaultFeatures = false; + } + ]; + features = { + "bytecheck" = [ "dep:bytecheck" ]; + "bytemuck" = [ "dep:bytemuck" ]; + "default" = [ "std" ]; + "std" = [ "bytecheck/std" ]; + "validation" = [ "bytecheck" ]; + }; + resolvedDefaultFeatures = [ "bytecheck" "std" ]; + }; "reqwest 0.11.27" = rec { crateName = "reqwest"; version = "0.11.27"; @@ -11010,6 +11814,121 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "dev_urandom_fallback" ]; }; + "rkyv" = rec { + crateName = "rkyv"; + version = "0.7.45"; + edition = "2021"; + sha256 = "16vp6m4sq41smhvym8ijy4id1hr3vm4na7wy4bc63qdrhmiws24h"; + authors = [ + "David Koloski " + ]; + dependencies = [ + { + name = "bitvec"; + packageId = "bitvec"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "bytecheck"; + packageId = "bytecheck"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "bytes"; + packageId = "bytes"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "hashbrown"; + packageId = "hashbrown 0.12.3"; + optional = true; + } + { + name = "ptr_meta"; + packageId = "ptr_meta"; + usesDefaultFeatures = false; + } + { + name = "rend"; + packageId = "rend"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "rkyv_derive"; + packageId = "rkyv_derive"; + } + { + name = "seahash"; + packageId = "seahash"; + } + { + name = "tinyvec"; + packageId = "tinyvec"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "uuid"; + packageId = "uuid"; + optional = true; + usesDefaultFeatures = false; + } + ]; + features = { + "alloc" = [ "hashbrown" "bitvec?/alloc" "tinyvec?/alloc" ]; + "arbitrary_enum_discriminant" = [ "rkyv_derive/arbitrary_enum_discriminant" ]; + "archive_be" = [ "rend" "rkyv_derive/archive_be" ]; + "archive_le" = [ "rend" "rkyv_derive/archive_le" ]; + "arrayvec" = [ "dep:arrayvec" ]; + "bitvec" = [ "dep:bitvec" ]; + "bytecheck" = [ "dep:bytecheck" ]; + "bytes" = [ "dep:bytes" ]; + "copy" = [ "rkyv_derive/copy" ]; + "default" = [ "size_32" "std" ]; + "hashbrown" = [ "dep:hashbrown" ]; + "indexmap" = [ "dep:indexmap" ]; + "rend" = [ "dep:rend" ]; + "smallvec" = [ "dep:smallvec" ]; + "smol_str" = [ "dep:smol_str" ]; + "std" = [ "alloc" "bytecheck?/std" "ptr_meta/std" "rend?/std" "uuid?/std" "bytes?/std" ]; + "strict" = [ "rkyv_derive/strict" ]; + "tinyvec" = [ "dep:tinyvec" ]; + "uuid" = [ "dep:uuid" "bytecheck?/uuid" ]; + "validation" = [ "alloc" "bytecheck" "rend/validation" ]; + }; + resolvedDefaultFeatures = [ "alloc" "hashbrown" "size_32" "std" ]; + }; + "rkyv_derive" = rec { + crateName = "rkyv_derive"; + version = "0.7.45"; + edition = "2021"; + sha256 = "1h1jwmyivx7g88d41gzcjrqnax98m9algjd49hx0laqab4kisgah"; + procMacro = true; + authors = [ + "David Koloski " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 1.0.109"; + } + ]; + features = { + }; + resolvedDefaultFeatures = [ "default" ]; + }; "rmp" = rec { crateName = "rmp"; version = "0.8.14"; @@ -11044,33 +11963,171 @@ rec { crateName = "rmp-serde"; version = "1.3.0"; edition = "2021"; - sha256 = "1nylmh7w2vpa1bwrnx1jfp2l4yz6i5qrmpic5zll166gfyj9kraj"; - libName = "rmp_serde"; + sha256 = "1nylmh7w2vpa1bwrnx1jfp2l4yz6i5qrmpic5zll166gfyj9kraj"; + libName = "rmp_serde"; + authors = [ + "Evgeny Safronov " + ]; + dependencies = [ + { + name = "byteorder"; + packageId = "byteorder"; + } + { + name = "rmp"; + packageId = "rmp"; + } + { + name = "serde"; + packageId = "serde"; + } + ]; + devDependencies = [ + { + name = "serde"; + packageId = "serde"; + features = [ "derive" ]; + } + ]; + + }; + "rust_decimal" = rec { + crateName = "rust_decimal"; + version = "1.36.0"; + edition = "2021"; + sha256 = "0mgmplkpawx9kggc4v3qymmdxx71dx1qsf1lsqp2pi9w7q7di0mh"; + authors = [ + "Paul Mason " + ]; + dependencies = [ + { + name = "arrayvec"; + packageId = "arrayvec"; + usesDefaultFeatures = false; + } + { + name = "borsh"; + packageId = "borsh"; + optional = true; + usesDefaultFeatures = false; + features = [ "derive" "unstable__schema" ]; + } + { + name = "bytes"; + packageId = "bytes"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "num-traits"; + packageId = "num-traits"; + usesDefaultFeatures = false; + features = [ "i128" ]; + } + { + name = "rand"; + packageId = "rand"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "rkyv"; + packageId = "rkyv"; + optional = true; + usesDefaultFeatures = false; + features = [ "size_32" "std" ]; + } + { + name = "serde"; + packageId = "serde"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "serde_json"; + packageId = "serde_json"; + optional = true; + usesDefaultFeatures = false; + } + ]; + devDependencies = [ + { + name = "bytes"; + packageId = "bytes"; + usesDefaultFeatures = false; + } + { + name = "rand"; + packageId = "rand"; + usesDefaultFeatures = false; + features = [ "getrandom" ]; + } + { + name = "serde"; + packageId = "serde"; + usesDefaultFeatures = false; + features = [ "derive" ]; + } + { + name = "serde_json"; + packageId = "serde_json"; + } + ]; + features = { + "borsh" = [ "dep:borsh" "std" ]; + "db-diesel-mysql" = [ "diesel/mysql" "std" ]; + "db-diesel-postgres" = [ "diesel/postgres" "std" ]; + "db-diesel2-mysql" = [ "db-diesel-mysql" ]; + "db-diesel2-postgres" = [ "db-diesel-postgres" ]; + "db-postgres" = [ "dep:bytes" "dep:postgres-types" "std" ]; + "db-tokio-postgres" = [ "dep:bytes" "dep:postgres-types" "std" ]; + "default" = [ "serde" "std" ]; + "diesel" = [ "dep:diesel" ]; + "maths-nopanic" = [ "maths" ]; + "ndarray" = [ "dep:ndarray" ]; + "proptest" = [ "dep:proptest" ]; + "rand" = [ "dep:rand" ]; + "rkyv" = [ "dep:rkyv" ]; + "rkyv-safe" = [ "rkyv/validation" ]; + "rocket-traits" = [ "dep:rocket" ]; + "rust-fuzz" = [ "dep:arbitrary" ]; + "serde" = [ "dep:serde" ]; + "serde-arbitrary-precision" = [ "serde-with-arbitrary-precision" ]; + "serde-bincode" = [ "serde-str" ]; + "serde-float" = [ "serde-with-float" ]; + "serde-str" = [ "serde-with-str" ]; + "serde-with-arbitrary-precision" = [ "serde" "serde_json/arbitrary_precision" "serde_json/std" ]; + "serde-with-float" = [ "serde" ]; + "serde-with-str" = [ "serde" ]; + "serde_json" = [ "dep:serde_json" ]; + "std" = [ "arrayvec/std" "borsh?/std" "bytes?/std" "rand?/std" "rkyv?/std" "serde?/std" "serde_json?/std" ]; + "tokio-pg" = [ "db-tokio-postgres" ]; + "tokio-postgres" = [ "dep:tokio-postgres" ]; + }; + resolvedDefaultFeatures = [ "serde" "serde-str" "serde-with-str" "std" ]; + }; + "rust_decimal_macros" = rec { + crateName = "rust_decimal_macros"; + version = "1.36.0"; + edition = "2021"; + sha256 = "0b1k94wjpwnnk68rk83vd6nl1rvqcm674jas85l45wv930iiz6fs"; + procMacro = true; authors = [ - "Evgeny Safronov " + "Paul Mason " ]; dependencies = [ { - name = "byteorder"; - packageId = "byteorder"; - } - { - name = "rmp"; - packageId = "rmp"; - } - { - name = "serde"; - packageId = "serde"; + name = "quote"; + packageId = "quote"; } - ]; - devDependencies = [ { - name = "serde"; - packageId = "serde"; - features = [ "derive" ]; + name = "rust_decimal"; + packageId = "rust_decimal"; + usesDefaultFeatures = false; } ]; - + features = { + }; }; "rustc-demangle" = rec { crateName = "rustc-demangle"; @@ -11115,6 +12172,117 @@ rec { ]; }; + "rustix" = rec { + crateName = "rustix"; + version = "0.38.38"; + edition = "2021"; + sha256 = "0ap3q9b90lnp1razrh4wdnfs0icrs2mplmzb7qlm53jkwqlh49ma"; + authors = [ + "Dan Gohman " + "Jakub Konka " + ]; + dependencies = [ + { + name = "bitflags"; + packageId = "bitflags 2.6.0"; + usesDefaultFeatures = false; + } + { + name = "errno"; + packageId = "errno"; + rename = "libc_errno"; + optional = true; + usesDefaultFeatures = false; + target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && (("little" == target."endian" or null) || ("s390x" == target."arch" or null)) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("s390x" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))); + } + { + name = "errno"; + packageId = "errno"; + rename = "libc_errno"; + usesDefaultFeatures = false; + target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && (("little" == target."endian" or null) || ("s390x" == target."arch" or null)) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("s390x" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))))))); + } + { + name = "errno"; + packageId = "errno"; + rename = "libc_errno"; + usesDefaultFeatures = false; + target = { target, features }: (target."windows" or false); + } + { + name = "libc"; + packageId = "libc"; + optional = true; + usesDefaultFeatures = false; + target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && (("little" == target."endian" or null) || ("s390x" == target."arch" or null)) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("s390x" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))); + } + { + name = "libc"; + packageId = "libc"; + usesDefaultFeatures = false; + target = { target, features }: ((!(target."windows" or false)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && (("little" == target."endian" or null) || ("s390x" == target."arch" or null)) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("s390x" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))))))); + } + { + name = "linux-raw-sys"; + packageId = "linux-raw-sys"; + usesDefaultFeatures = false; + target = { target, features }: ((("android" == target."os" or null) || ("linux" == target."os" or null)) && ((target."rustix_use_libc" or false) || (target."miri" or false) || (!(("linux" == target."os" or null) && (("little" == target."endian" or null) || ("s390x" == target."arch" or null)) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("s390x" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null))))))); + features = [ "general" "ioctl" "no_std" ]; + } + { + name = "linux-raw-sys"; + packageId = "linux-raw-sys"; + usesDefaultFeatures = false; + target = { target, features }: ((!(target."rustix_use_libc" or false)) && (!(target."miri" or false)) && ("linux" == target."os" or null) && (("little" == target."endian" or null) || ("s390x" == target."arch" or null)) && (("arm" == target."arch" or null) || (("aarch64" == target."arch" or null) && ("64" == target."pointer_width" or null)) || ("riscv64" == target."arch" or null) || ((target."rustix_use_experimental_asm" or false) && ("powerpc64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("s390x" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips32r6" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64" == target."arch" or null)) || ((target."rustix_use_experimental_asm" or false) && ("mips64r6" == target."arch" or null)) || ("x86" == target."arch" or null) || (("x86_64" == target."arch" or null) && ("64" == target."pointer_width" or null)))); + features = [ "general" "errno" "ioctl" "no_std" "elf" ]; + } + { + name = "windows-sys"; + packageId = "windows-sys 0.52.0"; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Foundation" "Win32_Networking_WinSock" "Win32_NetworkManagement_IpHelper" "Win32_System_Threading" ]; + } + ]; + devDependencies = [ + { + name = "errno"; + packageId = "errno"; + rename = "libc_errno"; + usesDefaultFeatures = false; + } + { + name = "libc"; + packageId = "libc"; + } + ]; + features = { + "all-apis" = [ "event" "fs" "io_uring" "mm" "mount" "net" "param" "pipe" "process" "procfs" "pty" "rand" "runtime" "shm" "stdio" "system" "termios" "thread" "time" ]; + "compiler_builtins" = [ "dep:compiler_builtins" ]; + "core" = [ "dep:core" ]; + "default" = [ "std" "use-libc-auxv" ]; + "io_uring" = [ "event" "fs" "net" "linux-raw-sys/io_uring" ]; + "itoa" = [ "dep:itoa" ]; + "libc" = [ "dep:libc" ]; + "libc-extra-traits" = [ "libc?/extra_traits" ]; + "libc_errno" = [ "dep:libc_errno" ]; + "linux_latest" = [ "linux_4_11" ]; + "net" = [ "linux-raw-sys/net" "linux-raw-sys/netlink" "linux-raw-sys/if_ether" "linux-raw-sys/xdp" ]; + "once_cell" = [ "dep:once_cell" ]; + "param" = [ "fs" ]; + "process" = [ "linux-raw-sys/prctl" ]; + "procfs" = [ "once_cell" "itoa" "fs" ]; + "pty" = [ "itoa" "fs" ]; + "runtime" = [ "linux-raw-sys/prctl" ]; + "rustc-dep-of-std" = [ "core" "rustc-std-workspace-alloc" "compiler_builtins" "linux-raw-sys/rustc-dep-of-std" "bitflags/rustc-dep-of-std" "compiler_builtins?/rustc-dep-of-std" ]; + "rustc-std-workspace-alloc" = [ "dep:rustc-std-workspace-alloc" ]; + "shm" = [ "fs" ]; + "std" = [ "bitflags/std" "alloc" "libc?/std" "libc_errno?/std" "libc-extra-traits" ]; + "system" = [ "linux-raw-sys/system" ]; + "thread" = [ "linux-raw-sys/prctl" ]; + "use-libc" = [ "libc_errno" "libc" "libc-extra-traits" ]; + }; + resolvedDefaultFeatures = [ "alloc" "default" "fs" "libc-extra-traits" "std" "use-libc-auxv" ]; + }; "rustls 0.21.12" = rec { crateName = "rustls"; version = "0.21.12"; @@ -11536,7 +12704,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "extra-traits" ]; } ]; @@ -11574,6 +12742,19 @@ rec { ]; }; + "seahash" = rec { + crateName = "seahash"; + version = "4.1.0"; + edition = "2015"; + sha256 = "0sxsb64np6bvnppjz5hg4rqpnkczhsl8w8kf2a5lr1c08xppn40w"; + authors = [ + "ticki " + "Tom Almeida " + ]; + features = { + }; + resolvedDefaultFeatures = [ "default" ]; + }; "semver" = rec { crateName = "semver"; version = "1.0.23"; @@ -12070,7 +13251,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "parsing" "printing" "proc-macro" ]; } @@ -12102,7 +13283,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; usesDefaultFeatures = false; features = [ "clone-impls" "derive" "parsing" "printing" ]; } @@ -12389,6 +13570,37 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "signal-hook-registry" = rec { + crateName = "signal-hook-registry"; + version = "1.4.2"; + edition = "2015"; + sha256 = "1cb5akgq8ajnd5spyn587srvs4n26ryq0p78nswffwhv46sf1sd9"; + libName = "signal_hook_registry"; + authors = [ + "Michal 'vorner' Vaner " + "Masaki Hara " + ]; + dependencies = [ + { + name = "libc"; + packageId = "libc"; + } + ]; + + }; + "simdutf8" = rec { + crateName = "simdutf8"; + version = "0.1.5"; + edition = "2018"; + sha256 = "0vmpf7xaa0dnaikib5jlx6y4dxd3hxqz6l830qb079g7wcsgxag3"; + authors = [ + "Hans Kratz " + ]; + features = { + "default" = [ "std" ]; + }; + resolvedDefaultFeatures = [ "std" ]; + }; "siphasher" = rec { crateName = "siphasher"; version = "0.3.11"; @@ -12426,6 +13638,27 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; + "slug" = rec { + crateName = "slug"; + version = "0.1.6"; + edition = "2021"; + sha256 = "0977cyp88xrwbpmqwzafkvv8vm9i0gdb5zjskb6f6pg45vvq0al8";type = [ "cdylib" "rlib" ]; + authors = [ + "Steven Allen " + ]; + dependencies = [ + { + name = "deunicode"; + packageId = "deunicode"; + } + { + name = "wasm-bindgen"; + packageId = "wasm-bindgen"; + target = { target, features }: (builtins.elem "wasm" target."family"); + } + ]; + + }; "smallvec" = rec { crateName = "smallvec"; version = "1.13.2"; @@ -12603,7 +13836,41 @@ rec { "default" = [ "std" "i128" ]; }; }; - "syn" = rec { + "syn 1.0.109" = rec { + crateName = "syn"; + version = "1.0.109"; + edition = "2018"; + sha256 = "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj"; + authors = [ + "David Tolnay " + ]; + dependencies = [ + { + name = "proc-macro2"; + packageId = "proc-macro2"; + usesDefaultFeatures = false; + } + { + name = "quote"; + packageId = "quote"; + optional = true; + usesDefaultFeatures = false; + } + { + name = "unicode-ident"; + packageId = "unicode-ident"; + } + ]; + features = { + "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ]; + "printing" = [ "quote" ]; + "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ]; + "quote" = [ "dep:quote" ]; + "test" = [ "syn-test-suite/all-features" ]; + }; + resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "full" "parsing" "printing" "proc-macro" "quote" ]; + }; + "syn 2.0.87" = rec { crateName = "syn"; version = "2.0.87"; edition = "2021"; @@ -12636,6 +13903,41 @@ rec { }; resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "extra-traits" "fold" "full" "parsing" "printing" "proc-macro" "visit" "visit-mut" ]; }; + "syn_derive" = rec { + crateName = "syn_derive"; + version = "0.1.8"; + edition = "2021"; + sha256 = "0yxydi22apcisjg0hff6dfm5x8hd6cqicav56sblx67z0af1ha8k"; + procMacro = true; + authors = [ + "Kyuuhachi " + ]; + dependencies = [ + { + name = "proc-macro-error"; + packageId = "proc-macro-error"; + usesDefaultFeatures = false; + } + { + name = "proc-macro2"; + packageId = "proc-macro2"; + } + { + name = "quote"; + packageId = "quote"; + } + { + name = "syn"; + packageId = "syn 2.0.87"; + features = [ "parsing" "derive" ]; + } + ]; + features = { + "default" = [ "full" ]; + "full" = [ "syn/full" ]; + }; + resolvedDefaultFeatures = [ "default" "full" ]; + }; "sync_wrapper 0.1.2" = rec { crateName = "sync_wrapper"; version = "0.1.2"; @@ -12692,7 +13994,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; usesDefaultFeatures = false; features = [ "derive" "parsing" "printing" "clone-impls" "visit" "extra-traits" ]; } @@ -12749,6 +14051,59 @@ rec { ]; }; + "tap" = rec { + crateName = "tap"; + version = "1.0.1"; + edition = "2015"; + sha256 = "0sc3gl4nldqpvyhqi3bbd0l9k7fngrcl4zs47n314nqqk4bpx4sm"; + authors = [ + "Elliott Linder " + "myrrlyn " + ]; + + }; + "tempfile" = rec { + crateName = "tempfile"; + version = "3.13.0"; + edition = "2021"; + sha256 = "0nyagmbd4v5g6nzfydiihcn6l9j1w9bxgzyca5lyzgnhcbyckwph"; + authors = [ + "Steven Allen " + "The Rust Project Developers" + "Ashley Mannix " + "Jason White " + ]; + dependencies = [ + { + name = "cfg-if"; + packageId = "cfg-if"; + } + { + name = "fastrand"; + packageId = "fastrand"; + } + { + name = "once_cell"; + packageId = "once_cell"; + usesDefaultFeatures = false; + features = [ "std" ]; + } + { + name = "rustix"; + packageId = "rustix"; + target = { target, features }: ((target."unix" or false) || ("wasi" == target."os" or null)); + features = [ "fs" ]; + } + { + name = "windows-sys"; + packageId = "windows-sys 0.59.0"; + target = { target, features }: (target."windows" or false); + features = [ "Win32_Storage_FileSystem" "Win32_Foundation" ]; + } + ]; + features = { + }; + }; "tera" = rec { crateName = "tera"; version = "1.20.0"; @@ -12758,14 +14113,36 @@ rec { "Vincent Prouillet " ]; dependencies = [ + { + name = "chrono"; + packageId = "chrono"; + optional = true; + usesDefaultFeatures = false; + features = [ "std" "clock" ]; + } + { + name = "chrono-tz"; + packageId = "chrono-tz"; + optional = true; + } { name = "globwalk"; packageId = "globwalk"; } + { + name = "humansize"; + packageId = "humansize"; + optional = true; + } { name = "lazy_static"; packageId = "lazy_static"; } + { + name = "percent-encoding"; + packageId = "percent-encoding"; + optional = true; + } { name = "pest"; packageId = "pest"; @@ -12774,6 +14151,11 @@ rec { name = "pest_derive"; packageId = "pest_derive"; } + { + name = "rand"; + packageId = "rand"; + optional = true; + } { name = "regex"; packageId = "regex"; @@ -12786,6 +14168,11 @@ rec { name = "serde_json"; packageId = "serde_json"; } + { + name = "slug"; + packageId = "slug"; + optional = true; + } { name = "unic-segment"; packageId = "unic-segment"; @@ -12804,6 +14191,7 @@ rec { "slug" = [ "dep:slug" ]; "urlencode" = [ "percent-encoding" ]; }; + resolvedDefaultFeatures = [ "builtins" "chrono" "chrono-tz" "humansize" "percent-encoding" "rand" "slug" "urlencode" ]; }; "termtree" = rec { crateName = "termtree"; @@ -12867,7 +14255,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } ]; @@ -12893,7 +14281,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } ]; @@ -13141,6 +14529,12 @@ rec { name = "pin-project-lite"; packageId = "pin-project-lite"; } + { + name = "signal-hook-registry"; + packageId = "signal-hook-registry"; + optional = true; + target = { target, features }: (target."unix" or false); + } { name = "socket2"; packageId = "socket2"; @@ -13197,7 +14591,7 @@ rec { "tracing" = [ "dep:tracing" ]; "windows-sys" = [ "dep:windows-sys" ]; }; - resolvedDefaultFeatures = [ "bytes" "default" "io-std" "io-util" "libc" "macros" "mio" "net" "parking_lot" "rt" "rt-multi-thread" "socket2" "sync" "time" "tokio-macros" "windows-sys" ]; + resolvedDefaultFeatures = [ "bytes" "default" "fs" "io-std" "io-util" "libc" "macros" "mio" "net" "parking_lot" "process" "rt" "rt-multi-thread" "signal-hook-registry" "socket2" "sync" "time" "tokio-macros" "windows-sys" ]; }; "tokio-macros" = rec { crateName = "tokio-macros"; @@ -13220,7 +14614,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "full" ]; } ]; @@ -13581,7 +14975,7 @@ rec { "perf" = [ "dep:kstring" ]; "serde" = [ "dep:serde" "toml_datetime/serde" "dep:serde_spanned" ]; }; - resolvedDefaultFeatures = [ "display" "parse" "serde" ]; + resolvedDefaultFeatures = [ "default" "display" "parse" "serde" ]; }; "totp-rs" = rec { crateName = "totp-rs"; @@ -13893,7 +15287,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; usesDefaultFeatures = false; features = [ "full" "parsing" "printing" "visit-mut" "clone-impls" "extra-traits" "proc-macro" ]; } @@ -14659,7 +16053,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "full" ]; } { @@ -14752,7 +16146,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "visit" "visit-mut" "full" ]; } { @@ -16690,7 +18084,7 @@ rec { "Win32_Web" = [ "Win32" ]; "Win32_Web_InternetExplorer" = [ "Win32_Web" ]; }; - resolvedDefaultFeatures = [ "Wdk" "Wdk_Foundation" "Wdk_Storage" "Wdk_Storage_FileSystem" "Wdk_System" "Wdk_System_IO" "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Pipes" "Win32_System_Registry" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "Win32_UI" "Win32_UI_Input" "Win32_UI_Input_KeyboardAndMouse" "Win32_UI_WindowsAndMessaging" "default" ]; + resolvedDefaultFeatures = [ "Wdk" "Wdk_Foundation" "Wdk_Storage" "Wdk_Storage_FileSystem" "Wdk_System" "Wdk_System_IO" "Win32" "Win32_Foundation" "Win32_NetworkManagement" "Win32_NetworkManagement_IpHelper" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Pipes" "Win32_System_Registry" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "Win32_UI" "Win32_UI_Input" "Win32_UI_Input_KeyboardAndMouse" "Win32_UI_WindowsAndMessaging" "default" ]; }; "windows-sys 0.59.0" = rec { crateName = "windows-sys"; @@ -17272,6 +18666,28 @@ rec { "either" = [ "dep:either" ]; }; }; + "wyz" = rec { + crateName = "wyz"; + version = "0.5.1"; + edition = "2018"; + sha256 = "1vdrfy7i2bznnzjdl9vvrzljvs4s3qm8bnlgqwln6a941gy61wq5"; + authors = [ + "myrrlyn " + ]; + dependencies = [ + { + name = "tap"; + packageId = "tap"; + } + ]; + features = { + "default" = [ "std" ]; + "garbage" = [ "once_cell" "typemap" ]; + "once_cell" = [ "dep:once_cell" ]; + "std" = [ "alloc" ]; + "typemap" = [ "dep:typemap" ]; + }; + }; "yansi" = rec { crateName = "yansi"; version = "1.0.1"; @@ -17360,7 +18776,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "fold" ]; } { @@ -17433,7 +18849,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; } ]; @@ -17481,7 +18897,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "fold" ]; } { @@ -17565,7 +18981,7 @@ rec { } { name = "syn"; - packageId = "syn"; + packageId = "syn 2.0.87"; features = [ "extra-traits" ]; } ]; diff --git a/Cargo.toml b/Cargo.toml index f8661f1..6f4a800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "academy_extern/*", "academy_models", "academy_persistence/*", + "academy_render/*", "academy_shared/*", "academy_templates/contracts", "academy_templates/impl", @@ -35,7 +36,6 @@ unsafe_code = "forbid" [workspace.lints.clippy] needless_return = "allow" -allow_attributes_without_reason = "warn" clone_on_ref_ptr = "warn" dbg_macro = "warn" renamed_function_params = "warn" @@ -80,6 +80,8 @@ academy_extern_impl.path = "academy_extern/impl" academy_models.path = "academy_models" academy_persistence_contracts.path = "academy_persistence/contracts" academy_persistence_postgres.path = "academy_persistence/postgres" +academy_render_contracts.path = "academy_render/contracts" +academy_render_impl.path = "academy_render/impl" academy_shared_contracts.path = "academy_shared/contracts" academy_shared_impl.path = "academy_shared/impl" academy_templates_contracts.path = "academy_templates/contracts" @@ -105,18 +107,20 @@ nutype = { version = "0.5.0", default-features = false, features = ["std", "rege oauth2 = { version = "4.4.2", default-features = false, features = ["reqwest", "rustls-tls"] } paste = { version = "1.0.15", default-features = false } pretty_assertions = { version = "1.4.1", default-features = false, features = ["std"] } +proc-macro2 = { version = "1.0.89", default-features = false, features = ["proc-macro"] } quote = { version = "1.0.37", default-features = false, features = ["proc-macro"] } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] } regex = { version = "1.11.1", default-features = false } reqwest = { version = "0.12.9", default-features = false, features = ["http2", "rustls-tls", "json"] } +rust_decimal = { version = "1.36.0", default-features = false, features = ["std", "serde-str"] } +rust_decimal_macros = { version = "1.36.0", default-features = false } schemars = { version = "0.8.21", default-features = false, features = ["derive", "preserve_order", "uuid1", "url"] } serde = { version = "1.0.214", default-features = false, features = ["derive", "std"] } serde_json = { version = "1.0.132", default-features = false, features = ["std"] } sha2 = { version = "0.10.8", default-features = false } syn = { version = "2.0.87", default-features = false, features = ["parsing", "proc-macro", "derive", "printing"] } -proc-macro2 = { version = "1.0.89", default-features = false, features = ["proc-macro"] } thiserror = { version = "2.0.2", default-features = false } -tokio = { version = "1.41.1", default-features = false, features = ["rt-multi-thread", "macros", "sync"] } +tokio = { version = "1.41.1", default-features = false, features = ["rt-multi-thread", "macros", "sync", "fs", "process"] } tracing = { version = "0.1.40", default-features = false, features = ["attributes"] } tracing-subscriber = { version = "0.3.18", default-features = false, features = ["ansi", "fmt", "env-filter"] } url = { version = "2.5.3", default-features = false, features = ["serde"] } diff --git a/academy/Cargo.toml b/academy/Cargo.toml index 8d6814c..09a62fe 100644 --- a/academy/Cargo.toml +++ b/academy/Cargo.toml @@ -35,6 +35,8 @@ academy_extern_impl.workspace = true academy_models.workspace = true academy_persistence_contracts.workspace = true academy_persistence_postgres.workspace = true +academy_render_contracts.workspace = true +academy_render_impl.workspace = true academy_shared_contracts.workspace = true academy_shared_impl.workspace = true academy_templates_impl.workspace = true diff --git a/academy/src/commands/email.rs b/academy/src/commands/email.rs index 95f99ed..5804539 100644 --- a/academy/src/commands/email.rs +++ b/academy/src/commands/email.rs @@ -32,6 +32,7 @@ async fn test(config: Config, recipient: EmailAddressWithName) -> anyhow::Result body: "Email deliverability seems to be working!".into(), content_type: ContentType::Text, reply_to: None, + attachments: Vec::new(), }) .await .and_then(|r| { diff --git a/academy/src/environment/mod.rs b/academy/src/environment/mod.rs index a9b1152..73e9bd8 100644 --- a/academy/src/environment/mod.rs +++ b/academy/src/environment/mod.rs @@ -14,6 +14,7 @@ use academy_extern_impl::{ paypal::PaypalApiServiceConfig, recaptcha::RecaptchaApiServiceConfig, vat::VatApiServiceConfig, }; use academy_models::oauth2::OAuth2Provider; +use academy_render_impl::pdf::RenderPdfServiceConfig; use academy_shared_impl::{ captcha::{CaptchaServiceConfig, RecaptchaCaptchaServiceConfig}, jwt::JwtServiceConfig, @@ -38,6 +39,9 @@ provider! { VatApiServiceConfig, PaypalApiServiceConfig, + // Render + RenderPdfServiceConfig, + // Shared CaptchaServiceConfig, JwtServiceConfig, @@ -80,6 +84,9 @@ provider! { vat_api_service_config: VatApiServiceConfig, paypal_api_service_config: PaypalApiServiceConfig, + // Render + render_pdf_service_config: RenderPdfServiceConfig, + // Shared captcha_service_config: CaptchaServiceConfig, jwt_service_config: JwtServiceConfig, @@ -129,6 +136,11 @@ impl ConfigProvider { config.paypal.client_secret.clone(), ); + // Render + let render_pdf_service_config = RenderPdfServiceConfig { + chrome_bin: config.render.chrome_bin.clone().into(), + }; + // Shared let captcha_service_config = match config.recaptcha.as_ref() { Some(recaptcha) => CaptchaServiceConfig::Recaptcha(RecaptchaCaptchaServiceConfig { @@ -214,6 +226,7 @@ impl ConfigProvider { let paypal_feature_config = PaypalFeatureConfig { purchase_range: config.coin.purchase_min..=config.coin.purchase_max, + vat_percent: config.finance.vat_percent, }; Ok(Self { @@ -227,6 +240,9 @@ impl ConfigProvider { vat_api_service_config, paypal_api_service_config, + // Render + render_pdf_service_config, + // Shared jwt_service_config, totp_service_config, diff --git a/academy/src/environment/types.rs b/academy/src/environment/types.rs index 50d8db9..dbafad9 100644 --- a/academy/src/environment/types.rs +++ b/academy/src/environment/types.rs @@ -37,6 +37,7 @@ use academy_persistence_postgres::{ paypal::PostgresPaypalRepository, session::PostgresSessionRepository, user::PostgresUserRepository, PostgresDatabase, }; +use academy_render_impl::pdf::RenderPdfServiceImpl; use academy_shared_impl::{ captcha::CaptchaServiceImpl, hash::HashServiceImpl, id::IdServiceImpl, jwt::JwtServiceImpl, password::PasswordServiceImpl, secret::SecretServiceImpl, time::TimeServiceImpl, @@ -77,6 +78,9 @@ pub type PaypalApi = PaypalApiServiceImpl; // Template pub type Template = TemplateServiceImpl; +// Render +pub type RenderPdf = RenderPdfServiceImpl; + // Shared pub type Captcha = CaptchaServiceImpl; pub type Hash = HashServiceImpl; @@ -172,8 +176,18 @@ pub type OAuth2Registration = OAuth2RegistrationServiceImpl; pub type CoinFeature = CoinFeatureServiceImpl; pub type Coin = CoinServiceImpl; -pub type PaypalFeature = - PaypalFeatureServiceImpl; +pub type PaypalFeature = PaypalFeatureServiceImpl< + Database, + Auth, + Time, + PaypalApi, + UserRepo, + PaypalRepo, + PaypalCoinOrder, + Template, + TemplateEmail, + RenderPdf, +>; pub type PaypalCoinOrder = PaypalCoinOrderServiceImpl; pub type Internal = InternalServiceImpl; diff --git a/academy_assets/assets/email/allgemeine_geschaeftsbedingungen.pdf b/academy_assets/assets/email/allgemeine_geschaeftsbedingungen.pdf new file mode 100644 index 0000000..dfc2f8b Binary files /dev/null and b/academy_assets/assets/email/allgemeine_geschaeftsbedingungen.pdf differ diff --git a/academy_assets/assets/email/logo-text.png b/academy_assets/assets/email/logo-text.png new file mode 100644 index 0000000..deecd36 Binary files /dev/null and b/academy_assets/assets/email/logo-text.png differ diff --git a/academy_assets/assets/email/widerrufsbelehrung.pdf b/academy_assets/assets/email/widerrufsbelehrung.pdf new file mode 100644 index 0000000..3365598 Binary files /dev/null and b/academy_assets/assets/email/widerrufsbelehrung.pdf differ diff --git a/academy_assets/assets/templates/invoice.html b/academy_assets/assets/templates/invoice.html new file mode 100644 index 0000000..fe60146 --- /dev/null +++ b/academy_assets/assets/templates/invoice.html @@ -0,0 +1,182 @@ + + + {{ title }} + + + + +
+
+ +
+ +
+

{{ title }}

+
+
bootstrap academy GmbH
+
Wittelsbacherplatz 1
+
80333 München
+ + +
USt.-IdNr.: DE354823768
+
Handelsregister: HRB 275681
+
+
+ +
+
+ {% for line in customer_details -%} +
{{ line }}
+ {%- endfor %} +
+ +
+
+ Datum +
{{ timestamp | date(format="%d.%m.%Y") }}
+
+
+ Rechnungs-Nr. +
{{ invoice_number }}
+
+
+ Gesamtbetrag +
{{ gross_total }} EUR
+
+
+
+ +
+
+
Bezeichnung
+
Einzelpreis
+
Menge
+
Gesamtpreis
+
+ + {% for item in items -%} +
+
{{ item.description }}
+
{{ item.net_unit }} EUR
+
{{ item.count }}
+
{{ item.net_total }} EUR
+
+ {%- endfor %} +
+ +
+
+
+ Nettobetrag +
{{ net_total }} EUR
+
+
+ zzgl. {{ vat_percent }}% MwSt. +
{{ vat_total }} EUR
+
+
+ Gesamtbetrag +
{{ gross_total }} EUR
+
+
+
+
+ + diff --git a/academy_assets/assets/templates/purchase_confirmation.html b/academy_assets/assets/templates/purchase_confirmation.html new file mode 100644 index 0000000..bd15235 --- /dev/null +++ b/academy_assets/assets/templates/purchase_confirmation.html @@ -0,0 +1,12 @@ +{% extends "base" %} +{% block title %}Kaufbestätigung{% endblock title %} +{% block content %} +

+ Du hast erfolgreich {{ coins }} MorphCoins gekauft! Das entspricht {{ gross_total }}€ inklusive {{ vat_percent }}% MwSt. von {{ vat_total }}€. + Wir hoffen, du kannst sie gut nutzen! +

+ +

+ Viel Spaß beim Lernen. +

+{% endblock content %} diff --git a/academy_config/Cargo.toml b/academy_config/Cargo.toml index 3390506..96e3b0e 100644 --- a/academy_config/Cargo.toml +++ b/academy_config/Cargo.toml @@ -15,6 +15,7 @@ academy_models.workspace = true anyhow.workspace = true config = { version = "0.14.1", default-features = false, features = ["toml"] } regex.workspace = true +rust_decimal.workspace = true serde.workspace = true [dev-dependencies] diff --git a/academy_config/src/lib.rs b/academy_config/src/lib.rs index 17ec460..a4abae5 100644 --- a/academy_config/src/lib.rs +++ b/academy_config/src/lib.rs @@ -1,7 +1,7 @@ use std::{ collections::HashMap, net::{IpAddr, SocketAddr}, - path::Path, + path::{Path, PathBuf}, }; use academy_assets::CONFIG_TOML; @@ -10,6 +10,7 @@ use anyhow::Context; use config::{File, FileFormat}; use duration::Duration; use regex::bytes::RegexSet; +use rust_decimal::Decimal; use serde::{Deserialize, Deserializer}; pub mod duration; @@ -91,6 +92,8 @@ pub struct Config { pub vat: VatConfig, pub paypal: PaypalConfig, pub coin: CoinConfig, + pub render: RenderConfig, + pub finance: FinanceConfig, pub sentry: Option, pub oauth2: Option, } @@ -216,6 +219,16 @@ pub struct CoinConfig { pub purchase_max: u64, } +#[derive(Debug, Deserialize)] +pub struct RenderConfig { + pub chrome_bin: PathBuf, +} + +#[derive(Debug, Deserialize)] +pub struct FinanceConfig { + pub vat_percent: Decimal, +} + #[derive(Debug, Deserialize)] pub struct SentryConfig { pub enable: Option, diff --git a/academy_core/contact/impl/src/lib.rs b/academy_core/contact/impl/src/lib.rs index ec47074..af18763 100644 --- a/academy_core/contact/impl/src/lib.rs +++ b/academy_core/contact/impl/src/lib.rs @@ -60,6 +60,7 @@ where .email .with_name(message.author.name.into_inner()), ), + attachments: Vec::new(), }; trace!("send email"); @@ -181,6 +182,7 @@ mod tests { .parse() .unwrap(), ), + attachments: Vec::new(), } } } diff --git a/academy_core/paypal/impl/Cargo.toml b/academy_core/paypal/impl/Cargo.toml index c817e8a..b6c9de8 100644 --- a/academy_core/paypal/impl/Cargo.toml +++ b/academy_core/paypal/impl/Cargo.toml @@ -13,19 +13,27 @@ workspace = true academy_auth_contracts.workspace = true academy_core_paypal_contracts.workspace = true academy_di.workspace = true +academy_email_contracts.workspace = true academy_extern_contracts.workspace = true academy_models.workspace = true academy_persistence_contracts.workspace = true +academy_render_contracts.workspace = true academy_shared_contracts.workspace = true +academy_templates_contracts.workspace = true academy_utils.workspace = true anyhow.workspace = true +rust_decimal.workspace = true +rust_decimal_macros.workspace = true tracing.workspace = true [dev-dependencies] academy_auth_contracts = { workspace = true, features = ["mock"] } academy_core_paypal_contracts = { workspace = true, features = ["mock"] } academy_demo.workspace = true +academy_email_contracts = { workspace = true, features = ["mock"] } academy_extern_contracts = { workspace = true, features = ["mock"] } academy_persistence_contracts = { workspace = true, features = ["mock"] } +academy_render_contracts = { workspace = true, features = ["mock"] } academy_shared_contracts = { workspace = true, features = ["mock"] } +academy_templates_contracts = { workspace = true, features = ["mock"] } tokio.workspace = true diff --git a/academy_core/paypal/impl/src/lib.rs b/academy_core/paypal/impl/src/lib.rs index 2450dc9..7c22db3 100644 --- a/academy_core/paypal/impl/src/lib.rs +++ b/academy_core/paypal/impl/src/lib.rs @@ -6,6 +6,7 @@ use academy_core_paypal_contracts::{ PaypalFeatureService, }; use academy_di::Build; +use academy_email_contracts::template::TemplateEmailService; use academy_extern_contracts::paypal::{ PaypalApiService, PaypalCaptureOrderError, PaypalCreateOrderError, }; @@ -13,8 +14,15 @@ use academy_models::{auth::AccessToken, coin::Balance, paypal::PaypalOrderId}; use academy_persistence_contracts::{ paypal::PaypalRepository, user::UserRepository, Database, Transaction, }; +use academy_render_contracts::pdf::RenderPdfService; +use academy_shared_contracts::time::TimeService; +use academy_templates_contracts::{ + InvoiceItem, InvoiceTemplate, PurchaseConfirmationTemplate, TemplateService, LOGO_BASE64, +}; use academy_utils::trace_instrument; -use anyhow::anyhow; +use anyhow::{anyhow, Context}; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; pub mod coin_order; @@ -23,30 +31,72 @@ mod tests; #[derive(Debug, Clone, Build)] #[cfg_attr(test, derive(Default))] -pub struct PaypalFeatureServiceImpl { +pub struct PaypalFeatureServiceImpl< + Db, + Auth, + Time, + PaypalApi, + UserRepo, + PaypalRepo, + PaypalCoinOrder, + Template, + TemplateEmail, + RenderPdf, +> { db: Db, auth: Auth, + time: Time, paypal_api: PaypalApi, user_repo: UserRepo, paypal_repo: PaypalRepo, paypal_coin_order: PaypalCoinOrder, + template_email: TemplateEmail, + template: Template, + render_pdf: RenderPdf, config: PaypalFeatureConfig, } #[derive(Debug, Clone)] pub struct PaypalFeatureConfig { pub purchase_range: RangeInclusive, + pub vat_percent: Decimal, } -impl PaypalFeatureService - for PaypalFeatureServiceImpl +impl< + Db, + Auth, + Time, + PaypalApi, + UserRepo, + PaypalRepo, + PaypalCoinOrder, + Template, + TemplateEmail, + RenderPdf, + > PaypalFeatureService + for PaypalFeatureServiceImpl< + Db, + Auth, + Time, + PaypalApi, + UserRepo, + PaypalRepo, + PaypalCoinOrder, + Template, + TemplateEmail, + RenderPdf, + > where Db: Database, Auth: AuthService, + Time: TimeService, PaypalApi: PaypalApiService, UserRepo: UserRepository, PaypalRepo: PaypalRepository, PaypalCoinOrder: PaypalCoinOrderService, + Template: TemplateService, + TemplateEmail: TemplateEmailService, + RenderPdf: RenderPdfService, { #[trace_instrument(skip(self))] fn get_client_id(&self) -> &str { @@ -135,8 +185,63 @@ where PaypalCaptureOrderError::Other(err) => err.into(), })?; + let invoice_number = order.invoice_number; + let coins = order.coins; + let timestamp = self.time.now(); let new_balance = self.paypal_coin_order.capture(&mut txn, order).await?; + if let Some(email) = user_composite.user.email { + let invoice_number = format!("R{invoice_number:07}"); + let vat_factor = self.config.vat_percent / dec!(100); + let net_unit = dec!(0.01) / (dec!(1) + vat_factor); + let net_total = net_unit * Decimal::from(coins); + let vat_total = net_total * vat_factor; + let gross_total = dec!(0.01) * Decimal::from(coins); + debug_assert_eq!(gross_total, (net_total + vat_total).round_dp(4)); + + let invoice_html = self + .template + .render(&InvoiceTemplate { + logo_base64: &LOGO_BASE64, + title: "Rechnung", + customer_details: user_composite.invoice_info.into_details(Some( + user_composite.profile.display_name.clone().into_inner(), + )), + timestamp, + invoice_number, + items: vec![InvoiceItem { + description: "MorphCoins".into(), + net_unit, + count: coins, + net_total, + }], + vat_percent: self.config.vat_percent, + net_total, + vat_total, + gross_total, + }) + .context("Failed to render invoice template")?; + let invoice_pdf = self + .render_pdf + .render(&invoice_html) + .await + .context("Failed to render invoice pdf")?; + + self.template_email + .send_purchase_confirmation_email( + email.with_name(user_composite.profile.display_name.into_inner()), + &PurchaseConfirmationTemplate { + coins, + vat_percent: self.config.vat_percent, + vat_total, + gross_total, + }, + invoice_pdf, + ) + .await + .context("Failed to send purchse confirmation email")?; + } + txn.commit().await?; Ok(new_balance) diff --git a/academy_core/paypal/impl/src/tests/capture_coin_order.rs b/academy_core/paypal/impl/src/tests/capture_coin_order.rs index b6d3b97..8dd0bfd 100644 --- a/academy_core/paypal/impl/src/tests/capture_coin_order.rs +++ b/academy_core/paypal/impl/src/tests/capture_coin_order.rs @@ -6,6 +6,7 @@ use academy_demo::{ session::{BAR_1, FOO_1}, user::{BAR, FOO}, }; +use academy_email_contracts::template::MockTemplateEmailService; use academy_extern_contracts::paypal::MockPaypalApiService; use academy_models::{ auth::{AuthError, AuthenticateError, AuthorizeError}, @@ -15,7 +16,14 @@ use academy_models::{ use academy_persistence_contracts::{ paypal::MockPaypalRepository, user::MockUserRepository, MockDatabase, }; +use academy_render_contracts::pdf::MockRenderPdfService; +use academy_shared_contracts::time::MockTimeService; +use academy_templates_contracts::{ + InvoiceItem, InvoiceTemplate, MockTemplateService, PurchaseConfirmationTemplate, LOGO_BASE64, +}; use academy_utils::{assert_matches, Apply}; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; use crate::{tests::Sut, PaypalFeatureServiceImpl}; @@ -36,10 +44,14 @@ async fn ok() { withheld_coins: 7, }; + let timestamp = Default::default(); + let auth = MockAuthService::new().with_authenticate(Some((FOO.user.clone(), FOO_1.clone()))); let db = MockDatabase::build(true); + let time = MockTimeService::new().with_now(timestamp); + let paypal_repo = MockPaypalRepository::new().with_get_coin_order(order.id.clone(), Some(order.clone())); @@ -49,13 +61,61 @@ async fn ok() { let paypal_coin_order = MockPaypalCoinOrderService::new().with_capture(order.clone(), expected); + let template = MockTemplateService::new().with_render( + InvoiceTemplate { + logo_base64: &LOGO_BASE64, + title: "Rechnung", + customer_details: FOO + .invoice_info + .clone() + .into_details(Some(FOO.profile.display_name.clone().into_inner())), + timestamp, + invoice_number: "R0000042".into(), + items: vec![InvoiceItem { + description: "MorphCoins".into(), + net_unit: dec!(0.01) / dec!(1.19), + count: order.coins, + net_total: dec!(0.01) / dec!(1.19) * Decimal::from(order.coins), + }], + vat_percent: dec!(19), + net_total: dec!(0.01) / dec!(1.19) * Decimal::from(order.coins), + vat_total: dec!(0.01) / dec!(1.19) * Decimal::from(order.coins) * dec!(0.19), + gross_total: dec!(0.01) * Decimal::from(order.coins), + }, + "invoice-template-html".into(), + ); + + let pdf = vec![1, 2, 3, 4, 5]; + let render_pdf = + MockRenderPdfService::new().with_render("invoice-template-html".into(), pdf.clone()); + + let template_email = MockTemplateEmailService::new().with_send_purchase_confirmation_email( + FOO.user + .email + .clone() + .unwrap() + .with_name(FOO.profile.display_name.clone().into_inner()), + PurchaseConfirmationTemplate { + coins: order.coins, + vat_percent: dec!(19), + vat_total: dec!(0.01) / dec!(1.19) * Decimal::from(order.coins) * dec!(0.19), + gross_total: dec!(0.01) * Decimal::from(order.coins), + }, + pdf, + true, + ); + let sut = PaypalFeatureServiceImpl { auth, db, + time, paypal_repo, user_repo, paypal_api, paypal_coin_order, + template, + render_pdf, + template_email, ..Sut::default() }; diff --git a/academy_core/paypal/impl/src/tests/mod.rs b/academy_core/paypal/impl/src/tests/mod.rs index 101233e..6d09832 100644 --- a/academy_core/paypal/impl/src/tests/mod.rs +++ b/academy_core/paypal/impl/src/tests/mod.rs @@ -1,9 +1,14 @@ use academy_auth_contracts::MockAuthService; use academy_core_paypal_contracts::coin_order::MockPaypalCoinOrderService; +use academy_email_contracts::template::MockTemplateEmailService; use academy_extern_contracts::paypal::MockPaypalApiService; use academy_persistence_contracts::{ paypal::MockPaypalRepository, user::MockUserRepository, MockDatabase, MockTransaction, }; +use academy_render_contracts::pdf::MockRenderPdfService; +use academy_shared_contracts::time::MockTimeService; +use academy_templates_contracts::MockTemplateService; +use rust_decimal_macros::dec; use crate::{PaypalFeatureConfig, PaypalFeatureServiceImpl}; @@ -13,16 +18,21 @@ mod create_coin_order; type Sut = PaypalFeatureServiceImpl< MockDatabase, MockAuthService, + MockTimeService, MockPaypalApiService, MockUserRepository, MockPaypalRepository, MockPaypalCoinOrderService, + MockTemplateService, + MockTemplateEmailService, + MockRenderPdfService, >; impl Default for PaypalFeatureConfig { fn default() -> Self { Self { purchase_range: 5..=5000, + vat_percent: dec!(19), } } } diff --git a/academy_email/contracts/src/lib.rs b/academy_email/contracts/src/lib.rs index 609e7b4..01e39ff 100644 --- a/academy_email/contracts/src/lib.rs +++ b/academy_email/contracts/src/lib.rs @@ -20,6 +20,14 @@ pub struct Email { pub body: String, pub content_type: ContentType, pub reply_to: Option, + pub attachments: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EmailAttachment { + pub filename: String, + pub content_type: AttachmentContentType, + pub content: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -28,6 +36,11 @@ pub enum ContentType { Html, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AttachmentContentType { + Pdf, +} + #[cfg(feature = "mock")] impl MockEmailService { pub fn with_send(mut self, email: Email, result: bool) -> Self { diff --git a/academy_email/contracts/src/template.rs b/academy_email/contracts/src/template.rs index ce757af..144abb7 100644 --- a/academy_email/contracts/src/template.rs +++ b/academy_email/contracts/src/template.rs @@ -2,7 +2,8 @@ use std::future::Future; use academy_models::email_address::EmailAddressWithName; use academy_templates_contracts::{ - ResetPasswordTemplate, SubscribeNewsletterTemplate, VerifyEmailTemplate, + PurchaseConfirmationTemplate, ResetPasswordTemplate, SubscribeNewsletterTemplate, + VerifyEmailTemplate, }; #[cfg_attr(feature = "mock", mockall::automock)] @@ -24,6 +25,13 @@ pub trait TemplateEmailService: Send + Sync + 'static { recipient: EmailAddressWithName, data: &VerifyEmailTemplate, ) -> impl Future> + Send; + + fn send_purchase_confirmation_email( + &self, + recipient: EmailAddressWithName, + data: &PurchaseConfirmationTemplate, + invoice: Vec, + ) -> impl Future> + Send; } #[cfg(feature = "mock")] @@ -75,4 +83,22 @@ impl MockTemplateEmailService { .return_once(move |_, _| Box::pin(std::future::ready(Ok(result)))); self } + + pub fn with_send_purchase_confirmation_email( + mut self, + recipient: EmailAddressWithName, + data: PurchaseConfirmationTemplate, + invoice: Vec, + result: bool, + ) -> Self { + self.expect_send_purchase_confirmation_email() + .once() + .with( + mockall::predicate::eq(recipient), + mockall::predicate::eq(data), + mockall::predicate::eq(invoice), + ) + .return_once(move |_, _, _| Box::pin(std::future::ready(Ok(result)))); + self + } } diff --git a/academy_email/impl/Cargo.toml b/academy_email/impl/Cargo.toml index 2d54809..0ef71df 100644 --- a/academy_email/impl/Cargo.toml +++ b/academy_email/impl/Cargo.toml @@ -13,6 +13,7 @@ workspace = true dummy = [] [dependencies] +academy_assets.workspace = true academy_di.workspace = true academy_email_contracts.workspace = true academy_models.workspace = true diff --git a/academy_email/impl/src/lib.rs b/academy_email/impl/src/lib.rs index 8c31ae1..aa9d8a6 100644 --- a/academy_email/impl/src/lib.rs +++ b/academy_email/impl/src/lib.rs @@ -1,9 +1,9 @@ -use academy_email_contracts::{ContentType, Email, EmailService}; +use academy_email_contracts::{AttachmentContentType, ContentType, Email, EmailService}; use academy_models::email_address::EmailAddressWithName; use academy_utils::{trace_instrument, Apply}; use anyhow::{anyhow, Context}; use lettre::{ - message::{header, MessageBuilder}, + message::{header, Attachment, MessageBuilder, MultiPart, SinglePart}, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, }; @@ -33,16 +33,31 @@ impl EmailServiceImpl { impl EmailService for EmailServiceImpl { #[trace_instrument(skip(self))] async fn send(&self, email: Email) -> anyhow::Result { + let body = SinglePart::builder() + .header(match email.content_type { + ContentType::Text => header::ContentType::TEXT_PLAIN, + ContentType::Html => header::ContentType::TEXT_HTML, + }) + .body(email.body); + + let multipart = email.attachments.into_iter().fold( + MultiPart::mixed().singlepart(body), + |acc, attachment| { + acc.singlepart(Attachment::new(attachment.filename).body( + attachment.content, + match attachment.content_type { + AttachmentContentType::Pdf => "application/pdf".parse().unwrap(), + }, + )) + }, + ); + let message = Message::builder() .from(self.from.0.clone()) .to(email.recipient.0) .apply_map(email.reply_to.map(|x| x.0), MessageBuilder::reply_to) .subject(email.subject) - .header(match email.content_type { - ContentType::Text => header::ContentType::TEXT_PLAIN, - ContentType::Html => header::ContentType::TEXT_HTML, - }) - .body(email.body) + .multipart(multipart) .context("Failed to build email message")?; self.transport diff --git a/academy_email/impl/src/template.rs b/academy_email/impl/src/template.rs index df95107..60c3213 100644 --- a/academy_email/impl/src/template.rs +++ b/academy_email/impl/src/template.rs @@ -1,9 +1,13 @@ +use academy_assets::email::{ALLGEMEINE_GESCHAEFTSBEDINGUNGEN_PDF, WIDERRUFSBELEHRUNG_PDF}; use academy_di::Build; -use academy_email_contracts::{template::TemplateEmailService, ContentType, Email, EmailService}; +use academy_email_contracts::{ + template::TemplateEmailService, AttachmentContentType, ContentType, Email, EmailAttachment, + EmailService, +}; use academy_models::email_address::EmailAddressWithName; use academy_templates_contracts::{ - ResetPasswordTemplate, SubscribeNewsletterTemplate, Template, TemplateService, - VerifyEmailTemplate, + PurchaseConfirmationTemplate, ResetPasswordTemplate, SubscribeNewsletterTemplate, Template, + TemplateService, VerifyEmailTemplate, }; use academy_utils::trace_instrument; @@ -24,8 +28,13 @@ where recipient: EmailAddressWithName, data: &ResetPasswordTemplate, ) -> anyhow::Result { - self.send_email(recipient, data, "Passwort zurücksetzen - Bootstrap Academy") - .await + self.send_email( + recipient, + data, + "Passwort zurücksetzen - Bootstrap Academy", + Vec::new(), + ) + .await } #[trace_instrument(skip(self))] @@ -34,8 +43,13 @@ where recipient: EmailAddressWithName, data: &SubscribeNewsletterTemplate, ) -> anyhow::Result { - self.send_email(recipient, data, "Newsletter abonnieren - Bootstrap Academy") - .await + self.send_email( + recipient, + data, + "Newsletter abonnieren - Bootstrap Academy", + Vec::new(), + ) + .await } #[trace_instrument(skip(self))] @@ -44,8 +58,47 @@ where recipient: EmailAddressWithName, data: &VerifyEmailTemplate, ) -> anyhow::Result { - self.send_email(recipient, data, "Willkommen bei der Bootstrap Academy!") - .await + self.send_email( + recipient, + data, + "Willkommen bei der Bootstrap Academy!", + Vec::new(), + ) + .await + } + + #[trace_instrument(skip(self, invoice))] + async fn send_purchase_confirmation_email( + &self, + recipient: EmailAddressWithName, + data: &PurchaseConfirmationTemplate, + invoice: Vec, + ) -> anyhow::Result { + let invoice = EmailAttachment { + filename: "rechnung.pdf".into(), + content_type: AttachmentContentType::Pdf, + content: invoice, + }; + + let terms = EmailAttachment { + filename: "allgemeine_geschaeftsbedingungen.pdf".into(), + content_type: AttachmentContentType::Pdf, + content: ALLGEMEINE_GESCHAEFTSBEDINGUNGEN_PDF.into(), + }; + + let revocation_policy = EmailAttachment { + filename: "widerrufsbelehrung.pdf".into(), + content_type: AttachmentContentType::Pdf, + content: WIDERRUFSBELEHRUNG_PDF.into(), + }; + + self.send_email( + recipient, + data, + "Kaufbestätigung - Bootstrap Academy", + vec![invoice, terms, revocation_policy], + ) + .await } } @@ -59,6 +112,7 @@ where recipient: EmailAddressWithName, data: &T, subject: impl Into, + attachments: Vec, ) -> anyhow::Result { self.email .send(Email { @@ -67,6 +121,7 @@ where body: self.template.render(data)?, content_type: ContentType::Html, reply_to: None, + attachments, }) .await } diff --git a/academy_email/impl/tests/email.rs b/academy_email/impl/tests/email.rs index 9d80c62..df87e10 100644 --- a/academy_email/impl/tests/email.rs +++ b/academy_email/impl/tests/email.rs @@ -19,6 +19,7 @@ async fn send_email() { body: "

Hello World!

".into(), content_type: ContentType::Html, reply_to: Some("replyto@example.com".parse().unwrap()), + attachments: Vec::new(), }) .await .unwrap(); diff --git a/academy_models/src/user.rs b/academy_models/src/user.rs index a43bbea..318519c 100644 --- a/academy_models/src/user.rs +++ b/academy_models/src/user.rs @@ -110,6 +110,37 @@ impl UserComposite { } } +impl UserInvoiceInfo { + pub fn into_details(self, name_fallback: Option) -> Vec { + let name = match (self.first_name, self.last_name) { + (Some(first_name), Some(last_name)) => Some(format!("{} {}", *first_name, *last_name)), + _ => name_fallback, + }; + + let city = match (self.zip_code, self.city) { + (Some(zip_code), Some(city)) => Some(format!("{} {}", *zip_code, *city)), + _ => None, + }; + + let vat_id = self + .business + .is_some_and(|x| x) + .then(|| Some(format!("USt.-IdNr.: {}", *self.vat_id?))) + .flatten(); + + [ + name, + self.street.map(UserStreet::into_inner), + city, + self.country.map(UserCountry::into_inner), + vat_id, + ] + .into_iter() + .flatten() + .collect() + } +} + nutype_string!(UserName(validate(regex = USER_NAME_REGEX))); nutype_string!(UserDisplayName(validate( len_char_min = 1, diff --git a/academy_render/contracts/Cargo.toml b/academy_render/contracts/Cargo.toml new file mode 100644 index 0000000..fc1dd22 --- /dev/null +++ b/academy_render/contracts/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "academy_render_contracts" +version.workspace = true +edition.workspace = true +publish.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +mock = ["dep:mockall"] + +[dependencies] +anyhow.workspace = true +mockall = { workspace = true, optional = true } diff --git a/academy_render/contracts/src/lib.rs b/academy_render/contracts/src/lib.rs new file mode 100644 index 0000000..0f137f1 --- /dev/null +++ b/academy_render/contracts/src/lib.rs @@ -0,0 +1 @@ +pub mod pdf; diff --git a/academy_render/contracts/src/pdf.rs b/academy_render/contracts/src/pdf.rs new file mode 100644 index 0000000..fcb159a --- /dev/null +++ b/academy_render/contracts/src/pdf.rs @@ -0,0 +1,18 @@ +use std::future::Future; + +#[cfg_attr(feature = "mock", mockall::automock)] +pub trait RenderPdfService: Send + Sync + 'static { + /// Render the given `html` source into a PDF file. + fn render(&self, html: &str) -> impl Future>> + Send; +} + +#[cfg(feature = "mock")] +impl MockRenderPdfService { + pub fn with_render(mut self, html: String, result: Vec) -> Self { + self.expect_render() + .once() + .with(mockall::predicate::eq(html)) + .return_once(|_| Box::pin(std::future::ready(Ok(result)))); + self + } +} diff --git a/academy_render/impl/Cargo.toml b/academy_render/impl/Cargo.toml new file mode 100644 index 0000000..cb3958e --- /dev/null +++ b/academy_render/impl/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "academy_render_impl" +version.workspace = true +edition.workspace = true +publish.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +academy_di.workspace = true +academy_render_contracts.workspace = true +academy_utils.workspace = true +anyhow.workspace = true +tempfile = { version = "3.13.0", default-features = false } +tokio.workspace = true +tracing.workspace = true diff --git a/academy_render/impl/src/lib.rs b/academy_render/impl/src/lib.rs new file mode 100644 index 0000000..0f137f1 --- /dev/null +++ b/academy_render/impl/src/lib.rs @@ -0,0 +1 @@ +pub mod pdf; diff --git a/academy_render/impl/src/pdf.rs b/academy_render/impl/src/pdf.rs new file mode 100644 index 0000000..ad783b9 --- /dev/null +++ b/academy_render/impl/src/pdf.rs @@ -0,0 +1,54 @@ +use std::{path::PathBuf, process::Stdio, sync::Arc}; + +use academy_di::Build; +use academy_render_contracts::pdf::RenderPdfService; +use academy_utils::trace_instrument; +use anyhow::{anyhow, Context}; +use tempfile::tempdir; + +#[derive(Debug, Clone, Build)] +pub struct RenderPdfServiceImpl { + pub config: RenderPdfServiceConfig, +} + +#[derive(Debug, Clone)] +pub struct RenderPdfServiceConfig { + pub chrome_bin: Arc, +} + +impl RenderPdfService for RenderPdfServiceImpl { + #[trace_instrument(skip(self))] + async fn render(&self, html: &str) -> anyhow::Result> { + let dir = tempdir().context("Failed to create tempdir")?; + let index_path = dir.path().join("index.html"); + let output_path = dir.path().join("output.pdf"); + + tokio::fs::write(&index_path, html) + .await + .context("Failed to write html source")?; + + tokio::process::Command::new(&*self.config.chrome_bin) + .arg("--headless") + .arg("--disable-gpu") + .arg("--no-pdf-header-footer") + .arg(format!("--print-to-pdf={}", output_path.display())) + .arg(index_path) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .context("Failed to spawn pdf render process")? + .wait() + .await + .context("Failed to await pdf render process")? + .success() + .then_some(()) + .ok_or_else(|| anyhow!("Failed to render pdf"))?; + + let pdf = tokio::fs::read(output_path) + .await + .context("Failed to read pdf output")?; + + Ok(pdf) + } +} diff --git a/academy_templates/contracts/Cargo.toml b/academy_templates/contracts/Cargo.toml index a7c031b..49cea75 100644 --- a/academy_templates/contracts/Cargo.toml +++ b/academy_templates/contracts/Cargo.toml @@ -15,5 +15,8 @@ mock = ["dep:mockall"] [dependencies] academy_assets.workspace = true anyhow.workspace = true +base64.workspace = true +chrono.workspace = true mockall = { workspace = true, optional = true } +rust_decimal.workspace = true serde.workspace = true diff --git a/academy_templates/contracts/src/lib.rs b/academy_templates/contracts/src/lib.rs index afb0279..5cc39aa 100644 --- a/academy_templates/contracts/src/lib.rs +++ b/academy_templates/contracts/src/lib.rs @@ -1,7 +1,13 @@ -use std::fmt::Debug; +use std::{fmt::Debug, sync::LazyLock}; use academy_assets::templates; -use serde::Serialize; +use base64::{prelude::BASE64_STANDARD, Engine}; +use chrono::{DateTime, Utc}; +use rust_decimal::Decimal; +use serde::{Serialize, Serializer}; + +pub static LOGO_BASE64: LazyLock = + LazyLock::new(|| BASE64_STANDARD.encode(academy_assets::email::LOGO_TEXT_PNG)); #[cfg_attr(feature = "mock", mockall::automock)] pub trait TemplateService: Send + Sync + 'static { @@ -48,6 +54,8 @@ templates! { ResetPasswordTemplate(templates::RESET_PASSWORD_HTML), VerifyEmailTemplate(templates::VERIFY_EMAIL_HTML), SubscribeNewsletterTemplate(templates::SUBSCRIBE_NEWSLETTER_HTML), + PurchaseConfirmationTemplate(templates::PURCHASE_CONFIRMATION_HTML), + InvoiceTemplate(templates::INVOICE_HTML), } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] @@ -67,3 +75,58 @@ pub struct SubscribeNewsletterTemplate { pub code: String, pub url: String, } + +macro_rules! rounded { + ($($ident:ident($digits:literal)),* $(,)?) => { $( + fn $ident(num: &Decimal, serializer: S) -> Result { + Serialize::serialize(&num.round_dp($digits), serializer) + } + )* }; +} +rounded! { + rounded_2(2), + rounded_4(4), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct PurchaseConfirmationTemplate { + pub coins: u64, + pub vat_percent: Decimal, + #[serde(serialize_with = "rounded_2")] + pub vat_total: Decimal, + #[serde(serialize_with = "rounded_2")] + pub gross_total: Decimal, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct InvoiceTemplate { + pub logo_base64: &'static str, + pub title: &'static str, + pub customer_details: Vec, + pub timestamp: DateTime, + pub invoice_number: String, + pub items: Vec, + pub vat_percent: Decimal, + #[serde(serialize_with = "rounded_2")] + pub net_total: Decimal, + #[serde(serialize_with = "rounded_2")] + pub vat_total: Decimal, + #[serde(serialize_with = "rounded_2")] + pub gross_total: Decimal, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct InvoiceDetail { + pub name: &'static str, + pub value: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct InvoiceItem { + pub description: String, + #[serde(serialize_with = "rounded_4")] + pub net_unit: Decimal, + pub count: u64, + #[serde(serialize_with = "rounded_2")] + pub net_total: Decimal, +} diff --git a/academy_templates/impl/Cargo.toml b/academy_templates/impl/Cargo.toml index d301532..7f104bb 100644 --- a/academy_templates/impl/Cargo.toml +++ b/academy_templates/impl/Cargo.toml @@ -16,5 +16,5 @@ academy_templates_contracts.workspace = true academy_utils.workspace = true anyhow.workspace = true serde.workspace = true -tera = { version = "1.20.0", default-features = false } +tera = { version = "1.20.0", default-features = false, features = ["builtins"] } tracing.workspace = true diff --git a/academy_templates/impl/src/lib.rs b/academy_templates/impl/src/lib.rs index 29dd67c..9c87a75 100644 --- a/academy_templates/impl/src/lib.rs +++ b/academy_templates/impl/src/lib.rs @@ -46,7 +46,8 @@ impl TemplateService for TemplateServiceImpl { #[cfg(test)] mod tests { use academy_templates_contracts::{ - ResetPasswordTemplate, SubscribeNewsletterTemplate, VerifyEmailTemplate, + InvoiceTemplate, PurchaseConfirmationTemplate, ResetPasswordTemplate, + SubscribeNewsletterTemplate, VerifyEmailTemplate, }; use super::*; @@ -75,6 +76,32 @@ mod tests { }); } + #[test] + fn purchase_confirmation() { + test_template(PurchaseConfirmationTemplate { + coins: 4207, + vat_percent: 19.into(), + vat_total: 7.into(), + gross_total: 49.into(), + }); + } + + #[test] + fn invoice() { + test_template(InvoiceTemplate { + logo_base64: "", + title: "Rechnung", + customer_details: ["foo", "bar", "baz"].into_iter().map(Into::into).collect(), + timestamp: Default::default(), + invoice_number: "R1234".into(), + items: vec![], + vat_percent: 19.into(), + net_total: 42.into(), + vat_total: 7.into(), + gross_total: 49.into(), + }); + } + fn test_template(template: T) { // Arrange let sut = TemplateServiceImpl { diff --git a/config.dev.toml b/config.dev.toml index dc4f5e8..112b676 100644 --- a/config.dev.toml +++ b/config.dev.toml @@ -35,6 +35,9 @@ base_url_override = "http://127.0.0.1:8004/" client_id = "test-client" client_secret = "test-secret" +[render] +chrome_bin = "/usr/bin/chromium" + [oauth2.providers.test] enable = true name = "Test" diff --git a/config.toml b/config.toml index 0e07c32..cccdd6f 100644 --- a/config.toml +++ b/config.toml @@ -74,6 +74,12 @@ min_score = 0.5 purchase_min = 500 purchase_max = 1000000 +[render] +# chrome_bin = "" + +[finance] +vat_percent = 19 + # [sentry] # enable = true # dsn = "" diff --git a/nix/dev.nix b/nix/dev.nix index 2d612b3..f659299 100644 --- a/nix/dev.nix +++ b/nix/dev.nix @@ -23,7 +23,7 @@ lcov smtp4dev oath-toolkit - (python3.withPackages (p: with p; [httpx pyotp])) + (python3.withPackages (p: with p; [httpx pyotp pypdf])) ]) ++ (lib.optional (!pkgs.cargo-llvm-cov.meta.broken) pkgs.cargo-llvm-cov) ++ (lib.optionals (pkgs.stdenv.hostPlatform.isDarwin) (with pkgs.darwin.apple_sdk.frameworks; [ @@ -81,7 +81,20 @@ PYTHONPATH = "${config.devenv.root}/nix/tests"; - ACADEMY_CONFIG = "${config.devenv.root}/config.dev.toml"; + ACADEMY_CONFIG = let + chrome = pkgs.ungoogled-chromium; + + renderConfig = (pkgs.formats.toml {}).generate "config.default.toml" { + render.chrome_bin = lib.getExe chrome; + }; + + configs = + lib.optional (lib.meta.availableOn {inherit (pkgs) system;} chrome) renderConfig + ++ [ + "${config.devenv.root}/config.dev.toml" + ]; + in + builtins.concatStringsSep ":" configs; }; process.manager.implementation = "hivemind"; diff --git a/nix/module.nix b/nix/module.nix index c333762..0f49292 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -14,6 +14,8 @@ in { default = self.packages.${pkgs.system}.default; }; + chromePackage = lib.mkPackageOption pkgs "ungoogled-chromium" {}; + localDatabase = lib.mkOption { type = lib.types.bool; default = true; @@ -132,8 +134,11 @@ in { }; users.groups.academy = {}; - services.academy.backend.settings.database.url = lib.mkIf cfg.localDatabase "host=/run/postgresql user=academy"; - services.academy.backend.settings.cache.url = lib.mkIf cfg.localCache "redis+unix://${config.services.redis.servers.academy.unixSocket}"; + services.academy.backend.settings = { + database.url = lib.mkIf cfg.localDatabase "host=/run/postgresql user=academy"; + cache.url = lib.mkIf cfg.localCache "redis+unix://${config.services.redis.servers.academy.unixSocket}"; + render.chrome_bin = lib.mkDefault (lib.getExe cfg.chromePackage); + }; environment.systemPackages = [wrapper]; }; diff --git a/nix/tests/contact.py b/nix/tests/contact.py index 9aef12b..73d9187 100644 --- a/nix/tests/contact.py +++ b/nix/tests/contact.py @@ -1,6 +1,4 @@ -from typing import cast - -from utils import c, fetch_mail +from utils import c, decode_mail_payload, fetch_mail msg = { "name": "Some User", @@ -16,8 +14,8 @@ mail = fetch_mail() assert mail["X-Original-To"] == "contact@academy" assert mail["Subject"] == "[Contact Form] Something Important" -content = cast(bytes, mail.get_payload(decode=True)).decode() -assert content == "Message from Some User (some.user@example.com):\n\nThis is a really important message.\n" +content = decode_mail_payload(mail) +assert content == "Message from Some User (some.user@example.com):\n\nThis is a really important message." for resp in ["success-0.3", "failure"]: resp = c.post("/auth/contact", json={**msg, "recaptcha_response": resp}) diff --git a/nix/tests/default.nix b/nix/tests/default.nix index 6496e05..a3af41c 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -154,7 +154,7 @@ nodes.machine = {pkgs, ...}: { imports = [defaultModule]; - environment.systemPackages = [(pkgs.python3.withPackages (p: with p; [httpx pyotp]))]; + environment.systemPackages = [(pkgs.python3.withPackages (p: with p; [httpx pyotp pypdf]))]; }; testScript = '' diff --git a/nix/tests/paypal.py b/nix/tests/paypal.py index f0bd31b..ff564db 100644 --- a/nix/tests/paypal.py +++ b/nix/tests/paypal.py @@ -1,4 +1,9 @@ -from utils import c, create_verified_account +import hashlib +from io import BytesIO +from typing import cast + +from pypdf import PdfReader +from utils import c, create_verified_account, decode_mail_header, decode_mail_part, fetch_mail, get_mail_parts login = create_verified_account("a", "a@a", "a") @@ -22,7 +27,6 @@ ## success resp = c.post("/shop/coins/paypal/orders", json={"coins": 1337}) -print(resp.status_code, resp.json()) assert resp.status_code == 200 order_id = resp.json() @@ -54,3 +58,32 @@ assert resp.json() == {"detail": "Order not found"} assert c.get(f"/shop/coins/me").json() == {"coins": 1337, "withheld_coins": 0} assert c.get(f"http://127.0.0.1:8004/v2/checkout/orders/{order_id}").json() == {"status": "Captured"} + +# invoice email +mail = fetch_mail() +assert mail["X-Original-To"] == "a@a" +assert decode_mail_header(mail["Subject"]) == "Kaufbestätigung - Bootstrap Academy" +payload, invoice, terms, revocation_policy = get_mail_parts(mail) +content = decode_mail_part(payload).decode() +assert "Du hast erfolgreich 1337 MorphCoins gekauft! Das entspricht 13.37€ inklusive 19% MwSt. von 2.13€." in content + +assert invoice["Content-Disposition"] == 'attachment; filename="rechnung.pdf"' +assert invoice["Content-Type"] == "application/pdf" +pdf = PdfReader(BytesIO(decode_mail_part(invoice))) +assert pdf.metadata and pdf.metadata.title == "Rechnung" +assert len(pdf.pages) == 1 +invoice_text = pdf.pages[0].extract_text() +assert "Nettobetrag 11.24 EUR" in invoice_text +assert "zzgl. 19% MwSt. 2.13 EUR" in invoice_text +assert "Gesamtbetrag 13.37 EUR" in invoice_text + +assert terms["Content-Disposition"] == 'attachment;\n filename*0="allgemeine_geschaeftsbedingungen.pdf"' +assert terms["Content-Type"] == "application/pdf" +hash = hashlib.sha256(decode_mail_part(terms)).hexdigest() +assert hash == "7a8568f6ee99b914463265a8e42bb9736f719aca832cee63019ab9ced284dcf8" + +print(repr(revocation_policy["Content-Disposition"])) +assert revocation_policy["Content-Disposition"] == 'attachment; filename="widerrufsbelehrung.pdf"' +assert revocation_policy["Content-Type"] == "application/pdf" +hash = hashlib.sha256(decode_mail_part(revocation_policy)).hexdigest() +assert hash == "046c90a8d66a67acbb6e5154b83a3b61ef3b17ec0f4a91bea189b1c5d1076d74" diff --git a/nix/tests/utils.py b/nix/tests/utils.py index 767bb37..0c886fb 100644 --- a/nix/tests/utils.py +++ b/nix/tests/utils.py @@ -25,8 +25,16 @@ def decode_mail_header(header): return str(email.header.make_header(email.header.decode_header(header))) +def get_mail_parts(mail: Message) -> list[Message]: + return cast(list[Message], mail.get_payload()) + + +def decode_mail_part(mail: Message) -> bytes: + return cast(bytes, mail.get_payload(decode=True)) + + def decode_mail_payload(mail: Message): - return cast(bytes, mail.get_payload(decode=True)).decode() + return decode_mail_part(get_mail_parts(mail)[0]).decode() def refresh_session(refresh_token=None, client=None):