From ee34b1bf22e58ef0f10a272695259494646099ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djk=C3=A1=C5=A5o?= Date: Sat, 9 Mar 2024 19:59:15 +0100 Subject: [PATCH] add regeneration to sitemap, other fixes --- .tokeinore | 6 + Cargo.lock | 704 +++++++++++------- Cargo.toml | 18 +- app-template/src/routes/mod.rs | 5 +- sdk/Cargo.toml | 17 +- .../middleware/verify_webhook_signature.rs | 80 +- simple-payment-gateway/Cargo.toml | 6 + simple-payment-gateway/src/main.rs | 3 + sitemap-generator/src/app.rs | 27 +- sitemap-generator/src/main.rs | 34 +- .../src/queries/event_subjects_updated.rs | 40 +- .../queries/get_all_categories_n_products.rs | 209 ++++++ .../src/queries/get_all_collections.rs | 52 ++ .../src/queries/get_all_pages.rs | 91 +++ sitemap-generator/src/queries/mod.rs | 4 +- .../src/queries/product_metadata_update.rs | 75 -- sitemap-generator/src/routes/manifest.rs | 2 +- sitemap-generator/src/routes/mod.rs | 5 +- sitemap-generator/src/routes/register.rs | 495 +++++++++++- sitemap-generator/src/routes/webhooks.rs | 199 ++++- 20 files changed, 1608 insertions(+), 464 deletions(-) create mode 100644 .tokeinore create mode 100644 simple-payment-gateway/Cargo.toml create mode 100644 simple-payment-gateway/src/main.rs create mode 100644 sitemap-generator/src/queries/get_all_categories_n_products.rs create mode 100644 sitemap-generator/src/queries/get_all_collections.rs create mode 100644 sitemap-generator/src/queries/get_all_pages.rs delete mode 100644 sitemap-generator/src/queries/product_metadata_update.rs diff --git a/.tokeinore b/.tokeinore new file mode 100644 index 0000000..86e5aae --- /dev/null +++ b/.tokeinore @@ -0,0 +1,6 @@ +/target +temp +**/*.graphql +app-template/src/schema.graphql +PolyForm-Noncommercial-1.0.0.md +app logo template.xcf diff --git a/Cargo.lock b/Cargo.lock index dff5c04..193d42e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,9 +297,9 @@ dependencies = [ "bytes 1.5.0", "futures-util", "http 1.0.0", - "http-body", + "http-body 1.0.0", "http-body-util", - "hyper", + "hyper 1.1.0", "hyper-util", "itoa", "matchit", @@ -330,7 +330,7 @@ dependencies = [ "bytes 1.5.0", "futures-util", "http 1.0.0", - "http-body", + "http-body 1.0.0", "http-body-util", "mime", "pin-project-lite", @@ -362,12 +362,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.13.1" @@ -375,10 +369,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "base64ct" -version = "1.6.0" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" @@ -535,12 +529,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const_fn" version = "0.4.9" @@ -554,16 +542,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "aes-gcm", - "base64", + "base64 0.13.1", "hkdf", "hmac", "percent-encoding", "rand 0.8.5", "sha2 0.9.9", - "time", + "time 0.2.27", "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -628,18 +626,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.6" @@ -781,13 +767,12 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.8" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "const-oid", - "zeroize", + "powerfmt", ] [[package]] @@ -806,7 +791,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", "crypto-common", ] @@ -828,24 +812,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "encoding_rs" version = "0.8.33" @@ -954,16 +920,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "flate2" version = "1.0.28" @@ -991,6 +947,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1125,7 +1096,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1146,8 +1116,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1175,8 +1147,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.2", ] [[package]] @@ -1213,14 +1185,22 @@ dependencies = [ ] [[package]] -name = "group" -version = "0.13.0" +name = "h2" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", + "bytes 1.5.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -1308,6 +1288,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes 1.5.0", + "http 0.2.11", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.0" @@ -1327,7 +1318,7 @@ dependencies = [ "bytes 1.5.0", "futures-util", "http 1.0.0", - "http-body", + "http-body 1.0.0", "pin-project-lite", ] @@ -1360,7 +1351,7 @@ dependencies = [ "anyhow", "async-channel 1.9.0", "async-std", - "base64", + "base64 0.13.1", "cookie", "futures-lite 1.13.0", "infer", @@ -1385,6 +1376,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes 1.5.0", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.1.0" @@ -1394,9 +1409,9 @@ dependencies = [ "bytes 1.5.0", "futures-channel", "futures-util", - "h2", + "h2 0.4.2", "http 1.0.0", - "http-body", + "http-body 1.0.0", "httparse", "httpdate", "itoa", @@ -1404,6 +1419,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.5.0", + "hyper 0.14.28", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-util" version = "0.1.3" @@ -1413,8 +1441,8 @@ dependencies = [ "bytes 1.5.0", "futures-util", "http 1.0.0", - "http-body", - "hyper", + "http-body 1.0.0", + "hyper 1.1.0", "pin-project-lite", "socket2 0.5.5", "tokio", @@ -1469,7 +1497,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata", + "regex-automata 0.4.6", "same-file", "walkdir", "winapi-util", @@ -1511,6 +1539,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "isahc" version = "0.9.14" @@ -1549,57 +1583,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jose-b64" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec69375368709666b21c76965ce67549f2d2db7605f1f8707d17c9656801b56" -dependencies = [ - "base64ct", - "serde", - "serde_json", - "subtle", - "zeroize", -] - -[[package]] -name = "jose-jwa" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab78e053fe886a351d67cf0d194c000f9d0dcb92906eb34d853d7e758a4b3a7" -dependencies = [ - "serde", -] - -[[package]] -name = "jose-jwk" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280fa263807fe0782ecb6f2baadc28dffc04e00558a58e33bfdb801d11fd58e7" -dependencies = [ - "jose-b64", - "jose-jwa", - "p256", - "p384", - "rsa", - "serde", - "zeroize", -] - -[[package]] -name = "jose-jws" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d49df4f553a811aa2e378155ade5c7aac0f410086d3010faca127417c1c26" -dependencies = [ - "jose-b64", - "jose-jwa", - "jose-jwk", - "rand_core 0.6.4", - "serde", - "serde_json", -] - [[package]] name = "js-sys" version = "0.3.68" @@ -1609,6 +1592,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1623,9 +1621,6 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -1633,12 +1628,6 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "libnghttp2-sys" version = "0.1.9+1.58.0" @@ -1692,6 +1681,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matchit" version = "0.7.3" @@ -1740,6 +1738,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1751,22 +1767,22 @@ dependencies = [ ] [[package]] -name = "num-bigint-dig" -version = "0.8.4" +name = "num-bigint" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ - "byteorder", - "lazy_static", - "libm", + "autocfg", "num-integer", - "num-iter", "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -1776,17 +1792,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.18" @@ -1794,7 +1799,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1828,6 +1832,32 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -1877,26 +1907,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "elliptic-curve", - "primeorder", -] - -[[package]] -name = "p384" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "elliptic-curve", - "primeorder", -] - [[package]] name = "parking" version = "2.2.0" @@ -1926,6 +1936,16 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.7", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2020,27 +2040,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.30" @@ -2088,21 +2087,18 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -2282,8 +2278,17 @@ checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.6", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2294,9 +2299,15 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -2304,23 +2315,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] -name = "rsa" -version = "0.9.6" +name = "reqwest" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ - "const-oid", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", + "base64 0.21.7", + "bytes 1.5.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.12", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -2365,6 +2411,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2383,13 +2438,13 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "axum", "dotenvy", "envy", "http 1.0.0", - "jose-b64", - "jose-jwk", - "jose-jws", + "jsonwebtoken", "redis", + "reqwest", "serde", "serde_json", "strum", @@ -2397,6 +2452,7 @@ dependencies = [ "tower", "tracing", "tracing-subscriber", + "url", ] [[package]] @@ -2447,16 +2503,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sec1" -version = "0.7.3" +name = "security-framework" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "base16ct", - "der", - "generic-array", - "subtle", - "zeroize", + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -2606,13 +2672,19 @@ dependencies = [ ] [[package]] -name = "signature" -version = "2.2.0" +name = "simple-payment-gateway" +version = "0.1.0" + +[[package]] +name = "simple_asn1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", + "num-bigint", + "num-traits", + "thiserror", + "time 0.3.34", ] [[package]] @@ -2704,9 +2776,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spinning_top" @@ -2717,16 +2789,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "standback" version = "0.2.17" @@ -2873,6 +2935,39 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.0.1", + "rustix 0.38.31", + "windows-sys 0.52.0", +] + [[package]] name = "tera" version = "1.19.1" @@ -2929,11 +3024,32 @@ dependencies = [ "libc", "standback", "stdweb", - "time-macros", + "time-macros 0.1.1", "version_check", "winapi", ] +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros 0.2.17", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "time-macros" version = "0.1.1" @@ -2944,6 +3060,16 @@ dependencies = [ "time-macros-impl", ] +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "time-macros-impl" version = "0.1.2" @@ -3012,6 +3138,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-retry" version = "0.3.0" @@ -3063,7 +3199,7 @@ dependencies = [ "bytes 1.5.0", "futures-util", "http 1.0.0", - "http-body", + "http-body 1.0.0", "http-body-util", "http-range-header", "httpdate", @@ -3160,14 +3296,24 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ + "matchers", "nu-ansi-term", + "once_cell", + "regex", "sharded-slab", "smallvec", "thread_local", + "tracing", "tracing-core", "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -3279,6 +3425,12 @@ dependencies = [ "void", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -3337,6 +3489,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3597,6 +3758,16 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "xml-builder" version = "0.5.2" @@ -3608,12 +3779,3 @@ name = "yansi" version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" - -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" -dependencies = [ - "serde", -] diff --git a/Cargo.toml b/Cargo.toml index 20e40e3..35a17f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,26 @@ [workspace] -members = ["sdk", "app-template", "sitemap-generator"] +members = ["sdk", "app-template", "sitemap-generator", "simple-payment-gateway"] resolver = "2" [workspace.dependencies] anyhow = "1.0.79" -cynic = {version="3.4.3", features = ["http-surf"]} +cynic = { version = "3.4.3", features = ["http-surf"] } surf = "2.3.2" serde = "1.0.196" serde_json = "1.0.113" -tokio = {version = "1.36.0", features = ["full"]} -redis = { version = "0.23.0", features = ["aio", "tokio-comp", "connection-manager"] } +tokio = { version = "1.36.0", features = ["full"] } +redis = { version = "0.23.0", features = [ + "aio", + "tokio-comp", + "connection-manager", +] } envy = "0.4.2" tracing = "0.1.40" tracing-serde = "0.1.3" -tracing-subscriber = { version = "0.3.18" } +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } dotenvy = "0.15.7" axum = "0.7.4" -saleor-app-sdk = {path = "sdk"} +saleor-app-sdk = { path = "sdk" } tower = { version = "0.4.13", features = ["util"] } tower-http = { version = "0.5.2", features = ["fs", "trace"] } -cynic-codegen= "3.4.3" +cynic-codegen = "3.4.3" diff --git a/app-template/src/routes/mod.rs b/app-template/src/routes/mod.rs index c565cf4..5124453 100644 --- a/app-template/src/routes/mod.rs +++ b/app-template/src/routes/mod.rs @@ -1,9 +1,11 @@ use axum::{ handler::HandlerWithoutStateExt, http::StatusCode, + middleware, routing::{get, post}, Router, }; +use saleor_app_sdk::middleware::verify_webhook_signature::webhook_signature_verifier; use tower_http::services::ServeDir; use crate::app::AppState; @@ -23,7 +25,9 @@ pub fn create_routes(state: AppState) -> Router { let serve_dir = ServeDir::new("saleor-app-template/public").not_found_service(service); Router::new() + .layer(middleware::from_fn(webhook_signature_verifier)) //handles just path, eg. localhost:3000/ + .route("/api/webhooks", post(webhooks)) .route( "/", get(|| async { "Your app got installed successfully!" }), @@ -32,6 +36,5 @@ pub fn create_routes(state: AppState) -> Router { .fallback_service(serve_dir) .route("/api/manifest", get(manifest)) .route("/api/register", post(register)) - .route("/api/webhooks", post(webhooks)) .with_state(state) } diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 87c6b25..754ec94 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" edition = "2021" description = "Unofficial Saleor App SDK like library, made to work with rust." keywords = ["saleor", "sdk", "plugin"] -categories = [ "api-bindings", "web-programming::http-server"] +categories = ["api-bindings", "web-programming::http-server"] homepage = "https://github.com/djkato/saleor-app-rs-template" repository = "https://github.com/djkato/saleor-app-rs-template" documentation = "https://github.com/djkato/saleor-app-rs-template" @@ -13,18 +13,23 @@ license = "MIT OR Apache-2.0" [dependencies] anyhow.workspace = true -redis = { workspace=true, features = ["aio", "tokio-comp", "connection-manager"] } +redis = { workspace = true, features = [ + "aio", + "tokio-comp", + "connection-manager", +] } serde.workspace = true +axum.workspace = true tracing.workspace = true tracing-subscriber.workspace = true serde_json.workspace = true envy.workspace = true dotenvy.workspace = true -async-trait = "0.1.77" -jose-jwk = "0.1.2" tower = { workspace = true } -jose-jws = "0.1.2" +reqwest = { version = "0.11.24", features = ["json"] } +jsonwebtoken = "9.2.0" +async-trait = "0.1.77" http = "1.0.0" -jose-b64 = {version = "0.1.2", features =["serde"] } +url = "2.5.0" strum = "0.26.0" strum_macros = "0.26.1" diff --git a/sdk/src/middleware/verify_webhook_signature.rs b/sdk/src/middleware/verify_webhook_signature.rs index 9751d06..73570a7 100644 --- a/sdk/src/middleware/verify_webhook_signature.rs +++ b/sdk/src/middleware/verify_webhook_signature.rs @@ -1,4 +1,82 @@ -/* +use axum::{body, extract::Request, http::StatusCode, middleware::Next, response::Response}; +use serde_json::Value; + +use jsonwebtoken::{crypto, Algorithm, DecodingKey}; +use tracing::{debug, error}; + +use crate::headers::{SALEOR_API_URL_HEADER, SALEOR_SIGNATURE_HEADER}; + +pub async fn webhook_signature_verifier(request: Request, next: Next) -> Response { + let unauthorized = Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(body::Body::from("Not authenticated\n")) + .unwrap(); + + let jwks_url = request + .headers() + .get(SALEOR_API_URL_HEADER) + .map_or(None, |h| { + h.to_str() + .map_or(None, |h| url::Url::parse(h).map_or(None, |h| Some(h))) + }); + + //get jwk from saleor api + let jwks: Value = 'block: { + if let Some(mut jwks_url) = jwks_url { + jwks_url.set_path("/.well-known/jwks.json"); + if let Ok(get_res) = reqwest::get(jwks_url).await { + if let Ok(val) = get_res.json::().await { + break 'block val; + } + } + } + error!("Saleor webhook signature not verified, failed fetching jwks from saleor"); + return unauthorized; + }; + let nstr = jwks["keys"][0]["n"].as_str().unwrap(); + let estr = jwks["keys"][0]["e"].as_str().unwrap(); + + let pubkey = DecodingKey::from_rsa_components(&nstr, &estr).unwrap(); + + let (parts, body) = request.into_parts(); + let payload = body::to_bytes(body, usize::MAX).await.unwrap(); + + if let Some(is_verified) = parts + .headers + .get(SALEOR_SIGNATURE_HEADER) + .and_then(|sig| sig.to_str().ok()) + .and_then(|sig| { + let parts: Vec<&str> = sig.split('.').collect(); + match parts.as_slice() { + [protected, _, signature] => Some((*protected, *signature)), + _ => None, + } + }) + .and_then(|(protected, signature)| { + let mut msg: Vec = Vec::new(); + msg.extend_from_slice(format!("{}.", protected).as_bytes()); + msg.extend_from_slice(&payload); + + crypto::verify(signature, &msg, &pubkey, Algorithm::RS256).ok() + }) + { + match is_verified { + true => { + debug!("Saleor webhook signature verified"); + next.run(Request::from_parts(parts, payload.into())).await + } + false => { + error!("Saleor webhook signature not correct"); + unauthorized + } + } + } else { + error!("Saleor webhook signature not verified, error parsing headers"); + unauthorized + } +} + +/* OLD use http::{Request, Response}; use std::task::{Context, Poll}; use tower::Service; diff --git a/simple-payment-gateway/Cargo.toml b/simple-payment-gateway/Cargo.toml new file mode 100644 index 0000000..a06934e --- /dev/null +++ b/simple-payment-gateway/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "simple-payment-gateway" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/simple-payment-gateway/src/main.rs b/simple-payment-gateway/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/simple-payment-gateway/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/sitemap-generator/src/app.rs b/sitemap-generator/src/app.rs index 0b314a0..e402da3 100644 --- a/sitemap-generator/src/app.rs +++ b/sitemap-generator/src/app.rs @@ -6,11 +6,12 @@ use axum::{ use chrono::{DateTime, FixedOffset}; use fd_lock::RwLock; use std::{fs::File, sync::Arc, time::Duration}; +use tracing_subscriber::EnvFilter; use redis::{AsyncCommands, Client, RedisError}; use saleor_app_sdk::{config::Config, manifest::AppManifest, SaleorApp}; use serde::{Deserialize, Serialize}; -use tracing::{debug, info}; +use tracing::{debug, info, level_filters::LevelFilter}; // Make our own error that wraps `anyhow::Error`. pub struct AppError(anyhow::Error); @@ -37,9 +38,20 @@ where } pub fn trace_to_std(config: &Config) { + let filter = EnvFilter::builder() + .with_default_directive(LevelFilter::DEBUG.into()) + .from_env() + .unwrap() + .add_directive( + format!("{}={}", env!("CARGO_PKG_NAME"), config.log_level) + .parse() + .unwrap(), + ); tracing_subscriber::fmt() .with_max_level(config.log_level) - .with_target(false) + .with_env_filter(filter) + .with_target(true) + .compact() .init(); } @@ -49,12 +61,7 @@ pub fn trace_to_std(config: &Config) { */ #[derive(Debug, Clone)] pub struct AppState { - pub sitemap_file_products: Vec>>, - pub sitemap_file_categories: Vec>>, - pub sitemap_file_collections: Vec>>, - pub sitemap_file_pages: Vec>>, - pub sitemap_file_index: Arc>, - pub xml_cache: XmlCache, + pub xml_cache: Arc>, pub saleor_app: Arc>, pub config: Config, pub sitemap_config: SitemapConfig, @@ -71,6 +78,8 @@ pub struct SitemapConfig { pub category_template: String, #[serde(rename = "sitemap_pages_template")] pub pages_template: String, + #[serde(rename = "sitemap_collection_template")] + pub collection_template: String, #[serde(rename = "sitemap_index_hostname")] pub index_hostname: String, } @@ -83,7 +92,7 @@ impl SitemapConfig { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct XmlCache { client: Client, app_api_base_url: String, diff --git a/sitemap-generator/src/main.rs b/sitemap-generator/src/main.rs index 8bd7c92..e0b224d 100644 --- a/sitemap-generator/src/main.rs +++ b/sitemap-generator/src/main.rs @@ -1,18 +1,19 @@ +#![feature(let_chains)] +#![deny(clippy::unwrap_used, clippy::expect_used)] mod app; mod queries; mod routes; use anyhow::Context; -use fd_lock::RwLock; use saleor_app_sdk::{ config::Config, manifest::{AppManifest, AppPermission}, webhooks::{AsyncWebhookEventType, WebhookManifest}, SaleorApp, }; -use std::{fs::File, sync::Arc}; +use std::sync::Arc; use tokio::sync::Mutex; -use tracing::debug; +use tracing::{debug, info}; use crate::{ app::{trace_to_std, AppState, SitemapConfig, XmlCache}, @@ -57,27 +58,24 @@ async fn main() -> anyhow::Result<()> { ) .build(); debug!("Created AppManifest..."); - - debug!("{}/sitemap_index.xml.gz", sitemap_config.target_folder); let app_state = AppState { - sitemap_file_index: Arc::new(RwLock::new(File::options().write(true).create(true).open( - format!("{}/sitemap_index.xml", sitemap_config.target_folder), - )?)), - sitemap_file_products: vec![], - sitemap_file_categories: vec![], - sitemap_file_collections: vec![], - sitemap_file_pages: vec![], sitemap_config, - xml_cache: XmlCache::new(&config.apl_url, &config.app_api_base_url)?, + xml_cache: Arc::new(Mutex::new(XmlCache::new( + &config.apl_url, + &config.app_api_base_url, + )?)), manifest: app_manifest, config: config.clone(), saleor_app: Arc::new(Mutex::new(saleor_app)), }; debug!("Created AppState..."); - app_state - .xml_cache - .delete_all("http://localhost:8000/graphpl/") - .await?; + { + let xml_cache = app_state.xml_cache.lock().await; + xml_cache + .delete_all("http://localhost:8000/graphpl/") + .await?; + debug!("Cleared Xml Cache"); + } let app = create_routes(app_state); let listener = tokio::net::TcpListener::bind( @@ -89,7 +87,7 @@ async fn main() -> anyhow::Result<()> { .context("APP_API_BASE_URL invalid format")?, ) .await?; - tracing::debug!("listening on {}", listener.local_addr().unwrap()); + info!("listening on {}", listener.local_addr().unwrap()); match axum::serve(listener, app).await { Ok(o) => Ok(o), Err(e) => anyhow::bail!(e), diff --git a/sitemap-generator/src/queries/event_subjects_updated.rs b/sitemap-generator/src/queries/event_subjects_updated.rs index cbde97f..e4509dc 100644 --- a/sitemap-generator/src/queries/event_subjects_updated.rs +++ b/sitemap-generator/src/queries/event_subjects_updated.rs @@ -78,18 +78,6 @@ subscription QueryProductsChanged { fragment BaseCategory on Category { id slug - products(first: 100) { - pageInfo { - endCursor - hasNextPage - } - edges { - node { - id - slug - } - } - } } fragment BaseProduct on Product { @@ -130,7 +118,7 @@ pub struct Product { pub category: Option, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Serialize)] pub struct PageUpdated { pub page: Option, } @@ -145,13 +133,13 @@ pub struct PageCreated { pub page: Option, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Serialize)] pub struct Page { pub slug: String, pub id: cynic::Id, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Serialize)] pub struct CollectionUpdated { pub collection: Option, } @@ -166,7 +154,7 @@ pub struct CollectionCreated { pub collection: Option, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Serialize)] pub struct Collection { pub id: cynic::Id, pub slug: String, @@ -198,26 +186,6 @@ pub struct Category { pub struct Category2 { pub id: cynic::Id, pub slug: String, - #[arguments(first: 100)] - pub products: Option, -} - -#[derive(cynic::QueryFragment, Debug, Serialize)] -pub struct ProductCountableConnection { - pub page_info: PageInfo, - pub edges: Vec, -} - -#[derive(cynic::QueryFragment, Debug, Serialize)] -pub struct ProductCountableEdge { - pub node: Product2, -} - -#[derive(cynic::QueryFragment, Debug, Serialize)] -#[cynic(graphql_type = "Product")] -pub struct Product2 { - pub id: cynic::Id, - pub slug: String, } #[derive(cynic::QueryFragment, Debug, Serialize)] diff --git a/sitemap-generator/src/queries/get_all_categories_n_products.rs b/sitemap-generator/src/queries/get_all_categories_n_products.rs new file mode 100644 index 0000000..d9d9e06 --- /dev/null +++ b/sitemap-generator/src/queries/get_all_categories_n_products.rs @@ -0,0 +1,209 @@ +#[cynic::schema("saleor")] +mod schema {} +pub struct CategorisedProduct { + pub product: Product, + pub category_id: cynic::Id, +} + +/* +query getCategoriesInitial { + categories(first: 50) { + totalCount + pageInfo { + hasNextPage + endCursor + } + edges { + node { + updatedAt + id + slug + } + } + } +} + +query getCategoriesNext($after: String) { + categories(first: 50, after: $after) { + pageInfo { + hasNextPage + endCursor + } + edges { + node { + updatedAt + id + slug + } + } + } +} + +query getCategoryProductsInitial($id: ID!) { + category(id: $id) { + slug + id + updatedAt + products(first: 50) { + pageInfo { + hasNextPage + endCursor + } + edges { + node { + id + slug + updatedAt + } + } + totalCount + } + } +} + +query getCategoryProductsNext($id: ID!, $after: String!) { + category(id: $id) { + products(first: 50, after: $after) { + pageInfo { + hasNextPage + endCursor + } + edges { + node { + id + slug + updatedAt + } + } + } + } +} +*/ + +#[derive(cynic::QueryVariables, Debug)] +pub struct GetCategoryProductsInitialVariables<'a> { + pub id: &'a cynic::Id, +} + +#[derive(cynic::QueryVariables, Debug)] +pub struct GetCategoryProductsNextVariables<'a> { + pub after: &'a str, + pub id: &'a cynic::Id, +} + +#[derive(cynic::QueryVariables, Debug)] +pub struct GetCategoriesNextVariables<'a> { + pub after: Option<&'a str>, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic( + graphql_type = "Query", + variables = "GetCategoryProductsInitialVariables" +)] +pub struct GetCategoryProductsInitial { + #[arguments(id: $id)] + pub category: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "GetCategoryProductsNextVariables")] +pub struct GetCategoryProductsNext { + #[arguments(id: $id)] + pub category: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "GetCategoriesNextVariables")] +pub struct GetCategoriesNext { + #[arguments(first: 50, after: $after)] + pub categories: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query")] +pub struct GetCategoriesInitial { + #[arguments(first: 50)] + pub categories: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "CategoryCountableConnection")] +pub struct CategoryCountableConnection2 { + pub total_count: Option, + pub page_info: PageInfo, + pub edges: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct CategoryCountableConnection { + pub page_info: PageInfo, + pub edges: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct CategoryCountableEdge { + pub node: Category3, +} + +#[derive(cynic::QueryFragment, Debug, Clone)] +#[cynic(graphql_type = "Category")] +pub struct Category3 { + pub updated_at: DateTime, + pub id: cynic::Id, + pub slug: String, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct Category { + pub slug: String, + pub id: cynic::Id, + pub updated_at: DateTime, + #[arguments(first: 50)] + pub products: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct ProductCountableConnection { + pub page_info: PageInfo, + pub edges: Vec, + pub total_count: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic( + graphql_type = "Category", + variables = "GetCategoryProductsNextVariables" +)] +pub struct Category2 { + #[arguments(first: 50, after: $after)] + pub products: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "ProductCountableConnection")] +pub struct ProductCountableConnection2 { + pub page_info: PageInfo, + pub edges: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct ProductCountableEdge { + pub node: Product, +} + +#[derive(cynic::QueryFragment, Debug, Clone)] +pub struct Product { + pub id: cynic::Id, + pub slug: String, + pub updated_at: DateTime, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct PageInfo { + pub has_next_page: bool, + pub end_cursor: Option, +} + +#[derive(cynic::Scalar, Debug, Clone)] +pub struct DateTime(pub String); diff --git a/sitemap-generator/src/queries/get_all_collections.rs b/sitemap-generator/src/queries/get_all_collections.rs new file mode 100644 index 0000000..f980f9e --- /dev/null +++ b/sitemap-generator/src/queries/get_all_collections.rs @@ -0,0 +1,52 @@ +#[cynic::schema("saleor")] +mod schema {} + +#[derive(cynic::QueryVariables, Debug)] +pub struct GetCollectionsNextVariables<'a> { + pub after: Option<&'a str>, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "GetCollectionsNextVariables")] +pub struct GetCollectionsNext { + #[arguments(first: 50, after: $after)] + pub collections: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query")] +pub struct GetCollectionsInitial { + #[arguments(first: 50)] + pub collections: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "CollectionCountableConnection")] +pub struct CollectionCountableConnection2 { + pub total_count: Option, + pub page_info: PageInfo, + pub edges: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct CollectionCountableConnection { + pub page_info: PageInfo, + pub edges: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct CollectionCountableEdge { + pub node: Collection, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct PageInfo { + pub has_next_page: bool, + pub end_cursor: Option, +} + +#[derive(cynic::QueryFragment, Debug, Clone)] +pub struct Collection { + pub id: cynic::Id, + pub slug: String, +} diff --git a/sitemap-generator/src/queries/get_all_pages.rs b/sitemap-generator/src/queries/get_all_pages.rs new file mode 100644 index 0000000..78d4990 --- /dev/null +++ b/sitemap-generator/src/queries/get_all_pages.rs @@ -0,0 +1,91 @@ +#[cynic::schema("saleor")] +mod schema {} + +/* +query getPagesInitial { + pages(first: 50) { + totalCount + pageInfo { + hasNextPage + endCursor + } + edges { + node { + publishedAt + id + slug + } + } + } +} + +query getPagesNext($after: String!) { + pages(first: 50, after: $after) { + pageInfo { + hasNextPage + endCursor + } + edges { + node { + publishedAt + id + slug + } + } + } +} +*/ + +#[derive(cynic::QueryVariables, Debug)] +pub struct GetPagesNextVariables<'a> { + pub after: &'a str, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "GetPagesNextVariables")] +pub struct GetPagesNext { + #[arguments(first: 50, after: $after)] + pub pages: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query")] +pub struct GetPagesInitial { + #[arguments(first: 50)] + pub pages: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "PageCountableConnection")] +pub struct PageCountableConnection2 { + pub total_count: Option, + pub page_info: PageInfo, + pub edges: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct PageCountableConnection { + pub page_info: PageInfo, + pub edges: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct PageCountableEdge { + pub node: Page, +} + +#[derive(cynic::QueryFragment, Debug)] +pub struct PageInfo { + pub has_next_page: bool, + pub end_cursor: Option, +} + +#[derive(cynic::QueryFragment, Debug, Clone)] +pub struct Page { + pub published_at: Option, + pub id: cynic::Id, + pub slug: String, +} + +#[derive(cynic::Scalar, Debug, Clone)] +pub struct DateTime(pub String); diff --git a/sitemap-generator/src/queries/mod.rs b/sitemap-generator/src/queries/mod.rs index bbdfa38..add6544 100644 --- a/sitemap-generator/src/queries/mod.rs +++ b/sitemap-generator/src/queries/mod.rs @@ -1,2 +1,4 @@ pub mod event_subjects_updated; -pub mod product_metadata_update; +pub mod get_all_categories_n_products; +pub mod get_all_collections; +pub mod get_all_pages; diff --git a/sitemap-generator/src/queries/product_metadata_update.rs b/sitemap-generator/src/queries/product_metadata_update.rs deleted file mode 100644 index 8dfa70a..0000000 --- a/sitemap-generator/src/queries/product_metadata_update.rs +++ /dev/null @@ -1,75 +0,0 @@ -#[cynic::schema("saleor")] -mod schema {} - -#[derive(cynic::QueryVariables, Debug)] -pub struct UpdateProductMetadataVariables<'a> { - pub metadata: Option>>, - pub product_id: &'a cynic::Id, -} - -#[derive(cynic::QueryFragment, Debug)] -#[cynic( - graphql_type = "Mutation", - variables = "UpdateProductMetadataVariables" -)] -pub struct UpdateProductMetadata { - #[arguments(id: $product_id, input: { metadata: $metadata })] - pub product_update: Option, -} - -#[derive(cynic::QueryFragment, Debug)] -pub struct ProductUpdate { - pub errors: Vec, - pub product: Option, -} - -#[derive(cynic::QueryFragment, Debug)] -pub struct Product { - pub id: cynic::Id, - pub metadata: Vec, -} - -#[derive(cynic::QueryFragment, Debug)] -pub struct ProductError { - pub field: Option, - pub message: Option, - pub code: ProductErrorCode, - pub attributes: Option>, - pub values: Option>, -} - -#[derive(cynic::QueryFragment, Debug)] -pub struct MetadataItem { - pub key: String, - pub value: String, -} - -#[derive(cynic::Enum, Clone, Copy, Debug)] -pub enum ProductErrorCode { - AlreadyExists, - AttributeAlreadyAssigned, - AttributeCannotBeAssigned, - AttributeVariantsDisabled, - MediaAlreadyAssigned, - DuplicatedInputItem, - GraphqlError, - Invalid, - InvalidPrice, - ProductWithoutCategory, - NotProductsImage, - NotProductsVariant, - NotFound, - Required, - Unique, - VariantNoDigitalContent, - CannotManageProductWithoutVariant, - ProductNotAssignedToChannel, - UnsupportedMediaProvider, - PreorderVariantCannotBeDeactivated, -} - -#[derive(cynic::InputObject, Debug)] -pub struct MetadataInput<'a> { - pub key: &'a str, - pub value: &'a str, -} diff --git a/sitemap-generator/src/routes/manifest.rs b/sitemap-generator/src/routes/manifest.rs index 3ab361c..481fa5c 100644 --- a/sitemap-generator/src/routes/manifest.rs +++ b/sitemap-generator/src/routes/manifest.rs @@ -1,5 +1,5 @@ use axum::{extract::State, Json}; -use saleor_app_sdk::{manifest::AppManifest}; +use saleor_app_sdk::manifest::AppManifest; use crate::app::{AppError, AppState}; diff --git a/sitemap-generator/src/routes/mod.rs b/sitemap-generator/src/routes/mod.rs index 7d2a804..ee95703 100644 --- a/sitemap-generator/src/routes/mod.rs +++ b/sitemap-generator/src/routes/mod.rs @@ -1,9 +1,11 @@ use axum::{ handler::HandlerWithoutStateExt, http::StatusCode, + middleware, routing::{any, get, post}, Router, }; +use saleor_app_sdk::middleware::verify_webhook_signature::webhook_signature_verifier; use tower_http::services::ServeDir; use crate::app::AppState; @@ -24,6 +26,8 @@ pub fn create_routes(state: AppState) -> Router { let serve_dir = ServeDir::new("./sitemap-generator/public").not_found_service(service); Router::new() + .route("/api/webhooks", any(webhooks)) + .layer(middleware::from_fn(webhook_signature_verifier)) //handles just path, eg. localhost:3000/ .route( "/", @@ -33,6 +37,5 @@ pub fn create_routes(state: AppState) -> Router { .fallback_service(serve_dir) .route("/api/manifest", get(manifest)) .route("/api/register", post(register)) - .route("/api/webhooks", any(webhooks)) .with_state(state) } diff --git a/sitemap-generator/src/routes/register.rs b/sitemap-generator/src/routes/register.rs index 2203984..5492178 100644 --- a/sitemap-generator/src/routes/register.rs +++ b/sitemap-generator/src/routes/register.rs @@ -1,13 +1,38 @@ +use std::{rc::Rc, str::FromStr, sync::Arc}; + use anyhow::Context; use axum::{ extract::Json, extract::State, http::{HeaderMap, StatusCode}, }; +use chrono::TimeZone; +use cynic::{http::SurfExt, QueryBuilder}; use saleor_app_sdk::{AuthData, AuthToken}; -use tracing::{debug, info}; +use sitemap_rs::url::Url; +use tinytemplate::TinyTemplate; +use tokio::spawn; +use tracing::{debug, error, info, trace}; -use crate::app::{AppError, AppState}; +use crate::{ + app::{AppError, AppState, XmlData, XmlDataType}, + queries::{ + event_subjects_updated::{ + self, CategoryUpdated, CollectionUpdated, PageUpdated, ProductUpdated, + }, + get_all_categories_n_products::{ + CategorisedProduct, Category, Category3, GetCategoriesInitial, GetCategoriesNext, + GetCategoriesNextVariables, GetCategoryProductsInitial, + GetCategoryProductsInitialVariables, GetCategoryProductsNext, + GetCategoryProductsNextVariables, + }, + get_all_collections::{ + Collection, GetCollectionsInitial, GetCollectionsNext, GetCollectionsNextVariables, + }, + get_all_pages::{self, GetPagesInitial, GetPagesNext, GetPagesNextVariables}, + }, + routes::webhooks::write_xml, +}; pub async fn register( headers: HeaderMap, @@ -29,12 +54,474 @@ pub async fn register( let auth_data = AuthData { jwks: None, token: auth_token.auth_token, - domain: Some(state.config.app_api_base_url), - app_id: state.manifest.id, + domain: Some(state.config.app_api_base_url.clone()), + app_id: state.manifest.id.clone(), saleor_api_url: saleor_api_url.clone(), }; app.apl.set(auth_data).await?; info!("registered app for{:?}", &saleor_api_url); + + //When app registers, start collecting everything of substance + info!("Starting caching and generation process"); + let cloned_state = state.clone(); + spawn(async move { + match regenerate(cloned_state, saleor_api_url).await { + Ok(_) => info!("Finished caching and regeneration"), + Err(e) => error!("Something went wrong during caching and regeneration, {e}"), + }; + }); Ok(StatusCode::OK) } + +pub async fn regenerate(state: AppState, saleor_api_url: String) -> anyhow::Result<()> { + info!("regeneration: fetching all categories, products, collections, pages"); + let xml_cache = state.xml_cache.lock().await; + let mut categories: Vec<(Category3, Vec>)> = + get_all_categories(&saleor_api_url) + .await? + .into_iter() + .map(|c| (c, vec![])) + .collect(); + let mut products = vec![]; + for category in categories.iter_mut() { + products.append(&mut get_all_products(&saleor_api_url, category).await?); + } + let pages = get_all_pages(&saleor_api_url).await?; + let collections = get_all_collections(&saleor_api_url).await?; + info!( + "regeneration: found {} products, {} categories, {} pages, {} collections", + products.len(), + categories.len(), + pages.len(), + collections.len() + ); + info!("regeneration: creating xml data and caching it"); + let mut xml_data = vec![]; + xml_data.append( + &mut categories + .into_iter() + .map(|c| XmlData { + slug: c.0.slug, + last_modified: chrono::DateTime::::from_str(&c.0.updated_at.0) + .map_or(chrono::offset::Utc::now().fixed_offset(), |d| { + d.fixed_offset() + }), + id: c.0.id, + relations: c.1.iter().map(|p| p.product.id.clone()).collect::>(), + data_type: XmlDataType::Category, + }) + .collect::>(), + ); + xml_data.append( + &mut products + .into_iter() + .map(|p| XmlData { + data_type: XmlDataType::Product, + relations: vec![p.category_id.clone()], + id: p.product.id.clone(), + last_modified: chrono::DateTime::::from_str(&p.product.updated_at.0) + .map_or(chrono::offset::Utc::now().fixed_offset(), |d| { + d.fixed_offset() + }), + slug: p.product.slug.clone(), + }) + .collect(), + ); + xml_data.append( + &mut pages + .into_iter() + .map(|p| XmlData { + data_type: XmlDataType::Page, + relations: vec![], + id: p.id.clone(), + last_modified: match p.published_at { + Some(d) => chrono::DateTime::::from_str(&d.0) + .map_or(chrono::offset::Utc::now().fixed_offset(), |d| { + d.fixed_offset() + }), + None => chrono::offset::Utc::now().fixed_offset(), + }, + slug: p.slug.clone(), + }) + .collect(), + ); + xml_data.append( + &mut collections + .into_iter() + .map(|c| XmlData { + slug: c.slug, + last_modified: chrono::offset::Utc::now().fixed_offset(), + id: c.id, + relations: vec![], + data_type: XmlDataType::Category, + }) + .collect::>(), + ); + + xml_cache.set(xml_data.clone(), &saleor_api_url).await?; + info!("regeneration: xml_cache was set"); + + //create urls + info!("regeneration: creating urls"); + let mut page_urls = vec![]; + let mut product_urls = vec![]; + let mut category_urls = vec![]; + let mut collection_urls = vec![]; + + for x in xml_data.iter() { + match x.data_type { + XmlDataType::Page => { + let mut tt = TinyTemplate::new(); + tt.add_template("page_url", &state.sitemap_config.pages_template)?; + let context = PageUpdated { + page: Some(event_subjects_updated::Page { + slug: x.slug.clone(), + id: x.id.clone(), + }), + }; + let page_url = Url::builder(tt.render("page_url", &context)?) + .last_modified(x.last_modified) + .build()?; + trace!("Created Page url: {}", &page_url.location); + page_urls.push(page_url); + } + XmlDataType::Product => { + let mut tt = TinyTemplate::new(); + tt.add_template("product_url", &state.sitemap_config.product_template)?; + let context = ProductUpdated { + product: Some(event_subjects_updated::Product { + id: x.id.clone(), + slug: x.slug.clone(), + category: match xml_data.iter().find(|all| { + x.relations + .iter() + .find(|rel| { + all.id == **rel && all.data_type == XmlDataType::Category + }) + .is_some() + }) { + Some(c) => Some(event_subjects_updated::Category { + slug: c.slug.clone(), + id: c.id.clone(), + }), + None => Some(event_subjects_updated::Category { + slug: "unknown".to_owned(), + id: cynic::Id::new("unknown".to_owned()), + }), + }, + }), + }; + let product_url = Url::builder(tt.render("product_url", &context)?) + .last_modified(x.last_modified) + .build()?; + + trace!("Created Page url: {}", &product_url.location); + product_urls.push(product_url); + } + XmlDataType::Category => { + let mut tt = TinyTemplate::new(); + tt.add_template("category_url", &state.sitemap_config.category_template)?; + let context = CategoryUpdated { + category: Some(event_subjects_updated::Category2 { + id: x.id.clone(), + slug: x.slug.clone(), + }), + }; + let category_url = Url::builder(tt.render("category_url", &context)?) + .last_modified(x.last_modified) + .build()?; + + trace!("Created category url: {}", &category_url.location); + category_urls.push(category_url); + } + XmlDataType::Collection => { + let mut tt = TinyTemplate::new(); + tt.add_template("coll_url", &state.sitemap_config.collection_template)?; + let context = CollectionUpdated { + collection: Some(event_subjects_updated::Collection { + slug: x.slug.clone(), + id: x.id.clone(), + }), + }; + let collection_url = Url::builder(tt.render("coll_url", &context)?) + .last_modified(x.last_modified) + .build()?; + + trace!("Created collection url: {}", &collection_url.location); + collection_urls.push(collection_url); + } + } + } + write_xml(page_urls, &state, XmlDataType::Page).await?; + write_xml(collection_urls, &state, XmlDataType::Collection).await?; + write_xml(category_urls, &state, XmlDataType::Category).await?; + write_xml(product_urls, &state, XmlDataType::Product).await?; + Ok(()) +} + +async fn get_all_pages(saleor_api_url: &str) -> anyhow::Result> { + let operation = GetPagesInitial::build(()); + let mut all_pages = vec![]; + let res = surf::post(&saleor_api_url).run_graphql(operation).await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(pages) = &data.pages + { + debug!("fetched first pages, eg.:{:?}", &pages.edges.get(0)); + all_pages.append( + &mut pages + .edges + .iter() + .map(|p| p.node.clone()) + .collect::>(), + ); + //Keep fetching next page + let mut next_cursor = pages.page_info.end_cursor.clone(); + loop { + if let Some(cursor) = &mut next_cursor { + let res = surf::post(&saleor_api_url) + .run_graphql(GetPagesNext::build(GetPagesNextVariables { + after: &cursor, + })) + .await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(pages) = &data.pages + { + all_pages.append( + &mut pages + .edges + .iter() + .map(|p| p.node.clone()) + .collect::>(), + ); + debug!("fetched next pages, eg.:{:?}", &pages.edges.get(0)); + if !pages.page_info.has_next_page { + break; + } + next_cursor = pages.page_info.end_cursor.clone(); + } else { + error!("Failed fetching initial pages! {:?}", &res); + anyhow::bail!("Failed fetching initial pages! {:?}", res); + } + } else { + break; + } + } + } else { + error!("Failed fetching initial pages! {:?}", &res); + anyhow::bail!("Failed fetching initial pages! {:?}", res); + }; + info!("fetched all pages"); + Ok(all_pages) +} + +async fn get_all_categories(saleor_api_url: &str) -> anyhow::Result> { + debug!("Collecting all categories..."); + let operation = GetCategoriesInitial::build(()); + let mut all_categories = vec![]; + let res = surf::post(&saleor_api_url).run_graphql(operation).await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(categories) = &data.categories + { + all_categories.append( + &mut categories + .edges + .iter() + .map(|p| p.node.clone()) + .collect::>(), + ); + debug!( + "fetched first categories, eg.:{:?}", + &categories.edges.get(0) + ); + //Keep fetching next page + let mut next_cursor = categories.page_info.end_cursor.clone(); + loop { + if let Some(cursor) = &mut next_cursor { + let res = surf::post(&saleor_api_url) + .run_graphql(GetCategoriesNext::build(GetCategoriesNextVariables { + after: Some(&cursor), + })) + .await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(categories) = &data.categories + { + all_categories.append( + &mut categories + .edges + .iter() + .map(|p| p.node.clone()) + .collect::>(), + ); + debug!( + "fetched first categories, eg.:{:?}", + &categories.edges.get(0) + ); + if !categories.page_info.has_next_page { + break; + } + next_cursor = categories.page_info.end_cursor.clone(); + } else { + error!("Failed fetching initial pages! {:?}", &res); + anyhow::bail!("Failed fetching initial pages! {:?}", res); + } + } else { + break; + } + } + } else { + error!("Failed fetching initial pages! {:?}", &res); + anyhow::bail!("Failed fetching initial pages! {:?}", res); + }; + info!("All categories collected"); + Ok(all_categories) +} + +async fn get_all_collections(saleor_api_url: &str) -> anyhow::Result> { + debug!("Collecting all Collections..."); + let operation = GetCollectionsInitial::build(()); + let mut all_collections = vec![]; + let res = surf::post(&saleor_api_url).run_graphql(operation).await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(collections) = &data.collections + { + all_collections.append( + &mut collections + .edges + .iter() + .map(|p| p.node.clone()) + .collect::>(), + ); + debug!( + "fetched first collections, eg.:{:?}", + &collections.edges.get(0) + ); + + //Keep fetching next page + let mut next_cursor = collections.page_info.end_cursor.clone(); + loop { + if let Some(cursor) = &mut next_cursor { + let res = surf::post(&saleor_api_url) + .run_graphql(GetCollectionsNext::build(GetCollectionsNextVariables { + after: Some(&cursor), + })) + .await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(collections) = &data.collections + { + all_collections.append( + &mut collections + .edges + .iter() + .map(|p| p.node.clone()) + .collect::>(), + ); + debug!( + "fetched next collections, eg.:{:?}", + &collections.edges.get(0) + ); + if !collections.page_info.has_next_page { + break; + } + next_cursor = collections.page_info.end_cursor.clone(); + } else { + error!("Failed fetching initial collecnios! {:?}", &res); + anyhow::bail!("Failed fetching initial collections! {:?}", res); + } + } else { + break; + } + } + } else { + error!("Failed fetching initial collections! {:?}", &res); + anyhow::bail!("Failed fetching initial collections! {:?}", res); + }; + info!("All Collections collected..."); + Ok(all_collections) +} +/** + * Gets all products of a category then assings them as related + */ +async fn get_all_products( + saleor_api_url: &str, + main_category: &mut (Category3, Vec>), +) -> anyhow::Result>> { + debug!("Collecting all products..."); + let operation = GetCategoryProductsInitial::build(GetCategoryProductsInitialVariables { + id: &main_category.0.id, + }); + let mut all_categorised_products: Vec> = vec![]; + let res = surf::post(&saleor_api_url).run_graphql(operation).await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(category) = &data.category + && let Some(products) = &category.products + { + all_categorised_products.append( + &mut products + .edges + .iter() + .map(|p| { + Arc::new(CategorisedProduct { + product: p.node.clone(), + category_id: main_category.0.id.clone(), + }) + }) + .collect::>(), + ); + //Keep fetching next page + debug!("fetched first products, eg: {:?}", products.edges.get(0)); + let mut next_cursor = products.page_info.end_cursor.clone(); + loop { + if let Some(cursor) = &mut next_cursor { + let res = surf::post(&saleor_api_url) + .run_graphql(GetCategoryProductsNext::build( + GetCategoryProductsNextVariables { + id: &main_category.0.id, + after: &cursor, + }, + )) + .await; + if let Ok(query) = &res + && let Some(data) = &query.data + && let Some(category) = &data.category + && let Some(products) = &category.products + { + all_categorised_products.append( + &mut products + .edges + .iter() + .map(|p| { + Arc::new(CategorisedProduct { + product: p.node.clone(), + category_id: main_category.0.id.clone(), + }) + }) + .collect::>(), + ); + debug!("fetched next products, eg: {:?}", products.edges.get(0)); + if !products.page_info.has_next_page { + break; + } + next_cursor = products.page_info.end_cursor.clone(); + } else { + error!("Failed fetching initial products! {:?}", &res); + anyhow::bail!("Failed fetching initial products! {:?}", res); + } + } else { + break; + } + } + } else { + error!("Failed fetching initial products! {:?}", &res); + anyhow::bail!("Failed fetching initial products! {:?}", res); + }; + info!("All products collected..."); + Ok(all_categorised_products) +} diff --git a/sitemap-generator/src/routes/webhooks.rs b/sitemap-generator/src/routes/webhooks.rs index 8fd8156..ab4bc82 100644 --- a/sitemap-generator/src/routes/webhooks.rs +++ b/sitemap-generator/src/routes/webhooks.rs @@ -27,8 +27,8 @@ use tracing::{debug, error, info}; use crate::{ app::{AppError, AppState, XmlData, XmlDataType}, queries::event_subjects_updated::{ - Category, Category2, CategoryUpdated, CollectionUpdated, PageInfo, PageUpdated, Product, - Product2, ProductCountableConnection, ProductCountableEdge, ProductUpdated, + Category, Category2, CategoryUpdated, Collection, CollectionUpdated, Page, PageInfo, + PageUpdated, Product, ProductUpdated, }, }; @@ -53,25 +53,41 @@ pub async fn webhooks( | AsyncWebhookEventType::ProductCreated | AsyncWebhookEventType::ProductDeleted => { let product: ProductUpdated = serde_json::from_str(&data)?; - spawn(async move { update_sitemap_product(product, &url, state).await }); + spawn(async move { + if let Err(e) = update_sitemap_product(product, &url, state).await { + error!("Error processing Product, e: {:?}", e); + } + }); } AsyncWebhookEventType::CategoryCreated | AsyncWebhookEventType::CategoryUpdated | AsyncWebhookEventType::CategoryDeleted => { let category: CategoryUpdated = serde_json::from_str(&data)?; - spawn(async move { update_sitemap_category(category, &url, state).await }); + spawn(async move { + if let Err(e) = update_sitemap_category(category, &url, state).await { + error!("Error processing Category, e: {:?}", e); + } + }); } AsyncWebhookEventType::PageCreated | AsyncWebhookEventType::PageUpdated | AsyncWebhookEventType::PageDeleted => { let page: PageUpdated = serde_json::from_str(&data)?; - spawn(async move { update_sitemap_page(page, &url, state).await }); + spawn(async move { + if let Err(e) = update_sitemap_page(page, &url, state).await { + error!("Error processing Page, e: {:?}", e); + } + }); } AsyncWebhookEventType::CollectionCreated | AsyncWebhookEventType::CollectionUpdated | AsyncWebhookEventType::CollectionDeleted => { let collection: CollectionUpdated = serde_json::from_str(&data)?; - spawn(async move { update_sitemap_collection(collection, &url, state).await }); + spawn(async move { + if let Err(e) = update_sitemap_collection(collection, &url, state).await { + error!("Error processing Collection, e: {:?}", e); + } + }); } _ => (), @@ -91,7 +107,8 @@ async fn update_sitemap_product( debug!("Product got changed!, {:?}", &product); if let Some(product) = product.product { // Update or add the product - let mut xml_data = match state.xml_cache.get_all(saleor_api_url).await { + let xml_cache = state.xml_cache.lock().await; + let mut xml_data = match xml_cache.get_all(saleor_api_url).await { Ok(d) => d, Err(e) => { error!("Error, {:?}. no xml cache present?", e); @@ -218,13 +235,17 @@ async fn update_sitemap_product( }, }), }; - urls.push(tt.render("product_url", &context)?); + urls.push( + Url::builder(tt.render("product_url", &context)?) + .last_modified(x.last_modified) + .build()?, + ); } } //debug!("new urls:{:?}", &urls); write_xml(urls, &state, XmlDataType::Product).await?; - state.xml_cache.set(xml_data, saleor_api_url).await?; + xml_cache.set(xml_data, saleor_api_url).await?; } else { error!("Failed to update product, e: {:?}", product); anyhow::bail!("product not present in in webhook"); @@ -239,7 +260,8 @@ async fn update_sitemap_category( state: AppState, ) -> anyhow::Result<()> { if let Some(category) = category.category { - let mut xml_data = state.xml_cache.get_all(saleor_api_url).await?; + let xml_cache = state.xml_cache.lock().await; + let mut xml_data = xml_cache.get_all(saleor_api_url).await?; let mut affected_product_ids = vec![]; let mut new_xml_data = vec![]; //check if template of product includes categories in url @@ -346,7 +368,11 @@ async fn update_sitemap_category( }; } } - product_urls.push(tt.render("product_url", &context)?); + product_urls.push( + Url::builder(tt.render("product_url", &context)?) + .last_modified(x.last_modified) + .build()?, + ); } if x.data_type == XmlDataType::Category { tt.add_template("category_url", &state.sitemap_config.category_template)?; @@ -354,10 +380,13 @@ async fn update_sitemap_category( category: Some(Category2 { id: x.id.clone(), slug: x.slug.clone(), - products: None, }), }; - category_urls.push(tt.render("category_url", &context)?); + category_urls.push( + Url::builder(tt.render("category_url", &context)?) + .last_modified(x.last_modified) + .build()?, + ); } } //and write @@ -365,6 +394,7 @@ async fn update_sitemap_category( write_xml(product_urls, &state, XmlDataType::Product).await?; } write_xml(category_urls, &state, XmlDataType::Category).await?; + xml_cache.set(xml_data, saleor_api_url).await?; } else { error!("Failed to update category, e:{:?}", category); anyhow::bail!("Category not present in webhook"); @@ -377,20 +407,133 @@ async fn update_sitemap_collection( saleor_api_url: &str, state: AppState, ) -> anyhow::Result<()> { + if let Some(collection) = collection.collection { + let xml_cache = state.xml_cache.lock().await; + let mut xml_data = xml_cache.get_all(saleor_api_url).await?; + let mut new_xml_data = vec![]; + + match xml_data + .iter_mut() + .find(|c| c.id == collection.id && c.data_type == XmlDataType::Collection) + { + Some(xml_col) => { + if xml_col.slug == collection.slug { + debug!("Collection url didn't change, skipping"); + return Ok(()); + } + xml_col.slug = collection.slug; + xml_col.last_modified = chrono::offset::Utc::now().fixed_offset(); + } + None => { + debug!("Collection not cached, adding..."); + new_xml_data.push(XmlData { + slug: collection.slug, + id: collection.id, + last_modified: chrono::offset::Utc::now().fixed_offset(), + relations: vec![], + data_type: XmlDataType::Collection, + }) + } + } + + xml_data.append(&mut new_xml_data); + + //create urls + let mut collection_urls = vec![]; + for xml_col in xml_data.iter() { + if xml_col.data_type == XmlDataType::Collection { + let mut tt = TinyTemplate::new(); + tt.add_template("collection_url", &state.sitemap_config.collection_template)?; + let context = CollectionUpdated { + collection: Some(Collection { + slug: xml_col.slug.clone(), + id: xml_col.id.clone(), + }), + }; + collection_urls.push( + Url::builder(tt.render("collection_url", &context)?) + .last_modified(xml_col.last_modified) + .build()?, + ); + } + } + write_xml(collection_urls, &state, XmlDataType::Collection).await?; + xml_cache.set(xml_data, saleor_api_url).await?; + } else { + error!("Failed to update collection, e:{:?}", collection); + anyhow::bail!("Collection not present in webhook"); + } + info!("Sitemap updated, cause: collection"); - todo!() + Ok(()) } async fn update_sitemap_page( page: PageUpdated, saleor_api_url: &str, state: AppState, ) -> anyhow::Result<()> { - info!("Sitemap updated, cause: collection"); - todo!() + if let Some(page) = page.page { + let xml_cache = state.xml_cache.lock().await; + let mut xml_data = xml_cache.get_all(saleor_api_url).await?; + let mut new_xml_data = vec![]; + + match xml_data + .iter_mut() + .find(|p| p.id == page.id && p.data_type == XmlDataType::Page) + { + Some(xml_page) => { + if xml_page.slug == page.slug { + debug!("Page url didn't change, skipping"); + return Ok(()); + } + xml_page.slug = page.slug; + xml_page.last_modified = chrono::offset::Utc::now().fixed_offset(); + } + None => { + debug!("Page not cached, adding..."); + new_xml_data.push(XmlData { + slug: page.slug, + id: page.id, + last_modified: chrono::offset::Utc::now().fixed_offset(), + relations: vec![], + data_type: XmlDataType::Page, + }) + } + } + + xml_data.append(&mut new_xml_data); + //create urls + let mut page_urls = vec![]; + for xml_page in xml_data.iter() { + if xml_page.data_type == XmlDataType::Page { + let mut tt = TinyTemplate::new(); + tt.add_template("page_url", &state.sitemap_config.pages_template)?; + let context = PageUpdated { + page: Some(Page { + slug: xml_page.slug.clone(), + id: xml_page.id.clone(), + }), + }; + page_urls.push( + Url::builder(tt.render("page_url", &context)?) + .last_modified(xml_page.last_modified) + .build()?, + ); + } + } + write_xml(page_urls, &state, XmlDataType::Page).await?; + xml_cache.set(xml_data, saleor_api_url).await?; + } else { + error!("Failed to update Page, e:{:?}", page); + anyhow::bail!("Page not present in webhook"); + } + + info!("Sitemap updated, cause: Page"); + Ok(()) } -async fn write_xml( - urls: Vec, +pub async fn write_xml( + urls: Vec, state: &AppState, type_group: XmlDataType, ) -> anyhow::Result<()> { @@ -405,12 +548,7 @@ async fn write_xml( .await?; let mut sitemap_urls: Vec = vec![]; for url in urls.clone() { - sitemap_urls.push( - Url::builder(url) - .change_frequency(ChangeFrequency::Weekly) - .last_modified(chrono::offset::Utc::now().fixed_offset()) - .build()?, - ); + sitemap_urls.push(url); } let url_set: UrlSet = UrlSet::new(sitemap_urls)?; debug!("Writing xml into file"); @@ -424,18 +562,13 @@ async fn write_xml( let len = buf.len() * std::mem::size_of::(); if len > 200000 { let file_amount = (len as f32 / 150000 as f32).ceil() as usize; - let sliced_urls: Vec<&[String]> = urls.chunks(file_amount).collect(); + let sliced_urls: Vec<&[Url]> = urls.chunks(file_amount).collect(); let mut sitemaps: Vec = vec![]; for urls in sliced_urls { - for url in urls { + for url in urls.iter().cloned() { let mut sitemap_urls: Vec = vec![]; - sitemap_urls.push( - Url::builder(url.to_owned()) - .change_frequency(ChangeFrequency::Weekly) - .last_modified(chrono::offset::Utc::now().fixed_offset()) - .build()?, - ); + sitemap_urls.push(url); sitemaps.push(UrlSet::new(sitemap_urls)?); } } @@ -475,7 +608,7 @@ async fn update_sitemap_index(state: &AppState) -> anyhow::Result<()> { if path .extension() .map_or(false, |ext| ext == "xml" || ext == "gz") - && !path.to_string_lossy().to_string().contains("sitemap_index") + && !path.to_string_lossy().to_string().contains("sitemap-index") { Some(path) } else {