diff --git a/Cargo.lock b/Cargo.lock index 91a8c9c..d7435e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,6 +382,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + [[package]] name = "argon2" version = "0.4.1" @@ -495,6 +501,15 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -615,6 +630,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "contract" +version = "0.1.0" +dependencies = [ + "base64", + "byteorder", + "chrono", + "serde", + "sqlx", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -989,6 +1015,171 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +[[package]] +name = "gloo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4bef6b277b3ab073253d4bca60761240cf8d6998f4bd142211957b69a61b20" +dependencies = [ + "gloo-console", + "gloo-dialogs", + "gloo-events", + "gloo-file", + "gloo-history", + "gloo-net", + "gloo-render", + "gloo-storage", + "gloo-timers", + "gloo-utils", + "gloo-worker", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd451019e0b7a2b8a7a7b23e74916601abf1135c54664e57ff71dcc26dfcdeb7" +dependencies = [ + "gloo-events", + "gloo-utils", + "serde", + "serde-wasm-bindgen", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode", + "gloo-console", + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -1416,6 +1607,7 @@ dependencies = [ "base64", "byteorder", "chrono", + "contract", "dotenv", "futures", "futures-util", @@ -1819,6 +2011,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.140" @@ -2419,6 +2622,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tracing-subscriber-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79804e80980173c6c8e53d98508eb24a2dbc4ee17a3e8d2ca8e5bad6bf13a898" +dependencies = [ + "gloo", + "tracing", + "tracing-subscriber", +] + [[package]] name = "twoway" version = "0.2.2" @@ -2602,9 +2816,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "serde", @@ -2614,13 +2828,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -2641,9 +2855,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2651,9 +2865,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -2664,9 +2878,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wasm-bindgen-test" @@ -2713,9 +2927,14 @@ dependencies = [ name = "web" version = "0.1.0" dependencies = [ + "console_error_panic_hook", + "contract", "serde", "serde_json", "sycamore", + "tracing", + "tracing-subscriber", + "tracing-subscriber-wasm", "wasm_request", ] diff --git a/Cargo.toml b/Cargo.toml index 36af4bf..836ae21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ - 'server', - 'web', + 'crates/server', + 'crates/web', + 'crates/contract', ] diff --git a/README.md b/README.md index 0acab1b..b31c4d5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ https://github.com/dominem/postgresql_fts_polish_dict ``` - +cp db/dictionary/polish.dict /usr/share/postgresql/tsearch_data/polish.dict +cp db/dictionary/polish.affix /usr/share/postgresql/tsearch_data/polish.affix +cp db/dictionary/polish.stop /usr/share/postgresql/tsearch_data/polish.stop ``` ```sql diff --git a/client/.swcrc b/crates/client/.swcrc similarity index 100% rename from client/.swcrc rename to crates/client/.swcrc diff --git a/client/package.json b/crates/client/package.json similarity index 100% rename from client/package.json rename to crates/client/package.json diff --git a/client/spack.config.js b/crates/client/spack.config.js similarity index 100% rename from client/spack.config.js rename to crates/client/spack.config.js diff --git a/client/src/admin.js b/crates/client/src/admin.js similarity index 100% rename from client/src/admin.js rename to crates/client/src/admin.js diff --git a/client/src/admin/businesses/admin-business.js b/crates/client/src/admin/businesses/admin-business.js similarity index 100% rename from client/src/admin/businesses/admin-business.js rename to crates/client/src/admin/businesses/admin-business.js diff --git a/client/src/admin/businesses/admin-businesses.js b/crates/client/src/admin/businesses/admin-businesses.js similarity index 100% rename from client/src/admin/businesses/admin-businesses.js rename to crates/client/src/admin/businesses/admin-businesses.js diff --git a/client/src/admin/businesses/admin-edit-business.js b/crates/client/src/admin/businesses/admin-edit-business.js similarity index 100% rename from client/src/admin/businesses/admin-edit-business.js rename to crates/client/src/admin/businesses/admin-edit-business.js diff --git a/client/src/admin/news/article-form.js b/crates/client/src/admin/news/article-form.js similarity index 100% rename from client/src/admin/news/article-form.js rename to crates/client/src/admin/news/article-form.js diff --git a/client/src/admin/news/edit-news-article.js b/crates/client/src/admin/news/edit-news-article.js similarity index 100% rename from client/src/admin/news/edit-news-article.js rename to crates/client/src/admin/news/edit-news-article.js diff --git a/client/src/admin/offers/admin-edit-offer.js b/crates/client/src/admin/offers/admin-edit-offer.js similarity index 100% rename from client/src/admin/offers/admin-edit-offer.js rename to crates/client/src/admin/offers/admin-edit-offer.js diff --git a/client/src/admin/offers/ow-admin-offers.js b/crates/client/src/admin/offers/ow-admin-offers.js similarity index 100% rename from client/src/admin/offers/ow-admin-offers.js rename to crates/client/src/admin/offers/ow-admin-offers.js diff --git a/client/src/admin/ow-admin.js b/crates/client/src/admin/ow-admin.js similarity index 100% rename from client/src/admin/ow-admin.js rename to crates/client/src/admin/ow-admin.js diff --git a/client/src/api.js b/crates/client/src/api.js similarity index 100% rename from client/src/api.js rename to crates/client/src/api.js diff --git a/client/src/app.js b/crates/client/src/app.js similarity index 100% rename from client/src/app.js rename to crates/client/src/app.js diff --git a/client/src/business-editor.js b/crates/client/src/business-editor.js similarity index 100% rename from client/src/business-editor.js rename to crates/client/src/business-editor.js diff --git a/client/src/business-items/business-item-editor.js b/crates/client/src/business-items/business-item-editor.js similarity index 100% rename from client/src/business-items/business-item-editor.js rename to crates/client/src/business-items/business-item-editor.js diff --git a/client/src/business-items/business-item.js b/crates/client/src/business-items/business-item.js similarity index 100% rename from client/src/business-items/business-item.js rename to crates/client/src/business-items/business-item.js diff --git a/client/src/contacts/contact-info-editor.js b/crates/client/src/contacts/contact-info-editor.js similarity index 100% rename from client/src/contacts/contact-info-editor.js rename to crates/client/src/contacts/contact-info-editor.js diff --git a/client/src/contacts/contact-info-list.js b/crates/client/src/contacts/contact-info-list.js similarity index 100% rename from client/src/contacts/contact-info-list.js rename to crates/client/src/contacts/contact-info-list.js diff --git a/client/src/contacts/contact-info.js b/crates/client/src/contacts/contact-info.js similarity index 100% rename from client/src/contacts/contact-info.js rename to crates/client/src/contacts/contact-info.js diff --git a/client/src/contacts/contact-type-icon.js b/crates/client/src/contacts/contact-type-icon.js similarity index 100% rename from client/src/contacts/contact-type-icon.js rename to crates/client/src/contacts/contact-type-icon.js diff --git a/client/src/contacts/edit-contact-info.js b/crates/client/src/contacts/edit-contact-info.js similarity index 100% rename from client/src/contacts/edit-contact-info.js rename to crates/client/src/contacts/edit-contact-info.js diff --git a/client/src/local-businesses/local-business-item.js b/crates/client/src/local-businesses/local-business-item.js similarity index 100% rename from client/src/local-businesses/local-business-item.js rename to crates/client/src/local-businesses/local-business-item.js diff --git a/client/src/local-businesses/local-business.js b/crates/client/src/local-businesses/local-business.js similarity index 100% rename from client/src/local-businesses/local-business.js rename to crates/client/src/local-businesses/local-business.js diff --git a/client/src/local-businesses/local-businesses.js b/crates/client/src/local-businesses/local-businesses.js similarity index 100% rename from client/src/local-businesses/local-businesses.js rename to crates/client/src/local-businesses/local-businesses.js diff --git a/client/src/local-businesses/single-local-business.js b/crates/client/src/local-businesses/single-local-business.js similarity index 100% rename from client/src/local-businesses/single-local-business.js rename to crates/client/src/local-businesses/single-local-business.js diff --git a/client/src/login-form.js b/crates/client/src/login-form.js similarity index 100% rename from client/src/login-form.js rename to crates/client/src/login-form.js diff --git a/client/src/marketplace/marketplace-editor.js b/crates/client/src/marketplace/marketplace-editor.js similarity index 100% rename from client/src/marketplace/marketplace-editor.js rename to crates/client/src/marketplace/marketplace-editor.js diff --git a/client/src/marketplace/marketplace-offer.js b/crates/client/src/marketplace/marketplace-offer.js similarity index 100% rename from client/src/marketplace/marketplace-offer.js rename to crates/client/src/marketplace/marketplace-offer.js diff --git a/client/src/marketplace/marketplace-offers.js b/crates/client/src/marketplace/marketplace-offers.js similarity index 100% rename from client/src/marketplace/marketplace-offers.js rename to crates/client/src/marketplace/marketplace-offers.js diff --git a/client/src/marketplace/offer-form.js b/crates/client/src/marketplace/offer-form.js similarity index 100% rename from client/src/marketplace/offer-form.js rename to crates/client/src/marketplace/offer-form.js diff --git a/client/src/marketplace/user-edit-offer.js b/crates/client/src/marketplace/user-edit-offer.js similarity index 100% rename from client/src/marketplace/user-edit-offer.js rename to crates/client/src/marketplace/user-edit-offer.js diff --git a/client/src/news/news-article.js b/crates/client/src/news/news-article.js similarity index 100% rename from client/src/news/news-article.js rename to crates/client/src/news/news-article.js diff --git a/client/src/news/ow-articles.js b/crates/client/src/news/ow-articles.js similarity index 100% rename from client/src/news/ow-articles.js rename to crates/client/src/news/ow-articles.js diff --git a/client/src/ow-account/account-view.js b/crates/client/src/ow-account/account-view.js similarity index 100% rename from client/src/ow-account/account-view.js rename to crates/client/src/ow-account/account-view.js diff --git a/client/src/ow-account/ow-account.js b/crates/client/src/ow-account/ow-account.js similarity index 100% rename from client/src/ow-account/ow-account.js rename to crates/client/src/ow-account/ow-account.js diff --git a/client/src/poly.js b/crates/client/src/poly.js similarity index 100% rename from client/src/poly.js rename to crates/client/src/poly.js diff --git a/client/src/register-form.js b/crates/client/src/register-form.js similarity index 100% rename from client/src/register-form.js rename to crates/client/src/register-form.js diff --git a/client/src/register-form/model.js b/crates/client/src/register-form/model.js similarity index 100% rename from client/src/register-form/model.js rename to crates/client/src/register-form/model.js diff --git a/client/src/register-form/register-account-type.js b/crates/client/src/register-form/register-account-type.js similarity index 100% rename from client/src/register-form/register-account-type.js rename to crates/client/src/register-form/register-account-type.js diff --git a/client/src/register-form/register-business-account-form.js b/crates/client/src/register-form/register-business-account-form.js similarity index 100% rename from client/src/register-form/register-business-account-form.js rename to crates/client/src/register-form/register-business-account-form.js diff --git a/client/src/register-form/register-business-contacts-form.js b/crates/client/src/register-form/register-business-contacts-form.js similarity index 100% rename from client/src/register-form/register-business-contacts-form.js rename to crates/client/src/register-form/register-business-contacts-form.js diff --git a/client/src/register-form/register-business-details-form.js b/crates/client/src/register-form/register-business-details-form.js similarity index 100% rename from client/src/register-form/register-business-details-form.js rename to crates/client/src/register-form/register-business-details-form.js diff --git a/client/src/register-form/register-business-item-form.js b/crates/client/src/register-form/register-business-item-form.js similarity index 100% rename from client/src/register-form/register-business-item-form.js rename to crates/client/src/register-form/register-business-item-form.js diff --git a/client/src/register-form/register-business-items-form.js b/crates/client/src/register-form/register-business-items-form.js similarity index 100% rename from client/src/register-form/register-business-items-form.js rename to crates/client/src/register-form/register-business-items-form.js diff --git a/client/src/register-form/register-business-submit-form.js b/crates/client/src/register-form/register-business-submit-form.js similarity index 100% rename from client/src/register-form/register-business-submit-form.js rename to crates/client/src/register-form/register-business-submit-form.js diff --git a/client/src/register-form/register-user-account-form.js b/crates/client/src/register-form/register-user-account-form.js similarity index 100% rename from client/src/register-form/register-user-account-form.js rename to crates/client/src/register-form/register-user-account-form.js diff --git a/client/src/shared.js b/crates/client/src/shared.js similarity index 100% rename from client/src/shared.js rename to crates/client/src/shared.js diff --git a/client/src/shared/date-time.js b/crates/client/src/shared/date-time.js similarity index 100% rename from client/src/shared/date-time.js rename to crates/client/src/shared/date-time.js diff --git a/client/src/shared/error-message.js b/crates/client/src/shared/error-message.js similarity index 100% rename from client/src/shared/error-message.js rename to crates/client/src/shared/error-message.js diff --git a/client/src/shared/facebook-button.js b/crates/client/src/shared/facebook-button.js similarity index 100% rename from client/src/shared/facebook-button.js rename to crates/client/src/shared/facebook-button.js diff --git a/client/src/shared/form-navigation.js b/crates/client/src/shared/form-navigation.js similarity index 100% rename from client/src/shared/form-navigation.js rename to crates/client/src/shared/form-navigation.js diff --git a/client/src/shared/image-input.js b/crates/client/src/shared/image-input.js similarity index 100% rename from client/src/shared/image-input.js rename to crates/client/src/shared/image-input.js diff --git a/client/src/shared/image-popup.js b/crates/client/src/shared/image-popup.js similarity index 100% rename from client/src/shared/image-popup.js rename to crates/client/src/shared/image-popup.js diff --git a/client/src/shared/nav/ow-nav.js b/crates/client/src/shared/nav/ow-nav.js similarity index 100% rename from client/src/shared/nav/ow-nav.js rename to crates/client/src/shared/nav/ow-nav.js diff --git a/client/src/shared/nav/ow-path.js b/crates/client/src/shared/nav/ow-path.js similarity index 100% rename from client/src/shared/nav/ow-path.js rename to crates/client/src/shared/nav/ow-path.js diff --git a/client/src/shared/popup-window.js b/crates/client/src/shared/popup-window.js similarity index 100% rename from client/src/shared/popup-window.js rename to crates/client/src/shared/popup-window.js diff --git a/client/src/shared/price/price-input.js b/crates/client/src/shared/price/price-input.js similarity index 100% rename from client/src/shared/price/price-input.js rename to crates/client/src/shared/price/price-input.js diff --git a/client/src/shared/price/price-view.js b/crates/client/src/shared/price/price-view.js similarity index 100% rename from client/src/shared/price/price-view.js rename to crates/client/src/shared/price/price-view.js diff --git a/client/src/shared/rich-text-editor.js b/crates/client/src/shared/rich-text-editor.js similarity index 100% rename from client/src/shared/rich-text-editor.js rename to crates/client/src/shared/rich-text-editor.js diff --git a/client/src/shared/search-input.js b/crates/client/src/shared/search-input.js similarity index 100% rename from client/src/shared/search-input.js rename to crates/client/src/shared/search-input.js diff --git a/client/src/terms_and_conditions/privacy-policy-bar.js b/crates/client/src/terms_and_conditions/privacy-policy-bar.js similarity index 100% rename from client/src/terms_and_conditions/privacy-policy-bar.js rename to crates/client/src/terms_and_conditions/privacy-policy-bar.js diff --git a/client/src/terms_and_conditions/privacy-policy.js b/crates/client/src/terms_and_conditions/privacy-policy.js similarity index 100% rename from client/src/terms_and_conditions/privacy-policy.js rename to crates/client/src/terms_and_conditions/privacy-policy.js diff --git a/client/src/terms_and_conditions/terms-and-condition-rule.js b/crates/client/src/terms_and_conditions/terms-and-condition-rule.js similarity index 100% rename from client/src/terms_and_conditions/terms-and-condition-rule.js rename to crates/client/src/terms_and_conditions/terms-and-condition-rule.js diff --git a/client/src/terms_and_conditions/terms-and-conditions.js b/crates/client/src/terms_and_conditions/terms-and-conditions.js similarity index 100% rename from client/src/terms_and_conditions/terms-and-conditions.js rename to crates/client/src/terms_and_conditions/terms-and-conditions.js diff --git a/client/yarn.lock b/crates/client/yarn.lock similarity index 100% rename from client/yarn.lock rename to crates/client/yarn.lock diff --git a/crates/contract/Cargo.toml b/crates/contract/Cargo.toml new file mode 100644 index 0000000..00abbef --- /dev/null +++ b/crates/contract/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "contract" +version = "0.1.0" +edition = "2021" + +[features] +db = ['sqlx', 'byteorder'] + +[dependencies] +serde = { version = "*", features = ['derive'] } +chrono = { version = "*", features = ['serde'] } +sqlx = { version = "*", optional = true } +byteorder = { version = "1.4.3", optional = true } +base64 = { version = "*" } diff --git a/crates/contract/src/db.rs b/crates/contract/src/db.rs new file mode 100644 index 0000000..151ce6f --- /dev/null +++ b/crates/contract/src/db.rs @@ -0,0 +1,78 @@ +use byteorder::ByteOrder; +use sqlx::database::{HasArguments, HasValueRef}; +use sqlx::encode::IsNull; +use sqlx::error::BoxDynError; +use sqlx::postgres::PgValueFormat; +use sqlx::Postgres; + +use crate::PriceRange; + +fn take_i32(bytes: &mut &[u8]) -> i32 { + let value = byteorder::BigEndian::read_i32(&bytes[0..4]); + *bytes = &bytes[4..]; + value +} + +impl<'l> sqlx::Decode<'l, Postgres> for PriceRange { + fn decode(value: >::ValueRef) -> Result { + match value.format() { + PgValueFormat::Text => { + let s = value.as_str()?; + eprintln!("{s:?}"); + Ok(Self::Free) + } + PgValueFormat::Binary => { + let mut bytes = value.as_bytes()?; + // println!("{bytes:?}"); + + let _len = take_i32(&mut bytes); + + let _ty = take_i32(&mut bytes); + let _min_len = take_i32(&mut bytes); + let min = take_i32(&mut bytes); + + let _ty = take_i32(&mut bytes); + let _max_len = take_i32(&mut bytes); + let max = take_i32(&mut bytes); + + Ok((min, max).into()) + } + } + } +} + +fn encode(n: i32, buf: &mut >::ArgumentBuffer) { + let _ = >::encode(n, buf); +} + +impl<'l> sqlx::Encode<'l, Postgres> for PriceRange { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + encode(2i32, buf); + fn write_value(n: &i32, buf: &mut >::ArgumentBuffer) { + encode(23i32, buf); + encode(4i32, buf); + encode(*n, buf); + } + match self { + PriceRange::Free => { + write_value(&0, buf); + write_value(&0, buf); + } + PriceRange::Fixed { value } => { + write_value(value, buf); + write_value(&0, buf); + } + PriceRange::Range { min, max } => { + write_value(min, buf); + write_value(max, buf); + } + } + IsNull::No + } +} + +impl sqlx::Type for PriceRange { + fn type_info() -> sqlx::postgres::PgTypeInfo { + sqlx::postgres::PgTypeInfo::with_name("PriceRange") + } +} diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs new file mode 100644 index 0000000..46dad01 --- /dev/null +++ b/crates/contract/src/lib.rs @@ -0,0 +1,454 @@ +use std::fmt::{Display, Formatter}; + +use chrono::{NaiveDateTime, Utc}; +#[cfg(feature = "db")] +pub use db::*; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "db")] +mod db; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Account { + pub id: i32, + pub login: String, + pub email: String, + pub pass: String, + pub facebook_id: Option, + pub account_type: AccountType, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct NewsArticle { + pub id: i32, + pub title: String, + pub body: String, + pub status: NewsStatus, + pub published_at: Option, + pub created_at: NaiveDateTime, +} + +impl Default for NewsArticle { + fn default() -> Self { + Self { + id: 0, + title: "".to_string(), + body: "".to_string(), + status: NewsStatus::Pending, + published_at: None, + created_at: Utc::now().naive_utc(), + } + } +} + +#[cfg_attr(feature = "db", derive(sqlx::Type))] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum NewsStatus { + Pending, + Published, + Hidden, +} + +impl NewsStatus { + pub fn as_str(&self) -> &str { + match self { + Self::Pending => "Pending", + Self::Published => "Published", + Self::Hidden => "Hidden", + } + } +} + +#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)] +pub enum PriceRange { + #[default] + Free, + Fixed { + value: i32, + }, + Range { + min: i32, + max: i32, + }, +} + +impl From<(i32, i32)> for PriceRange { + fn from((min, max): (i32, i32)) -> Self { + match (min, max) { + (0, 0) => Self::Free, + (_, 0) => Self::Fixed { value: min }, + _ => Self::Range { min, max }, + } + } +} + +#[cfg_attr(feature = "db", derive(sqlx::Type))] +#[derive(Debug, Default, PartialOrd, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] +pub enum AccountType { + #[default] + User = 1, + Business = 10, + Admin = 100, +} + +#[cfg_attr(feature = "db", derive(sqlx::Type))] +#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)] +pub enum LocalBusinessState { + #[default] + Pending = 1, + Approved = 2, + Banned = 3, + Pinned = 4, + Internal = 5, +} + +impl Display for LocalBusinessState { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl LocalBusinessState { + pub fn as_str(&self) -> &str { + match self { + Self::Pending => "Pending", + Self::Approved => "Approved", + Self::Banned => "Banned", + Self::Pinned => "Pinned", + Self::Internal => "Internal", + } + } +} + +#[cfg_attr(feature = "db", derive(sqlx::Type))] +#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] +pub enum OfferState { + #[default] + Pending = 0, + Approved = 1, + Banned = 2, + Finished = 3, +} + +impl OfferState { + pub fn as_str(&self) -> &str { + match self { + OfferState::Pending => "Pending", + OfferState::Approved => "Approved", + OfferState::Banned => "Banned", + OfferState::Finished => "Finished", + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub enum Page { + #[default] + LocalBusinesses, + News, + Account, + Register, + Login, + AccountBusinessItems, + Marketplace, + AccountOffers, + AdminNews, + AdminCreateNews, + AdminBusinesses, + AdminOffers, + Terms, + Privacy, + Business, +} + +impl Page { + pub fn is_public(&self) -> bool { + !self.is_admin() + } + + pub fn is_admin(&self) -> bool { + matches!( + self, + Page::AdminNews | Page::AdminCreateNews | Page::AdminBusinesses | Page::AdminOffers + ) + } + + pub fn select_index(&self) -> &str { + if matches!(self, Page::LocalBusinesses) { + "selected" + } else { + "" + } + } + + pub fn select_news(&self) -> &str { + if matches!(self, Page::News) { + "selected" + } else { + "" + } + } + + pub fn select_account(&self) -> &str { + if matches!(self, Page::Account) { + "selected" + } else { + "" + } + } + + pub fn select_marketplace(&self) -> &str { + if matches!(self, Page::Marketplace | Page::AccountOffers) { + "selected" + } else { + "" + } + } + + pub fn select_admin_news(&self) -> &str { + if matches!(self, Page::AdminNews) { + "selected" + } else { + "" + } + } + + pub fn select_admin_businesses(&self) -> &str { + if matches!(self, Page::AdminBusinesses) { + "selected" + } else { + "" + } + } + + pub fn select_admin_offers(&self) -> &str { + if matches!(self, Page::AdminOffers) { + "selected" + } else { + "" + } + } +} + +#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct BusinessItemInput { + pub name: String, + pub price: u32, + pub picture_url: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SetStateBusinessInput { + pub id: i32, + pub state: LocalBusinessState, +} + +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +pub struct LocalBusiness { + pub id: i32, + pub owner_id: i32, + pub name: String, + pub description: String, + pub state: LocalBusinessState, + pub items: Vec, + pub contacts: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ContactInfo { + pub id: i32, + pub owner_id: i32, + pub contact_type: String, + pub content: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LocalBusinessItem { + pub id: i32, + pub local_business_id: i32, + pub name: String, + pub price: i64, + pub item_order: i32, + pub picture_url: String, +} + +#[derive(Debug, Deserialize)] +pub struct CreateBusinessItemInput { + pub name: String, + pub price: i64, + pub picture_url: String, + pub item_order: i32, +} + +#[derive(Debug, Deserialize)] +pub struct AtomicUpdateBusinessItemInput { + pub id: i32, + pub name: String, + pub description: String, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateBusinessItemInput { + pub id: i32, + pub name: String, + pub price: i64, + pub picture_url: String, + pub item_order: i32, +} + +#[derive(Debug, Deserialize)] +pub struct ModifyBusinessItemInput { + pub id: i32, +} + +#[derive(Debug, Deserialize)] +pub struct MoveBusinessItemInput { + pub id: i32, + pub item_order: i32, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateNewsArticleInput { + pub id: i32, + pub title: String, + pub body: String, + pub status: NewsStatus, +} + +#[derive(Debug, Deserialize)] +pub struct CreateNewsArticleInput { + pub title: String, + pub body: String, + pub status: NewsStatus, +} + +#[derive(Debug, Deserialize)] +pub struct DeleteNewsArticleInput { + pub id: i32, +} + +#[derive(Debug, Deserialize)] +pub struct CreateContactInfoInput { + #[serde(rename = "type")] + pub contact_type: String, + pub content: String, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateContactInfoInput { + pub id: i32, + #[serde(rename = "type")] + pub contact_type: String, + pub content: String, +} + +#[derive(Debug, Deserialize)] +pub struct DeleteContactInfoInput { + pub id: i32, +} + +#[derive(Debug, Deserialize)] +pub struct CreateOfferInput { + pub description: String, + pub picture_url: String, + pub price_min: i32, + pub price_max: i32, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateOfferInput { + pub id: i32, + pub description: String, + pub picture_url: String, + pub price_min: i32, + pub price_max: i32, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Offer { + pub id: i32, + pub owner_id: i32, + pub price_range: PriceRange, + pub description: String, + pub picture_url: String, + pub state: OfferState, + pub created_at: NaiveDateTime, + pub contacts: Vec, +} + +impl Default for Offer { + fn default() -> Self { + Self { + id: 0, + owner_id: 0, + price_range: Default::default(), + description: "".to_string(), + picture_url: "".to_string(), + state: Default::default(), + created_at: chrono::Utc::now().naive_utc(), + contacts: vec![], + } + } +} + +pub mod businesses { + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Debug, Default, Serialize, Deserialize)] + pub struct BusinessList { + pub businesses: Vec, + pub account: Option, + pub error: Option, + pub page: Page, + #[serde(skip)] + pub h: Helper, + } +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct Helper; + +impl Helper { + pub fn is_admin(&self, account: &Option) -> bool { + account + .as_ref() + .map(|a| a.account_type == AccountType::Admin) + .unwrap_or_default() + } + + pub fn is_above_user(&self, account: &Option) -> bool { + account + .as_ref() + .map(|a| a.account_type > AccountType::User) + .unwrap_or_default() + } + + pub fn account_id_tag(&self, account: &Option) -> String { + account + .as_ref() + .map(|a| format!("account-id={}", a.id)) + .unwrap_or_default() + } + + pub fn email<'a>(&self, account: &'a Option) -> &'a str { + account + .as_ref() + .map(|a| a.email.as_str()) + .unwrap_or_default() + } + + pub fn render_contact(&self, ty: &str, val: &str) -> String { + if ty == "mobile" { + self.mobile(val) + } else { + val.into() + } + } + + fn mobile(&self, phone: &str) -> String { + base64::encode(phone) + } +} diff --git a/server/Cargo.toml b/crates/server/Cargo.toml similarity index 93% rename from server/Cargo.toml rename to crates/server/Cargo.toml index 2bf9e9c..7e13483 100644 --- a/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -26,7 +26,7 @@ postgres = { version = "0.19.3" } rand = { version = "0.8.5", features = [] } serde = { version = "*", features = ["derive"] } serde_json = { version = "*" } -sqlx = { version = "*", features = ["runtime-actix-rustls", "postgres", "uuid", "chrono"] } +sqlx = { version = "*", features = ["runtime-actix-rustls", "postgres", "uuid", "chrono", "migrate"] } sqlx-core = { version = "0.6.0" } tracing = { version = "*" } tracing-actix-web = { version = "*" } @@ -34,3 +34,4 @@ tracing-subscriber = { version = "*" } uuid = { version = "*", features = ["serde"] } validator = { version = "0.14", features = ["derive"] } base64 = { version = "0.13.0" } +contract = { path = "../contract", features = ['db'] } diff --git a/server/askama.toml b/crates/server/askama.toml similarity index 100% rename from server/askama.toml rename to crates/server/askama.toml diff --git a/server/assets/templates/account.html b/crates/server/assets/templates/account.html similarity index 100% rename from server/assets/templates/account.html rename to crates/server/assets/templates/account.html diff --git a/server/assets/templates/admin/businesses.html b/crates/server/assets/templates/admin/businesses.html similarity index 100% rename from server/assets/templates/admin/businesses.html rename to crates/server/assets/templates/admin/businesses.html diff --git a/server/assets/templates/admin/edit.html b/crates/server/assets/templates/admin/edit.html similarity index 100% rename from server/assets/templates/admin/edit.html rename to crates/server/assets/templates/admin/edit.html diff --git a/server/assets/templates/admin/layout.html b/crates/server/assets/templates/admin/layout.html similarity index 100% rename from server/assets/templates/admin/layout.html rename to crates/server/assets/templates/admin/layout.html diff --git a/server/assets/templates/admin/news.html b/crates/server/assets/templates/admin/news.html similarity index 100% rename from server/assets/templates/admin/news.html rename to crates/server/assets/templates/admin/news.html diff --git a/server/assets/templates/admin/offers/index.html b/crates/server/assets/templates/admin/offers/index.html similarity index 100% rename from server/assets/templates/admin/offers/index.html rename to crates/server/assets/templates/admin/offers/index.html diff --git a/server/assets/templates/base.html b/crates/server/assets/templates/base.html similarity index 100% rename from server/assets/templates/base.html rename to crates/server/assets/templates/base.html diff --git a/server/assets/templates/businesses/editor.html b/crates/server/assets/templates/businesses/editor.html similarity index 100% rename from server/assets/templates/businesses/editor.html rename to crates/server/assets/templates/businesses/editor.html diff --git a/server/assets/templates/businesses/index.html b/crates/server/assets/templates/businesses/index.html similarity index 100% rename from server/assets/templates/businesses/index.html rename to crates/server/assets/templates/businesses/index.html diff --git a/server/assets/templates/businesses/show.html b/crates/server/assets/templates/businesses/show.html similarity index 100% rename from server/assets/templates/businesses/show.html rename to crates/server/assets/templates/businesses/show.html diff --git a/server/assets/templates/marketplace/edit.html b/crates/server/assets/templates/marketplace/edit.html similarity index 100% rename from server/assets/templates/marketplace/edit.html rename to crates/server/assets/templates/marketplace/edit.html diff --git a/server/assets/templates/marketplace/index.html b/crates/server/assets/templates/marketplace/index.html similarity index 100% rename from server/assets/templates/marketplace/index.html rename to crates/server/assets/templates/marketplace/index.html diff --git a/server/assets/templates/marketplace/new.html b/crates/server/assets/templates/marketplace/new.html similarity index 100% rename from server/assets/templates/marketplace/new.html rename to crates/server/assets/templates/marketplace/new.html diff --git a/server/assets/templates/marketplace/show.html b/crates/server/assets/templates/marketplace/show.html similarity index 100% rename from server/assets/templates/marketplace/show.html rename to crates/server/assets/templates/marketplace/show.html diff --git a/server/assets/templates/nav.html b/crates/server/assets/templates/nav.html similarity index 100% rename from server/assets/templates/nav.html rename to crates/server/assets/templates/nav.html diff --git a/server/assets/templates/news.html b/crates/server/assets/templates/news.html similarity index 100% rename from server/assets/templates/news.html rename to crates/server/assets/templates/news.html diff --git a/server/assets/templates/privacy-policy.html b/crates/server/assets/templates/privacy-policy.html similarity index 100% rename from server/assets/templates/privacy-policy.html rename to crates/server/assets/templates/privacy-policy.html diff --git a/server/assets/templates/register.html b/crates/server/assets/templates/register.html similarity index 100% rename from server/assets/templates/register.html rename to crates/server/assets/templates/register.html diff --git a/server/assets/templates/sign-in.html b/crates/server/assets/templates/sign-in.html similarity index 100% rename from server/assets/templates/sign-in.html rename to crates/server/assets/templates/sign-in.html diff --git a/server/assets/templates/terms-and-condition.html b/crates/server/assets/templates/terms-and-condition.html similarity index 100% rename from server/assets/templates/terms-and-condition.html rename to crates/server/assets/templates/terms-and-condition.html diff --git a/server/src/auth.rs b/crates/server/src/auth.rs similarity index 100% rename from server/src/auth.rs rename to crates/server/src/auth.rs diff --git a/server/src/main.rs b/crates/server/src/main.rs similarity index 100% rename from server/src/main.rs rename to crates/server/src/main.rs diff --git a/crates/server/src/model/db.rs b/crates/server/src/model/db.rs new file mode 100644 index 0000000..f09484c --- /dev/null +++ b/crates/server/src/model/db.rs @@ -0,0 +1,182 @@ +use std::collections::HashMap; + +use chrono::{NaiveDateTime, Utc}; +use contract::{AccountType, LocalBusinessState, NewsStatus, OfferState, PriceRange}; +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct Account { + pub id: i32, + pub login: String, + pub email: String, + pub pass: String, + pub facebook_id: Option, + pub account_type: AccountType, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct Token { + pub id: i32, + pub claims: HashMap, + pub iss: String, /* issuer */ + pub sub: i32, /* subject */ + pub aud: String, /* audience */ + pub exp: NaiveDateTime, /* expiration time */ + pub nbt: NaiveDateTime, /* not before time */ + pub iat: NaiveDateTime, /* issued at time */ + pub jti: Uuid, /* JWT ID - unique */ + pub role: AccountType, +} + +#[derive(Debug, Default, Serialize, Deserialize, FromRow)] +pub struct LocalBusiness { + pub id: i32, + pub owner_id: i32, + pub name: String, + pub description: String, + pub state: LocalBusinessState, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct LocalBusinessItem { + pub id: i32, + pub local_business_id: i32, + pub name: String, + pub price: i64, + pub item_order: i32, + pub picture_url: String, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct ContactInfo { + pub id: i32, + pub owner_id: i32, + pub contact_type: String, + pub content: String, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct NewsArticle { + pub id: i32, + pub title: String, + pub body: String, + pub status: NewsStatus, + pub published_at: Option, + pub created_at: NaiveDateTime, +} + +impl Default for NewsArticle { + fn default() -> Self { + Self { + id: 0, + title: "".to_string(), + body: "".to_string(), + status: NewsStatus::Pending, + published_at: None, + created_at: Utc::now().naive_utc(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct CreateNewsArticleInput { + pub title: String, + pub body: String, + pub status: NewsStatus, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct UpdateNewsArticleInput { + pub id: i32, + pub title: String, + pub body: String, + pub status: NewsStatus, + pub published_at: Option, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct UpdateLocalBusinessInput { + pub id: i32, + pub name: String, + pub description: String, +} + +#[derive(Debug, Serialize, Deserialize, FromRow)] +pub struct Offer { + pub id: i32, + pub owner_id: i32, + pub price_range: PriceRange, + pub description: String, + pub picture_url: String, + pub state: OfferState, + pub created_at: NaiveDateTime, +} + +#[derive(Debug)] +pub struct CreateLocalBusinessItemInput { + pub account_id: i32, + pub local_business_id: i32, + pub name: String, + pub price: i64, + pub item_order: i32, + pub picture_url: Option, +} + +#[derive(Debug)] +pub struct UpdateLocalBusinessItemInput { + pub id: i32, + pub local_business_id: i32, + pub name: String, + pub price: i64, + pub item_order: i32, + pub picture_url: String, +} + +#[derive(Debug, Deserialize)] +pub struct DeleteNewsArticleInput { + pub id: i32, +} + +#[derive(Debug)] +pub struct CreateContactInput { + pub owner_id: i32, + pub contact_type: String, + pub content: String, +} + +#[derive(Debug)] +pub struct UpdateContactInput { + pub id: i32, + pub owner_id: i32, + pub contact_type: String, + pub content: String, +} + +#[derive(Debug)] +pub struct CreateAccountInput { + pub login: String, + pub email: String, + pub pass: String, + pub facebook_id: Option, + pub account_type: AccountType, +} + +#[derive(Debug)] +pub struct CreateOfferInput { + pub description: String, + pub picture_url: String, + pub state: OfferState, + pub owner_id: i32, + pub price_range: PriceRange, +} + +#[derive(Debug)] +pub struct UpdateOfferInput { + pub id: i32, + pub description: String, + pub picture_url: String, + pub state: OfferState, + pub price_range: PriceRange, +} diff --git a/server/src/model/mod.rs b/crates/server/src/model/mod.rs similarity index 100% rename from server/src/model/mod.rs rename to crates/server/src/model/mod.rs diff --git a/crates/server/src/model/view.rs b/crates/server/src/model/view.rs new file mode 100644 index 0000000..3cea79f --- /dev/null +++ b/crates/server/src/model/view.rs @@ -0,0 +1,145 @@ +use std::sync::Arc; + +pub use contract::*; + +use crate::model::db; + +pub fn business_item_to_view(item: &db::LocalBusinessItem) -> LocalBusinessItem { + let db::LocalBusinessItem { + id, + local_business_id, + name, + price, + item_order, + picture_url, + } = item; + LocalBusinessItem { + id: *id, + local_business_id: *local_business_id, + name: name.clone(), + price: *price, + item_order: *item_order, + picture_url: picture_url.to_string(), + } +} + +pub fn business_to_view<'items, 'contacts>( + service: db::LocalBusiness, + items: &'items mut Vec, + contacts: &'contacts mut Vec, +) -> LocalBusiness { + LocalBusiness { + id: service.id, + owner_id: service.owner_id, + name: service.name, + description: service.description, + state: service.state, + items: items + .drain_filter(|i| i.local_business_id == service.id) + .map(|i| business_item_to_view(&i)) + .collect(), + contacts: contacts + .drain_filter(|c| c.owner_id == service.owner_id) + .map(|c| contact_to_view(&c)) + .collect(), + } +} + +pub fn _info_to_view(offer: db::Offer, contacts: &Vec>) -> Offer { + let db::Offer { + id, + owner_id, + price_range, + description, + picture_url, + state, + created_at, + } = offer; + Offer { + id, + owner_id, + price_range, + description, + picture_url, + state, + created_at, + contacts: contacts + .iter() + .filter(|contact| contact.owner_id == owner_id) + .map(|info| contact_to_view(info)) + .collect(), + } +} + +pub fn contact_to_view(info: &db::ContactInfo) -> ContactInfo { + ContactInfo { + id: info.id, + owner_id: info.owner_id, + contact_type: info.contact_type.clone(), + content: info.content.clone(), + } +} + +pub fn account_to_view(account: db::Account) -> Account { + let db::Account { + id, + login, + email, + pass, + facebook_id, + account_type, + } = account; + Account { + id, + login, + email, + pass, + facebook_id, + account_type, + } +} + +pub fn offer_to_view<'l, I>(offer: db::Offer, contacts: I) -> Offer +where + I: Iterator, +{ + let db::Offer { + id, + owner_id, + price_range, + description, + picture_url, + state, + created_at, + } = offer; + + Offer { + id, + owner_id, + price_range, + description, + picture_url, + state, + created_at, + contacts: contacts.map(|c| contact_to_view(c)).collect(), + } +} + +pub fn news_to_view(news: &db::NewsArticle) -> NewsArticle { + let db::NewsArticle { + id, + title, + body, + status, + published_at, + created_at, + } = news; + NewsArticle { + id: *id, + title: title.to_string(), + body: body.to_string(), + status: *status, + published_at: published_at.clone(), + created_at: *created_at, + } +} diff --git a/server/src/queries/accounts.rs b/crates/server/src/queries/accounts.rs similarity index 100% rename from server/src/queries/accounts.rs rename to crates/server/src/queries/accounts.rs diff --git a/server/src/queries/businesses.rs b/crates/server/src/queries/businesses.rs similarity index 98% rename from server/src/queries/businesses.rs rename to crates/server/src/queries/businesses.rs index 75298e8..a6a004e 100644 --- a/server/src/queries/businesses.rs +++ b/crates/server/src/queries/businesses.rs @@ -1,3 +1,4 @@ +use contract::LocalBusinessState; use tracing::error; use crate::model::db; @@ -7,7 +8,7 @@ use crate::queries::{Error, Result, T}; pub async fn set_business_state( t: &mut T<'_>, id: i32, - state: db::LocalBusinessState, + state: LocalBusinessState, ) -> Result { sqlx::query_as( r#" diff --git a/server/src/queries/contacts.rs b/crates/server/src/queries/contacts.rs similarity index 100% rename from server/src/queries/contacts.rs rename to crates/server/src/queries/contacts.rs diff --git a/server/src/queries/local_business_items.rs b/crates/server/src/queries/local_business_items.rs similarity index 100% rename from server/src/queries/local_business_items.rs rename to crates/server/src/queries/local_business_items.rs diff --git a/server/src/queries/mod.rs b/crates/server/src/queries/mod.rs similarity index 95% rename from server/src/queries/mod.rs rename to crates/server/src/queries/mod.rs index 9cfd82d..7c91996 100644 --- a/server/src/queries/mod.rs +++ b/crates/server/src/queries/mod.rs @@ -5,6 +5,7 @@ mod local_business_items; mod news; mod offers; +use contract::{LocalBusinessState, OfferState}; use sqlx::PgPool; use crate::model::db; @@ -46,7 +47,7 @@ pub enum Error { }, BusinessItemState { id: i32, - state: db::LocalBusinessState, + state: LocalBusinessState, }, AllItems, OwnedBusiness { @@ -105,7 +106,7 @@ pub enum Error { }, SetOfferState { offer_id: i32, - state: db::OfferState, + state: OfferState, }, Offer { offer_id: i32, diff --git a/server/src/queries/news.rs b/crates/server/src/queries/news.rs similarity index 100% rename from server/src/queries/news.rs rename to crates/server/src/queries/news.rs diff --git a/server/src/queries/offers.rs b/crates/server/src/queries/offers.rs similarity index 99% rename from server/src/queries/offers.rs rename to crates/server/src/queries/offers.rs index 960ca00..21ea219 100644 --- a/server/src/queries/offers.rs +++ b/crates/server/src/queries/offers.rs @@ -1,7 +1,7 @@ +use contract::OfferState; use tracing::error; use crate::model::db; -use crate::model::db::OfferState; use crate::queries::{Error, Result, T}; #[tracing::instrument] diff --git a/server/src/routes/mod.rs b/crates/server/src/routes/mod.rs similarity index 100% rename from server/src/routes/mod.rs rename to crates/server/src/routes/mod.rs diff --git a/server/src/routes/restricted.rs b/crates/server/src/routes/restricted.rs similarity index 72% rename from server/src/routes/restricted.rs rename to crates/server/src/routes/restricted.rs index d2ec0f6..2ab13a6 100644 --- a/server/src/routes/restricted.rs +++ b/crates/server/src/routes/restricted.rs @@ -2,10 +2,14 @@ use actix_http::StatusCode; use actix_web::web::{Data, ServiceConfig}; use actix_web::{get, HttpRequest}; use askama::*; +use contract::{Account, ContactInfo, Helper, LocalBusiness, LocalBusinessItem}; use serde::Serialize; use sqlx::PgPool; -use crate::model::{db, view}; +use crate::model::view; +use crate::model::view::{ + account_to_view, business_item_to_view, business_to_view, contact_to_view, +}; use crate::queries; use crate::routes::{HttpResult, Identity}; @@ -14,17 +18,15 @@ mod business_item; mod contacts; mod offers; -use crate::view::Helper; - #[derive(Debug, Default, Serialize, Template)] #[template(path = "businesses/editor.html")] struct BusinessItemsTemplate { page: view::Page, error: Option, - account: Option, - items: Vec, - business: db::LocalBusiness, - contacts: Vec, + account: Option, + items: Vec, + business: LocalBusiness, + contacts: Vec, h: Helper, } @@ -61,18 +63,18 @@ async fn handle_business_items_page( req, queries::account_business_by_owner_id(t, account.id).await ); - let items = queries::account_items(t, account.id).await; - let contacts = crate::ok_or_internal!(req, queries::account_contacts(t, account.id).await); + let mut items = queries::account_items(t, account.id).await; + let mut contacts = crate::ok_or_internal!(req, queries::account_contacts(t, account.id).await); HttpResult::res( req, StatusCode::OK, BusinessItemsTemplate { page: view::Page::AccountBusinessItems, - account: Some(account), - items, - business, - contacts, + account: Some(account_to_view(account)), + items: items.iter().map(business_item_to_view).collect(), + contacts: contacts.iter().map(contact_to_view).collect(), + business: business_to_view(business, &mut items, &mut contacts), ..Default::default() }, ) diff --git a/server/src/routes/restricted/admin.rs b/crates/server/src/routes/restricted/admin.rs similarity index 88% rename from server/src/routes/restricted/admin.rs rename to crates/server/src/routes/restricted/admin.rs index 16598d9..a39c7e6 100644 --- a/server/src/routes/restricted/admin.rs +++ b/crates/server/src/routes/restricted/admin.rs @@ -9,7 +9,7 @@ use actix_web::web::ServiceConfig; macro_rules! require_admin { ($req: expr, $t: expr, $id: expr) => {{ let account = authorize!($req, &mut $t, $id); - if account.account_type != $crate::model::db::AccountType::Admin { + if account.account_type != contract::AccountType::Admin { return HttpResult::err($req, $crate::routes::Error::Forbidden); } account diff --git a/server/src/routes/restricted/admin/businesses.rs b/crates/server/src/routes/restricted/admin/businesses.rs similarity index 93% rename from server/src/routes/restricted/admin/businesses.rs rename to crates/server/src/routes/restricted/admin/businesses.rs index dfb6020..cb87019 100644 --- a/server/src/routes/restricted/admin/businesses.rs +++ b/crates/server/src/routes/restricted/admin/businesses.rs @@ -2,13 +2,13 @@ use actix_http::StatusCode; use actix_web::web::{Data, Form, ServiceConfig}; use actix_web::{get, post, web, HttpRequest}; use askama::*; +use contract::{Account, Helper}; use serde::Serialize; use sqlx::PgPool; +use crate::model::view; use crate::model::view::{Page, SetStateBusinessInput}; -use crate::model::{db, view}; use crate::routes::{HttpResult, Identity}; -use crate::view::Helper; use crate::{authorize, ok_or_internal, queries, require_admin}; #[derive(Debug, Default, Serialize, Template)] @@ -16,7 +16,7 @@ use crate::{authorize, ok_or_internal, queries, require_admin}; struct AdminBusinessesTemplate { page: Page, error: Option, - account: Option, + account: Option, businesses: Vec, h: Helper, } @@ -41,7 +41,7 @@ async fn admin_businesses(req: HttpRequest, db: Data, id: Identity) -> H let businesses: Vec<_> = services .into_iter() - .map(|service| view::LocalBusiness::from((service, &mut items, &mut contacts))) + .map(|service| view::business_to_view(service, &mut items, &mut contacts)) .collect(); t.commit().await.ok(); diff --git a/server/src/routes/restricted/admin/news.rs b/crates/server/src/routes/restricted/admin/news.rs similarity index 92% rename from server/src/routes/restricted/admin/news.rs rename to crates/server/src/routes/restricted/admin/news.rs index b518a34..beb1078 100644 --- a/server/src/routes/restricted/admin/news.rs +++ b/crates/server/src/routes/restricted/admin/news.rs @@ -2,23 +2,24 @@ use actix_http::StatusCode; use actix_web::web::{Data, Form, Path, ServiceConfig}; use actix_web::{get, post, web, HttpRequest}; use askama::*; +use contract::{Account, Helper, NewsArticle, NewsStatus}; use serde::Serialize; use sqlx::PgPool; -use crate::model::view::Page; +use crate::model::view::{news_to_view, Page}; use crate::model::{db, view}; use crate::routes::{HttpResult, Identity}; -use crate::view::{filters, Helper}; +use crate::view::filters; use crate::{authorize, ok_or_internal, queries, require_admin}; #[derive(Debug, Default, Template, Serialize)] #[template(path = "admin/news.html")] struct AdminNewsTemplate { #[serde(skip)] - page: view::Page, + page: Page, error: Option, - account: Option, - news: Vec, + account: Option, + news: Vec, #[serde(skip)] h: Helper, } @@ -47,7 +48,7 @@ async fn admin_news(req: HttpRequest, db: Data, id: Identity) -> HttpRes StatusCode::OK, AdminNewsTemplate { page: Page::AdminNews, - news, + news: news.iter().map(news_to_view).collect(), ..Default::default() }, ) @@ -58,8 +59,8 @@ async fn admin_news(req: HttpRequest, db: Data, id: Identity) -> HttpRes struct EditTemplate { page: Page, error: Option, - account: Option, - article: db::NewsArticle, + account: Option, + article: NewsArticle, h: Helper, } @@ -90,7 +91,7 @@ async fn edit_news_article( StatusCode::OK, EditTemplate { page: Page::AdminNews, - article, + article: news_to_view(&article), ..Default::default() }, ) @@ -159,7 +160,7 @@ async fn update_news_article( title: form.title, body: form.body, status: form.status, - published_at: if matches!(form.status, db::NewsStatus::Published) { + published_at: if matches!(form.status, NewsStatus::Published) { Some(chrono::Utc::now().naive_utc()) } else { None diff --git a/server/src/routes/restricted/admin/offers.rs b/crates/server/src/routes/restricted/admin/offers.rs similarity index 87% rename from server/src/routes/restricted/admin/offers.rs rename to crates/server/src/routes/restricted/admin/offers.rs index 576ce6d..343c9b2 100644 --- a/server/src/routes/restricted/admin/offers.rs +++ b/crates/server/src/routes/restricted/admin/offers.rs @@ -2,14 +2,13 @@ use actix_http::StatusCode; use actix_web::web::{Data, Form, ServiceConfig}; use actix_web::{get, post, web, HttpRequest}; use askama::*; +use contract::{Account, Helper, OfferState, PriceRange}; use serde::{Deserialize, Serialize}; use sqlx_core::postgres::PgPool; use crate::model::db; -use crate::model::db::PriceRange; -use crate::model::view::Page; +use crate::model::view::{account_to_view, Page}; use crate::routes::{HttpResult, Identity}; -use crate::view::Helper; use crate::{authorize, ok_or_internal, queries, require_admin}; #[derive(Debug, Default, Serialize, Template)] @@ -17,7 +16,7 @@ use crate::{authorize, ok_or_internal, queries, require_admin}; struct AdminOffersTemplate { page: Page, error: Option, - account: Option, + account: Option, h: Helper, offers: Vec, } @@ -37,7 +36,7 @@ async fn admin_offers(req: HttpRequest, db: Data, id: Identity) -> HttpR &req, StatusCode::OK, AdminOffersTemplate { - account: Some(account), + account: Some(account_to_view(account)), page: Page::AdminOffers, offers, error: None, @@ -62,7 +61,7 @@ async fn admin_ban_offer( let mut t = ok_or_internal!(&req, pool.begin().await); let account = require_admin!(&req, &mut t, id); - match queries::set_offer_state(&mut t, form.id, db::OfferState::Banned).await { + match queries::set_offer_state(&mut t, form.id, OfferState::Banned).await { Ok(_) => { t.commit().await.ok(); HttpResult::goto(&req, "/admin/offers") @@ -75,7 +74,7 @@ async fn admin_ban_offer( &req, StatusCode::BAD_REQUEST, AdminOffersTemplate { - account: Some(account), + account: Some(account_to_view(account)), page: Page::AdminOffers, offers, error: Some("Nie można zbanować oferty".into()), @@ -97,7 +96,7 @@ async fn admin_approve_offer( let mut t = ok_or_internal!(&req, pool.begin().await); let account = require_admin!(&req, &mut t, id); - match queries::set_offer_state(&mut t, form.id, db::OfferState::Approved).await { + match queries::set_offer_state(&mut t, form.id, OfferState::Approved).await { Ok(_) => { t.commit().await.ok(); HttpResult::goto(&req, "/admin/offers") @@ -110,7 +109,7 @@ async fn admin_approve_offer( &req, StatusCode::BAD_REQUEST, AdminOffersTemplate { - account: Some(account), + account: Some(account_to_view(account)), page: Page::AdminOffers, offers, error: Some("Nie można zbanować oferty".into()), diff --git a/server/src/routes/restricted/business_item.rs b/crates/server/src/routes/restricted/business_item.rs similarity index 100% rename from server/src/routes/restricted/business_item.rs rename to crates/server/src/routes/restricted/business_item.rs diff --git a/server/src/routes/restricted/contacts.rs b/crates/server/src/routes/restricted/contacts.rs similarity index 100% rename from server/src/routes/restricted/contacts.rs rename to crates/server/src/routes/restricted/contacts.rs diff --git a/server/src/routes/restricted/offers.rs b/crates/server/src/routes/restricted/offers.rs similarity index 89% rename from server/src/routes/restricted/offers.rs rename to crates/server/src/routes/restricted/offers.rs index 6e6cd86..2efabd3 100644 --- a/server/src/routes/restricted/offers.rs +++ b/crates/server/src/routes/restricted/offers.rs @@ -2,20 +2,19 @@ use actix_http::StatusCode; use actix_web::web::{Data, Form, ServiceConfig}; use actix_web::{get, post, web, HttpRequest}; use askama::*; +use contract::{Account, ContactInfo, Helper, OfferState}; use serde::{Deserialize, Serialize}; use sqlx::PgPool; -use crate::model::db::{self, ContactInfo, OfferState}; -use crate::model::view; -use crate::model::view::Page; +use crate::model::view::{account_to_view, contact_to_view, Page}; +use crate::model::{db, view}; use crate::routes::{HttpResult, Identity}; -use crate::view::Helper; use crate::{authorize, not_xss, ok_or_internal, queries}; #[derive(Default, Serialize, Template)] #[template(path = "./marketplace/new.html")] struct NewOfferTemplate { - account: Option, + account: Option, error: Option, page: Page, #[serde(skip)] @@ -39,7 +38,7 @@ async fn new_offer(req: HttpRequest, db: Data, id: Identity) -> HttpResu StatusCode::NOT_FOUND, NewOfferTemplate { page: Page::Marketplace, - account, + account: account.map(account_to_view), error: Some("Musisz być zalogowany żeby to zrobić".into()), ..Default::default() }, @@ -58,8 +57,8 @@ async fn new_offer(req: HttpRequest, db: Data, id: Identity) -> HttpResu StatusCode::OK, NewOfferTemplate { page: Page::Marketplace, - account: Some(account), - contacts, + account: Some(account_to_view(account)), + contacts: contacts.iter().map(contact_to_view).collect(), ..Default::default() }, ) @@ -106,7 +105,7 @@ async fn create_offer( &req, StatusCode::BAD_REQUEST, NewOfferTemplate { - account: Some(account), + account: Some(account_to_view(account)), page: Page::AccountOffers, error: Some("Problem z utworzeniem wpisu".into()), ..Default::default() @@ -156,7 +155,7 @@ async fn update_offer( &req, StatusCode::BAD_REQUEST, NewOfferTemplate { - account: Some(account), + account: Some(account_to_view(account)), page: Page::AccountOffers, error: Some("Problem z utworzeniem wpisu".into()), ..Default::default() @@ -184,7 +183,7 @@ async fn user_finish_offer( let form = form.into_inner(); dbg!(&form); - match queries::set_offer_state(&mut t, form.id, db::OfferState::Finished).await { + match queries::set_offer_state(&mut t, form.id, OfferState::Finished).await { Ok(_) => { t.commit().await.ok(); HttpResult::goto(&req, "/marketplace") @@ -196,7 +195,7 @@ async fn user_finish_offer( &req, StatusCode::BAD_REQUEST, NewOfferTemplate { - account: Some(account), + account: Some(account_to_view(account)), page: Page::AccountOffers, error: Some("Problem z utworzeniem wpisu".into()), ..Default::default() diff --git a/server/src/routes/unrestricted.rs b/crates/server/src/routes/unrestricted.rs similarity index 80% rename from server/src/routes/unrestricted.rs rename to crates/server/src/routes/unrestricted.rs index cf4a2cd..69cadac 100644 --- a/server/src/routes/unrestricted.rs +++ b/crates/server/src/routes/unrestricted.rs @@ -4,34 +4,37 @@ mod marketplace; mod news; mod terms; +use std::ops::Deref; + use actix_files::Files; use actix_web::web::ServiceConfig; use actix_web::*; use askama::Template; +use contract::businesses::BusinessList; +use contract::Page; use serde::Serialize; -use crate::model::db; -use crate::model::view::{self, Page}; use crate::routes::HttpResult; -use crate::view::Helper; #[derive(Default, Debug, Serialize, Template)] #[template(path = "businesses/index.html")] -pub struct IndexTemplate { - businesses: Vec, - account: Option, - error: Option, - page: Page, - h: Helper, +pub struct IndexTemplate(BusinessList); + +impl Deref for IndexTemplate { + type Target = BusinessList; + + fn deref(&self) -> &Self::Target { + &self.0 + } } #[tracing::instrument] pub async fn render_index() -> HttpResponse { HttpResponse::NotFound().content_type("text/html").body( - IndexTemplate { + IndexTemplate(BusinessList { page: Page::LocalBusinesses, ..Default::default() - } + }) .render() .unwrap(), ) @@ -62,13 +65,13 @@ pub fn configure(config: &mut ServiceConfig) { .service(Files::new("/uploads", "./uploads")) .service(Files::new("/assets/images", "./assets/images")) .service(Files::new("/assets/fonts", "./assets/fonts")) - .service(Files::new("/assets/css", "./client/dist")) - .service( + // .service(Files::new("/assets/css", "./client/dist")) + /*.service( Files::new("/assets/js", "./client/dist") .use_etag(true) .prefer_utf8(true) .show_files_listing(), - ) + )*/ .service(index) .service(gpc); } diff --git a/server/src/routes/unrestricted/account.rs b/crates/server/src/routes/unrestricted/account.rs similarity index 95% rename from server/src/routes/unrestricted/account.rs rename to crates/server/src/routes/unrestricted/account.rs index 824e8f3..86906f1 100644 --- a/server/src/routes/unrestricted/account.rs +++ b/crates/server/src/routes/unrestricted/account.rs @@ -2,16 +2,16 @@ use actix_http::StatusCode; use actix_web::web::{Data, Path, ServiceConfig}; use actix_web::{get, post, web, HttpRequest, HttpResponse}; use askama::*; +use contract::businesses::BusinessList; +use contract::{Account, AccountType, Helper}; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use tracing::error; -use crate::model::db::AccountType; -use crate::model::view::Page; +use crate::model::view::{account_to_view, Page}; use crate::model::{db, view}; use crate::routes::unrestricted::IndexTemplate; use crate::routes::{HttpResult, Identity}; -use crate::view::Helper; use crate::{not_xss, queries, routes, utils}; #[post("/logout")] @@ -21,10 +21,10 @@ async fn logout(id: Identity) -> HttpResponse { HttpResponse::SeeOther() .append_header(("Location", "/")) .body( - IndexTemplate { + IndexTemplate(BusinessList { page: Page::LocalBusinesses, ..Default::default() - } + }) .render() .unwrap(), ) @@ -33,10 +33,10 @@ async fn logout(id: Identity) -> HttpResponse { #[derive(Debug, Default, Serialize, Template)] #[template(path = "sign-in.html")] struct SignInTemplate { - account: Option, + account: Option, error: Option, - #[serde(skip)] page: Page, + #[serde(skip)] h: Helper, } @@ -70,7 +70,7 @@ async fn display_sign_in_form( let pool = db.into_inner(); let mut t = crate::ok_or_internal!(&req, pool.begin().await); let form = form.into_inner(); - let record: db::Account = match queries::account_by_email(&mut t, form.email).await { + let account: db::Account = match queries::account_by_email(&mut t, form.email).await { Ok(record) => record, Err(e) => { error!("{e:?}"); @@ -79,7 +79,7 @@ async fn display_sign_in_form( return AccountTemplate::bad_request(&req, "Nie znaleziono konta", Page::Login); } }; - if let Err(e) = utils::validate(&form.password, &record.pass) { + if let Err(e) = utils::validate(&form.password, &account.pass) { error!("{e}"); dbg!(e); t.rollback().await.ok(); @@ -89,14 +89,14 @@ async fn display_sign_in_form( Page::Login, ); } - id.remember(format!("{}", record.id)); + id.remember(format!("{}", account.id)); t.commit().await.ok(); HttpResult::res( &req, StatusCode::OK, AccountTemplate { - account: Some(record), + account: Some(account_to_view(account)), page: Page::Login, ..Default::default() }, @@ -106,7 +106,7 @@ async fn display_sign_in_form( #[derive(Default, Serialize, Template)] #[template(path = "account.html")] struct AccountTemplate { - account: Option, + account: Option, error: Option, #[serde(skip)] page: Page, @@ -149,7 +149,7 @@ async fn display_account_info(req: HttpRequest, id: Identity, db: Data) &req, StatusCode::OK, AccountTemplate { - account, + account: account.map(account_to_view), page: Page::Account, ..Default::default() }, @@ -159,7 +159,7 @@ async fn display_account_info(req: HttpRequest, id: Identity, db: Data) #[derive(Default, Serialize, Template)] #[template(path = "register.html")] struct RegisterTemplate { - account: Option, + account: Option, error: Option, #[serde(skip)] page: Page, @@ -353,7 +353,7 @@ async fn save_account_details( } } - if matches!(form.account_type, db::AccountType::Business) { + if matches!(form.account_type, AccountType::Business) { let name = form.name.as_deref().unwrap_or_default(); let owner_id = account.id; let description = form.description.as_deref().unwrap_or_default(); @@ -474,7 +474,7 @@ async fn display_register_page( &req, StatusCode::OK, RegisterTemplate { - account, + account: account.map(account_to_view), page: Page::Account, register_page: page, ..Default::default() diff --git a/server/src/routes/unrestricted/businesses.rs b/crates/server/src/routes/unrestricted/businesses.rs similarity index 84% rename from server/src/routes/unrestricted/businesses.rs rename to crates/server/src/routes/unrestricted/businesses.rs index 974517f..25106be 100644 --- a/server/src/routes/unrestricted/businesses.rs +++ b/crates/server/src/routes/unrestricted/businesses.rs @@ -2,22 +2,23 @@ use actix_http::StatusCode; use actix_web::web::{Data, ServiceConfig}; use actix_web::{get, web, HttpRequest}; use askama::Template; +use contract::businesses::BusinessList; +use contract::{Account, Helper}; use serde::Serialize; use sqlx::PgPool; -use crate::model::view::Page; -use crate::model::{db, view}; +use crate::model::view; +use crate::model::view::{account_to_view, business_to_view, Page}; use crate::queries; use crate::routes::unrestricted::IndexTemplate; use crate::routes::{HttpResult, Identity}; -use crate::view::Helper; #[get("")] #[tracing::instrument] pub async fn businesses_page(req: HttpRequest, db: Data, id: Identity) -> HttpResult { let pool = db.into_inner(); let mut t = crate::ok_or_internal!(&req, pool.begin().await); - let record = match id.identity() { + let account = match id.identity() { Some(id) => queries::account_by_id(&mut t, id).await, _ => None, }; @@ -37,7 +38,7 @@ pub async fn businesses_page(req: HttpRequest, db: Data, id: Identity) - let services: Vec<_> = services .into_iter() - .map(|service| view::LocalBusiness::from((service, &mut items, &mut contacts))) + .map(|service| business_to_view(service, &mut items, &mut contacts)) .collect::>(); t.commit().await.ok(); @@ -45,12 +46,12 @@ pub async fn businesses_page(req: HttpRequest, db: Data, id: Identity) - HttpResult::res( &req, StatusCode::OK, - IndexTemplate { + IndexTemplate(BusinessList { businesses: services, - account: record, + account: account.map(account_to_view), page: Page::LocalBusinesses, ..Default::default() - }, + }), ) } @@ -59,7 +60,7 @@ pub async fn businesses_page(req: HttpRequest, db: Data, id: Identity) - pub async fn businesses_json(req: HttpRequest, db: Data, id: Identity) -> HttpResult { let pool = db.into_inner(); let mut t = crate::ok_or_internal!(&req, pool.begin().await); - let record = match id.identity() { + let account = match id.identity() { Some(id) => queries::account_by_id(&mut t, id).await, _ => None, }; @@ -79,19 +80,19 @@ pub async fn businesses_json(req: HttpRequest, db: Data, id: Identity) - let services: Vec<_> = services .into_iter() - .map(|service| view::LocalBusiness::from((service, &mut items, &mut contacts))) + .map(|service| business_to_view(service, &mut items, &mut contacts)) .collect::>(); t.commit().await.ok(); HttpResult::json( StatusCode::OK, - IndexTemplate { + IndexTemplate(BusinessList { businesses: services, - account: record, + account: account.map(account_to_view), page: Page::LocalBusinesses, ..Default::default() - }, + }), ) } @@ -99,7 +100,7 @@ pub async fn businesses_json(req: HttpRequest, db: Data, id: Identity) - #[template(path = "businesses/show.html")] pub struct ShowTemplate { business: view::LocalBusiness, - account: Option, + account: Option, error: Option, page: Page, h: Helper, @@ -130,7 +131,7 @@ pub async fn show_business_page( &req, StatusCode::BAD_REQUEST, ShowTemplate { - account, + account: account.map(account_to_view), page: Page::Business, error: Some("Page not found".into()), ..Default::default() @@ -147,7 +148,7 @@ pub async fn show_business_page( (business, items, contacts) }; - let business = view::LocalBusiness::from((business, &mut items, &mut contacts)); + let business = business_to_view(business, &mut items, &mut contacts); t.commit().await.ok(); @@ -156,7 +157,7 @@ pub async fn show_business_page( StatusCode::OK, ShowTemplate { business, - account, + account: account.map(account_to_view), page: Page::LocalBusinesses, ..Default::default() }, diff --git a/server/src/routes/unrestricted/marketplace.rs b/crates/server/src/routes/unrestricted/marketplace.rs similarity index 86% rename from server/src/routes/unrestricted/marketplace.rs rename to crates/server/src/routes/unrestricted/marketplace.rs index 60af957..3469b5d 100644 --- a/server/src/routes/unrestricted/marketplace.rs +++ b/crates/server/src/routes/unrestricted/marketplace.rs @@ -1,15 +1,13 @@ -use std::sync::Arc; - use actix_http::StatusCode; use actix_web::web::{self, Data, ServiceConfig}; use actix_web::{get, HttpRequest}; use askama::*; +use contract::{Account, PriceRange}; use serde::Serialize; use sqlx::PgPool; -use crate::model::db::{self, PriceRange}; use crate::model::view; -use crate::model::view::Page; +use crate::model::view::{account_to_view, offer_to_view, Page}; use crate::queries; use crate::routes::{HttpResult, Identity}; use crate::view::Helper; @@ -17,7 +15,7 @@ use crate::view::Helper; #[derive(Default, Serialize, Template)] #[template(path = "./marketplace/index.html")] struct MarketplaceTemplate { - account: Option, + account: Option, error: Option, page: Page, #[serde(skip)] @@ -35,18 +33,13 @@ async fn marketplace(req: HttpRequest, db: Data, id: Identity) -> HttpRe _ => None, }; - let contacts = queries::all_contacts(&mut t) - .await - .unwrap_or_default() - .into_iter() - .map(Arc::new) - .collect::>(); + let contacts = queries::all_contacts(&mut t).await.unwrap_or_default(); let mut offers = queries::visible_offers(&mut t, account.as_ref().map(|a| a.id)) .await .unwrap_or_default() .into_iter() - .map(|o| view::Offer::from((o, &contacts))) + .map(|o| offer_to_view(o, contacts.iter())) .collect::>(); let my_offers = account @@ -65,7 +58,7 @@ async fn marketplace(req: HttpRequest, db: Data, id: Identity) -> HttpRe StatusCode::OK, MarketplaceTemplate { page: Page::Marketplace, - account, + account: account.map(account_to_view), offers, my_offers, ..Default::default() @@ -76,7 +69,7 @@ async fn marketplace(req: HttpRequest, db: Data, id: Identity) -> HttpRe #[derive(Default, Serialize, Template)] #[template(path = "./marketplace/edit.html")] struct EditOfferTemplate { - account: Option, + account: Option, error: Option, page: Page, #[serde(skip)] @@ -106,7 +99,7 @@ async fn edit_marketplace( StatusCode::NOT_FOUND, EditOfferTemplate { page: Page::Marketplace, - account, + account: account.map(account_to_view), error: Some("Oferta nie istnieje".into()), ..Default::default() }, @@ -123,7 +116,7 @@ async fn edit_marketplace( StatusCode::NOT_FOUND, EditOfferTemplate { page: Page::Marketplace, - account: Some(account), + account: Some(account_to_view(account)), error: Some("Oferta nie istnieje".into()), ..Default::default() }, @@ -138,8 +131,8 @@ async fn edit_marketplace( StatusCode::OK, EditOfferTemplate { page: Page::Marketplace, - account: Some(account), - offer: view::Offer::from((offer, &vec![])), + account: Some(account_to_view(account)), + offer: offer_to_view(offer, vec![].iter()), ..Default::default() }, ) @@ -148,7 +141,7 @@ async fn edit_marketplace( #[derive(Default, Serialize, Template)] #[template(path = "./marketplace/show.html")] struct ShowOfferTemplate { - account: Option, + account: Option, error: Option, page: Page, #[serde(skip)] @@ -178,7 +171,7 @@ async fn show_marketplace( StatusCode::NOT_FOUND, ShowOfferTemplate { page: Page::Marketplace, - account, + account: account.map(account_to_view), error: Some("Oferta nie istnieje".into()), ..Default::default() }, @@ -195,7 +188,7 @@ async fn show_marketplace( StatusCode::NOT_FOUND, ShowOfferTemplate { page: Page::Marketplace, - account: Some(account), + account: Some(account_to_view(account)), error: Some("Oferta nie istnieje".into()), ..Default::default() }, @@ -210,8 +203,8 @@ async fn show_marketplace( StatusCode::OK, ShowOfferTemplate { page: Page::Marketplace, - account: Some(account), - offer: view::Offer::from((offer, &vec![])), + account: Some(account_to_view(account)), + offer: offer_to_view(offer, vec![].iter()), ..Default::default() }, ) diff --git a/server/src/routes/unrestricted/news.rs b/crates/server/src/routes/unrestricted/news.rs similarity index 88% rename from server/src/routes/unrestricted/news.rs rename to crates/server/src/routes/unrestricted/news.rs index 1ac81a4..d2979ef 100644 --- a/server/src/routes/unrestricted/news.rs +++ b/crates/server/src/routes/unrestricted/news.rs @@ -2,11 +2,12 @@ use actix_http::StatusCode; use actix_web::web::{Data, ServiceConfig}; use actix_web::{get, HttpRequest}; use askama::*; +use contract::Account; use serde::Serialize; use sqlx::PgPool; use crate::model::db; -use crate::model::view::Page; +use crate::model::view::{account_to_view, Page}; use crate::queries; use crate::routes::{HttpResult, Identity}; use crate::view::{filters, Helper}; @@ -14,7 +15,7 @@ use crate::view::{filters, Helper}; #[derive(Default, Debug, Serialize, Template)] #[template(path = "news.html")] pub struct NewsTemplate { - account: Option, + account: Option, error: Option, page: Page, news: Vec, @@ -37,7 +38,7 @@ async fn news(req: HttpRequest, id: Identity, db: Data) -> HttpResult { &req, StatusCode::OK, NewsTemplate { - account, + account: account.map(account_to_view), page: Page::News, news, ..Default::default() diff --git a/server/src/routes/unrestricted/terms.rs b/crates/server/src/routes/unrestricted/terms.rs similarity index 92% rename from server/src/routes/unrestricted/terms.rs rename to crates/server/src/routes/unrestricted/terms.rs index e94c90f..f46445e 100644 --- a/server/src/routes/unrestricted/terms.rs +++ b/crates/server/src/routes/unrestricted/terms.rs @@ -1,15 +1,15 @@ use actix_web::web::ServiceConfig; use actix_web::{get, HttpResponse}; use askama::*; +use contract::Account; -use crate::model::db; use crate::model::view::Page; use crate::view::Helper; #[derive(Default, Template)] #[template(path = "terms-and-condition.html")] pub struct TermsTemplate { - account: Option, + account: Option, error: Option, page: Page, h: Helper, @@ -30,7 +30,7 @@ pub async fn terms_and_condition() -> HttpResponse { #[derive(Default, Template)] #[template(path = "privacy-policy.html")] struct PrivacyPolicyTemplate { - account: Option, + account: Option, error: Option, page: Page, h: Helper, diff --git a/server/src/routes/uploads.rs b/crates/server/src/routes/uploads.rs similarity index 100% rename from server/src/routes/uploads.rs rename to crates/server/src/routes/uploads.rs diff --git a/server/src/utils.rs b/crates/server/src/utils.rs similarity index 100% rename from server/src/utils.rs rename to crates/server/src/utils.rs diff --git a/crates/server/src/view/mod.rs b/crates/server/src/view/mod.rs new file mode 100644 index 0000000..0f21b52 --- /dev/null +++ b/crates/server/src/view/mod.rs @@ -0,0 +1,10 @@ +pub use contract::*; + +pub mod filters { + + pub fn opt_time(s: &Option) -> ::askama::Result { + Ok(s.as_ref() + .map(|t| serde_json::to_string(t).unwrap_or_default()) + .unwrap_or_default()) + } +} diff --git a/crates/web/Cargo.toml b/crates/web/Cargo.toml new file mode 100644 index 0000000..9da5182 --- /dev/null +++ b/crates/web/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "web" +version = "0.1.0" +edition = "2021" + +[dependencies] +sycamore = { version = "0.8.2", features = ['suspense'] } +wasm_request = { version = "0.1.1" } +serde = { version = "*", features = ['derive'] } +serde_json = { version = "*" } +contract = { path = "../contract" } +tracing = { version = "*" } +tracing-subscriber = { version = "*" } +tracing-subscriber-wasm = { version = "0.1.0" } +console_error_panic_hook = "0.1.7" diff --git a/web/Trunk.toml b/crates/web/Trunk.toml similarity index 100% rename from web/Trunk.toml rename to crates/web/Trunk.toml diff --git a/web/assets/tailwind.css b/crates/web/assets/tailwind.css similarity index 100% rename from web/assets/tailwind.css rename to crates/web/assets/tailwind.css diff --git a/web/index.html b/crates/web/index.html similarity index 100% rename from web/index.html rename to crates/web/index.html diff --git a/web/package.json b/crates/web/package.json similarity index 55% rename from web/package.json rename to crates/web/package.json index dc538ff..159640f 100644 --- a/web/package.json +++ b/crates/web/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "@tailwindcss/line-clamp": "^0.4.4", "tailwind": "^4.0.0" } } diff --git a/web/pnpm-lock.yaml b/crates/web/pnpm-lock.yaml similarity index 71% rename from web/pnpm-lock.yaml rename to crates/web/pnpm-lock.yaml index 95c715d..9459e6a 100644 --- a/web/pnpm-lock.yaml +++ b/crates/web/pnpm-lock.yaml @@ -1,6 +1,9 @@ lockfileVersion: '6.0' dependencies: + '@tailwindcss/line-clamp': + specifier: ^0.4.4 + version: 0.4.4(tailwindcss@3.3.1) tailwind: specifier: ^4.0.0 version: 4.0.0 @@ -25,6 +28,69 @@ packages: regenerator-runtime: 0.12.1 dev: false + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.18 + dev: false + + /@jridgewell/resolve-uri@3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/sourcemap-codec@1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: false + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: false + + /@jridgewell/trace-mapping@0.3.18: + resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: false + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: false + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: false + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: false + + /@tailwindcss/line-clamp@0.4.4(tailwindcss@3.3.1): + resolution: {integrity: sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==} + peerDependencies: + tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' + dependencies: + tailwindcss: 3.3.1(postcss@8.4.23) + dev: false + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -60,11 +126,27 @@ packages: color-convert: 1.9.3 dev: false + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: false + /app-root-path@2.1.0: resolution: {integrity: sha512-z5BqVjscbjmJBybKlICogJR2jCr2q/Ixu7Pvui5D4y97i7FLsJlvEG9XOR/KJRlkxxZz7UaaS2TMwQh1dRJ2dA==} engines: {node: '>= 4.0.0'} dev: false + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: false + /array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} dependencies: @@ -113,6 +195,10 @@ packages: regenerator-runtime: 0.11.1 dev: false + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: false + /basic-auth@2.0.1: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} @@ -120,6 +206,11 @@ packages: safe-buffer: 5.1.2 dev: false + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: false + /bitsyntax@0.0.4: resolution: {integrity: sha512-Pav3HSZXD2NLQOWfJldY3bpJLt8+HS2nUo5Z1bLLmHg2vCE/cM1qfEvNjlYo7GgYQPneNr715Bh42i01ZHZPvw==} engines: {node: '>=0.6'} @@ -149,6 +240,20 @@ packages: - supports-color dev: false + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: false + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: false + /buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} dev: false @@ -169,6 +274,11 @@ packages: get-intrinsic: 1.2.0 dev: false + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + dev: false + /chalk@2.4.1: resolution: {integrity: sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==} engines: {node: '>=4'} @@ -178,6 +288,21 @@ packages: supports-color: 5.5.0 dev: false + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -188,6 +313,15 @@ packages: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: false + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: false + /commands-events@1.0.4: resolution: {integrity: sha512-HdP/+1Anoc7z+6L2h7nd4Imz54+LW+BjMGt30riBZrZ3ZeP/8el93wD8Jj8ltAaqVslqNgjX6qlhSBJwuDSmpg==} dependencies: @@ -222,6 +356,10 @@ packages: - supports-color dev: false + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: false + /content-disposition@0.5.2: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} @@ -267,6 +405,12 @@ packages: util.promisify: 1.0.0 dev: false + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: false + /datasette@1.0.1: resolution: {integrity: sha512-aJdlCBToEJUP4M57r67r4V6tltwGKa3qetnjpBtXYIlqbX9tM9jsoDMxb4xd9AGjpp3282oHRmqI5Z8TVAU0Mg==} dependencies: @@ -303,6 +447,14 @@ packages: resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} dev: false + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: false + + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: false + /draht@1.0.1: resolution: {integrity: sha512-yNNHL864dniNmIE9ZKD++mKypiAUAvVZtyV0QrbXH/ak3ebzFqo5xsmRBRqV8pZVhImOSBiyq500Wcmrf44zAg==} dependencies: @@ -446,10 +598,34 @@ packages: resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} dev: false + /fast-glob@3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: false + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: false + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: false + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: false + /finalhandler@1.1.1: resolution: {integrity: sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==} engines: {node: '>= 0.8'} @@ -509,6 +685,18 @@ packages: engines: {node: '>= 0.6'} dev: false + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: false + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false + optional: true + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: false @@ -547,6 +735,31 @@ packages: get-intrinsic: 1.2.0 dev: false + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: false + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: false + + /glob@7.1.6: + resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: false + /globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} @@ -623,6 +836,13 @@ packages: safer-buffer: 2.1.2 dev: false + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: false + /inherits@2.0.3: resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} dev: false @@ -659,6 +879,13 @@ packages: has-bigints: 1.0.2 dev: false + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: false + /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} @@ -672,6 +899,12 @@ packages: engines: {node: '>= 0.4'} dev: false + /is-core-module@2.12.0: + resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==} + dependencies: + has: 1.0.3 + dev: false + /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} @@ -679,6 +912,18 @@ packages: has-tostringtag: 1.0.0 dev: false + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: false + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: false + /is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} @@ -691,6 +936,11 @@ packages: has-tostringtag: 1.0.0 dev: false + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: false + /is-obj@1.0.1: resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} engines: {node: '>=0.10.0'} @@ -754,6 +1004,11 @@ packages: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: false + /jiti@1.18.2: + resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} + hasBin: true + dev: false + /json-lines@1.0.0: resolution: {integrity: sha512-ytuLZb4RBQb3bTRsG/QBenyIo5oHLpjeCVph3s2NnoAsZE9K6h+uR+OWpEOWV1UeHdX63tYctGppBpGAc+JNMA==} dependencies: @@ -795,6 +1050,11 @@ packages: safe-buffer: 5.2.1 dev: false + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: false + /limes@2.0.0: resolution: {integrity: sha512-evWD0pnTgPX7QueaSoJl5JBUL30T1ZVzo34ke97tIKmeagqhBTYK/JkKL0vtG3MpNApw8ZY9TlbybfwEz9knBA==} dependencies: @@ -802,6 +1062,10 @@ packages: jsonwebtoken: 8.5.0 dev: false + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: false + /lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} dev: false @@ -854,11 +1118,24 @@ packages: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} dev: false + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: false + /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} dev: false + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: false + /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -876,6 +1153,12 @@ packages: hasBin: true dev: false + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: false + /moment@2.22.2: resolution: {integrity: sha512-LRvkBHaJGnrcWvqsElsOhHCzj8mU39wLx5pQ0pc6s153GynCTsPdGdqsVNKAQD9sKnWj11iF7TZx9fpLwdD3fw==} dev: false @@ -901,6 +1184,20 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: false + + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -921,11 +1218,21 @@ packages: engines: {node: '>=0.1.97'} dev: false + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: false + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} dev: false + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: false + /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} dev: false @@ -968,6 +1275,12 @@ packages: engines: {node: '>= 0.8'} dev: false + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: false + /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -977,10 +1290,108 @@ packages: resolution: {integrity: sha512-+TXdhKCySpJDynCxgAPoGVyAkiK3QPusQ63/BdU5t68QcYzyU6zkP/T7F3gkMQBVUYqdWEADKa6Kx5zg8QIKrg==} dev: false + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: false + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: false + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: false + + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: false + + /pirates@4.0.5: + resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + engines: {node: '>= 6'} + dev: false + + /postcss-import@14.1.0(postcss@8.4.23): + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.23 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.2 + dev: false + + /postcss-js@4.0.1(postcss@8.4.23): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.23 + dev: false + + /postcss-load-config@3.1.4(postcss@8.4.23): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.1.0 + postcss: 8.4.23 + yaml: 1.10.2 + dev: false + + /postcss-nested@6.0.0(postcss@8.4.23): + resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.23 + postcss-selector-parser: 6.0.11 + dev: false + + /postcss-selector-parser@6.0.11: + resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: false + + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: false + + /postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + /processenv@1.1.0: resolution: {integrity: sha512-SymqIsn8GjEUy8nG7HiyEjgbfk1xFosRIakUX1NHLpriq3vVpKniGrr9RdMWCaGYWByIovbRt2f/WvmP/IOApQ==} dependencies: @@ -1005,6 +1416,15 @@ packages: engines: {node: '>=0.6'} dev: false + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: false + + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + dev: false + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -1020,6 +1440,12 @@ packages: unpipe: 1.0.0 dev: false + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + dev: false + /readable-stream@1.1.14: resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} dependencies: @@ -1038,6 +1464,13 @@ packages: util-deprecate: 1.0.2 dev: false + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: false + /regenerator-runtime@0.11.1: resolution: {integrity: sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==} dev: false @@ -1055,11 +1488,31 @@ packages: functions-have-names: 1.2.3 dev: false + /resolve@1.22.2: + resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + hasBin: true + dependencies: + is-core-module: 2.12.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: false + /retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} dev: false + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: false + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: false + /safe-array-concat@1.0.0: resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} engines: {node: '>=0.4'} @@ -1144,6 +1597,11 @@ packages: object-inspect: 1.12.3 dev: false + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: false + /split2@3.0.0: resolution: {integrity: sha512-Cp7G+nUfKJyHCrAI8kze3Q00PFGEG1pMgrAlTFlDbn+GW24evSZHJuMl+iUJx1w/NTRDeBiTgvwnf6YOt94FMw==} dependencies: @@ -1214,6 +1672,20 @@ packages: is-regexp: 1.0.0 dev: false + /sucrase@3.32.0: + resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} + engines: {node: '>=8'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 7.1.6 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.5 + ts-interface-checker: 0.1.13 + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -1221,6 +1693,11 @@ packages: has-flag: 3.0.0 dev: false + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: false + /tailwind@4.0.0: resolution: {integrity: sha512-LlUNoD/5maFG1h5kQ6/hXfFPdcnYw+1Z7z+kUD/W/E71CUMwcnrskxiBM8c3G8wmPsD1VvCuqGYMHviI8+yrmg==} dependencies: @@ -1257,10 +1734,69 @@ packages: - utf-8-validate dev: false + /tailwindcss@3.3.1(postcss@8.4.23): + resolution: {integrity: sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==} + engines: {node: '>=12.13.0'} + hasBin: true + peerDependencies: + postcss: ^8.0.9 + dependencies: + arg: 5.0.2 + chokidar: 3.5.3 + color-name: 1.1.4 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.2.12 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.18.2 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.23 + postcss-import: 14.1.0(postcss@8.4.23) + postcss-js: 4.0.1(postcss@8.4.23) + postcss-load-config: 3.1.4(postcss@8.4.23) + postcss-nested: 6.0.0(postcss@8.4.23) + postcss-selector-parser: 6.0.11 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.2 + sucrase: 3.32.0 + transitivePeerDependencies: + - ts-node + dev: false + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: false + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: false + /timer2@1.0.0: resolution: {integrity: sha512-UOZql+P2ET0da+B7V3/RImN3IhC5ghb+9cpecfUhmYGIm0z73dDr3A781nBLnFYmRzeT1AmoT4w9Lgr8n7n7xg==} dev: false + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: false + + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: false + /tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} @@ -1374,6 +1910,10 @@ packages: is-typed-array: 1.1.10 dev: false + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: false + /ws@6.2.0: resolution: {integrity: sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==} peerDependencies: @@ -1387,3 +1927,8 @@ packages: dependencies: async-limiter: 1.0.1 dev: false + + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: false diff --git a/web/src/components/card.rs b/crates/web/src/components/card.rs similarity index 99% rename from web/src/components/card.rs rename to crates/web/src/components/card.rs index f0baeba..76cdb4d 100644 --- a/web/src/components/card.rs +++ b/crates/web/src/components/card.rs @@ -19,7 +19,6 @@ pub fn Card(cx: Scope) -> View { h4(class = "text-2xl font-bold leading-tight mt-0 mb-2") { "Slack" } p(class="text-blueGray-500 px-8") {"We are more than happy to work at such a great project." } div(class = "flex justify-center mt-8 mb-2 text-blueGray-400") { - // div(class = "flex items-center") { a(class = "text-white bg-blueGray-500 inline-flex items-center justify-center shadow-lg rounded rounded-full relative border-2 border-white -ml-4 hover:z-1 w-10 h-10") { img(class="rounded-full w-full", src="https://demos.creative-tim.com/notus-pro-react/static/media/team-1.26905a67.jpg") {} diff --git a/crates/web/src/components/header.rs b/crates/web/src/components/header.rs new file mode 100644 index 0000000..2012898 --- /dev/null +++ b/crates/web/src/components/header.rs @@ -0,0 +1,22 @@ +use sycamore::prelude::*; + +#[component] +pub fn Header(cx: Scope) -> View { + view!(cx, div(class = "px-4 py-5 flex-auto ct-docs-frame") { + div(class = "relative flex flex-wrap justify-center bg-blueGray-100") { + div(class = "w-full h-16 relative") { + nav(class = "absolute w-full z-50 flex flex-wrap items-center justify-between px-2 py-3 mb-3 lg:bg-transparent bg-white") { + div(class = "flex container mx-auto flex flex-wrap items-center justify-between px-0 lg:px-4") { + a( + class = "text-sm font-bold leading-relaxed inline-flex items-center mr-4 py-2 whitespace-nowrap uppercase text-blueGray-500", + href = "/" + ) { + img(class = "rounded-full mr-2", style = "width: 30px") {} + span() { "OS Wilno" } + } + } + } + } + } + }) +} diff --git a/crates/web/src/components/mod.rs b/crates/web/src/components/mod.rs new file mode 100644 index 0000000..a294cf4 --- /dev/null +++ b/crates/web/src/components/mod.rs @@ -0,0 +1,5 @@ +mod card; +mod header; + +pub use card::*; +pub use header::*; diff --git a/crates/web/src/main.rs b/crates/web/src/main.rs new file mode 100644 index 0000000..6232f99 --- /dev/null +++ b/crates/web/src/main.rs @@ -0,0 +1,22 @@ +mod components; +mod pages; + +use components::Card; +use pages::local_businesses::LocalBusinesses; +use sycamore::prelude::*; +use tracing_subscriber_wasm::MakeConsoleWriter; + +fn main() { + console_error_panic_hook::set_once(); + tracing_subscriber::fmt::fmt() + .with_ansi(false) + .with_writer(MakeConsoleWriter::default().map_trace_level_to(tracing::Level::DEBUG)) + .without_time() + .init(); + + sycamore::render(|cx| { + view! { cx, + LocalBusinesses() + } + }); +} diff --git a/crates/web/src/pages/local_businesses.rs b/crates/web/src/pages/local_businesses.rs new file mode 100644 index 0000000..e4add66 --- /dev/null +++ b/crates/web/src/pages/local_businesses.rs @@ -0,0 +1,57 @@ +pub mod local_business; + +use contract::businesses::BusinessList; +use contract::*; +use local_business::LocalBusinessCard; +use sycamore::prelude::*; +use sycamore::suspense::Suspense; +use tracing::info; + +use crate::components::Header; + +#[component] +async fn LocalBusinessesList(cx: Scope<'_>) -> View { + let req = wasm_request::get_options::>( + "/api/local-businesses/all.json", + wasm_request::Method::Get, + None, + None, + ); + let page = wasm_request::request(req) + .await + .unwrap_or_default() + .into_serde::() + .unwrap_or_default(); + info!("{page:#?}"); + + let businesses = { + let businesses = page.businesses.clone(); + create_signal(cx, businesses) + }; + let page = create_signal(cx, || page); + + view! { + cx, + div(class = "container mx-auto px-4") { + div(class = "mb-12 flex flex-wrap -mx-4") { + Indexed( + iterable=businesses, + view=|cx, business| view! { cx, + LocalBusinessCard(name = business.name, desc = business.description, items = business.items) + } + ) + } + } + } +} + +#[component] +pub fn LocalBusinesses(cx: Scope) -> View { + view!( + cx, + Header() + Suspense(fallback = view! { cx, "Loading..." }) { + LocalBusinessesList() + } + ) +} diff --git a/crates/web/src/pages/local_businesses/local_business.rs b/crates/web/src/pages/local_businesses/local_business.rs new file mode 100644 index 0000000..e4dd35d --- /dev/null +++ b/crates/web/src/pages/local_businesses/local_business.rs @@ -0,0 +1,59 @@ +use contract::LocalBusinessItem; +use sycamore::component::Prop; +use sycamore::prelude::*; + +#[derive(Prop)] +pub struct LocalBusinessCardProps { + name: String, + desc: String, + items: Vec, +} + +#[component] +pub fn LocalBusinessCard(cx: Scope<'_>, props: LocalBusinessCardProps) -> View { + let items = create_signal(cx, props.items); + view! { + cx, + div(class = "px-4 relative w-full md:w-4/12") { + div(class = "mb-6 text-center shadow-lg rounded-lg relative flex flex-col bg-white p-6 w-full mb-6") { + div(class = "py-6 flex-auto") { + div(class = "shadow-lg mt-6 rounded-full my-6 mx-auto w-100-px p-6 bg-white") { + img(class = "mx-auto", src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAABmFBMVEX////89ef56cv01JXuu0vssSXrrQD12KDrrxPssy3rsBv23rD78d345cLstS3xx3DxyHXvv1jz+vjH59uk2cWFzbNdv51ZvptjwaC34NHp9vnP7PG85eyy4emi3OXZ8PSJ09+D0d6E0d7y+vvW7eS+sVOUq195mz2BnDyBmS2Hx8CBmzaEsYSJ1ubLrDWIy8mEuJiipDvDqjbLypeDqWqGvqiV07xGpotHoJWDrXY0lIIli3QbhmxqvL+dnSK4qDdZraiZnSX+9vn2ytXwoLTtkqnqeZb0u8n64ejnY4bfAFLeAEvfAFDeAEjjNmnyssLgE1bgGlnkSHTtuS3pnS3idEnmjS3eWyvWICfVDSbWICXbHUTXHyzaHjz4093pcpHjgSzZOSnpoi3idyvzyJ752+NlyKWVkZJnpY9cfXRKNEcvACkfACgxCC8zFDMgFDCxGE3nGltYbmpEJz8sFDJyFj9FFDZlnopSYmJflINPVVqPF0VBSVHKGVTphJAaFDDgbSthFTuiF0rQGVVTFTlKbmg+N0akADww/u2HAAAL5klEQVR4AezUhXUEMAwDUAWUWj6+/XctPigzRS9/AqPgbVmWZTFQCnzV1jmusbcKQxcMUrfIyFbgZZOpBzIajJQeeiJV4GJL6hmMCg/boJ43dnBQknrJ2MJAp17EPebXUq/gAbMrQ68aFZM7Uq/iCXMroTu2YbNJvYENUztRbwlMbehNUTGxbUjeZ7oLvYN30FwbW/cOs7l3qL39H8bWIEt9z3ToHdjx187X8CkHXjFzHmqq20AY1S3engpYppmRIGt2fb29933/l8rOrZgY9CsZT+55g/PN8dAkEMWN//Ubm063l1pr06zX/7OtVbOzaZoYDEfjPM9Hw0GLw+umb25fePPMOgKfLcBMJ2Nyznsi752jUUuSXdZbJN7xDyjTrXfLes55WsC7fNrCALMlP8b2Ws6U9Xxd76vjSPwBtFkTafanzLcYzZ/0JzlPrxGXawgyNkrxY8Q2HeY8vZV4kix1ZrOVpDGKm1CmW5vTYfP06oqCz2CarTHsyW7TJPn4135Qj/FyofbWGWa2H5VpWK+YHxwSghsqNAo8inCmyfZnvTfKTxWmOG1/hEzaFdimSXLEel8pMEM/VhhhYNlgmSbbC3rMwTEpDrGbZgFsPz7T+vTmxbxGeQIOcWQEqAvi6xT5kMjTOy1Yb4n9ijAEBP8MRhq3a2p6Zw166pl2EMN+9A9syRo9NgQzdRPzn+nbTDbTjYBeVKZuKLRoJLfpmjjjM3UjJUPbMTDnYT08U69kiGc6zN3xwRygvFAz7COGmTVhvn5gqIAJApnKGc4sZNhB9IipTkrI8LJS2qV/QobpFabH4JkqvW3LINLVeiPWWwDNlBB03pcydmaaGIzIeapTfcIzVflwMQMzBfTEM3UDY9QytZgeU83FMjVGM1NATzhT3qSKmXa/641Zbx2HeKY6X0XZDGLxO+oAc4gyNEIjxRWa6SQwvehMgTWjmOn1TQXoRWV6WwEPoQiA3t39/YMjlKrEMgU+GWpkynrMwyOhoJke0go84YmimQb0mBtiFDL1bmyEsav07hd5IJxyDtHYpxtPDdN2pmldLzLT23+ZqXP50LRAxwb1IjO9hDKt/4DhWW9q2iEN6zGEw4YA1Q89Gk9Me/TSsF5cphcxmfL0BPSATK9Zbw03FWwIZwpMTwQb1GMexDMtVPSYy5qeVqZF8TQzGkzG9PiAGIpmWsxPj7a3djX0vPNE9xhxmQb0Ej5co6LH3NwrZFrTUziqWDuShGX6cEMxmQb0mJ22Mv3nmZ0HjUyL+fNRkijc9lrSaynT/bJuV9Nr86LQoEEPz/QFNzw5WNQrn39lvWXkt+k0r+m1menxQV1P52bpaJUfVa1kGtCTz3TsqBksUyYm07IoTs9+TQJHFSWvseeOVuPBTCvC8O74lfVCJL+rTJC5wzJ1BPDlQPo2+wXZMVIMHa3lBd+miB5+ova91BYNCJLDMr0Lxcl6+EUhwUxzH9oMeKaIHoNlmhgRBo4YiUwraqThi6QPmpmOPZFQpg2GvkFPO1NHYeBMG/QCRxXbz3SCGKKZgnqcaaKW6cgTiWaKfQ3ImSpdYs+JpDP1zoe/J2MBndvBBAFm+gjq4ZnuCS8agUzHAwPxXi1TR2CmEBMDk2xDqBm+hO2urzM7E/hbHun/WiCMx4egXuQ1mvdal9hzTxBr9dLsGwYnUbrEPvL4Ng3pZfKZCmzTgYMzDei1lOmm1oNIIb34THd+rkyrm4ewnnymEj9gTOMzvVuhx4YRmf7N3Hlop60EYVhpWKE5vfeHuL1ihNGuJFYQF3C3L6Sakt57ee3LnHtQjnIXZmRGe/y/wXfm8yQadnYL02hamis7lUrFma8SikhEjPCQo4r02LQldg3evCukJyCeHzgo5DWiphEe1z7b7J7uWijVXN9TYRQlfLfK0E5vXP/9V8DD99nYNc3F8OoS8OJR0sHHificjHii9nR6mlbLdaieLl5QQv8UETz9GTBzmgIeVG9clKrSEDVzMsAjLl1CzpxnvxIkN8QLoHqTosRkxKvXbuBjQAIhhPWCpWKjeHMhgOphUQH648UNbAx4mlVT/OaaYrHRWFxabrZWQkq8CvZPP5xHv6Gfk5nXFKp3dHFptdlcXm6thaT46/ho8dIV+PnkymUNHoRGeHrqK0GgesVh9QAP0qIRqrqVPOY1BTyoXnOEB4QrbEVEcpJh7RLRtBjJGYWuqdpgWLvk1jTzPd7mUoyOrinE34eaHj4Sw9vS4AGhTyOUc1NreoZb05nsCG9bg5dUU6+8DzW1/8PbAbwJoRGKjf2oaQPDg7QkiVC5DGv63Jr+g+PRNVV100vsSOCDQbSWSTFDCJrSl9iRjD4Y1lBEuqbKNXCrC3HkVnVGHwwrQMikqXCs/aFp2wl8EX0P0QhJmno1s0vs+qw7gQS8KIya+lXLsKYavNCP4bFqqkILYkZT3WR4rhLGq0fXFEL+MzSgKUSLF2qzSiuihxLKSFLTmpbmNjwNHremosJ0EC6ppvMbAvDGR9EIV9E+U7IgZm+uKc27vlQhEqKmCgGsWRCTV4KUaq6H49E1XUFGbUbvWuh0frwV/wElbU29usUXHO/2nbv37uN4jJpKlxEQNMXw7u12e/0BjsakqfIdQ3ctdM7dfvAQ8IZ59BgITWiqZLBu8WZs9QCvO8qj0IimQoZ4E2XQFKr35BsepNcPU9dUSLGBztcYNO10nj7rxvFMaCqk3JgvpX/XQufc02f3IrxYUtQU5KwwVA+5EgTwnr+Iy7lHTdeSaKqEVAx4qKYdwIPGOS6PXvJrCnh+6LSttAPV0+LFM2DX1JMG8CCnnr9C8CC917yaNputNybw4IOhfx/BS6ypbKF4y0uLdsEAXs2VUoRvu5Q8SaAphvfuZrFRTP3d2ehI0uD9IwrhWwZNAW/r3c1Go8i4dongAeGHXteEpoDXGuFB+DUdd+LqY8+EplC97UaxqNlnY4z+xNXgk15Tvm4KeDtxPIidhpzxE1dGNAW8TcAz8ChrWehnER/fkgg/JdC0heClomk7kKE+RE3ffkyqKeAdBTwjj7LWfBWOI3zJrqnfajabS5PxmDUt++H4vO7xaqqEbC4tAp65t4NrkwDDwZMuJT2SpsqTgdM+DngG71pYnwQImj4iEX4YoHijA/e5rNlHWRUy8HzN0k0BrzzEgxww+yir44WQ5JrSuynICXiaM2AGHmUtyRCSnqZQvTrgJX5kL5M3U0JyN30/0FcP8LRHFQ3ss0FCPIMusZvqqlfTjgENaroOkqaiqZDC1eLRNbUPMBCWcUmhmybVVEhv4hA3nzHWTV0VEkLUNMEQFwDMaBpQCAePaZr2B6ES0kPx6DfXGGk0kD6tmz72yCP4vG3qEW9FIhxQarh7b9dZ531Z184bqiFoiuLde/EcPzOMa8r+BRUooqYo3rlOorXLfMZIDfFeinfT4dT/2dNzncT7bKb+Dh0xhaaA1x3hQRJpesTMzTxzkt5NNXifbwOedjuYoZvyzPZFSEvvf3gPP9/udKbYDiZtlTKksgdNd7V4/JpmeObcPo1wpCngfbnzI+BNuXZ5IGPoQsUNoqePULykms4ewTqpqY98CPzONsR79fycHo+uKbmI2UO8w0Qkyvt6P8Ljemx+xp7kKOPQu+JheMIPK+uAx7jPBjme5fr8ReJ6E/FgiEs/UQuaMvz/O8P725MrJ1QP8JgeZdUgZvWKsv+45vhq3AhecwaMS1PIMV27yR7NW9xZD6TSzaiTP7J3xkqWwpHs9wXMQBflz3y0hQV4UjMGTENTyMwR+8g3vKw9m7dSStupexLijRkDnmHYZ9OmMJu17ewwtn145oCVaqrVdrs65VuQ1l5y4GBumPyBf9u3g1MHYhgIw1ODvJ6x71uD+y/tHV9CWNAlBA36OhD6D7bB+KmRzbQuJTOt63BloKZ8poR7pgrUNZmf0DhThX2mgHmmE/DOlBcqC2V+lZpnSlSWuEJxozjvFSaWqI3yjvLPNEUtrkcMGAiaHtj+xcMWyRsmYmp90gz42OJ6R12wEkfk63wn4Cb2pCRS0twBT/cYe4870FprrbX2PX8tcLSFlWeS9QAAAABJRU5ErkJggg==") {} + } + h4(class = "text-2xl font-bold leading-tight mt-0 mb-2") { (props.name) } + p(class="text-blueGray-500 px-8 truncate") { (props.desc) } + div(class = "flex justify-center mt-8 mb-2 text-blueGray-400") { + div(class = "flex items-center") { + Indexed( + iterable=items, + view=|cx, item| view! { cx, + BusinessItem(url = item.picture_url) + } + ) + } + } + } + } + } + } +} + +#[derive(Prop)] +pub struct BusinessItemProps { + url: String, +} + +#[component] +pub fn BusinessItem(cx: Scope<'_>, props: BusinessItemProps) -> View { + view!( + cx, + a(class = "text-white bg-blueGray-500 inline-flex items-center justify-center shadow-lg rounded rounded-full relative border-2 border-white -ml-4 hover:z-1 w-10 h-10") { + img(class="rounded-full w-full", src=(props.url)) {} + div(class = "hidden") { + div(class = "border-0 mb-3 block z-50 font-normal leading-normal text-sm text-left no-underline break-words rounded") { + div(class = "py-1 px-2 text-center rounded text-white bg-black") { "Photo" } + } + } + } + ) +} diff --git a/web/src/pages/mod.rs b/crates/web/src/pages/mod.rs similarity index 100% rename from web/src/pages/mod.rs rename to crates/web/src/pages/mod.rs diff --git a/web/tailwind.config.js b/crates/web/tailwind.config.js similarity index 73% rename from web/tailwind.config.js rename to crates/web/tailwind.config.js index b1c946c..b30f58a 100644 --- a/web/tailwind.config.js +++ b/crates/web/tailwind.config.js @@ -7,6 +7,8 @@ module.exports = { ], theme: {}, variants: {}, - plugins: [], + plugins: [ + require('@tailwindcss/line-clamp'), + ], }; diff --git a/db/seed/001_init.psql b/db/seed/001_init.psql index c1cf962..766f92f 100644 --- a/db/seed/001_init.psql +++ b/db/seed/001_init.psql @@ -6,13 +6,13 @@ VALUES ('Cheap Tees', 'Unlimited possiblities! You can move the water masks to ('Tema Model Agency Ltd', 'Special for website developers and app ui designers, to preview their apps in a professional way, showcasing details and focus on Responsive Design for Website and apps, Vol 09.', 1), ('Neet Online Test Series - Nots', 'Advanced, easy to edit mockup. It contains everything you need to create a realistic look of your project. Guarantees the a good look for bright and dark designs and perfect fit to the shape. Easy to navigate, well described layers, friendly help file.', 1); -INSERT INTO local_business_items (name, price, local_business_id, item_order) -VALUES ('Water Frame', 23423, 1, 1), - ('Macbook Laptop Display 2.0', 927, 1, 2), - ('Paper Band', 920, 1, 3), - ('Artbox', 2500, 2, 1), - ('School Backpacks', 2150, 2, 2), - ('Premium Paper Cup', 2090, 3, 1), - ('Beer Black Bottle', 5000, 3, 2), - ('Circle Photo Frame', 2300, 3, 3), - ('Grey T-Shirt', 3430, 3, 4); +INSERT INTO local_business_items (name, price, local_business_id, item_order, picture_url) +VALUES ('Water Frame', 23423, 1, 1, 'https://picsum.photos/200?1'), + ('Macbook Laptop Display 2.0', 927, 1, 2, 'https://picsum.photos/200?2'), + ('Paper Band', 920, 1, 3, 'https://picsum.photos/200?3'), + ('Artbox', 2500, 2, 1, 'https://picsum.photos/200?4'), + ('School Backpacks', 2150, 2, 2, 'https://picsum.photos/200?5'), + ('Premium Paper Cup', 2090, 3, 1, 'https://picsum.photos/200?6'), + ('Beer Black Bottle', 5000, 3, 2, 'https://picsum.photos/200?7'), + ('Circle Photo Frame', 2300, 3, 3, 'https://picsum.photos/200?8'), + ('Grey T-Shirt', 3430, 3, 4, 'https://picsum.photos/200?9'); diff --git a/server/src/model/db.rs b/server/src/model/db.rs deleted file mode 100644 index ba3ee41..0000000 --- a/server/src/model/db.rs +++ /dev/null @@ -1,354 +0,0 @@ -use std::collections::HashMap; -use std::fmt::{Display, Formatter}; - -use byteorder::ByteOrder; -use chrono::{NaiveDateTime, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, Type}; -use sqlx_core::database::{HasArguments, HasValueRef}; -use sqlx_core::encode::IsNull; -use sqlx_core::error::BoxDynError; -use sqlx_core::postgres::{PgValueFormat, Postgres}; -use uuid::Uuid; - -#[derive(Debug, Default, PartialOrd, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, Type)] -pub enum AccountType { - #[default] - User = 1, - Business = 10, - Admin = 100, -} - -#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, Type)] -pub enum LocalBusinessState { - #[default] - Pending = 1, - Approved = 2, - Banned = 3, - Pinned = 4, - Internal = 5, -} - -#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize, Type)] -pub enum OfferState { - #[default] - Pending = 0, - Approved = 1, - Banned = 2, - Finished = 3, -} - -impl OfferState { - pub fn as_str(&self) -> &str { - match self { - OfferState::Pending => "Pending", - OfferState::Approved => "Approved", - OfferState::Banned => "Banned", - OfferState::Finished => "Finished", - } - } -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct Account { - pub id: i32, - pub login: String, - pub email: String, - pub pass: String, - pub facebook_id: Option, - pub account_type: AccountType, -} - -impl Display for LocalBusinessState { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -impl LocalBusinessState { - pub fn as_str(&self) -> &str { - match self { - Self::Pending => "Pending", - Self::Approved => "Approved", - Self::Banned => "Banned", - Self::Pinned => "Pinned", - Self::Internal => "Internal", - } - } -} - -#[derive(Debug, Copy, Clone, Serialize, Deserialize, Type)] -#[serde(rename_all = "snake_case")] -pub enum NewsStatus { - Pending, - Published, - Hidden, -} - -impl NewsStatus { - pub fn as_str(&self) -> &str { - match self { - Self::Pending => "Pending", - Self::Published => "Published", - Self::Hidden => "Hidden", - } - } -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct Token { - pub id: i32, - pub claims: HashMap, - pub iss: String, /* issuer */ - pub sub: i32, /* subject */ - pub aud: String, /* audience */ - pub exp: NaiveDateTime, /* expiration time */ - pub nbt: NaiveDateTime, /* not before time */ - pub iat: NaiveDateTime, /* issued at time */ - pub jti: Uuid, /* JWT ID - unique */ - pub role: AccountType, -} - -#[derive(Debug, Default, Serialize, Deserialize, FromRow)] -pub struct LocalBusiness { - pub id: i32, - pub owner_id: i32, - pub name: String, - pub description: String, - pub state: LocalBusinessState, -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct LocalBusinessItem { - pub id: i32, - pub local_business_id: i32, - pub name: String, - pub price: i64, - pub item_order: i32, - pub picture_url: String, -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct ContactInfo { - pub id: i32, - pub owner_id: i32, - pub contact_type: String, - pub content: String, -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct NewsArticle { - pub id: i32, - pub title: String, - pub body: String, - pub status: NewsStatus, - pub published_at: Option, - pub created_at: NaiveDateTime, -} - -impl Default for NewsArticle { - fn default() -> Self { - Self { - id: 0, - title: "".to_string(), - body: "".to_string(), - status: NewsStatus::Pending, - published_at: None, - created_at: Utc::now().naive_utc(), - } - } -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct CreateNewsArticleInput { - pub title: String, - pub body: String, - pub status: NewsStatus, -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct UpdateNewsArticleInput { - pub id: i32, - pub title: String, - pub body: String, - pub status: NewsStatus, - pub published_at: Option, -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct UpdateLocalBusinessInput { - pub id: i32, - pub name: String, - pub description: String, -} - -#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)] -pub enum PriceRange { - #[default] - Free, - Fixed { - value: i32, - }, - Range { - min: i32, - max: i32, - }, -} - -impl From<(i32, i32)> for PriceRange { - fn from((min, max): (i32, i32)) -> Self { - match (min, max) { - (0, 0) => Self::Free, - (_, 0) => Self::Fixed { value: min }, - _ => Self::Range { min, max }, - } - } -} - -impl sqlx::Type for PriceRange { - fn type_info() -> sqlx::postgres::PgTypeInfo { - sqlx::postgres::PgTypeInfo::with_name("PriceRange") - } -} - -fn take_i32(bytes: &mut &[u8]) -> i32 { - let value = byteorder::BigEndian::read_i32(&bytes[0..4]); - *bytes = &bytes[4..]; - value -} - -impl<'l> sqlx::Decode<'l, Postgres> for PriceRange { - fn decode(value: >::ValueRef) -> Result { - match value.format() { - PgValueFormat::Text => { - let s = value.as_str()?; - eprintln!("{s:?}"); - Ok(Self::Free) - } - PgValueFormat::Binary => { - let mut bytes = value.as_bytes()?; - // println!("{bytes:?}"); - - let _len = take_i32(&mut bytes); - - let _ty = take_i32(&mut bytes); - let _min_len = take_i32(&mut bytes); - let min = take_i32(&mut bytes); - - let _ty = take_i32(&mut bytes); - let _max_len = take_i32(&mut bytes); - let max = take_i32(&mut bytes); - - Ok((min, max).into()) - } - } - } -} - -fn encode(n: i32, buf: &mut >::ArgumentBuffer) { - let _ = >::encode(n, buf); -} - -impl<'l> sqlx::Encode<'l, Postgres> for PriceRange { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { - encode(2i32, buf); - fn write_value(n: &i32, buf: &mut >::ArgumentBuffer) { - encode(23i32, buf); - encode(4i32, buf); - encode(*n, buf); - } - match self { - PriceRange::Free => { - write_value(&0, buf); - write_value(&0, buf); - } - PriceRange::Fixed { value } => { - write_value(value, buf); - write_value(&0, buf); - } - PriceRange::Range { min, max } => { - write_value(min, buf); - write_value(max, buf); - } - } - IsNull::No - } -} - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct Offer { - pub id: i32, - pub owner_id: i32, - pub price_range: PriceRange, - pub description: String, - pub picture_url: String, - pub state: OfferState, - pub created_at: NaiveDateTime, -} - -#[derive(Debug)] -pub struct CreateLocalBusinessItemInput { - pub account_id: i32, - pub local_business_id: i32, - pub name: String, - pub price: i64, - pub item_order: i32, - pub picture_url: Option, -} - -#[derive(Debug)] -pub struct UpdateLocalBusinessItemInput { - pub id: i32, - pub local_business_id: i32, - pub name: String, - pub price: i64, - pub item_order: i32, - pub picture_url: String, -} - -#[derive(Debug, Deserialize)] -pub struct DeleteNewsArticleInput { - pub id: i32, -} - -#[derive(Debug)] -pub struct CreateContactInput { - pub owner_id: i32, - pub contact_type: String, - pub content: String, -} - -#[derive(Debug)] -pub struct UpdateContactInput { - pub id: i32, - pub owner_id: i32, - pub contact_type: String, - pub content: String, -} - -#[derive(Debug)] -pub struct CreateAccountInput { - pub login: String, - pub email: String, - pub pass: String, - pub facebook_id: Option, - pub account_type: AccountType, -} - -#[derive(Debug)] -pub struct CreateOfferInput { - pub description: String, - pub picture_url: String, - pub state: OfferState, - pub owner_id: i32, - pub price_range: PriceRange, -} - -#[derive(Debug)] -pub struct UpdateOfferInput { - pub id: i32, - pub description: String, - pub picture_url: String, - pub state: OfferState, - pub price_range: PriceRange, -} diff --git a/server/src/model/view.rs b/server/src/model/view.rs deleted file mode 100644 index 8a644b1..0000000 --- a/server/src/model/view.rs +++ /dev/null @@ -1,296 +0,0 @@ -use std::sync::Arc; - -use chrono::NaiveDateTime; -use serde::{Deserialize, Serialize}; - -use crate::model::db; - -#[derive(Debug, Default, Serialize)] -pub enum Page { - #[default] - LocalBusinesses, - News, - Account, - Register, - Login, - AccountBusinessItems, - Marketplace, - AccountOffers, - AdminNews, - AdminCreateNews, - AdminBusinesses, - AdminOffers, - Terms, - Privacy, - Business, -} - -impl Page { - pub fn is_public(&self) -> bool { - !self.is_admin() - } - - pub fn is_admin(&self) -> bool { - matches!( - self, - Page::AdminNews | Page::AdminCreateNews | Page::AdminBusinesses | Page::AdminOffers - ) - } - - pub fn select_index(&self) -> &str { - if matches!(self, Page::LocalBusinesses) { - "selected" - } else { - "" - } - } - - pub fn select_news(&self) -> &str { - if matches!(self, Page::News) { - "selected" - } else { - "" - } - } - - pub fn select_account(&self) -> &str { - if matches!(self, Page::Account) { - "selected" - } else { - "" - } - } - - pub fn select_marketplace(&self) -> &str { - if matches!(self, Page::Marketplace | Page::AccountOffers) { - "selected" - } else { - "" - } - } - - pub fn select_admin_news(&self) -> &str { - if matches!(self, Page::AdminNews) { - "selected" - } else { - "" - } - } - - pub fn select_admin_businesses(&self) -> &str { - if matches!(self, Page::AdminBusinesses) { - "selected" - } else { - "" - } - } - - pub fn select_admin_offers(&self) -> &str { - if matches!(self, Page::AdminOffers) { - "selected" - } else { - "" - } - } -} - -#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct BusinessItemInput { - pub name: String, - pub price: u32, - pub picture_url: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct SetStateBusinessInput { - pub id: i32, - pub state: db::LocalBusinessState, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct LocalBusiness { - pub id: i32, - pub owner_id: i32, - pub name: String, - pub description: String, - pub state: db::LocalBusinessState, - pub items: Vec, - pub contacts: Vec, -} - -impl<'items, 'contacts> - From<( - db::LocalBusiness, - &'items mut Vec, - &'contacts mut Vec, - )> for LocalBusiness -{ - fn from( - (service, items, contacts): ( - db::LocalBusiness, - &'items mut Vec, - &'contacts mut Vec, - ), - ) -> Self { - Self { - id: service.id, - owner_id: service.owner_id, - name: service.name, - description: service.description, - state: service.state, - items: items - .drain_filter(|i| i.local_business_id == service.id) - .collect(), - contacts: contacts - .drain_filter(|c| c.owner_id == service.owner_id) - .collect(), - } - } -} - -#[derive(Debug, Deserialize)] -pub struct CreateBusinessItemInput { - pub name: String, - pub price: i64, - pub picture_url: String, - pub item_order: i32, -} - -#[derive(Debug, Deserialize)] -pub struct AtomicUpdateBusinessItemInput { - pub id: i32, - pub name: String, - pub description: String, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateBusinessItemInput { - pub id: i32, - pub name: String, - pub price: i64, - pub picture_url: String, - pub item_order: i32, -} - -#[derive(Debug, Deserialize)] -pub struct ModifyBusinessItemInput { - pub id: i32, -} - -#[derive(Debug, Deserialize)] -pub struct MoveBusinessItemInput { - pub id: i32, - pub item_order: i32, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateNewsArticleInput { - pub id: i32, - pub title: String, - pub body: String, - pub status: db::NewsStatus, -} - -#[derive(Debug, Deserialize)] -pub struct CreateNewsArticleInput { - pub title: String, - pub body: String, - pub status: db::NewsStatus, -} - -#[derive(Debug, Deserialize)] -pub struct DeleteNewsArticleInput { - pub id: i32, -} - -#[derive(Debug, Deserialize)] -pub struct CreateContactInfoInput { - #[serde(rename = "type")] - pub contact_type: String, - pub content: String, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateContactInfoInput { - pub id: i32, - #[serde(rename = "type")] - pub contact_type: String, - pub content: String, -} - -#[derive(Debug, Deserialize)] -pub struct DeleteContactInfoInput { - pub id: i32, -} - -#[derive(Debug, Deserialize)] -pub struct CreateOfferInput { - pub description: String, - pub picture_url: String, - pub price_min: i32, - pub price_max: i32, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateOfferInput { - pub id: i32, - pub description: String, - pub picture_url: String, - pub price_min: i32, - pub price_max: i32, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Offer { - pub id: i32, - pub owner_id: i32, - pub price_range: db::PriceRange, - pub description: String, - pub picture_url: String, - pub state: db::OfferState, - pub created_at: NaiveDateTime, - pub contacts: Vec>, -} - -impl Default for Offer { - fn default() -> Self { - Self { - id: 0, - owner_id: 0, - price_range: Default::default(), - description: "".to_string(), - picture_url: "".to_string(), - state: Default::default(), - created_at: chrono::Utc::now().naive_utc(), - contacts: vec![], - } - } -} - -impl From<(db::Offer, &Vec>)> for Offer { - fn from((offer, contacts): (db::Offer, &Vec>)) -> Self { - let db::Offer { - id, - owner_id, - price_range, - description, - picture_url, - state, - created_at, - } = offer; - Self { - id, - owner_id, - price_range, - description, - picture_url, - state, - created_at, - contacts: contacts - .iter() - .filter(|contact| contact.owner_id == owner_id) - .cloned() - .collect(), - } - } -} diff --git a/server/src/view/mod.rs b/server/src/view/mod.rs deleted file mode 100644 index 88a8850..0000000 --- a/server/src/view/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -use serde::Serialize; - -use crate::model::db::AccountType; - -pub mod filters { - - pub fn opt_time(s: &Option) -> ::askama::Result { - Ok(s.as_ref() - .map(|t| serde_json::to_string(t).unwrap_or_default()) - .unwrap_or_default()) - } -} - -#[derive(Default, Debug, Serialize)] -pub struct Helper; - -impl Helper { - pub fn is_admin(&self, account: &Option) -> bool { - account - .as_ref() - .map(|a| a.account_type == AccountType::Admin) - .unwrap_or_default() - } - - pub fn is_above_user(&self, account: &Option) -> bool { - account - .as_ref() - .map(|a| a.account_type > AccountType::User) - .unwrap_or_default() - } - - pub fn account_id_tag(&self, account: &Option) -> String { - account - .as_ref() - .map(|a| format!("account-id={}", a.id)) - .unwrap_or_default() - } - - pub fn email<'a>(&self, account: &'a Option) -> &'a str { - account - .as_ref() - .map(|a| a.email.as_str()) - .unwrap_or_default() - } - - pub fn render_contact(&self, ty: &str, val: &str) -> String { - if ty == "mobile" { - self.mobile(val) - } else { - val.into() - } - } - - fn mobile(&self, phone: &str) -> String { - base64::encode(phone) - } -} diff --git a/web/Cargo.toml b/web/Cargo.toml deleted file mode 100644 index e494881..0000000 --- a/web/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "web" -version = "0.1.0" -edition = "2021" - -[dependencies] -sycamore = { version = "0.8.2", features = ['suspense'] } -wasm_request = { version = "0.1.1" } -serde = { version = "*" } -serde_json = { version = "*" } diff --git a/web/src/components/mod.rs b/web/src/components/mod.rs deleted file mode 100644 index 633e627..0000000 --- a/web/src/components/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod card; - -pub use card::*; diff --git a/web/src/main.rs b/web/src/main.rs deleted file mode 100644 index e5ee69d..0000000 --- a/web/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod components; -mod pages; - -use components::Card; -use sycamore::prelude::*; - -fn main() { - sycamore::render(|cx| { - view! { cx, - div(class = "mb-12 flex flex-wrap -mx-4") { - Card {} - Card {} - Card {} - Card {} - Card {} - Card {} - Card {} - } - } - }); -} diff --git a/web/src/pages/local_businesses.rs b/web/src/pages/local_businesses.rs deleted file mode 100644 index b87715a..0000000 --- a/web/src/pages/local_businesses.rs +++ /dev/null @@ -1,45 +0,0 @@ -use serde::{Deserialize, Serialize}; -use sycamore::futures::spawn_local_scoped; -use sycamore::prelude::*; - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct LocalBusiness { - pub id: i32, - pub owner_id: i32, - pub name: String, - pub description: String, - pub state: db::LocalBusinessState, - pub items: Vec, - pub contacts: Vec, -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct IndexTemplate { - businesses: Vec, - account: Option, - error: Option, - page: Page, - h: Helper, -} - -#[component] -pub fn LocalBusinesses(cx: Scope) -> View { - let submit_registration = || { - spawn_local_scoped(cx, async move { - let req = wasm_request::get_options::>( - "/api/local-businesses/all.json", - wasm_request::Method::Get, - None, - None, - ); - wasm_request::request(req).await.unwrap_or_default(); - }) - }; - - view!( - cx, - article() { - // - } - ) -}